fix(appearance): scale inline px font sizes so text-size reaches all content

The global text-size control only set the root font-size, which scales rem-based text (navbar, menus) but not the dense inline px sizes used across the trip planner, budget, journey and panels — so place titles and addresses stayed fixed. applyAppearance now also exposes the factor as --fs-scale-text, and a codemod wraps inline numeric fontSize in calc(<px> * var(--fs-scale-text, 1)) across components and pages (map popups and PDF excluded). Sizes are byte-identical at 100%; the control now visibly resizes the actual content.
This commit is contained in:
Maurice
2026-06-29 11:47:11 +02:00
parent bc9da6b022
commit e800699191
112 changed files with 938 additions and 930 deletions
+1
View File
@@ -48,6 +48,7 @@
setScale('--fs-scale-caption', ts.caption); setScale('--fs-scale-caption', ts.caption);
if (typeof s.fontScale === 'number' && s.fontScale !== 1) { if (typeof s.fontScale === 'number' && s.fontScale !== 1) {
root.style.fontSize = s.fontScale * 100 + '%'; root.style.fontSize = s.fontScale * 100 + '%';
root.style.setProperty('--fs-scale-text', String(s.fontScale));
} }
function setScale(name, v) { function setScale(name, v) {
+6 -6
View File
@@ -473,10 +473,10 @@ export default function BackupPanel() {
<AlertTriangle size={20} className="text-white" /> <AlertTriangle size={20} className="text-white" />
</div> </div>
<div> <div>
<h3 className="text-white" style={{ margin: 0, fontSize: 16, fontWeight: 700 }}> <h3 className="text-white" style={{ margin: 0, fontSize: 'calc(16px * var(--fs-scale-text, 1))', fontWeight: 700 }}>
{t('backup.restoreConfirmTitle')} {t('backup.restoreConfirmTitle')}
</h3> </h3>
<p className="text-[rgba(255,255,255,0.8)]" style={{ margin: '2px 0 0', fontSize: 12 }}> <p className="text-[rgba(255,255,255,0.8)]" style={{ margin: '2px 0 0', fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>
{restoreConfirm.filename} {restoreConfirm.filename}
</p> </p>
</div> </div>
@@ -484,11 +484,11 @@ export default function BackupPanel() {
{/* Body */} {/* Body */}
<div style={{ padding: '20px 24px' }}> <div style={{ padding: '20px 24px' }}>
<p className="text-gray-700 dark:text-gray-300" style={{ fontSize: 13, lineHeight: 1.6, margin: 0 }}> <p className="text-gray-700 dark:text-gray-300" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', lineHeight: 1.6, margin: 0 }}>
{t('backup.restoreWarning')} {t('backup.restoreWarning')}
</p> </p>
<div style={{ marginTop: 14, padding: '10px 12px', borderRadius: 10, fontSize: 12, lineHeight: 1.5 }} <div style={{ marginTop: 14, padding: '10px 12px', borderRadius: 10, fontSize: 'calc(12px * var(--fs-scale-text, 1))', lineHeight: 1.5 }}
className="bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-800" className="bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-800"
> >
{t('backup.restoreTip')} {t('backup.restoreTip')}
@@ -500,14 +500,14 @@ export default function BackupPanel() {
<button <button
onClick={() => setRestoreConfirm(null)} onClick={() => setRestoreConfirm(null)}
className="text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700" className="text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
style={{ padding: '9px 20px', borderRadius: 10, fontSize: 13, fontWeight: 600, border: 'none', cursor: 'pointer', fontFamily: 'inherit' }} style={{ padding: '9px 20px', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, border: 'none', cursor: 'pointer', fontFamily: 'inherit' }}
> >
{t('common.cancel')} {t('common.cancel')}
</button> </button>
<button <button
onClick={executeRestore} onClick={executeRestore}
className="bg-[#dc2626] text-white" className="bg-[#dc2626] text-white"
style={{ padding: '9px 20px', borderRadius: 10, fontSize: 13, fontWeight: 600, border: 'none', cursor: 'pointer', fontFamily: 'inherit' }} style={{ padding: '9px 20px', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, border: 'none', cursor: 'pointer', fontFamily: 'inherit' }}
onMouseEnter={e => e.currentTarget.style.background = '#b91c1c'} onMouseEnter={e => e.currentTarget.style.background = '#b91c1c'}
onMouseLeave={e => e.currentTarget.style.background = '#dc2626'} onMouseLeave={e => e.currentTarget.style.background = '#dc2626'}
> >
@@ -89,7 +89,7 @@ function OptionButton({
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 20px', borderRadius: 10, cursor: 'pointer', padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
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)', border: active ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: active ? 'var(--bg-hover)' : 'var(--bg-card)', background: active ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
@@ -186,7 +186,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
}], []) }], [])
if (!loaded) { if (!loaded) {
return <p className="text-content-faint" style={{ fontSize: 12, fontStyle: 'italic', padding: 16 }}>Loading</p> return <p className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontStyle: 'italic', padding: 16 }}>Loading</p>
} }
const darkMode = defaults.dark_mode const darkMode = defaults.dark_mode
@@ -112,12 +112,12 @@ export default function BackgroundTasksWidget() {
</div> </div>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <div style={{ fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{task.label} {task.label}
</div> </div>
{task.status === 'running' && ( {task.status === 'running' && (
<div style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: 1 }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginTop: 1 }}>
{t('reservations.import.parsing')} {t('reservations.import.parsing')}
{task.total > 1 ? ` · ${task.done}/${task.total}` : ''} {task.total > 1 ? ` · ${task.done}/${task.total}` : ''}
</div> </div>
@@ -126,22 +126,22 @@ export default function BackgroundTasksWidget() {
{task.status === 'done' && ( {task.status === 'done' && (
task.items === undefined ? ( task.items === undefined ? (
// Restored from a reload; items are being re-fetched (see the poll backstop). // Restored from a reload; items are being re-fetched (see the poll backstop).
<div style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: 1 }}>{t('reservations.import.parsing')}</div> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginTop: 1 }}>{t('reservations.import.parsing')}</div>
) : task.items.length > 0 ? ( ) : task.items.length > 0 ? (
<button <button
onClick={() => review(task)} onClick={() => review(task)}
className="bg-accent text-accent-text" className="bg-accent text-accent-text"
style={{ marginTop: 4, border: 'none', borderRadius: 8, padding: '4px 12px', fontSize: 11.5, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }} style={{ marginTop: 4, border: 'none', borderRadius: 8, padding: '4px 12px', fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}
> >
{t('common.import')} {t('common.import')}
</button> </button>
) : ( ) : (
<div style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: 1 }}>{t('reservations.import.previewEmpty')}</div> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginTop: 1 }}>{t('reservations.import.previewEmpty')}</div>
) )
)} )}
{task.status === 'error' && ( {task.status === 'error' && (
<div style={{ fontSize: 11, color: '#b91c1c', marginTop: 1, whiteSpace: 'pre-wrap' }}>{task.error}</div> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: '#b91c1c', marginTop: 1, whiteSpace: 'pre-wrap' }}>{task.error}</div>
)} )}
</div> </div>
+7 -7
View File
@@ -38,14 +38,14 @@ export default function BudgetPanel({ tripId, tripMembers = [] }: BudgetPanelPro
<div style={{ width: 64, height: 64, borderRadius: 16, background: 'var(--bg-tertiary)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 20px' }}> <div style={{ width: 64, height: 64, borderRadius: 16, background: 'var(--bg-tertiary)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 20px' }}>
<Calculator size={28} color="#6b7280" /> <Calculator size={28} color="#6b7280" />
</div> </div>
<h2 style={{ fontSize: 20, fontWeight: 700, color: 'var(--text-primary)', margin: '0 0 8px' }}>{t('budget.emptyTitle')}</h2> <h2 style={{ fontSize: 'calc(20px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', margin: '0 0 8px' }}>{t('budget.emptyTitle')}</h2>
<p style={{ fontSize: 14, color: 'var(--text-muted)', margin: '0 0 24px', lineHeight: 1.5 }}>{t('budget.emptyText')}</p> <p style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', color: 'var(--text-muted)', margin: '0 0 24px', lineHeight: 1.5 }}>{t('budget.emptyText')}</p>
{canEdit && ( {canEdit && (
<div style={{ display: 'flex', gap: 6, justifyContent: 'center', alignItems: 'stretch', maxWidth: 320, margin: '0 auto' }}> <div style={{ display: 'flex', gap: 6, justifyContent: 'center', alignItems: 'stretch', maxWidth: 320, margin: '0 auto' }}>
<input value={newCategoryName} onChange={e => setNewCategoryName(e.target.value)} <input value={newCategoryName} onChange={e => setNewCategoryName(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleAddCategory()} onKeyDown={e => e.key === 'Enter' && handleAddCategory()}
placeholder={t('budget.emptyPlaceholder')} placeholder={t('budget.emptyPlaceholder')}
style={{ flex: 1, padding: '9px 14px', borderRadius: 10, border: '1px solid var(--border-primary)', fontSize: 13, fontFamily: 'inherit', outline: 'none', background: 'var(--bg-input)', color: 'var(--text-primary)', minWidth: 0 }} /> style={{ flex: 1, padding: '9px 14px', borderRadius: 10, border: '1px solid var(--border-primary)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'inherit', outline: 'none', background: 'var(--bg-input)', color: 'var(--text-primary)', minWidth: 0 }} />
<button onClick={handleAddCategory} disabled={!newCategoryName.trim()} <button onClick={handleAddCategory} disabled={!newCategoryName.trim()}
style={{ background: 'var(--accent)', color: 'var(--accent-text)', border: 'none', borderRadius: 10, padding: '0 12px', cursor: 'pointer', display: 'flex', alignItems: 'center', opacity: newCategoryName.trim() ? 1 : 0.5, flexShrink: 0 }}> style={{ background: 'var(--accent)', color: 'var(--accent-text)', border: 'none', borderRadius: 10, padding: '0 12px', cursor: 'pointer', display: 'flex', alignItems: 'center', opacity: newCategoryName.trim() ? 1 : 0.5, flexShrink: 0 }}>
<Plus size={16} /> <Plus size={16} />
@@ -65,7 +65,7 @@ export default function BudgetPanel({ tripId, tripMembers = [] }: BudgetPanelPro
padding: '14px 16px 14px 22px', padding: '14px 16px 14px 22px',
display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, flexWrap: 'wrap', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, flexWrap: 'wrap',
}}> }}>
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600, color: 'var(--text-primary)', letterSpacing: '-0.01em', flexShrink: 0 }}> <h2 style={{ margin: 0, fontSize: 'calc(18px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', letterSpacing: '-0.01em', flexShrink: 0 }}>
{t('budget.title')} {t('budget.title')}
</h2> </h2>
<div className="flex flex-wrap max-md:!w-full max-md:!mt-2" style={{ alignItems: 'center', gap: 8, marginLeft: 'auto', flexShrink: 0 }}> <div className="flex flex-wrap max-md:!w-full max-md:!mt-2" style={{ alignItems: 'center', gap: 8, marginLeft: 'auto', flexShrink: 0 }}>
@@ -85,14 +85,14 @@ export default function BudgetPanel({ tripId, tripMembers = [] }: BudgetPanelPro
onChange={e => setNewCategoryName(e.target.value)} onChange={e => setNewCategoryName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleAddCategory() }} onKeyDown={e => { if (e.key === 'Enter') handleAddCategory() }}
placeholder={t('budget.categoryName')} placeholder={t('budget.categoryName')}
style={{ flex: 1, minWidth: 0, border: '1px solid var(--border-primary)', borderRadius: 10, padding: '9px 14px', fontSize: 13, outline: 'none', fontFamily: 'inherit', background: 'var(--bg-card)', color: 'var(--text-primary)' }} style={{ flex: 1, minWidth: 0, border: '1px solid var(--border-primary)', borderRadius: 10, padding: '9px 14px', fontSize: 'calc(13px * var(--fs-scale-text, 1))', outline: 'none', fontFamily: 'inherit', background: 'var(--bg-card)', color: 'var(--text-primary)' }}
/> />
<button onClick={handleAddCategory} disabled={!newCategoryName.trim()} <button onClick={handleAddCategory} disabled={!newCategoryName.trim()}
title={t('budget.addCategory')} title={t('budget.addCategory')}
style={{ style={{
appearance: 'none', border: 'none', cursor: newCategoryName.trim() ? 'pointer' : 'default', fontFamily: 'inherit', appearance: 'none', border: 'none', cursor: newCategoryName.trim() ? 'pointer' : 'default', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6, display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500, padding: '9px 14px', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0, background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
opacity: newCategoryName.trim() ? 1 : 0.4, opacity: newCategoryName.trim() ? 1 : 0.4,
transition: 'opacity 0.15s ease', transition: 'opacity 0.15s ease',
@@ -105,7 +105,7 @@ export default function BudgetPanel({ tripId, tripMembers = [] }: BudgetPanelPro
style={{ style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6, display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500, padding: '9px 14px', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0, background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
transition: 'opacity 0.15s ease', transition: 'opacity 0.15s ease',
}} }}
@@ -23,7 +23,7 @@ export default function AddItemRow({ onAdd, t }: AddItemRowProps) {
setTimeout(() => nameRef.current?.focus(), 50) setTimeout(() => nameRef.current?.focus(), 50)
} }
const inp = { border: '1px solid var(--border-primary)', borderRadius: 4, padding: '4px 6px', fontSize: 13, outline: 'none', fontFamily: 'inherit', width: '100%', background: 'var(--bg-input)', color: 'var(--text-primary)' } const inp = { border: '1px solid var(--border-primary)', borderRadius: 4, padding: '4px 6px', fontSize: 'calc(13px * var(--fs-scale-text, 1))', outline: 'none', fontFamily: 'inherit', width: '100%', background: 'var(--bg-input)', color: 'var(--text-primary)' }
return ( return (
<tr className="bg-surface-secondary"> <tr className="bg-surface-secondary">
@@ -44,9 +44,9 @@ export default function AddItemRow({ onAdd, t }: AddItemRowProps) {
<input value={days} onChange={e => setDays(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleAdd()} <input value={days} onChange={e => setDays(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleAdd()}
placeholder="-" inputMode="numeric" style={{ ...inp, textAlign: 'center', maxWidth: 60, margin: '0 auto' }} /> placeholder="-" inputMode="numeric" style={{ ...inp, textAlign: 'center', maxWidth: 60, margin: '0 auto' }} />
</td> </td>
<td className="hidden md:table-cell text-content-faint" style={{ padding: '4px 6px', fontSize: 12, textAlign: 'center' }}>-</td> <td className="hidden md:table-cell text-content-faint" style={{ padding: '4px 6px', fontSize: 'calc(12px * var(--fs-scale-text, 1))', textAlign: 'center' }}>-</td>
<td className="hidden md:table-cell text-content-faint" style={{ padding: '4px 6px', fontSize: 12, textAlign: 'center' }}>-</td> <td className="hidden md:table-cell text-content-faint" style={{ padding: '4px 6px', fontSize: 'calc(12px * var(--fs-scale-text, 1))', textAlign: 'center' }}>-</td>
<td className="hidden lg:table-cell text-content-faint" style={{ padding: '4px 6px', fontSize: 12, textAlign: 'center' }}>-</td> <td className="hidden lg:table-cell text-content-faint" style={{ padding: '4px 6px', fontSize: 'calc(12px * var(--fs-scale-text, 1))', textAlign: 'center' }}>-</td>
<td className="hidden sm:table-cell" style={{ padding: '4px 6px', textAlign: 'center' }}> <td className="hidden sm:table-cell" style={{ padding: '4px 6px', textAlign: 'center' }}>
<div style={{ maxWidth: 90, margin: '0 auto' }}> <div style={{ maxWidth: 90, margin: '0 auto' }}>
<CustomDatePicker value={expenseDate} onChange={setExpenseDate} placeholder="-" compact /> <CustomDatePicker value={expenseDate} onChange={setExpenseDate} placeholder="-" compact />
@@ -103,11 +103,11 @@ export default function BudgetCategoryTable({ cat, grouped, categoryColor, canEd
onChange={e => setEditingCat({ ...editingCat, value: e.target.value })} onChange={e => setEditingCat({ ...editingCat, value: e.target.value })}
onBlur={() => { handleRenameCategory(cat, editingCat.value); setEditingCat(null) }} onBlur={() => { handleRenameCategory(cat, editingCat.value); setEditingCat(null) }}
onKeyDown={e => { if (e.key === 'Enter') { handleRenameCategory(cat, editingCat.value); setEditingCat(null) } if (e.key === 'Escape') setEditingCat(null) }} onKeyDown={e => { if (e.key === 'Enter') { handleRenameCategory(cat, editingCat.value); setEditingCat(null) } if (e.key === 'Escape') setEditingCat(null) }}
style={{ fontWeight: 600, fontSize: 13, background: 'rgba(255,255,255,0.15)', border: 'none', borderRadius: 4, color: '#fff', padding: '1px 6px', outline: 'none', fontFamily: 'inherit', width: '100%' }} style={{ fontWeight: 600, fontSize: 'calc(13px * var(--fs-scale-text, 1))', background: 'rgba(255,255,255,0.15)', border: 'none', borderRadius: 4, color: '#fff', padding: '1px 6px', outline: 'none', fontFamily: 'inherit', width: '100%' }}
/> />
) : ( ) : (
<> <>
<span style={{ fontWeight: 600, fontSize: 13 }}>{cat}</span> <span style={{ fontWeight: 600, fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>{cat}</span>
{canEdit && ( {canEdit && (
<button onClick={() => setEditingCat({ name: cat, value: cat })} <button onClick={() => setEditingCat({ name: cat, value: cat })}
style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'rgba(255,255,255,0.4)', display: 'flex', padding: 1 }} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'rgba(255,255,255,0.4)', display: 'flex', padding: 1 }}
@@ -119,7 +119,7 @@ export default function BudgetCategoryTable({ cat, grouped, categoryColor, canEd
)} )}
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<span style={{ fontSize: 13, fontWeight: 500, opacity: 0.9 }}>{fmt(subtotal, currency)}</span> <span style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, opacity: 0.9 }}>{fmt(subtotal, currency)}</span>
{canEdit && ( {canEdit && (
<button onClick={() => handleDeleteCategory(cat)} title={t('budget.deleteCategory')} <button onClick={() => handleDeleteCategory(cat)} title={t('budget.deleteCategory')}
style={{ background: 'rgba(255,255,255,0.1)', border: 'none', borderRadius: 4, color: '#fff', cursor: 'pointer', padding: '3px 6px', display: 'flex', alignItems: 'center', opacity: 0.6 }} style={{ background: 'rgba(255,255,255,0.1)', border: 'none', borderRadius: 4, color: '#fff', cursor: 'pointer', padding: '3px 6px', display: 'flex', alignItems: 'center', opacity: 0.6 }}
@@ -233,7 +233,7 @@ export default function BudgetCategoryTable({ cat, grouped, categoryColor, canEd
<CustomDatePicker value={item.expense_date || ''} onChange={v => handleUpdateField(item.id, 'expense_date', v || null)} placeholder="—" compact borderless /> <CustomDatePicker value={item.expense_date || ''} onChange={v => handleUpdateField(item.id, 'expense_date', v || null)} placeholder="—" compact borderless />
</div> </div>
) : ( ) : (
<span style={{ fontSize: 11, color: item.expense_date ? 'var(--text-secondary)' : 'var(--text-faint)' }}>{item.expense_date || '—'}</span> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: item.expense_date ? 'var(--text-secondary)' : 'var(--text-faint)' }}>{item.expense_date || '—'}</span>
)} )}
</td> </td>
<td className="hidden sm:table-cell" style={td}><InlineEditCell value={item.note} onSave={v => handleUpdateField(item.id, 'note', v)} placeholder={t('budget.table.note')} locale={locale} editTooltip={t('budget.editTooltip')} readOnly={!canEdit} /></td> <td className="hidden sm:table-cell" style={td}><InlineEditCell value={item.note} onSave={v => handleUpdateField(item.id, 'note', v)} placeholder={t('budget.table.note')} locale={locale} editTooltip={t('budget.editTooltip')} readOnly={!canEdit} /></td>
@@ -50,7 +50,7 @@ export default function InlineEditCell({ value, onSave, type = 'text', style = {
return <input ref={inputRef} type="text" inputMode={type === 'number' ? 'decimal' : 'text'} value={editValue} return <input ref={inputRef} type="text" inputMode={type === 'number' ? 'decimal' : 'text'} value={editValue}
onChange={e => setEditValue(e.target.value)} onBlur={save} onPaste={handlePaste} onChange={e => setEditValue(e.target.value)} onBlur={save} onPaste={handlePaste}
onKeyDown={e => { if (e.key === 'Enter') save(); if (e.key === 'Escape') { setEditValue(value ?? ''); setEditing(false) } }} onKeyDown={e => { if (e.key === 'Enter') save(); if (e.key === 'Escape') { setEditValue(value ?? ''); setEditing(false) } }}
style={{ width: '100%', border: '1px solid var(--accent)', borderRadius: 4, padding: '4px 6px', fontSize: 13, outline: 'none', background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', ...style }} style={{ width: '100%', border: '1px solid var(--accent)', borderRadius: 4, padding: '4px 6px', fontSize: 'calc(13px * var(--fs-scale-text, 1))', outline: 'none', background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', ...style }}
placeholder={placeholder} /> placeholder={placeholder} />
} }
@@ -62,7 +62,7 @@ export default function InlineEditCell({ value, onSave, type = 'text', style = {
<div onClick={() => { if (readOnly) return; setEditValue(value ?? ''); setEditing(true) }} title={readOnly ? undefined : editTooltip} <div onClick={() => { if (readOnly) return; setEditValue(value ?? ''); setEditing(true) }} title={readOnly ? undefined : editTooltip}
style={{ cursor: readOnly ? 'default' : 'pointer', padding: '2px 4px', borderRadius: 4, minHeight: 22, display: 'flex', alignItems: 'center', style={{ cursor: readOnly ? 'default' : 'pointer', padding: '2px 4px', borderRadius: 4, minHeight: 22, display: 'flex', alignItems: 'center',
justifyContent: style?.textAlign === 'center' ? 'center' : 'flex-start', transition: 'background 0.15s', justifyContent: style?.textAlign === 'center' ? 'center' : 'flex-start', transition: 'background 0.15s',
color: display ? 'var(--text-primary)' : 'var(--text-faint)', fontSize: 13, ...style }} color: display ? 'var(--text-primary)' : 'var(--text-faint)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', ...style }}
onMouseEnter={e => { if (!readOnly) e.currentTarget.style.background = 'var(--bg-hover)' }} onMouseEnter={e => { if (!readOnly) e.currentTarget.style.background = 'var(--bg-hover)' }}
onMouseLeave={e => { if (!readOnly) e.currentTarget.style.background = 'transparent' }}> onMouseLeave={e => { if (!readOnly) e.currentTarget.style.background = 'transparent' }}>
{display || placeholder || '-'} {display || placeholder || '-'}
@@ -56,13 +56,13 @@ export function ChipWithTooltip({ label, avatarUrl, size = 20, paid, onClick }:
pointerEvents: 'none', zIndex: 10000, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 10000, whiteSpace: 'nowrap',
display: 'flex', alignItems: 'center', gap: 5, display: 'flex', alignItems: 'center', gap: 5,
background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)', background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)',
fontSize: 11, fontWeight: 500, padding: '5px 10px', borderRadius: 8, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, padding: '5px 10px', borderRadius: 8,
boxShadow: '0 4px 12px rgba(0,0,0,0.15)', border: '1px solid var(--border-faint, #e5e7eb)', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', border: '1px solid var(--border-faint, #e5e7eb)',
}}> }}>
{label} {label}
{paid && ( {paid && (
<span style={{ <span style={{
fontSize: 9, fontWeight: 700, padding: '1px 5px', borderRadius: 4, fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 700, padding: '1px 5px', borderRadius: 4,
background: 'rgba(34,197,94,0.15)', color: '#16a34a', background: 'rgba(34,197,94,0.15)', color: '#16a34a',
textTransform: 'uppercase', letterSpacing: '0.03em', textTransform: 'uppercase', letterSpacing: '0.03em',
}}>Paid</span> }}>Paid</span>
@@ -151,14 +151,14 @@ export default function BudgetMemberChips({ members = [], tripMembers = [], onSe
<button key={tm.id} onClick={() => toggleMember(tm.id)} style={{ <button key={tm.id} onClick={() => toggleMember(tm.id)} style={{
display: 'flex', alignItems: 'center', gap: 6, width: '100%', padding: '5px 8px', display: 'flex', alignItems: 'center', gap: 6, width: '100%', padding: '5px 8px',
borderRadius: 6, border: 'none', background: isActive ? 'var(--bg-hover)' : 'none', cursor: 'pointer', borderRadius: 6, border: 'none', background: isActive ? 'var(--bg-hover)' : 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 11, color: 'var(--text-primary)', textAlign: 'left', fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-primary)', textAlign: 'left',
}} }}
onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = 'var(--bg-hover)' }} onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = 'var(--bg-hover)' }}
onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = 'none' }} onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = 'none' }}
> >
<div style={{ <div style={{
width: 18, height: 18, borderRadius: '50%', background: 'var(--bg-tertiary)', width: 18, height: 18, borderRadius: '50%', background: 'var(--bg-tertiary)',
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 8, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 'calc(8px * var(--fs-scale-text, 1))', fontWeight: 700,
color: 'var(--text-muted)', overflow: 'hidden', flexShrink: 0, color: 'var(--text-muted)', overflow: 'hidden', flexShrink: 0,
}}> }}>
{tm.avatar_url {tm.avatar_url
@@ -51,10 +51,10 @@ export default function PerPersonInline({ tripId, budgetItems, currency, locale,
<div key={p.user_id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '6px 0' }}> <div key={p.user_id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '6px 0' }}>
<RingAvatar userId={p.user_id} username={p.username} avatarUrl={p.avatar_url} size={34} innerBg={theme.centerBg} textColor={theme.text} /> <RingAvatar userId={p.user_id} username={p.username} avatarUrl={p.avatar_url} size={34} innerBg={theme.centerBg} textColor={theme.text} />
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13.5, fontWeight: 500, letterSpacing: '-0.01em', color: theme.text }}>{p.username}</div> <div style={{ fontSize: 'calc(13.5px * var(--fs-scale-text, 1))', fontWeight: 500, letterSpacing: '-0.01em', color: theme.text }}>{p.username}</div>
<div style={{ fontSize: 11, color: theme.faint, marginTop: 1 }}>{percent}%</div> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: theme.faint, marginTop: 1 }}>{percent}%</div>
</div> </div>
<div style={{ fontSize: 13.5, fontWeight: 600, color: theme.text, letterSpacing: '-0.01em' }}>{fmt(p.total_assigned)}</div> <div style={{ fontSize: 'calc(13.5px * var(--fs-scale-text, 1))', fontWeight: 600, color: theme.text, letterSpacing: '-0.01em' }}>{fmt(p.total_assigned)}</div>
</div> </div>
) )
})} })}
@@ -46,7 +46,7 @@ export default function PieChart({ segments, size = 200, totalLabel }: PieChartP
boxShadow: 'inset 0 0 12px rgba(0,0,0,0.04)', boxShadow: 'inset 0 0 12px rgba(0,0,0,0.04)',
}}> }}>
<Wallet size={18} color="var(--text-faint)" style={{ marginBottom: 2 }} /> <Wallet size={18} color="var(--text-faint)" style={{ marginBottom: 2 }} />
<span style={{ fontSize: 10, color: 'var(--text-faint)', fontWeight: 500 }}>{totalLabel}</span> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 500 }}>{totalLabel}</span>
</div> </div>
</div> </div>
) )
@@ -47,7 +47,7 @@ export default function BudgetSummary({ theme, currency, locale, grandTotal, has
<Wallet size={20} strokeWidth={2} /> <Wallet size={20} strokeWidth={2} />
</div> </div>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 11, color: theme.faint, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.09em' }}>{t('budget.totalBudget')}</div> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: theme.faint, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.09em' }}>{t('budget.totalBudget')}</div>
</div> </div>
</div> </div>
@@ -58,13 +58,13 @@ export default function BudgetSummary({ theme, currency, locale, grandTotal, has
const [integerPart, decimalPart] = decimals > 0 ? full.split(sep) : [full, ''] const [integerPart, decimalPart] = decimals > 0 ? full.split(sep) : [full, '']
return ( return (
<div style={{ display: 'flex', alignItems: 'baseline', gap: 4, letterSpacing: '-0.03em', lineHeight: 1 }}> <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, letterSpacing: '-0.03em', lineHeight: 1 }}>
<span style={{ fontSize: 38, fontWeight: 700 }}>{integerPart}</span> <span style={{ fontSize: 'calc(38px * var(--fs-scale-text, 1))', fontWeight: 700 }}>{integerPart}</span>
{decimalPart && <span style={{ fontSize: 22, fontWeight: 500, color: theme.sub }}>{sep}{decimalPart}</span>} {decimalPart && <span style={{ fontSize: 'calc(22px * var(--fs-scale-text, 1))', fontWeight: 500, color: theme.sub }}>{sep}{decimalPart}</span>}
<span style={{ fontSize: 22, fontWeight: 500, color: theme.sub, marginLeft: 2 }}>{SYMBOLS[currency] || currency}</span> <span style={{ fontSize: 'calc(22px * var(--fs-scale-text, 1))', fontWeight: 500, color: theme.sub, marginLeft: 2 }}>{SYMBOLS[currency] || currency}</span>
</div> </div>
) )
})()} })()}
<div style={{ color: theme.faint, fontSize: 12, marginTop: 8, fontWeight: 500, letterSpacing: '0.04em', display: 'flex', alignItems: 'center', gap: 6 }}> <div style={{ color: theme.faint, fontSize: 'calc(12px * var(--fs-scale-text, 1))', marginTop: 8, fontWeight: 500, letterSpacing: '0.04em', display: 'flex', alignItems: 'center', gap: 6 }}>
<span>{currency}</span> <span>{currency}</span>
</div> </div>
@@ -78,7 +78,7 @@ export default function BudgetSummary({ theme, currency, locale, grandTotal, has
<button onClick={() => setSettlementOpen(v => !v)} style={{ <button onClick={() => setSettlementOpen(v => !v)} style={{
display: 'flex', alignItems: 'center', gap: 6, width: '100%', display: 'flex', alignItems: 'center', gap: 6, width: '100%',
background: 'none', border: 'none', cursor: 'pointer', padding: 0, fontFamily: 'inherit', background: 'none', border: 'none', cursor: 'pointer', padding: 0, fontFamily: 'inherit',
color: theme.sub, fontSize: 11, fontWeight: 600, letterSpacing: 0.5, color: theme.sub, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, letterSpacing: 0.5,
}}> }}>
{settlementOpen ? <ChevronDown size={13} /> : <ChevronRight size={13} />} {settlementOpen ? <ChevronDown size={13} /> : <ChevronRight size={13} />}
{t('budget.settlement')} {t('budget.settlement')}
@@ -95,7 +95,7 @@ export default function BudgetSummary({ theme, currency, locale, grandTotal, has
marginTop: 6, width: 220, padding: '10px 12px', borderRadius: 10, zIndex: 100, marginTop: 6, width: 220, padding: '10px 12px', borderRadius: 10, zIndex: 100,
background: 'var(--bg-card)', border: '1px solid var(--border-faint)', background: 'var(--bg-card)', border: '1px solid var(--border-faint)',
boxShadow: '0 4px 16px rgba(0,0,0,0.12)', boxShadow: '0 4px 16px rgba(0,0,0,0.12)',
fontSize: 11, fontWeight: 400, color: 'var(--text-secondary)', lineHeight: 1.5, textAlign: 'left', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 400, color: 'var(--text-secondary)', lineHeight: 1.5, textAlign: 'left',
}}> }}>
{t('budget.settlementInfo')} {t('budget.settlementInfo')}
</div> </div>
@@ -117,7 +117,7 @@ export default function BudgetSummary({ theme, currency, locale, grandTotal, has
> >
<RingAvatar userId={flow.from.user_id} username={flow.from.username} avatarUrl={flow.from.avatar_url} size={32} innerBg={theme.centerBg} textColor={theme.text} /> <RingAvatar userId={flow.from.user_id} username={flow.from.username} avatarUrl={flow.from.avatar_url} size={32} innerBg={theme.centerBg} textColor={theme.text} />
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5 }}> <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5 }}>
<span style={{ fontSize: 13, fontWeight: 700, color: '#ef4444', letterSpacing: '-0.01em' }}> <span style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 700, color: '#ef4444', letterSpacing: '-0.01em' }}>
{fmt(flow.amount, currency)} {fmt(flow.amount, currency)}
</span> </span>
<div style={{ width: '100%', height: 2, borderRadius: 2, background: 'linear-gradient(90deg, rgba(239,68,68,0.1), rgba(239,68,68,0.55), rgba(239,68,68,0.3))', position: 'relative' }}> <div style={{ width: '100%', height: 2, borderRadius: 2, background: 'linear-gradient(90deg, rgba(239,68,68,0.1), rgba(239,68,68,0.55), rgba(239,68,68,0.3))', position: 'relative' }}>
@@ -130,7 +130,7 @@ export default function BudgetSummary({ theme, currency, locale, grandTotal, has
{settlement.balances.filter(b => Math.abs(b.balance) > 0.01).length > 0 && ( {settlement.balances.filter(b => Math.abs(b.balance) > 0.01).length > 0 && (
<div style={{ marginTop: 8, borderTop: `1px solid ${theme.divider}`, paddingTop: 12 }}> <div style={{ marginTop: 8, borderTop: `1px solid ${theme.divider}`, paddingTop: 12 }}>
<div style={{ fontSize: 10, fontWeight: 700, color: theme.faint, textTransform: 'uppercase', letterSpacing: '0.11em', marginBottom: 10 }}> <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, color: theme.faint, textTransform: 'uppercase', letterSpacing: '0.11em', marginBottom: 10 }}>
{t('budget.netBalances')} {t('budget.netBalances')}
</div> </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
@@ -140,13 +140,13 @@ export default function BudgetSummary({ theme, currency, locale, grandTotal, has
return ( return (
<div key={b.user_id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '5px 0' }}> <div key={b.user_id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '5px 0' }}>
<RingAvatar userId={b.user_id} username={b.username} avatarUrl={b.avatar_url} size={26} innerBg={theme.centerBg} textColor={theme.text} /> <RingAvatar userId={b.user_id} username={b.username} avatarUrl={b.avatar_url} size={26} innerBg={theme.centerBg} textColor={theme.text} />
<span style={{ flex: 1, fontSize: 13, color: theme.text, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <span style={{ flex: 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: theme.text, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{b.username} {b.username}
</span> </span>
<span style={{ <span style={{
display: 'inline-flex', alignItems: 'center', gap: 4, display: 'inline-flex', alignItems: 'center', gap: 4,
padding: '4px 10px', borderRadius: 8, padding: '4px 10px', borderRadius: 8,
fontSize: 12, fontWeight: 700, letterSpacing: '-0.01em', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 700, letterSpacing: '-0.01em',
background: positive ? 'rgba(16,185,129,0.13)' : 'rgba(239,68,68,0.13)', background: positive ? 'rgba(16,185,129,0.13)' : 'rgba(239,68,68,0.13)',
color: positive ? '#10b981' : '#ef4444', color: positive ? '#10b981' : '#ef4444',
}}> }}>
@@ -192,7 +192,7 @@ export default function BudgetSummary({ theme, currency, locale, grandTotal, has
<PieChartIcon size={18} strokeWidth={2} /> <PieChartIcon size={18} strokeWidth={2} />
</div> </div>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 11, color: theme.faint, textTransform: 'uppercase', letterSpacing: '0.09em', fontWeight: 600 }}>{t('budget.byCategory')}</div> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: theme.faint, textTransform: 'uppercase', letterSpacing: '0.09em', fontWeight: 600 }}>{t('budget.byCategory')}</div>
</div> </div>
</div> </div>
@@ -226,12 +226,12 @@ export default function BudgetSummary({ theme, currency, locale, grandTotal, has
})} })}
</svg> </svg>
<div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', textAlign: 'center', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, pointerEvents: 'none' }}> <div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', textAlign: 'center', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, pointerEvents: 'none' }}>
<div style={{ fontSize: 10.5, color: theme.faint, textTransform: 'uppercase', letterSpacing: '0.12em', fontWeight: 700 }}>{t('budget.total')}</div> <div style={{ fontSize: 'calc(10.5px * var(--fs-scale-text, 1))', color: theme.faint, textTransform: 'uppercase', letterSpacing: '0.12em', fontWeight: 700 }}>{t('budget.total')}</div>
<div style={{ fontSize: 22, fontWeight: 700, letterSpacing: '-0.03em', lineHeight: 1, display: 'flex', alignItems: 'baseline', gap: 2 }}> <div style={{ fontSize: 'calc(22px * var(--fs-scale-text, 1))', fontWeight: 700, letterSpacing: '-0.03em', lineHeight: 1, display: 'flex', alignItems: 'baseline', gap: 2 }}>
<span>{totalInt}</span> <span>{totalInt}</span>
{totalDec && <span style={{ fontSize: 13, fontWeight: 500, color: theme.sub }}>{decimalSep}{totalDec}</span>} {totalDec && <span style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, color: theme.sub }}>{decimalSep}{totalDec}</span>}
</div> </div>
<div style={{ fontSize: 10.5, color: theme.faint, fontWeight: 500, marginTop: 2 }}>{currency}</div> <div style={{ fontSize: 'calc(10.5px * var(--fs-scale-text, 1))', color: theme.faint, fontWeight: 500, marginTop: 2 }}>{currency}</div>
</div> </div>
</div> </div>
@@ -256,13 +256,13 @@ export default function BudgetSummary({ theme, currency, locale, grandTotal, has
boxShadow: `0 0 12px ${seg.color}80`, boxShadow: `0 0 12px ${seg.color}80`,
}} /> }} />
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13.5, fontWeight: 500, letterSpacing: '-0.01em', color: theme.text, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{seg.name}</div> <div style={{ fontSize: 'calc(13.5px * var(--fs-scale-text, 1))', fontWeight: 500, letterSpacing: '-0.01em', color: theme.text, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{seg.name}</div>
<div style={{ fontSize: 11.5, color: theme.sub, fontWeight: 500, marginTop: 1 }}>{fmt(seg.value, currency)}</div> <div style={{ fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', color: theme.sub, fontWeight: 500, marginTop: 1 }}>{fmt(seg.value, currency)}</div>
</div> </div>
<span style={{ <span style={{
flexShrink: 0, flexShrink: 0,
padding: '4px 9px', borderRadius: 7, padding: '4px 9px', borderRadius: 7,
fontSize: 11, fontWeight: 700, letterSpacing: '-0.01em', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 700, letterSpacing: '-0.01em',
background: `${seg.color}26`, background: `${seg.color}26`,
border: `1px solid ${seg.color}40`, border: `1px solid ${seg.color}40`,
color: chipColor, color: chipColor,
+69 -69
View File
@@ -223,17 +223,17 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
<div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 24, marginBottom: 28, flexWrap: 'wrap' }}> <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 24, marginBottom: 28, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
{dateMeta && ( {dateMeta && (
<span className="bg-surface-card border border-edge text-content-muted" style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '8px 14px', borderRadius: 999, fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap' }}> <span className="bg-surface-card border border-edge text-content-muted" style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '8px 14px', borderRadius: 999, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, whiteSpace: 'nowrap' }}>
{dateMeta.range} · <b className="text-content">{t('costs.daysCount', { count: dateMeta.days })}</b> {dateMeta.range} · <b className="text-content">{t('costs.daysCount', { count: dateMeta.days })}</b>
</span> </span>
)} )}
<span className="bg-surface-card border border-edge text-content-muted" style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '8px 14px 8px 10px', borderRadius: 999, fontSize: 13, fontWeight: 500 }}> <span className="bg-surface-card border border-edge text-content-muted" style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '8px 14px 8px 10px', borderRadius: 999, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500 }}>
<span style={{ display: 'inline-flex' }}> <span style={{ display: 'inline-flex' }}>
{people.slice(0, 4).map((p, i) => { {people.slice(0, 4).map((p, i) => {
const common = { width: 22, height: 22, borderRadius: '50%', border: '2px solid var(--bg-card)', marginLeft: i ? -8 : 0, flexShrink: 0 } as const const common = { width: 22, height: 22, borderRadius: '50%', border: '2px solid var(--bg-card)', marginLeft: i ? -8 : 0, flexShrink: 0 } as const
return p.avatar_url return p.avatar_url
? <img key={p.id} src={p.avatar_url} alt="" style={{ ...common, objectFit: 'cover', display: 'block' }} /> ? <img key={p.id} src={p.avatar_url} alt="" style={{ ...common, objectFit: 'cover', display: 'block' }} />
: <span key={p.id} style={{ ...common, background: colorFor(p.id), color: '#fff', display: 'grid', placeItems: 'center', fontSize: 9, fontWeight: 700 }}>{(p.id === me ? t('costs.youShort') : p.username.charAt(0)).toUpperCase()}</span> : <span key={p.id} style={{ ...common, background: colorFor(p.id), color: '#fff', display: 'grid', placeItems: 'center', fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 700 }}>{(p.id === me ? t('costs.youShort') : p.username.charAt(0)).toUpperCase()}</span>
})} })}
</span> </span>
<b className="text-content">{t('costs.travelers', { count: people.length })}</b> <b className="text-content">{t('costs.travelers', { count: people.length })}</b>
@@ -243,12 +243,12 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
<div style={{ display: 'flex', gap: 10 }}> <div style={{ display: 'flex', gap: 10 }}>
<button onClick={settleAll} disabled={!(settlement?.flows || []).length} <button onClick={settleAll} disabled={!(settlement?.flows || []).length}
className="bg-surface-card border border-edge text-content disabled:opacity-40" className="bg-surface-card border border-edge text-content disabled:opacity-40"
style={{ display: 'inline-flex', alignItems: 'center', gap: 7, padding: '10px 16px', borderRadius: 12, fontSize: 14, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit' }}> style={{ display: 'inline-flex', alignItems: 'center', gap: 7, padding: '10px 16px', borderRadius: 12, fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit' }}>
<Check size={16} /> {t('costs.settleUp')} <Check size={16} /> {t('costs.settleUp')}
</button> </button>
<button onClick={() => { setEditing(null); setModalOpen(true) }} <button onClick={() => { setEditing(null); setModalOpen(true) }}
className="bg-[var(--text-primary)] text-[var(--bg-primary)]" className="bg-[var(--text-primary)] text-[var(--bg-primary)]"
style={{ display: 'inline-flex', alignItems: 'center', gap: 7, padding: '10px 18px', borderRadius: 12, fontSize: 14, fontWeight: 600, border: 0, cursor: 'pointer', fontFamily: 'inherit' }}> style={{ display: 'inline-flex', alignItems: 'center', gap: 7, padding: '10px 18px', borderRadius: 12, fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, border: 0, cursor: 'pointer', fontFamily: 'inherit' }}>
<Plus size={16} /> {t('costs.addExpense')} <Plus size={16} /> {t('costs.addExpense')}
</button> </button>
</div> </div>
@@ -277,20 +277,20 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
{/* expenses */} {/* expenses */}
<div> <div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, gap: 12, flexWrap: 'wrap' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, gap: 12, flexWrap: 'wrap' }}>
<h3 className="text-content" style={{ fontSize: 24, fontWeight: 600, letterSpacing: '-0.025em', margin: 0 }}> <h3 className="text-content" style={{ fontSize: 'calc(24px * var(--fs-scale-text, 1))', fontWeight: 600, letterSpacing: '-0.025em', margin: 0 }}>
{t('costs.expenses')} {t('costs.expenses')}
</h3> </h3>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}> <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<div className="bg-surface-input border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 6, borderRadius: 10, padding: '0 10px', height: 34 }}> <div className="bg-surface-input border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 6, borderRadius: 10, padding: '0 10px', height: 34 }}>
<Search size={15} className="text-content-faint" /> <Search size={15} className="text-content-faint" />
<input value={search} onChange={e => setSearch(e.target.value)} placeholder={t('costs.searchPlaceholder')} <input value={search} onChange={e => setSearch(e.target.value)} placeholder={t('costs.searchPlaceholder')}
className="text-content" style={{ border: 0, background: 'none', outline: 'none', fontSize: 13, width: 150, fontFamily: 'inherit' }} /> className="text-content" style={{ border: 0, background: 'none', outline: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', width: 150, fontFamily: 'inherit' }} />
</div> </div>
<div className="bg-surface-secondary" style={{ display: 'flex', borderRadius: 9, padding: 3 }}> <div className="bg-surface-secondary" style={{ display: 'flex', borderRadius: 9, padding: 3 }}>
{(['all', 'mine', 'owed'] as const).map(f => ( {(['all', 'mine', 'owed'] as const).map(f => (
<button key={f} onClick={() => setFilter(f)} <button key={f} onClick={() => setFilter(f)}
className={filter === f ? 'bg-surface-card text-content' : 'text-content-muted'} className={filter === f ? 'bg-surface-card text-content' : 'text-content-muted'}
style={{ padding: '6px 11px', fontSize: 12, borderRadius: 7, fontWeight: 500, border: 0, cursor: 'pointer', fontFamily: 'inherit' }}> style={{ padding: '6px 11px', fontSize: 'calc(12px * var(--fs-scale-text, 1))', borderRadius: 7, fontWeight: 500, border: 0, cursor: 'pointer', fontFamily: 'inherit' }}>
{t('costs.filter.' + f)} {t('costs.filter.' + f)}
</button> </button>
))} ))}
@@ -307,7 +307,7 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
return ( return (
<div key={g.day} style={{ marginBottom: 22 }}> <div key={g.day} style={{ marginBottom: 22 }}>
<div className={labelCls} style={{ display: 'flex', alignItems: 'center', margin: '0 0 10px 4px' }}> <div className={labelCls} style={{ display: 'flex', alignItems: 'center', margin: '0 0 10px 4px' }}>
{g.day}<span className="text-content-muted" style={{ marginLeft: 'auto', textTransform: 'none', letterSpacing: 0, fontWeight: 500, fontSize: 12 }}>{t('costs.spent', { amount: fmt(dtot) })}</span> {g.day}<span className="text-content-muted" style={{ marginLeft: 'auto', textTransform: 'none', letterSpacing: 0, fontWeight: 500, fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>{t('costs.spent', { amount: fmt(dtot) })}</span>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{g.entries.map(en => en.kind === 'expense' {g.entries.map(en => en.kind === 'expense'
@@ -328,7 +328,7 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
{canEdit && ( {canEdit && (
<button onClick={() => setAddingPayment(true)} <button onClick={() => setAddingPayment(true)}
className="text-content-muted bg-surface-secondary border border-edge" className="text-content-muted bg-surface-secondary border border-edge"
style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '5px 9px', borderRadius: 8, fontSize: 11.5, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}> style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '5px 9px', borderRadius: 8, fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
<Plus size={13} /> {t('costs.addPayment')} <Plus size={13} /> {t('costs.addPayment')}
</button> </button>
)} )}
@@ -407,8 +407,8 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
if (flows.length === 0) return ( if (flows.length === 0) return (
<div style={{ textAlign: 'center', padding: '14px 8px' }}> <div style={{ textAlign: 'center', padding: '14px 8px' }}>
<div style={{ width: 46, height: 46, borderRadius: '50%', margin: '0 auto 10px', display: 'grid', placeItems: 'center', background: 'rgba(22,163,74,0.12)', color: '#16a34a' }}><Check size={22} /></div> <div style={{ width: 46, height: 46, borderRadius: '50%', margin: '0 auto 10px', display: 'grid', placeItems: 'center', background: 'rgba(22,163,74,0.12)', color: '#16a34a' }}><Check size={22} /></div>
<div className="text-content" style={{ fontSize: 14.5, fontWeight: 600 }}>{t('costs.everyoneSquare')}</div> <div className="text-content" style={{ fontSize: 'calc(14.5px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('costs.everyoneSquare')}</div>
<div className="text-content-faint" style={{ fontSize: 12, marginTop: 2 }}>{t('costs.nothingOutstanding')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', marginTop: 2 }}>{t('costs.nothingOutstanding')}</div>
</div> </div>
) )
return ( return (
@@ -419,8 +419,8 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
<Avatar id={f.from.user_id} size={32} /><ArrowRight size={15} className="text-content-faint" /><Avatar id={f.to.user_id} size={32} /> <Avatar id={f.from.user_id} size={32} /><ArrowRight size={15} className="text-content-faint" /><Avatar id={f.to.user_id} size={32} />
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
<span className="text-content" style={{ fontSize: 14, fontWeight: 700 }}>{fmt(f.amount)}</span> <span className="text-content" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700 }}>{fmt(f.amount)}</span>
{canEdit && <button onClick={() => settleFlow(f.from.user_id, f.to.user_id, f.amount)} className="bg-[var(--text-primary)] text-[var(--bg-primary)]" style={{ padding: '7px 12px', borderRadius: 9, fontSize: 12, fontWeight: 600, border: 0, cursor: 'pointer', fontFamily: 'inherit' }}>{t('costs.settle')}</button>} {canEdit && <button onClick={() => settleFlow(f.from.user_id, f.to.user_id, f.amount)} className="bg-[var(--text-primary)] text-[var(--bg-primary)]" style={{ padding: '7px 12px', borderRadius: 9, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, border: 0, cursor: 'pointer', fontFamily: 'inherit' }}>{t('costs.settle')}</button>}
</div> </div>
</div> </div>
))} ))}
@@ -434,14 +434,14 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, paddingTop: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16, paddingTop: 8 }}>
{/* Total card */} {/* Total card */}
<section style={{ background: 'linear-gradient(135deg,#1f2937,#111827)', color: '#fff', borderRadius: 22, padding: '20px 20px 16px', boxShadow: '0 8px 24px -8px rgba(0,0,0,0.28)' }}> <section style={{ background: 'linear-gradient(135deg,#1f2937,#111827)', color: '#fff', borderRadius: 22, padding: '20px 20px 16px', boxShadow: '0 8px 24px -8px rgba(0,0,0,0.28)' }}>
<div style={{ fontSize: 11.5, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'rgba(255,255,255,0.6)', fontWeight: 600 }}>{t('costs.totalSpend')}</div> <div style={{ fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', textTransform: 'uppercase', letterSpacing: '0.12em', color: 'rgba(255,255,255,0.6)', fontWeight: 600 }}>{t('costs.totalSpend')}</div>
<div style={{ fontSize: 44, fontWeight: 700, letterSpacing: '-0.04em', lineHeight: 1, marginTop: 8, display: 'flex', alignItems: 'baseline' }}>{bigMoney(totals.totalSpend, 24, 'rgba(255,255,255,0.6)')}</div> <div style={{ fontSize: 'calc(44px * var(--fs-scale-text, 1))', fontWeight: 700, letterSpacing: '-0.04em', lineHeight: 1, marginTop: 8, display: 'flex', alignItems: 'baseline' }}>{bigMoney(totals.totalSpend, 24, 'rgba(255,255,255,0.6)')}</div>
<div style={{ display: 'flex', gap: 18, marginTop: 12, fontSize: 12, color: 'rgba(255,255,255,0.6)', flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: 18, marginTop: 12, fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'rgba(255,255,255,0.6)', flexWrap: 'wrap' }}>
<span>{t('costs.yourShare')} · <b style={{ color: '#fff', fontWeight: 600 }}>{fmt0(totals.myShare)}</b></span> <span>{t('costs.yourShare')} · <b style={{ color: '#fff', fontWeight: 600 }}>{fmt0(totals.myShare)}</b></span>
<span>{t('costs.youPaid')} · <b style={{ color: '#fff', fontWeight: 600 }}>{fmt0(totals.myPaid)}</b></span> <span>{t('costs.youPaid')} · <b style={{ color: '#fff', fontWeight: 600 }}>{fmt0(totals.myPaid)}</b></span>
</div> </div>
{canEdit && ( {canEdit && (
<button onClick={() => { setEditing(null); setModalOpen(true) }} style={{ marginTop: 16, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, background: 'rgba(255,255,255,0.14)', border: '1px solid rgba(255,255,255,0.16)', color: '#fff', padding: 13, borderRadius: 14, fontSize: 14, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}> <button onClick={() => { setEditing(null); setModalOpen(true) }} style={{ marginTop: 16, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, background: 'rgba(255,255,255,0.14)', border: '1px solid rgba(255,255,255,0.16)', color: '#fff', padding: 13, borderRadius: 14, fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
<Plus size={17} /> {t('costs.addExpense')} <Plus size={17} /> {t('costs.addExpense')}
</button> </button>
)} )}
@@ -451,24 +451,24 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
<div className={cardCls} style={{ borderRadius: 18, padding: 16 }}> <div className={cardCls} style={{ borderRadius: 18, padding: 16 }}>
<div style={{ width: 34, height: 34, borderRadius: 10, display: 'grid', placeItems: 'center', marginBottom: 10, background: '#dc262622', color: '#dc2626' }}><ArrowDown size={17} /></div> <div style={{ width: 34, height: 34, borderRadius: 10, display: 'grid', placeItems: 'center', marginBottom: 10, background: '#dc262622', color: '#dc2626' }}><ArrowDown size={17} /></div>
<div className="text-content" style={{ fontSize: 12.5, fontWeight: 600 }}>{t('costs.youOwe')}</div> <div className="text-content" style={{ fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('costs.youOwe')}</div>
<div className="text-content-faint" style={{ fontSize: 10.5 }}>{t('costs.youOweSub')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(10.5px * var(--fs-scale-text, 1))' }}>{t('costs.youOweSub')}</div>
<div style={{ fontSize: 27, fontWeight: 700, letterSpacing: '-0.03em', lineHeight: 1, marginTop: 12, display: 'flex', alignItems: 'baseline', color: '#dc2626' }}>{bigMoney(totals.owe, 16, 'var(--c-ink3)')}</div> <div style={{ fontSize: 'calc(27px * var(--fs-scale-text, 1))', fontWeight: 700, letterSpacing: '-0.03em', lineHeight: 1, marginTop: 12, display: 'flex', alignItems: 'baseline', color: '#dc2626' }}>{bigMoney(totals.owe, 16, 'var(--c-ink3)')}</div>
</div> </div>
<div className={cardCls} style={{ borderRadius: 18, padding: 16 }}> <div className={cardCls} style={{ borderRadius: 18, padding: 16 }}>
<div style={{ width: 34, height: 34, borderRadius: 10, display: 'grid', placeItems: 'center', marginBottom: 10, background: '#16a34a22', color: '#16a34a' }}><ArrowUp size={17} /></div> <div style={{ width: 34, height: 34, borderRadius: 10, display: 'grid', placeItems: 'center', marginBottom: 10, background: '#16a34a22', color: '#16a34a' }}><ArrowUp size={17} /></div>
<div className="text-content" style={{ fontSize: 12.5, fontWeight: 600 }}>{t('costs.youreOwed')}</div> <div className="text-content" style={{ fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('costs.youreOwed')}</div>
<div className="text-content-faint" style={{ fontSize: 10.5 }}>{t('costs.youreOwedSub')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(10.5px * var(--fs-scale-text, 1))' }}>{t('costs.youreOwedSub')}</div>
<div style={{ fontSize: 27, fontWeight: 700, letterSpacing: '-0.03em', lineHeight: 1, marginTop: 12, display: 'flex', alignItems: 'baseline', color: '#16a34a' }}>{bigMoney(totals.owed, 16, 'var(--c-ink3)')}</div> <div style={{ fontSize: 'calc(27px * var(--fs-scale-text, 1))', fontWeight: 700, letterSpacing: '-0.03em', lineHeight: 1, marginTop: 12, display: 'flex', alignItems: 'baseline', color: '#16a34a' }}>{bigMoney(totals.owed, 16, 'var(--c-ink3)')}</div>
</div> </div>
</div> </div>
{/* Settle up */} {/* Settle up */}
<div className={cardCls} style={{ borderRadius: 18, padding: 16 }}> <div className={cardCls} style={{ borderRadius: 18, padding: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14, gap: 8 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14, gap: 8 }}>
<div className="text-content" style={{ fontSize: 19, fontWeight: 700, letterSpacing: '-0.02em', display: 'flex', alignItems: 'baseline', gap: 8 }}>{t('costs.settleUp')} <span className="text-content-faint" style={{ fontSize: 12, fontWeight: 500 }}>{(settlement?.flows || []).length}</span></div> <div className="text-content" style={{ fontSize: 'calc(19px * var(--fs-scale-text, 1))', fontWeight: 700, letterSpacing: '-0.02em', display: 'flex', alignItems: 'baseline', gap: 8 }}>{t('costs.settleUp')} <span className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500 }}>{(settlement?.flows || []).length}</span></div>
{canEdit && ( {canEdit && (
<button onClick={() => setAddingPayment(true)} className="text-content-muted bg-surface-card border border-edge" style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '6px 10px', borderRadius: 9, fontSize: 11.5, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}><Plus size={13} /> {t('costs.addPayment')}</button> <button onClick={() => setAddingPayment(true)} className="text-content-muted bg-surface-card border border-edge" style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '6px 10px', borderRadius: 9, fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}><Plus size={13} /> {t('costs.addPayment')}</button>
)} )}
</div> </div>
<SettleFlows /> <SettleFlows />
@@ -476,23 +476,23 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
{/* Expenses */} {/* Expenses */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<div className="text-content" style={{ fontSize: 19, fontWeight: 700, letterSpacing: '-0.02em' }}>{t('costs.expenses')}</div> <div className="text-content" style={{ fontSize: 'calc(19px * var(--fs-scale-text, 1))', fontWeight: 700, letterSpacing: '-0.02em' }}>{t('costs.expenses')}</div>
<div className="bg-surface-card border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 8, borderRadius: 12, padding: '0 12px', height: 42 }}> <div className="bg-surface-card border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 8, borderRadius: 12, padding: '0 12px', height: 42 }}>
<Search size={16} className="text-content-faint" /> <Search size={16} className="text-content-faint" />
<input value={search} onChange={e => setSearch(e.target.value)} placeholder={t('costs.searchPlaceholder')} className="text-content" style={{ border: 0, background: 'none', outline: 'none', fontSize: 14, width: '100%', fontFamily: 'inherit' }} /> <input value={search} onChange={e => setSearch(e.target.value)} placeholder={t('costs.searchPlaceholder')} className="text-content" style={{ border: 0, background: 'none', outline: 'none', fontSize: 'calc(14px * var(--fs-scale-text, 1))', width: '100%', fontFamily: 'inherit' }} />
</div> </div>
<div className="bg-surface-secondary" style={{ display: 'flex', borderRadius: 11, padding: 3, gap: 2 }}> <div className="bg-surface-secondary" style={{ display: 'flex', borderRadius: 11, padding: 3, gap: 2 }}>
{(['all', 'mine', 'owed'] as const).map(f => ( {(['all', 'mine', 'owed'] as const).map(f => (
<button key={f} onClick={() => setFilter(f)} className={filter === f ? 'bg-surface-card text-content' : 'text-content-muted'} style={{ flex: 1, padding: '8px 6px', fontSize: 12.5, fontWeight: 500, borderRadius: 8, border: 0, cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' }}>{t('costs.filter.' + f)}</button> <button key={f} onClick={() => setFilter(f)} className={filter === f ? 'bg-surface-card text-content' : 'text-content-muted'} style={{ flex: 1, padding: '8px 6px', fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontWeight: 500, borderRadius: 8, border: 0, cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' }}>{t('costs.filter.' + f)}</button>
))} ))}
</div> </div>
{dayGroups.length === 0 {dayGroups.length === 0
? <div className="text-content-faint" style={{ textAlign: 'center', padding: '36px 16px', fontSize: 13 }}>{search ? t('costs.noMatch') : t('costs.emptyText')}</div> ? <div className="text-content-faint" style={{ textAlign: 'center', padding: '36px 16px', fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>{search ? t('costs.noMatch') : t('costs.emptyText')}</div>
: dayGroups.map(g => { : dayGroups.map(g => {
const dtot = g.entries.reduce((a, en) => en.kind === 'expense' ? a + baseTotal(en.e) : a, 0) const dtot = g.entries.reduce((a, en) => en.kind === 'expense' ? a + baseTotal(en.e) : a, 0)
return ( return (
<div key={g.day} style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> <div key={g.day} style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div className={labelCls} style={{ display: 'flex', alignItems: 'center', padding: '0 2px' }}>{g.day}<span className="text-content-muted" style={{ marginLeft: 'auto', textTransform: 'none', letterSpacing: 0, fontWeight: 500, fontSize: 11.5 }}>{t('costs.spent', { amount: fmt(dtot) })}</span></div> <div className={labelCls} style={{ display: 'flex', alignItems: 'center', padding: '0 2px' }}>{g.day}<span className="text-content-muted" style={{ marginLeft: 'auto', textTransform: 'none', letterSpacing: 0, fontWeight: 500, fontSize: 'calc(11.5px * var(--fs-scale-text, 1))' }}>{t('costs.spent', { amount: fmt(dtot) })}</span></div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>{g.entries.map(en => en.kind === 'expense' <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>{g.entries.map(en => en.kind === 'expense'
? <ExpenseRow key={'e' + en.e.id} e={en.e} /> ? <ExpenseRow key={'e' + en.e.id} e={en.e} />
: <SettlementRow key={'s' + en.s.id} s={en.s} />)}</div> : <SettlementRow key={'s' + en.s.id} s={en.s} />)}</div>
@@ -531,15 +531,15 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
<span style={{ position: 'relative', width: 46, height: 46, borderRadius: 13, display: 'grid', placeItems: 'center', background: c.color + '22', color: c.color }}> <span style={{ position: 'relative', width: 46, height: 46, borderRadius: 13, display: 'grid', placeItems: 'center', background: c.color + '22', color: c.color }}>
<Icon size={21} /> <Icon size={21} />
{isMobile && isUnfinished && ( {isMobile && isUnfinished && (
<span title={t('costs.unfinishedHint')} style={{ position: 'absolute', bottom: -4, right: -4, width: 20, height: 20, borderRadius: '50%', background: '#d97706', color: '#fff', display: 'grid', placeItems: 'center', fontSize: 12, fontWeight: 800, lineHeight: 1, border: '2px solid var(--bg-card)' }}>!</span> <span title={t('costs.unfinishedHint')} style={{ position: 'absolute', bottom: -4, right: -4, width: 20, height: 20, borderRadius: '50%', background: '#d97706', color: '#fff', display: 'grid', placeItems: 'center', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 800, lineHeight: 1, border: '2px solid var(--bg-card)' }}>!</span>
)} )}
</span> </span>
<div style={{ minWidth: 0 }}> <div style={{ minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 6 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 6 }}>
<span className="text-content" style={{ fontSize: 15, fontWeight: 600 }}>{e.name}</span> <span className="text-content" style={{ fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{e.name}</span>
{isUnfinished && !isMobile && ( {isUnfinished && !isMobile && (
<span title={t('costs.unfinishedHint')} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 8px 2px 6px', borderRadius: 999, background: 'rgba(217,119,6,0.14)', color: '#d97706', fontSize: 11, fontWeight: 700, flexShrink: 0 }}> <span title={t('costs.unfinishedHint')} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 8px 2px 6px', borderRadius: 999, background: 'rgba(217,119,6,0.14)', color: '#d97706', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 700, flexShrink: 0 }}>
<span style={{ width: 14, height: 14, borderRadius: '50%', background: '#d97706', color: '#fff', display: 'grid', placeItems: 'center', fontSize: 10, fontWeight: 800 }}>!</span> <span style={{ width: 14, height: 14, borderRadius: '50%', background: '#d97706', color: '#fff', display: 'grid', placeItems: 'center', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 800 }}>!</span>
{t('costs.unfinished')} {t('costs.unfinished')}
</span> </span>
)} )}
@@ -547,7 +547,7 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
{payers.length > 0 && ( {payers.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginBottom: 5 }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginBottom: 5 }}>
{payers.map(p => ( {payers.map(p => (
<span key={p.user_id} className="bg-surface-secondary border border-edge" title={personName(p.user_id)} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '3px 10px 3px 3px', borderRadius: 999, fontSize: 11.5 }}> <span key={p.user_id} className="bg-surface-secondary border border-edge" title={personName(p.user_id)} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '3px 10px 3px 3px', borderRadius: 999, fontSize: 'calc(11.5px * var(--fs-scale-text, 1))' }}>
<Avatar id={p.user_id} size={18} /> <Avatar id={p.user_id} size={18} />
<span className="text-content" style={{ fontWeight: 700 }}>{fmt(convert(p.amount, cur))}</span> <span className="text-content" style={{ fontWeight: 700 }}>{fmt(convert(p.amount, cur))}</span>
</span> </span>
@@ -555,16 +555,16 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
</div> </div>
)} )}
{!isMobile && ( {!isMobile && (
<div className="text-content-faint" style={{ fontSize: 12, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}> <div className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{t(c.labelKey)}{cur !== base ? ` · ${fmt(e.total_price, cur)}${fmt(baseTotal(e))}` : ''} {t(c.labelKey)}{cur !== base ? ` · ${fmt(e.total_price, cur)}${fmt(baseTotal(e))}` : ''}
</div> </div>
)} )}
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, alignSelf: 'center' }}> <div style={{ display: 'flex', alignItems: 'center', gap: 10, alignSelf: 'center' }}>
<div style={{ textAlign: 'right', whiteSpace: 'nowrap' }}> <div style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
<div className="text-content" style={{ fontSize: 18, fontWeight: 600 }}>{fmt(baseTotal(e))}</div> <div className="text-content" style={{ fontSize: 'calc(18px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{fmt(baseTotal(e))}</div>
{!isUnfinished && (e.members || []).length > 0 && Math.abs(net) > 0.01 && ( {!isUnfinished && (e.members || []).length > 0 && Math.abs(net) > 0.01 && (
<div style={{ fontSize: 12, marginTop: 2, fontWeight: 500, whiteSpace: 'nowrap', color: net > 0 ? '#16a34a' : '#dc2626' }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', marginTop: 2, fontWeight: 500, whiteSpace: 'nowrap', color: net > 0 ? '#16a34a' : '#dc2626' }}>
{net > 0 ? t('costs.youLent', { amount: fmt(net) }) : t('costs.youBorrowed', { amount: fmt(-net) })} {net > 0 ? t('costs.youLent', { amount: fmt(net) }) : t('costs.youBorrowed', { amount: fmt(-net) })}
</div> </div>
)} )}
@@ -587,14 +587,14 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
<div className="bg-surface-card border border-edge exp-row" style={{ display: 'grid', gridTemplateColumns: '46px 1fr auto', gap: 16, alignItems: 'center', borderRadius: 18, padding: '16px 20px' }}> <div className="bg-surface-card border border-edge exp-row" style={{ display: 'grid', gridTemplateColumns: '46px 1fr auto', gap: 16, alignItems: 'center', borderRadius: 18, padding: '16px 20px' }}>
<span style={{ width: 46, height: 46, borderRadius: 13, display: 'grid', placeItems: 'center', background: 'rgba(22,163,74,0.12)', color: '#16a34a' }}><ArrowLeftRight size={21} /></span> <span style={{ width: 46, height: 46, borderRadius: 13, display: 'grid', placeItems: 'center', background: 'rgba(22,163,74,0.12)', color: '#16a34a' }}><ArrowLeftRight size={21} /></span>
<div style={{ minWidth: 0 }}> <div style={{ minWidth: 0 }}>
<div className="text-content" style={{ fontSize: 15, fontWeight: 600, marginBottom: 6 }}>{t('costs.payment')}</div> <div className="text-content" style={{ fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 600, marginBottom: 6 }}>{t('costs.payment')}</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }} title={`${personName(s.from_user_id)}${personName(s.to_user_id)}`}> <div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }} title={`${personName(s.from_user_id)}${personName(s.to_user_id)}`}>
<Avatar id={s.from_user_id} size={20} /><ArrowRight size={13} className="text-content-faint" /><Avatar id={s.to_user_id} size={20} /> <Avatar id={s.from_user_id} size={20} /><ArrowRight size={13} className="text-content-faint" /><Avatar id={s.to_user_id} size={20} />
<span className="text-content-faint" style={{ fontSize: 12, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{personName(s.from_user_id)} {personName(s.to_user_id)}</span> <span className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{personName(s.from_user_id)} {personName(s.to_user_id)}</span>
</div> </div>
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, alignSelf: 'center' }}> <div style={{ display: 'flex', alignItems: 'center', gap: 10, alignSelf: 'center' }}>
<div className="text-content" style={{ fontSize: 18, fontWeight: 600, whiteSpace: 'nowrap' }}>{fmt(s.amount)}</div> <div className="text-content" style={{ fontSize: 'calc(18px * var(--fs-scale-text, 1))', fontWeight: 600, whiteSpace: 'nowrap' }}>{fmt(s.amount)}</div>
{canEdit && ( {canEdit && (
<div className="exp-actions" style={{ display: 'flex', flexDirection: 'column', gap: 6, flexShrink: 0 }}> <div className="exp-actions" style={{ display: 'flex', flexDirection: 'column', gap: 6, flexShrink: 0 }}>
<button title={t('common.edit')} onClick={() => setEditingSettlement(s)} className="bg-surface-secondary border border-edge text-content-muted" style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 28, height: 28, borderRadius: 999, cursor: 'pointer' }}><Pencil size={13} /></button> <button title={t('common.edit')} onClick={() => setEditingSettlement(s)} className="bg-surface-secondary border border-edge text-content-muted" style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 28, height: 28, borderRadius: 999, cursor: 'pointer' }}><Pencil size={13} /></button>
@@ -618,14 +618,14 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
<div key={r.user_id} style={{ display: 'grid', gridTemplateColumns: '28px 1fr auto', gap: 10, alignItems: 'center' }}> <div key={r.user_id} style={{ display: 'grid', gridTemplateColumns: '28px 1fr auto', gap: 10, alignItems: 'center' }}>
<Avatar id={r.user_id} size={28} /> <Avatar id={r.user_id} size={28} />
<div> <div>
<div className="text-content" style={{ fontSize: 13, fontWeight: 600 }}>{personName(r.user_id)}</div> <div className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{personName(r.user_id)}</div>
<div className="bg-surface-secondary" style={{ height: 5, borderRadius: 3, marginTop: 5, position: 'relative', overflow: 'hidden' }}> <div className="bg-surface-secondary" style={{ height: 5, borderRadius: 3, marginTop: 5, position: 'relative', overflow: 'hidden' }}>
<span style={{ position: 'absolute', left: '50%', top: -1, bottom: -1, width: 1, background: 'var(--border-primary)' }} /> <span style={{ position: 'absolute', left: '50%', top: -1, bottom: -1, width: 1, background: 'var(--border-primary)' }} />
{pos && <span style={{ position: 'absolute', left: '50%', top: 0, bottom: 0, width: pct / 2 + '%', background: '#16a34a', borderRadius: 3 }} />} {pos && <span style={{ position: 'absolute', left: '50%', top: 0, bottom: 0, width: pct / 2 + '%', background: '#16a34a', borderRadius: 3 }} />}
{neg && <span style={{ position: 'absolute', right: '50%', top: 0, bottom: 0, width: pct / 2 + '%', background: '#dc2626', borderRadius: 3 }} />} {neg && <span style={{ position: 'absolute', right: '50%', top: 0, bottom: 0, width: pct / 2 + '%', background: '#dc2626', borderRadius: 3 }} />}
</div> </div>
</div> </div>
<div style={{ fontSize: 13, fontWeight: 600, textAlign: 'right', color: pos ? '#16a34a' : neg ? '#dc2626' : 'var(--text-faint)' }}> <div style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, textAlign: 'right', color: pos ? '#16a34a' : neg ? '#dc2626' : 'var(--text-faint)' }}>
{pos ? '+' + fmt(r.balance) : neg ? '' + fmt(-r.balance) : fmt(0)} {pos ? '+' + fmt(r.balance) : neg ? '' + fmt(-r.balance) : fmt(0)}
</div> </div>
</div> </div>
@@ -639,7 +639,7 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
const tot: Record<string, number> = {} const tot: Record<string, number> = {}
for (const e of budgetItems) { const k = catMeta(e.category).key; tot[k] = (tot[k] || 0) + baseTotal(e) } for (const e of budgetItems) { const k = catMeta(e.category).key; tot[k] = (tot[k] || 0) + baseTotal(e) }
const rows = COST_CATEGORY_LIST.filter(c => (tot[c.key] || 0) > 0).sort((a, b) => (tot[b.key] || 0) - (tot[a.key] || 0)) const rows = COST_CATEGORY_LIST.filter(c => (tot[c.key] || 0) > 0).sort((a, b) => (tot[b.key] || 0) - (tot[a.key] || 0))
if (rows.length === 0) return <div className="text-content-faint" style={{ fontSize: 12.5 }}>{t('costs.noCategories')}</div> if (rows.length === 0) return <div className="text-content-faint" style={{ fontSize: 'calc(12.5px * var(--fs-scale-text, 1))' }}>{t('costs.noCategories')}</div>
// Bars are scaled relative to the most expensive category (the top row fills the // Bars are scaled relative to the most expensive category (the top row fills the
// bar), not to the trip grand total — makes the relative ranking readable. // bar), not to the trip grand total — makes the relative ranking readable.
const maxCat = Math.max(0, ...rows.map(c => tot[c.key] || 0)) const maxCat = Math.max(0, ...rows.map(c => tot[c.key] || 0))
@@ -650,8 +650,8 @@ export default function CostsPanel({ tripId, tripMembers = [] }: CostsPanelProps
return ( return (
<div key={c.key} style={{ display: 'grid', gridTemplateColumns: 'auto 1fr auto', gap: 10, alignItems: 'center' }}> <div key={c.key} style={{ display: 'grid', gridTemplateColumns: 'auto 1fr auto', gap: 10, alignItems: 'center' }}>
<span style={{ width: 10, height: 10, borderRadius: 3, background: c.color }} /> <span style={{ width: 10, height: 10, borderRadius: 3, background: c.color }} />
<span className="text-content" style={{ fontSize: 13, fontWeight: 500 }}>{t(c.labelKey)}</span> <span className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500 }}>{t(c.labelKey)}</span>
<span className="text-content-muted" style={{ fontSize: 13, fontWeight: 600 }}>{fmt0(v)}</span> <span className="text-content-muted" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{fmt0(v)}</span>
<div className="bg-surface-secondary" style={{ gridColumn: '1 / -1', height: 5, borderRadius: 3, overflow: 'hidden', marginTop: -2 }}> <div className="bg-surface-secondary" style={{ gridColumn: '1 / -1', height: 5, borderRadius: 3, overflow: 'hidden', marginTop: -2 }}>
<span style={{ display: 'block', height: '100%', width: pct + '%', background: c.color, borderRadius: 3 }} /> <span style={{ display: 'block', height: '100%', width: pct + '%', background: c.color, borderRadius: 3 }} />
</div> </div>
@@ -682,16 +682,16 @@ function SummaryCard({ label, sub, amount, currency, locale, icon, foot, tone }:
<div style={{ display: 'flex', alignItems: 'center', gap: 11 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
<span style={{ width: 36, height: 36, borderRadius: 11, display: 'grid', placeItems: 'center', background: total ? 'rgba(255,255,255,0.12)' : (accent + '22'), color: total ? '#fff' : accent }}>{icon}</span> <span style={{ width: 36, height: 36, borderRadius: 11, display: 'grid', placeItems: 'center', background: total ? 'rgba(255,255,255,0.12)' : (accent + '22'), color: total ? '#fff' : accent }}>{icon}</span>
<div> <div>
<div style={{ fontSize: 13, fontWeight: 600 }} className={total ? '' : 'text-content'}>{label}</div> <div style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }} className={total ? '' : 'text-content'}>{label}</div>
<div style={{ fontSize: 12, opacity: total ? 0.6 : 1 }} className={total ? '' : 'text-content-faint'}>{sub}</div> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', opacity: total ? 0.6 : 1 }} className={total ? '' : 'text-content-faint'}>{sub}</div>
</div> </div>
</div> </div>
<div style={{ fontSize: 46, fontWeight: 600, letterSpacing: '-0.035em', lineHeight: 1, marginTop: 20, display: 'flex', alignItems: 'baseline', color: total ? '#fff' : accent }}> <div style={{ fontSize: 'calc(46px * var(--fs-scale-text, 1))', fontWeight: 600, letterSpacing: '-0.035em', lineHeight: 1, marginTop: 20, display: 'flex', alignItems: 'baseline', color: total ? '#fff' : accent }}>
{parts {parts
? parts.map((p, i) => <span key={i} style={big(p) ? undefined : { fontSize: 26, fontWeight: 500, color: muted }}>{p.value}</span>) ? parts.map((p, i) => <span key={i} style={big(p) ? undefined : { fontSize: 'calc(26px * var(--fs-scale-text, 1))', fontWeight: 500, color: muted }}>{p.value}</span>)
: <span>{formatMoney(amount, currency, locale)}</span>} : <span>{formatMoney(amount, currency, locale)}</span>}
</div> </div>
<div style={{ marginTop: 16, fontSize: 12.5, display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap', opacity: total ? 0.85 : 1 }}>{foot}</div> <div style={{ marginTop: 16, fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap', opacity: total ? 0.85 : 1 }}>{foot}</div>
</div> </div>
) )
} }
@@ -702,7 +702,7 @@ function FlowPills({ ids, lead, Avatar, name }: { ids: number[]; lead: string; A
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}> <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
<span className="text-content-faint">{lead}</span> <span className="text-content-faint">{lead}</span>
{uniq.map(id => ( {uniq.map(id => (
<span key={id} className="bg-surface-secondary border border-edge text-content" style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '3px 10px 3px 3px', borderRadius: 999, fontSize: 12, fontWeight: 600 }}> <span key={id} className="bg-surface-secondary border border-edge text-content" style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '3px 10px 3px 3px', borderRadius: 999, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600 }}>
<Avatar id={id} size={18} />{name(id)} <Avatar id={id} size={18} />{name(id)}
</span> </span>
))} ))}
@@ -746,8 +746,8 @@ function SettlementModal({ tripId, people, me, editing, onClose, onSaved }: {
<Modal isOpen onClose={onClose} title={editing ? t('costs.editPayment') : t('costs.addPayment')} size="md" <Modal isOpen onClose={onClose} title={editing ? t('costs.editPayment') : t('costs.addPayment')} size="md"
footer={ footer={
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}> <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
<button onClick={onClose} className="text-content-muted border border-edge" style={{ padding: '8px 16px', borderRadius: 10, background: 'none', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>{t('common.cancel')}</button> <button onClick={onClose} className="text-content-muted border border-edge" style={{ padding: '8px 16px', borderRadius: 10, background: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit' }}>{t('common.cancel')}</button>
<button onClick={save} disabled={!valid || saving} className="bg-[var(--text-primary)] text-[var(--bg-primary)]" style={{ padding: '8px 20px', borderRadius: 10, border: 0, fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', opacity: !valid || saving ? 0.5 : 1 }}>{editing ? t('common.save') : t('costs.addPayment')}</button> <button onClick={save} disabled={!valid || saving} className="bg-[var(--text-primary)] text-[var(--bg-primary)]" style={{ padding: '8px 20px', borderRadius: 10, border: 0, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', opacity: !valid || saving ? 0.5 : 1 }}>{editing ? t('common.save') : t('costs.addPayment')}</button>
</div> </div>
}> }>
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
@@ -762,7 +762,7 @@ function SettlementModal({ tripId, people, me, editing, onClose, onSaved }: {
<div> <div>
<label className={labelCls}>{t('costs.amount')}</label> <label className={labelCls}>{t('costs.amount')}</label>
<input type="text" inputMode="decimal" placeholder="0.00" value={amount} <input type="text" inputMode="decimal" placeholder="0.00" value={amount}
onChange={e => setAmount(e.target.value.replace(',', '.'))} className={inputCls} style={{ borderRadius: 10, padding: '11px 13px', fontSize: 14, outline: 'none', fontWeight: 600 }} /> onChange={e => setAmount(e.target.value.replace(',', '.'))} className={inputCls} style={{ borderRadius: 10, padding: '11px 13px', fontSize: 'calc(14px * var(--fs-scale-text, 1))', outline: 'none', fontWeight: 600 }} />
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -894,23 +894,23 @@ export function ExpenseModal({ tripId, base, people, me, editing, prefill, onClo
<Modal isOpen onClose={onClose} title={editing ? t('costs.editExpense') : t('costs.addExpense')} size="2xl" <Modal isOpen onClose={onClose} title={editing ? t('costs.editExpense') : t('costs.addExpense')} size="2xl"
footer={ footer={
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}> <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
<button onClick={onClose} className="text-content-muted border border-edge" style={{ padding: '8px 16px', borderRadius: 10, background: 'none', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>{t('common.cancel')}</button> <button onClick={onClose} className="text-content-muted border border-edge" style={{ padding: '8px 16px', borderRadius: 10, background: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit' }}>{t('common.cancel')}</button>
<button onClick={save} disabled={!valid || saving} className="bg-[var(--text-primary)] text-[var(--bg-primary)]" style={{ padding: '8px 20px', borderRadius: 10, border: 0, fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', opacity: !valid || saving ? 0.5 : 1 }}>{editing ? t('common.save') : t('costs.addExpense')}</button> <button onClick={save} disabled={!valid || saving} className="bg-[var(--text-primary)] text-[var(--bg-primary)]" style={{ padding: '8px 20px', borderRadius: 10, border: 0, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', opacity: !valid || saving ? 0.5 : 1 }}>{editing ? t('common.save') : t('costs.addExpense')}</button>
</div> </div>
}> }>
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
<div> <div>
<label className={labelCls}>{t('costs.whatFor')}</label> <label className={labelCls}>{t('costs.whatFor')}</label>
<input value={name} onChange={e => setName(e.target.value)} placeholder={t('costs.namePlaceholder')} className={inputCls} style={{ borderRadius: 10, padding: '11px 13px', fontSize: 14, outline: 'none' }} /> <input value={name} onChange={e => setName(e.target.value)} placeholder={t('costs.namePlaceholder')} className={inputCls} style={{ borderRadius: 10, padding: '11px 13px', fontSize: 'calc(14px * var(--fs-scale-text, 1))', outline: 'none' }} />
</div> </div>
<div> <div>
<label className={labelCls}>{t('costs.totalAmount')}</label> <label className={labelCls}>{t('costs.totalAmount')}</label>
<div className="bg-surface-input border border-edge" style={{ height: FIELD_H, boxSizing: 'border-box', display: 'flex', alignItems: 'center', borderRadius: 10, padding: '0 12px' }}> <div className="bg-surface-input border border-edge" style={{ height: FIELD_H, boxSizing: 'border-box', display: 'flex', alignItems: 'center', borderRadius: 10, padding: '0 12px' }}>
<span className="text-content-faint" style={{ fontSize: 15 }}>{sym(currency)}</span> <span className="text-content-faint" style={{ fontSize: 'calc(15px * var(--fs-scale-text, 1))' }}>{sym(currency)}</span>
<input type="text" inputMode="decimal" placeholder="0.00" value={total} <input type="text" inputMode="decimal" placeholder="0.00" value={total}
onChange={e => onTotalChange(e.target.value)} onChange={e => onTotalChange(e.target.value)}
className="text-content" style={{ flex: 1, border: 0, background: 'none', outline: 'none', fontSize: 15, fontWeight: 600, paddingLeft: 6, width: '100%' }} /> className="text-content" style={{ flex: 1, border: 0, background: 'none', outline: 'none', fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 600, paddingLeft: 6, width: '100%' }} />
</div> </div>
</div> </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
@@ -927,7 +927,7 @@ export function ExpenseModal({ tripId, base, people, me, editing, prefill, onClo
</div> </div>
{currency !== base && totalNum > 0 && ( {currency !== base && totalNum > 0 && (
<div className="bg-surface-secondary border border-edge text-content-muted" style={{ borderRadius: 10, padding: '10px 12px', fontSize: 12.5, display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}> <div className="bg-surface-secondary border border-edge text-content-muted" style={{ borderRadius: 10, padding: '10px 12px', fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<span>{formatMoney(totalNum, currency, locale)}</span> <span>{formatMoney(totalNum, currency, locale)}</span>
<span className="text-content-faint"></span> <span className="text-content-faint"></span>
<span className="text-content" style={{ fontWeight: 600 }}>{formatMoney(convert(totalNum, currency), base, locale)}</span> <span className="text-content" style={{ fontWeight: 600 }}>{formatMoney(convert(totalNum, currency), base, locale)}</span>
@@ -943,7 +943,7 @@ export function ExpenseModal({ tripId, base, people, me, editing, prefill, onClo
return ( return (
<button key={c.key} onClick={() => setCat(c.key)} <button key={c.key} onClick={() => setCat(c.key)}
className={on ? 'bg-surface-card text-content border' : 'bg-surface-secondary text-content-muted border border-edge'} className={on ? 'bg-surface-card text-content border' : 'bg-surface-secondary text-content-muted border border-edge'}
style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '6px 11px 6px 7px', borderRadius: 999, fontSize: 12.5, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit', borderColor: on ? 'var(--text-primary)' : undefined }}> style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '6px 11px 6px 7px', borderRadius: 999, fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit', borderColor: on ? 'var(--text-primary)' : undefined }}>
<span style={{ width: 20, height: 20, borderRadius: 6, display: 'grid', placeItems: 'center', background: c.color + '22', color: c.color }}><Icon size={12} /></span> <span style={{ width: 20, height: 20, borderRadius: 6, display: 'grid', placeItems: 'center', background: c.color + '22', color: c.color }}><Icon size={12} /></span>
{t(c.labelKey)} {t(c.labelKey)}
</button> </button>
@@ -962,24 +962,24 @@ export function ExpenseModal({ tripId, base, people, me, editing, prefill, onClo
<button onClick={() => toggleParticipant(p.id)} style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: 'none', border: 0, cursor: 'pointer', fontFamily: 'inherit', padding: 0, minWidth: 0, textAlign: 'left' }}> <button onClick={() => toggleParticipant(p.id)} style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: 'none', border: 0, cursor: 'pointer', fontFamily: 'inherit', padding: 0, minWidth: 0, textAlign: 'left' }}>
{p.avatar_url {p.avatar_url
? <img src={p.avatar_url} alt="" style={{ width: 22, height: 22, borderRadius: '50%', objectFit: 'cover', display: 'block', flexShrink: 0, opacity: on ? 1 : 0.45 }} /> ? <img src={p.avatar_url} alt="" style={{ width: 22, height: 22, borderRadius: '50%', objectFit: 'cover', display: 'block', flexShrink: 0, opacity: on ? 1 : 0.45 }} />
: <span style={{ width: 22, height: 22, borderRadius: '50%', background: SPLIT_COLORS[idx % SPLIT_COLORS.length].gradient, color: '#fff', display: 'grid', placeItems: 'center', fontSize: 9, fontWeight: 700, flexShrink: 0, opacity: on ? 1 : 0.45 }}>{(p.id === me ? t('costs.youShort') : p.username.charAt(0)).toUpperCase()}</span>} : <span style={{ width: 22, height: 22, borderRadius: '50%', background: SPLIT_COLORS[idx % SPLIT_COLORS.length].gradient, color: '#fff', display: 'grid', placeItems: 'center', fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 700, flexShrink: 0, opacity: on ? 1 : 0.45 }}>{(p.id === me ? t('costs.youShort') : p.username.charAt(0)).toUpperCase()}</span>}
<span className="text-content" style={{ fontSize: 14, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.id === me ? t('costs.you') : p.username}</span> <span className="text-content" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.id === me ? t('costs.you') : p.username}</span>
</button> </button>
{on ? ( {on ? (
<div className="bg-surface-input border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 4, borderRadius: 8, padding: '0 10px' }}> <div className="bg-surface-input border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 4, borderRadius: 8, padding: '0 10px' }}>
<span className="text-content-faint" style={{ fontSize: 13 }}>{sym(currency)}</span> <span className="text-content-faint" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>{sym(currency)}</span>
<input type="text" inputMode="decimal" placeholder="0.00" value={paid[p.id] || ''} <input type="text" inputMode="decimal" placeholder="0.00" value={paid[p.id] || ''}
onChange={e => onPaidChange(p.id, e.target.value)} onChange={e => onPaidChange(p.id, e.target.value)}
className="text-content" style={{ width: '100%', border: 0, background: 'none', outline: 'none', fontSize: 14, fontWeight: 600, padding: '8px 0', textAlign: 'right' }} /> className="text-content" style={{ width: '100%', border: 0, background: 'none', outline: 'none', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, padding: '8px 0', textAlign: 'right' }} />
</div> </div>
) : ( ) : (
<button onClick={() => toggleParticipant(p.id)} className="text-content-faint" style={{ background: 'none', border: 0, cursor: 'pointer', fontFamily: 'inherit', fontSize: 12, textAlign: 'right' }}>{t('costs.tapToInclude')}</button> <button onClick={() => toggleParticipant(p.id)} className="text-content-faint" style={{ background: 'none', border: 0, cursor: 'pointer', fontFamily: 'inherit', fontSize: 'calc(12px * var(--fs-scale-text, 1))', textAlign: 'right' }}>{t('costs.tapToInclude')}</button>
)} )}
</div> </div>
) )
})} })}
</div> </div>
<div style={{ marginTop: 10, fontSize: 12.5, display: 'flex', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}> <div style={{ marginTop: 10, fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', display: 'flex', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
<span className="text-content-faint"> <span className="text-content-faint">
{participants.size > 0 && t('costs.splitSummary', { count: participants.size, amount: sym(currency) + each.toFixed(2) })} {participants.size > 0 && t('costs.splitSummary', { count: participants.size, amount: sym(currency) + each.toFixed(2) })}
</span> </span>
+2 -2
View File
@@ -33,7 +33,7 @@ export default function CollabChat({ tripId, currentUser }: CollabChatProps) {
<div style={{ <div style={{
display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8, display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8,
padding: '6px 10px', borderRadius: 10, background: 'var(--bg-secondary)', padding: '6px 10px', borderRadius: 10, background: 'var(--bg-secondary)',
borderLeft: '3px solid #007AFF', fontSize: 12, color: 'var(--text-muted)', borderLeft: '3px solid #007AFF', fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-muted)',
}}> }}>
<Reply size={12} style={{ flexShrink: 0, opacity: 0.5 }} /> <Reply size={12} style={{ flexShrink: 0, opacity: 0.5 }} />
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}> <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>
@@ -67,7 +67,7 @@ export default function CollabChat({ tripId, currentUser }: CollabChatProps) {
disabled={!canEdit} disabled={!canEdit}
style={{ style={{
flex: 1, resize: 'none', border: '1px solid var(--border-primary)', borderRadius: 20, flex: 1, resize: 'none', border: '1px solid var(--border-primary)', borderRadius: 20,
padding: '8px 14px', fontSize: 14, lineHeight: 1.4, fontFamily: 'inherit', padding: '8px 14px', fontSize: 'calc(14px * var(--fs-scale-text, 1))', lineHeight: 1.4, fontFamily: 'inherit',
background: 'var(--bg-input)', color: 'var(--text-primary)', outline: 'none', background: 'var(--bg-input)', color: 'var(--text-primary)', outline: 'none',
maxHeight: 100, overflowY: 'hidden', maxHeight: 100, overflowY: 'hidden',
opacity: canEdit ? 1 : 0.5, opacity: canEdit ? 1 : 0.5,
@@ -49,7 +49,7 @@ export function EmojiPicker({ onSelect, onClose, anchorRef, containerRef }: Emoj
<button key={c} onClick={() => setCat(c)} style={{ <button key={c} onClick={() => setCat(c)} style={{
flex: 1, padding: '4px 0', borderRadius: 6, border: 'none', cursor: 'pointer', flex: 1, padding: '4px 0', borderRadius: 6, border: 'none', cursor: 'pointer',
background: cat === c ? 'var(--bg-hover)' : 'transparent', background: cat === c ? 'var(--bg-hover)' : 'transparent',
color: 'var(--text-primary)', fontSize: 10, fontWeight: 600, fontFamily: 'inherit', color: 'var(--text-primary)', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, fontFamily: 'inherit',
}}> }}>
{c} {c}
</button> </button>
@@ -45,17 +45,17 @@ export function LinkPreview({ url, tripId, own, onLoad }: LinkPreviewProps) {
)} )}
<div style={{ padding: '8px 10px' }}> <div style={{ padding: '8px 10px' }}>
{domain && ( {domain && (
<div style={{ fontSize: 10, fontWeight: 600, color: own ? 'rgba(255,255,255,0.5)' : 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: 0.3, marginBottom: 2 }}> <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: own ? 'rgba(255,255,255,0.5)' : 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: 0.3, marginBottom: 2 }}>
{data.site_name || domain} {data.site_name || domain}
</div> </div>
)} )}
{data.title && ( {data.title && (
<div style={{ fontSize: 12, fontWeight: 600, color: own ? '#fff' : 'var(--text-primary)', lineHeight: 1.3, marginBottom: 2, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: own ? '#fff' : 'var(--text-primary)', lineHeight: 1.3, marginBottom: 2, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
{data.title} {data.title}
</div> </div>
)} )}
{data.description && ( {data.description && (
<div style={{ fontSize: 11, color: own ? 'rgba(255,255,255,0.7)' : 'var(--text-muted)', lineHeight: 1.3, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: own ? 'rgba(255,255,255,0.7)' : 'var(--text-muted)', lineHeight: 1.3, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
{data.description} {data.description}
</div> </div>
)} )}
@@ -14,8 +14,8 @@ export function ChatMessages(props: any) {
{messages.length === 0 ? ( {messages.length === 0 ? (
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 8, color: 'var(--text-faint)', padding: 32, textAlign: 'center' }}> <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 8, color: 'var(--text-faint)', padding: 32, textAlign: 'center' }}>
<MessageCircle size={40} strokeWidth={1.2} style={{ opacity: 0.4 }} /> <MessageCircle size={40} strokeWidth={1.2} style={{ opacity: 0.4 }} />
<span style={{ fontSize: 14, fontWeight: 600 }}>{t('collab.chat.empty')}</span> <span style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('collab.chat.empty')}</span>
<span style={{ fontSize: 12, opacity: 0.6, fontFamily: 'var(--font-subtext)' }}>{t('collab.chat.emptyDesc') || ''}</span> <span style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', opacity: 0.6, fontFamily: 'var(--font-subtext)' }}>{t('collab.chat.emptyDesc') || ''}</span>
</div> </div>
) : ( ) : (
<div ref={scrollRef} onScroll={checkAtBottom} className="chat-scroll" style={{ <div ref={scrollRef} onScroll={checkAtBottom} className="chat-scroll" style={{
@@ -25,7 +25,7 @@ export function ChatMessages(props: any) {
{hasMore && ( {hasMore && (
<div style={{ display: 'flex', justifyContent: 'center', padding: '4px 0 10px' }}> <div style={{ display: 'flex', justifyContent: 'center', padding: '4px 0 10px' }}>
<button onClick={handleLoadMore} disabled={loadingMore} style={{ <button onClick={handleLoadMore} disabled={loadingMore} style={{
display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 11, fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600,
color: 'var(--text-muted)', background: 'var(--bg-secondary)', border: '1px solid var(--border-faint)', color: 'var(--text-muted)', background: 'var(--bg-secondary)', border: '1px solid var(--border-faint)',
borderRadius: 99, padding: '5px 14px', cursor: 'pointer', fontFamily: 'inherit', borderRadius: 99, padding: '5px 14px', cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
@@ -51,13 +51,13 @@ export function ChatMessages(props: any) {
<React.Fragment key={msg.id}> <React.Fragment key={msg.id}>
{showDate && ( {showDate && (
<div style={{ display: 'flex', justifyContent: 'center', padding: '14px 0 6px' }}> <div style={{ display: 'flex', justifyContent: 'center', padding: '14px 0 6px' }}>
<span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', background: 'var(--bg-secondary)', padding: '3px 12px', borderRadius: 99, letterSpacing: 0.3, textTransform: 'uppercase' }}> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', background: 'var(--bg-secondary)', padding: '3px 12px', borderRadius: 99, letterSpacing: 0.3, textTransform: 'uppercase' }}>
{formatDateSeparator(msg.created_at, t)} {formatDateSeparator(msg.created_at, t)}
</span> </span>
</div> </div>
)} )}
<div style={{ display: 'flex', justifyContent: 'center', padding: '4px 0' }}> <div style={{ display: 'flex', justifyContent: 'center', padding: '4px 0' }}>
<span style={{ fontSize: 11, color: 'var(--text-faint)', fontStyle: 'italic' }}> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontStyle: 'italic' }}>
{msg.username} {t('collab.chat.deletedMessage') || 'deleted a message'} · {formatTime(msg.created_at, is12h)} {msg.username} {t('collab.chat.deletedMessage') || 'deleted a message'} · {formatTime(msg.created_at, is12h)}
</span> </span>
</div> </div>
@@ -76,7 +76,7 @@ export function ChatMessages(props: any) {
{showDate && ( {showDate && (
<div style={{ display: 'flex', justifyContent: 'center', padding: '14px 0 6px' }}> <div style={{ display: 'flex', justifyContent: 'center', padding: '14px 0 6px' }}>
<span style={{ <span style={{
fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)',
background: 'var(--bg-secondary)', padding: '3px 12px', borderRadius: 99, background: 'var(--bg-secondary)', padding: '3px 12px', borderRadius: 99,
letterSpacing: 0.3, textTransform: 'uppercase', letterSpacing: 0.3, textTransform: 'uppercase',
}}> }}>
@@ -103,7 +103,7 @@ export function ChatMessages(props: any) {
<div style={{ <div style={{
width: 28, height: 28, borderRadius: '50%', background: 'var(--bg-tertiary)', width: 28, height: 28, borderRadius: '50%', background: 'var(--bg-tertiary)',
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 11, fontWeight: 700, color: 'var(--text-muted)', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-muted)',
}}> }}>
{(msg.username || '?')[0].toUpperCase()} {(msg.username || '?')[0].toUpperCase()}
</div> </div>
@@ -115,7 +115,7 @@ export function ChatMessages(props: any) {
<div style={{ display: 'flex', flexDirection: 'column', alignItems: own ? 'flex-end' : 'flex-start', maxWidth: '78%', minWidth: 0 }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: own ? 'flex-end' : 'flex-start', maxWidth: '78%', minWidth: 0 }}>
{/* Username for others at group start */} {/* Username for others at group start */}
{!own && isNewGroup && ( {!own && isNewGroup && (
<span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', marginBottom: 2, paddingLeft: 4 }}> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', marginBottom: 2, paddingLeft: 4 }}>
{msg.username} {msg.username}
</span> </span>
)} )}
@@ -138,7 +138,7 @@ export function ChatMessages(props: any) {
}} }}
> >
{bigEmoji ? ( {bigEmoji ? (
<div style={{ fontSize: 40, lineHeight: 1.2, padding: '2px 0' }}> <div style={{ fontSize: 'calc(40px * var(--fs-scale-text, 1))', lineHeight: 1.2, padding: '2px 0' }}>
{msg.text} {msg.text}
</div> </div>
) : ( ) : (
@@ -146,16 +146,16 @@ export function ChatMessages(props: any) {
background: own ? '#007AFF' : 'var(--bg-secondary)', background: own ? '#007AFF' : 'var(--bg-secondary)',
color: own ? '#fff' : 'var(--text-primary)', color: own ? '#fff' : 'var(--text-primary)',
borderRadius: br, padding: hasReply ? '4px 4px 8px 4px' : '8px 14px', borderRadius: br, padding: hasReply ? '4px 4px 8px 4px' : '8px 14px',
fontSize: 14, lineHeight: 1.4, wordBreak: 'break-word', whiteSpace: 'pre-wrap', fontSize: 'calc(14px * var(--fs-scale-text, 1))', lineHeight: 1.4, wordBreak: 'break-word', whiteSpace: 'pre-wrap',
}}> }}>
{/* Inline reply quote */} {/* Inline reply quote */}
{hasReply && ( {hasReply && (
<div style={{ <div style={{
padding: '5px 10px', marginBottom: 4, borderRadius: 12, padding: '5px 10px', marginBottom: 4, borderRadius: 12,
background: own ? 'rgba(255,255,255,0.15)' : 'var(--bg-tertiary)', background: own ? 'rgba(255,255,255,0.15)' : 'var(--bg-tertiary)',
fontSize: 12, lineHeight: 1.3, fontSize: 'calc(12px * var(--fs-scale-text, 1))', lineHeight: 1.3,
}}> }}>
<div style={{ fontWeight: 600, fontSize: 11, opacity: 0.7, marginBottom: 1 }}> <div style={{ fontWeight: 600, fontSize: 'calc(11px * var(--fs-scale-text, 1))', opacity: 0.7, marginBottom: 1 }}>
{msg.reply_username || ''} {msg.reply_username || ''}
</div> </div>
<div style={{ opacity: 0.8, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <div style={{ opacity: 0.8, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
@@ -233,7 +233,7 @@ export function ChatMessages(props: any) {
{/* Timestamp — only on last message of group */} {/* Timestamp — only on last message of group */}
{isLastInGroup && ( {isLastInGroup && (
<span style={{ fontSize: 9, color: 'var(--text-faint)', marginTop: 2, padding: '0 4px' }}> <span style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginTop: 2, padding: '0 4px' }}>
{formatTime(msg.created_at, is12h)} {formatTime(msg.created_at, is12h)}
</span> </span>
)} )}
@@ -34,14 +34,14 @@ export function ReactionBadge({ reaction, currentUserId, onReact }: ReactionBadg
}} }}
> >
<TwemojiImg emoji={reaction.emoji} size={16} /> <TwemojiImg emoji={reaction.emoji} size={16} />
{reaction.count > 1 && <span style={{ fontSize: 10, fontWeight: 700, color: 'var(--text-muted)', minWidth: 8 }}>{reaction.count}</span>} {reaction.count > 1 && <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-muted)', minWidth: 8 }}>{reaction.count}</span>}
</button> </button>
{hover && names && ReactDOM.createPortal( {hover && names && ReactDOM.createPortal(
<div style={{ <div style={{
position: 'fixed', top: pos.top, left: pos.left, transform: 'translate(-50%, -100%)', position: 'fixed', top: pos.top, left: pos.left, transform: 'translate(-50%, -100%)',
pointerEvents: 'none', zIndex: 10000, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 10000, whiteSpace: 'nowrap',
background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)', background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)',
fontSize: 11, fontWeight: 500, padding: '5px 10px', borderRadius: 8, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, padding: '5px 10px', borderRadius: 8,
boxShadow: '0 4px 12px rgba(0,0,0,0.15)', border: '1px solid var(--border-faint, #e5e7eb)', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', border: '1px solid var(--border-faint, #e5e7eb)',
}}> }}>
{names} {names}
+13 -13
View File
@@ -243,7 +243,7 @@ function CollabNotesLoading({ t }: NotesState) {
return ( return (
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', fontFamily: FONT }}> <div style={{ height: '100%', display: 'flex', flexDirection: 'column', fontFamily: FONT }}>
<div style={{ padding: '12px 16px', borderBottom: '1px solid var(--border-faint)' }}> <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--border-faint)' }}>
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-primary)', margin: 0, fontFamily: FONT }}> <h3 style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', margin: 0, fontFamily: FONT }}>
{t('collab.notes.title')} {t('collab.notes.title')}
</h3> </h3>
</div> </div>
@@ -263,7 +263,7 @@ function CollabNotesHeader({ t, canEdit, setShowSettings, setShowNewModal }: Not
return ( return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', flexShrink: 0 }}>
<h3 style={{ <h3 style={{
fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', margin: 0, fontFamily: FONT, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)', margin: 0, fontFamily: FONT,
letterSpacing: 0.3, textTransform: 'uppercase', display: 'flex', alignItems: 'center', gap: 7, letterSpacing: 0.3, textTransform: 'uppercase', display: 'flex', alignItems: 'center', gap: 7,
}}> }}>
<StickyNote size={14} color="var(--text-faint)" /> <StickyNote size={14} color="var(--text-faint)" />
@@ -277,7 +277,7 @@ function CollabNotesHeader({ t, canEdit, setShowSettings, setShowNewModal }: Not
<Settings size={14} /> <Settings size={14} />
</button>} </button>}
{canEdit && <button onClick={() => setShowNewModal(true)} {canEdit && <button onClick={() => setShowNewModal(true)}
style={{ display: 'inline-flex', alignItems: 'center', gap: 4, borderRadius: 99, padding: '6px 12px', background: 'var(--accent)', color: 'var(--accent-text)', fontSize: 11, fontWeight: 600, fontFamily: FONT, border: 'none', cursor: 'pointer', whiteSpace: 'nowrap' }}> style={{ display: 'inline-flex', alignItems: 'center', gap: 4, borderRadius: 99, padding: '6px 12px', background: 'var(--accent)', color: 'var(--accent-text)', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, fontFamily: FONT, border: 'none', cursor: 'pointer', whiteSpace: 'nowrap' }}>
<Plus size={12} /> <Plus size={12} />
{t('collab.notes.new')} {t('collab.notes.new')}
</button>} </button>}
@@ -292,7 +292,7 @@ function CollabCategoryPills({ categories, activeCategory, setActiveCategory, t
<button <button
onClick={() => setActiveCategory(null)} onClick={() => setActiveCategory(null)}
style={{ style={{
flexShrink: 0, borderRadius: 99, padding: '3px 10px', fontSize: 10, fontWeight: 600, fontFamily: FONT, flexShrink: 0, borderRadius: 99, padding: '3px 10px', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, fontFamily: FONT,
border: activeCategory === null ? '1px solid var(--accent)' : '1px solid var(--border-faint)', border: activeCategory === null ? '1px solid var(--accent)' : '1px solid var(--border-faint)',
background: activeCategory === null ? 'var(--accent)' : 'transparent', background: activeCategory === null ? 'var(--accent)' : 'transparent',
color: activeCategory === null ? 'var(--accent-text)' : 'var(--text-secondary)', color: activeCategory === null ? 'var(--accent-text)' : 'var(--text-secondary)',
@@ -306,7 +306,7 @@ function CollabCategoryPills({ categories, activeCategory, setActiveCategory, t
key={cat} key={cat}
onClick={() => setActiveCategory(prev => prev === cat ? null : cat)} onClick={() => setActiveCategory(prev => prev === cat ? null : cat)}
style={{ style={{
flexShrink: 0, borderRadius: 99, padding: '3px 10px', fontSize: 10, fontWeight: 600, fontFamily: FONT, flexShrink: 0, borderRadius: 99, padding: '3px 10px', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, fontFamily: FONT,
border: activeCategory === cat ? '1px solid var(--accent)' : '1px solid var(--border-faint)', border: activeCategory === cat ? '1px solid var(--accent)' : '1px solid var(--border-faint)',
background: activeCategory === cat ? 'var(--accent)' : 'transparent', background: activeCategory === cat ? 'var(--accent)' : 'transparent',
color: activeCategory === cat ? 'var(--accent-text)' : 'var(--text-secondary)', color: activeCategory === cat ? 'var(--accent-text)' : 'var(--text-secondary)',
@@ -334,10 +334,10 @@ function CollabNotesGrid(S: NotesState) {
padding: '48px 20px', textAlign: 'center', height: '100%', padding: '48px 20px', textAlign: 'center', height: '100%',
}}> }}>
<Pencil size={36} color="var(--text-faint)" style={{ marginBottom: 12 }} /> <Pencil size={36} color="var(--text-faint)" style={{ marginBottom: 12 }} />
<div style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4, fontFamily: FONT }}> <div style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4, fontFamily: FONT }}>
{t('collab.notes.empty')} {t('collab.notes.empty')}
</div> </div>
<div style={{ fontSize: 12, color: 'var(--text-faint)', fontFamily: FONT }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontFamily: FONT }}>
{t('collab.notes.emptyDesc') || 'Create a note to get started'} {t('collab.notes.emptyDesc') || 'Create a note to get started'}
</div> </div>
</div> </div>
@@ -397,10 +397,10 @@ function ViewNoteModal(S: NotesState) {
display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12,
}}> }}>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 17, fontWeight: 600, color: 'var(--text-primary)' }}>{viewingNote.title}</div> <div style={{ fontSize: 'calc(17px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)' }}>{viewingNote.title}</div>
{viewingNote.category && ( {viewingNote.category && (
<span style={{ <span style={{
display: 'inline-block', marginTop: 4, fontSize: 10, fontWeight: 600, display: 'inline-block', marginTop: 4, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600,
color: getCategoryColor(viewingNote.category), color: getCategoryColor(viewingNote.category),
background: `${getCategoryColor(viewingNote.category)}18`, background: `${getCategoryColor(viewingNote.category)}18`,
padding: '2px 8px', borderRadius: 6, padding: '2px 8px', borderRadius: 6,
@@ -422,11 +422,11 @@ function ViewNoteModal(S: NotesState) {
</button> </button>
</div> </div>
</div> </div>
<div className="collab-note-md-full" style={{ padding: '16px 20px', overflowY: 'auto', fontSize: 14, color: 'var(--text-primary)', lineHeight: 1.7 }}> <div className="collab-note-md-full" style={{ padding: '16px 20px', overflowY: 'auto', fontSize: 'calc(14px * var(--fs-scale-text, 1))', color: 'var(--text-primary)', lineHeight: 1.7 }}>
<Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{viewingNote.content || ''}</Markdown> <Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{viewingNote.content || ''}</Markdown>
{(viewingNote.attachments || []).length > 0 && ( {(viewingNote.attachments || []).length > 0 && (
<div style={{ marginTop: 16, paddingTop: 16, borderTop: '1px solid var(--border-primary)' }}> <div style={{ marginTop: 16, paddingTop: 16, borderTop: '1px solid var(--border-primary)' }}>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 10 }}>{t('files.title')}</div> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 10 }}>{t('files.title')}</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
{(viewingNote.attachments || []).map(a => { {(viewingNote.attachments || []).map(a => {
const isImage = a.mime_type?.startsWith('image/') const isImage = a.mime_type?.startsWith('image/')
@@ -449,10 +449,10 @@ function ViewNoteModal(S: NotesState) {
}} }}
onMouseEnter={e => { e.currentTarget.style.transform = 'scale(1.06)'; e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)' }} onMouseEnter={e => { e.currentTarget.style.transform = 'scale(1.06)'; e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)' }}
onMouseLeave={e => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = 'none' }}> onMouseLeave={e => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = 'none' }}>
<span style={{ fontSize: 10, fontWeight: 700, color: a.mime_type === 'application/pdf' ? '#ef4444' : 'var(--text-muted)', letterSpacing: 0.3 }}>{ext}</span> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, color: a.mime_type === 'application/pdf' ? '#ef4444' : 'var(--text-muted)', letterSpacing: 0.3 }}>{ext}</span>
</div> </div>
)} )}
<span style={{ fontSize: 9, color: 'var(--text-faint)', textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', width: '100%' }}>{a.original_name}</span> <span style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', width: '100%' }}>{a.original_name}</span>
</div> </div>
) )
})} })}
@@ -63,11 +63,11 @@ export function NoteCard({ note, currentUser, canEdit, onUpdate, onDelete, onEdi
}}> }}>
{!!note.pinned && <Pin size={9} color={color} style={{ flexShrink: 0 }} />} {!!note.pinned && <Pin size={9} color={color} style={{ flexShrink: 0 }} />}
<span style={{ display: 'flex', alignItems: 'center', gap: 5, overflow: 'hidden', flex: 1, minWidth: 0 }}> <span style={{ display: 'flex', alignItems: 'center', gap: 5, overflow: 'hidden', flex: 1, minWidth: 0 }}>
<span style={{ fontSize: 11, fontWeight: 700, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{note.title} {note.title}
</span> </span>
{note.category && ( {note.category && (
<span style={{ fontSize: 8, fontWeight: 600, color, background: `${color}18`, padding: '2px 6px', borderRadius: 99, flexShrink: 0, letterSpacing: '0.02em', textTransform: 'uppercase' }}> <span style={{ fontSize: 'calc(8px * var(--fs-scale-text, 1))', fontWeight: 600, color, background: `${color}18`, padding: '2px 6px', borderRadius: 99, flexShrink: 0, letterSpacing: '0.02em', textTransform: 'uppercase' }}>
{note.category} {note.category}
</span> </span>
)} )}
@@ -115,7 +115,7 @@ export function NoteCard({ note, currentUser, canEdit, onUpdate, onDelete, onEdi
marginBottom: 6, pointerEvents: 'none', opacity: 0, transition: 'opacity 0.12s', marginBottom: 6, pointerEvents: 'none', opacity: 0, transition: 'opacity 0.12s',
whiteSpace: 'nowrap', zIndex: 10, whiteSpace: 'nowrap', zIndex: 10,
background: 'var(--bg-card)', color: 'var(--text-primary)', background: 'var(--bg-card)', color: 'var(--text-primary)',
fontSize: 11, fontWeight: 500, padding: '5px 10px', borderRadius: 8, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, padding: '5px 10px', borderRadius: 8,
boxShadow: '0 4px 12px rgba(0,0,0,0.15)', border: '1px solid var(--border-faint)', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', border: '1px solid var(--border-faint)',
}}> }}>
{author.username} {author.username}
@@ -137,7 +137,7 @@ export function NoteCard({ note, currentUser, canEdit, onUpdate, onDelete, onEdi
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
{note.content && ( {note.content && (
<div className="collab-note-md" style={{ <div className="collab-note-md" style={{
fontSize: 11.5, color: 'var(--text-muted)', lineHeight: 1.5, margin: 0, fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', color: 'var(--text-muted)', lineHeight: 1.5, margin: 0,
maxHeight: '4.5em', overflow: 'hidden', maxHeight: '4.5em', overflow: 'hidden',
wordBreak: 'break-word', fontFamily: FONT, wordBreak: 'break-word', fontFamily: FONT,
}}> }}>
@@ -151,14 +151,14 @@ export function NoteCard({ note, currentUser, canEdit, onUpdate, onDelete, onEdi
{/* Website */} {/* Website */}
{note.website && ( {note.website && (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
<span style={{ fontSize: 7, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: 0.3 }}>Link</span> <span style={{ fontSize: 'calc(7px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: 0.3 }}>Link</span>
<WebsiteThumbnail url={note.website} tripId={tripId} color={color} /> <WebsiteThumbnail url={note.website} tripId={tripId} color={color} />
</div> </div>
)} )}
{/* Files */} {/* Files */}
{(note.attachments || []).length > 0 && ( {(note.attachments || []).length > 0 && (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
<span style={{ fontSize: 7, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: 0.3 }}>{t('files.title')}</span> <span style={{ fontSize: 'calc(7px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: 0.3 }}>{t('files.title')}</span>
<div style={{ display: 'flex', gap: 4 }}> <div style={{ display: 'flex', gap: 4 }}>
{(note.attachments || []).slice(0, note.website ? 1 : 2).map(a => { {(note.attachments || []).slice(0, note.website ? 1 : 2).map(a => {
const isImage = a.mime_type?.startsWith('image/') const isImage = a.mime_type?.startsWith('image/')
@@ -179,12 +179,12 @@ export function NoteCard({ note, currentUser, canEdit, onUpdate, onDelete, onEdi
}} }}
onMouseEnter={e => { e.currentTarget.style.transform = 'scale(1.08)'; e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)' }} onMouseEnter={e => { e.currentTarget.style.transform = 'scale(1.08)'; e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)' }}
onMouseLeave={e => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = 'none' }}> onMouseLeave={e => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = 'none' }}>
<span style={{ fontSize: 9, fontWeight: 700, color: a.mime_type === 'application/pdf' ? '#ef4444' : 'var(--text-muted)', letterSpacing: 0.3 }}>{ext}</span> <span style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 700, color: a.mime_type === 'application/pdf' ? '#ef4444' : 'var(--text-muted)', letterSpacing: 0.3 }}>{ext}</span>
</div> </div>
) )
})} })}
{(note.attachments?.length || 0) > (note.website ? 1 : 2) && ( {(note.attachments?.length || 0) > (note.website ? 1 : 2) && (
<span style={{ fontSize: 8, color: 'var(--text-faint)', textAlign: 'center' }}>+{(note.attachments?.length || 0) - (note.website ? 1 : 2)}</span> <span style={{ fontSize: 'calc(8px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', textAlign: 'center' }}>+{(note.attachments?.length || 0) - (note.website ? 1 : 2)}</span>
)} )}
</div> </div>
</div> </div>
@@ -71,7 +71,7 @@ export function CategorySettingsModal({ onClose, categories, categoryColors, onS
}} onClick={e => e.stopPropagation()}> }} onClick={e => e.stopPropagation()}>
{/* Header */} {/* Header */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 16px 12px', borderBottom: '1px solid var(--border-faint)' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 16px 12px', borderBottom: '1px solid var(--border-faint)' }}>
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-primary)', margin: 0 }}> <h3 style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', margin: 0 }}>
{t('collab.notes.categorySettings') || 'Category Settings'} {t('collab.notes.categorySettings') || 'Category Settings'}
</h3> </h3>
<button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 2, display: 'flex' }}> <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 2, display: 'flex' }}>
@@ -82,7 +82,7 @@ export function CategorySettingsModal({ onClose, categories, categoryColors, onS
{/* Categories list */} {/* Categories list */}
<div style={{ padding: '12px 16px', display: 'flex', flexDirection: 'column', gap: 10 }}> <div style={{ padding: '12px 16px', display: 'flex', flexDirection: 'column', gap: 10 }}>
{allCats.length === 0 && ( {allCats.length === 0 && (
<p style={{ fontSize: 12, color: 'var(--text-faint)', textAlign: 'center', padding: 16 }}> <p style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', textAlign: 'center', padding: 16 }}>
{t('collab.notes.noCategoriesYet') || 'No categories yet'} {t('collab.notes.noCategoriesYet') || 'No categories yet'}
</p> </p>
)} )}
@@ -119,7 +119,7 @@ export function CategorySettingsModal({ onClose, categories, categoryColors, onS
placeholder={t('collab.notes.newCategory')} placeholder={t('collab.notes.newCategory')}
style={{ style={{
flex: 1, border: '1px solid var(--border-primary)', borderRadius: 10, padding: '8px 12px', flex: 1, border: '1px solid var(--border-primary)', borderRadius: 10, padding: '8px 12px',
fontSize: 13, background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none',
}} /> }} />
<button onClick={handleAddCategory} disabled={!newCatName.trim()} style={{ <button onClick={handleAddCategory} disabled={!newCatName.trim()} style={{
background: newCatName.trim() ? 'var(--accent)' : 'var(--border-primary)', color: 'var(--accent-text)', background: newCatName.trim() ? 'var(--accent)' : 'var(--border-primary)', color: 'var(--accent-text)',
@@ -133,7 +133,7 @@ export function CategorySettingsModal({ onClose, categories, categoryColors, onS
{/* Save */} {/* Save */}
<button onClick={handleSave} style={{ <button onClick={handleSave} style={{
width: '100%', borderRadius: 99, padding: '9px 14px', background: 'var(--accent)', color: 'var(--accent-text)', width: '100%', borderRadius: 99, padding: '9px 14px', background: 'var(--accent)', color: 'var(--accent-text)',
fontSize: 13, fontWeight: 600, border: 'none', cursor: 'pointer', marginTop: 8, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, border: 'none', cursor: 'pointer', marginTop: 8,
}}> }}>
{t('collab.notes.save')} {t('collab.notes.save')}
</button> </button>
@@ -21,12 +21,12 @@ export function EditableCatName({ name, onRename }: EditableCatNameProps) {
if (editing) { if (editing) {
return <input ref={inputRef} value={value} onChange={e => setValue(e.target.value)} return <input ref={inputRef} value={value} onChange={e => setValue(e.target.value)}
onBlur={save} onKeyDown={e => { if (e.key === 'Enter') save(); if (e.key === 'Escape') { setValue(name); setEditing(false) } }} onBlur={save} onKeyDown={e => { if (e.key === 'Enter') save(); if (e.key === 'Escape') { setValue(name); setEditing(false) } }}
style={{ flex: 1, fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', border: '1px solid var(--border-primary)', borderRadius: 6, padding: '2px 8px', background: 'var(--bg-input)', fontFamily: 'inherit', outline: 'none' }} /> style={{ flex: 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', border: '1px solid var(--border-primary)', borderRadius: 6, padding: '2px 8px', background: 'var(--bg-input)', fontFamily: 'inherit', outline: 'none' }} />
} }
return ( return (
<span onClick={() => { setValue(name); setEditing(true) }} <span onClick={() => { setValue(name); setEditing(true) }}
style={{ flex: 1, fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', cursor: 'pointer', padding: '2px 0' }} style={{ flex: 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', cursor: 'pointer', padding: '2px 0' }}
title="Click to rename"> title="Click to rename">
{name} {name}
</span> </span>
@@ -37,7 +37,7 @@ export function FilePreviewPortal({ file, onClose }: FilePreviewPortalProps) {
: <Loader2 size={32} className="animate-spin text-[rgba(255,255,255,0.5)]" /> : <Loader2 size={32} className="animate-spin text-[rgba(255,255,255,0.5)]" />
} }
<div style={{ position: 'absolute', top: -36, left: 0, right: 0, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 4px' }}> <div style={{ position: 'absolute', top: -36, left: 0, right: 0, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 4px' }}>
<span style={{ fontSize: 11, color: 'rgba(255,255,255,0.7)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '70%' }}>{file.original_name}</span> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'rgba(255,255,255,0.7)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '70%' }}>{file.original_name}</span>
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
<button onClick={openInNewTab} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'rgba(255,255,255,0.7)', display: 'flex', padding: 0 }}><ExternalLink size={15} /></button> <button onClick={openInNewTab} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'rgba(255,255,255,0.7)', display: 'flex', padding: 0 }}><ExternalLink size={15} /></button>
<button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'rgba(255,255,255,0.7)', display: 'flex', padding: 0 }}><X size={17} /></button> <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'rgba(255,255,255,0.7)', display: 'flex', padding: 0 }}><X size={17} /></button>
@@ -48,21 +48,21 @@ export function FilePreviewPortal({ file, onClose }: FilePreviewPortalProps) {
/* Document viewer — card with header */ /* Document viewer — card with header */
<div style={{ width: '100%', maxWidth: 950, height: '94vh', display: 'flex', flexDirection: 'column', background: 'var(--bg-card)', borderRadius: 12, overflow: 'hidden', boxShadow: '0 20px 60px rgba(0,0,0,0.3)' }} onClick={e => e.stopPropagation()}> <div style={{ width: '100%', maxWidth: 950, height: '94vh', display: 'flex', flexDirection: 'column', background: 'var(--bg-card)', borderRadius: 12, overflow: 'hidden', boxShadow: '0 20px 60px rgba(0,0,0,0.3)' }} onClick={e => e.stopPropagation()}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', borderBottom: '1px solid var(--border-primary)', flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', borderBottom: '1px solid var(--border-primary)', flexShrink: 0 }}>
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{file.original_name}</span> <span style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{file.original_name}</span>
<div style={{ display: 'flex', gap: 8, flexShrink: 0 }}> <div style={{ display: 'flex', gap: 8, flexShrink: 0 }}>
<button onClick={openInNewTab} style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 3, fontSize: 11, color: 'var(--text-muted)', padding: 0 }}><ExternalLink size={13} /></button> <button onClick={openInNewTab} style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 3, fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-muted)', padding: 0 }}><ExternalLink size={13} /></button>
<button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', display: 'flex', padding: 2 }}><X size={18} /></button> <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', display: 'flex', padding: 2 }}><X size={18} /></button>
</div> </div>
</div> </div>
{(isPdf || isTxt) ? ( {(isPdf || isTxt) ? (
<object data={authUrl ? `${authUrl}#view=FitH` : ''} type={file.mime_type} style={{ flex: 1, width: '100%', border: 'none', background: '#fff' }} title={file.original_name}> <object data={authUrl ? `${authUrl}#view=FitH` : ''} type={file.mime_type} style={{ flex: 1, width: '100%', border: 'none', background: '#fff' }} title={file.original_name}>
<p style={{ padding: 24, textAlign: 'center', color: 'var(--text-muted)' }}> <p style={{ padding: 24, textAlign: 'center', color: 'var(--text-muted)' }}>
<button onClick={openInNewTab} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-primary)', textDecoration: 'underline', fontSize: 14, padding: 0 }}>Download</button> <button onClick={openInNewTab} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-primary)', textDecoration: 'underline', fontSize: 'calc(14px * var(--fs-scale-text, 1))', padding: 0 }}>Download</button>
</p> </p>
</object> </object>
) : ( ) : (
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 40 }}> <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 40 }}>
<button onClick={openInNewTab} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-primary)', textDecoration: 'underline', fontSize: 14, padding: 0 }}>Download {file.original_name}</button> <button onClick={openInNewTab} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-primary)', textDecoration: 'underline', fontSize: 'calc(14px * var(--fs-scale-text, 1))', padding: 0 }}>Download {file.original_name}</button>
</div> </div>
)} )}
</div> </div>
@@ -118,7 +118,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
borderBottom: '1px solid var(--border-faint)', borderBottom: '1px solid var(--border-faint)',
}}> }}>
<h3 style={{ <h3 style={{
fontSize: 14, fontSize: 'calc(14px * var(--fs-scale-text, 1))',
fontWeight: 700, fontWeight: 700,
color: 'var(--text-primary)', color: 'var(--text-primary)',
margin: 0, margin: 0,
@@ -153,7 +153,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
{/* Title */} {/* Title */}
<div> <div>
<div style={{ <div style={{
fontSize: 9, fontSize: 'calc(9px * var(--fs-scale-text, 1))',
fontWeight: 600, fontWeight: 600,
color: 'var(--text-faint)', color: 'var(--text-faint)',
textTransform: 'uppercase', textTransform: 'uppercase',
@@ -173,7 +173,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
border: '1px solid var(--border-primary)', border: '1px solid var(--border-primary)',
borderRadius: 10, borderRadius: 10,
padding: '8px 12px', padding: '8px 12px',
fontSize: 13, fontSize: 'calc(13px * var(--fs-scale-text, 1))',
background: 'var(--bg-input)', background: 'var(--bg-input)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
fontFamily: 'inherit', fontFamily: 'inherit',
@@ -186,7 +186,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
{/* Content */} {/* Content */}
<div> <div>
<div style={{ <div style={{
fontSize: 9, fontSize: 'calc(9px * var(--fs-scale-text, 1))',
fontWeight: 600, fontWeight: 600,
color: 'var(--text-faint)', color: 'var(--text-faint)',
textTransform: 'uppercase', textTransform: 'uppercase',
@@ -205,7 +205,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
border: '1px solid var(--border-primary)', border: '1px solid var(--border-primary)',
borderRadius: 10, borderRadius: 10,
padding: '8px 12px', padding: '8px 12px',
fontSize: 13, fontSize: 'calc(13px * var(--fs-scale-text, 1))',
background: 'var(--bg-input)', background: 'var(--bg-input)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
fontFamily: 'inherit', fontFamily: 'inherit',
@@ -220,7 +220,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
{/* Category pills */} {/* Category pills */}
<div> <div>
<div style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6, fontFamily: FONT }}> <div style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6, fontFamily: FONT }}>
{t('collab.notes.category')} {t('collab.notes.category')}
</div> </div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
@@ -229,7 +229,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
const active = category === cat const active = category === cat
return ( return (
<button key={cat} type="button" onClick={() => setCategory(cat)} <button key={cat} type="button" onClick={() => setCategory(cat)}
style={{ padding: '4px 12px', borderRadius: 99, border: active ? `1.5px solid ${c}` : '1px solid var(--border-faint)', background: active ? `${c}18` : 'transparent', color: active ? c : 'var(--text-muted)', fontSize: 11, fontWeight: 600, cursor: 'pointer', fontFamily: FONT }}> style={{ padding: '4px 12px', borderRadius: 99, border: active ? `1.5px solid ${c}` : '1px solid var(--border-faint)', background: active ? `${c}18` : 'transparent', color: active ? c : 'var(--text-muted)', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: FONT }}>
{cat} {cat}
</button> </button>
) )
@@ -239,17 +239,17 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
{/* Website */} {/* Website */}
<div> <div>
<div style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4, fontFamily: FONT }}> <div style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4, fontFamily: FONT }}>
{t('collab.notes.website')} {t('collab.notes.website')}
</div> </div>
<input value={website} onChange={e => setWebsite(e.target.value)} <input value={website} onChange={e => setWebsite(e.target.value)}
placeholder={t('collab.notes.websitePlaceholder')} placeholder={t('collab.notes.websitePlaceholder')}
style={{ width: '100%', border: '1px solid var(--border-primary)', borderRadius: 10, padding: '8px 12px', fontSize: 13, background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box' }} /> style={{ width: '100%', border: '1px solid var(--border-primary)', borderRadius: 10, padding: '8px 12px', fontSize: 'calc(13px * var(--fs-scale-text, 1))', background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box' }} />
</div> </div>
{/* File attachments */} {/* File attachments */}
{canUploadFiles && <div> {canUploadFiles && <div>
<div style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4, fontFamily: FONT }}> <div style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4, fontFamily: FONT }}>
{t('collab.notes.attachFiles')} {t('collab.notes.attachFiles')}
</div> </div>
<input ref={fileRef} type="file" multiple style={{ display: 'none' }} onChange={e => { const files = e.target.files; if (files?.length) setPendingFiles(prev => [...prev, ...Array.from(files)]); e.target.value = '' }} /> <input ref={fileRef} type="file" multiple style={{ display: 'none' }} onChange={e => { const files = e.target.files; if (files?.length) setPendingFiles(prev => [...prev, ...Array.from(files)]); e.target.value = '' }} />
@@ -258,7 +258,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
{existingAttachments.map(a => { {existingAttachments.map(a => {
const isImage = a.mime_type?.startsWith('image/') const isImage = a.mime_type?.startsWith('image/')
return ( return (
<div key={a.id} style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '3px 8px', borderRadius: 8, background: 'var(--bg-secondary)', fontSize: 11, color: 'var(--text-muted)' }}> <div key={a.id} style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '3px 8px', borderRadius: 8, background: 'var(--bg-secondary)', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-muted)' }}>
{isImage && <AuthedImg src={a.url} style={{ width: 18, height: 18, objectFit: 'cover', borderRadius: 3 }} />} {isImage && <AuthedImg src={a.url} style={{ width: 18, height: 18, objectFit: 'cover', borderRadius: 3 }} />}
{(a.original_name || '').length > 20 ? a.original_name.slice(0, 17) + '...' : a.original_name} {(a.original_name || '').length > 20 ? a.original_name.slice(0, 17) + '...' : a.original_name}
<button type="button" onClick={() => handleDeleteAttachment(a.id)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#ef4444', padding: 0, display: 'flex' }}> <button type="button" onClick={() => handleDeleteAttachment(a.id)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#ef4444', padding: 0, display: 'flex' }}>
@@ -269,7 +269,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
})} })}
{/* New pending files */} {/* New pending files */}
{pendingFiles.map((f, i) => ( {pendingFiles.map((f, i) => (
<div key={`new-${i}`} style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '3px 8px', borderRadius: 8, background: 'var(--bg-secondary)', fontSize: 11, color: 'var(--text-muted)' }}> <div key={`new-${i}`} style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '3px 8px', borderRadius: 8, background: 'var(--bg-secondary)', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-muted)' }}>
{f.name.length > 20 ? f.name.slice(0, 17) + '...' : f.name} {f.name.length > 20 ? f.name.slice(0, 17) + '...' : f.name}
<button type="button" onClick={() => setPendingFiles(prev => prev.filter((_, j) => j !== i))} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 0, display: 'flex' }}> <button type="button" onClick={() => setPendingFiles(prev => prev.filter((_, j) => j !== i))} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 0, display: 'flex' }}>
<X size={10} /> <X size={10} />
@@ -277,7 +277,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
</div> </div>
))} ))}
<button type="button" onClick={() => fileRef.current?.click()} <button type="button" onClick={() => fileRef.current?.click()}
style={{ padding: '4px 10px', borderRadius: 8, border: '1px dashed var(--border-faint)', background: 'transparent', cursor: 'pointer', color: 'var(--text-faint)', fontSize: 11, fontFamily: FONT, display: 'inline-flex', alignItems: 'center', gap: 4 }}> style={{ padding: '4px 10px', borderRadius: 8, border: '1px dashed var(--border-faint)', background: 'transparent', cursor: 'pointer', color: 'var(--text-faint)', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontFamily: FONT, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
<Plus size={11} /> {t('files.attach') || 'Add'} <Plus size={11} /> {t('files.attach') || 'Add'}
</button> </button>
</div> </div>
@@ -293,7 +293,7 @@ export function NoteFormModal({ onClose, onSubmit, onDeleteFile, existingCategor
padding: '7px 14px', padding: '7px 14px',
background: canSubmit ? 'var(--accent)' : 'var(--border-primary)', background: canSubmit ? 'var(--accent)' : 'var(--border-primary)',
color: canSubmit ? 'var(--accent-text)' : 'var(--text-faint)', color: canSubmit ? 'var(--accent-text)' : 'var(--text-faint)',
fontSize: 12, fontSize: 'calc(12px * var(--fs-scale-text, 1))',
fontWeight: 600, fontWeight: 600,
fontFamily: FONT, fontFamily: FONT,
border: 'none', border: 'none',
@@ -37,7 +37,7 @@ export function WebsiteThumbnail({ url, tripId, color }: WebsiteThumbnailProps)
) : ( ) : (
<> <>
<ExternalLink size={14} color="var(--text-muted)" /> <ExternalLink size={14} color="var(--text-muted)" />
<span style={{ fontSize: 7, fontWeight: 600, color: 'var(--text-muted)', maxWidth: 42, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' }}> <span style={{ fontSize: 'calc(7px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)', maxWidth: 42, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' }}>
{domain} {domain}
</span> </span>
</> </>
+1 -1
View File
@@ -175,7 +175,7 @@ export default function CollabPanel({ tripId, tripMembers = [], collabFeatures }
padding: '8px 0', borderRadius: 10, border: 'none', cursor: 'pointer', padding: '8px 0', borderRadius: 10, border: 'none', cursor: 'pointer',
background: active ? 'var(--accent)' : 'transparent', background: active ? 'var(--accent)' : 'transparent',
color: active ? 'var(--accent-text)' : 'var(--text-muted)', color: active ? 'var(--accent-text)' : 'var(--text-muted)',
fontSize: 11, fontWeight: 600, fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, fontFamily: 'inherit',
transition: 'all 0.15s', transition: 'all 0.15s',
}}> }}>
{tab.label} {tab.label}
+22 -22
View File
@@ -88,30 +88,30 @@ function CreatePollModal({ onClose, onCreate, t }: CreatePollModalProps) {
<div style={{ position: 'fixed', inset: 0, background: 'var(--overlay-bg, rgba(0,0,0,0.35))', backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999, padding: 16, fontFamily: FONT }} onClick={onClose}> <div style={{ position: 'fixed', inset: 0, background: 'var(--overlay-bg, rgba(0,0,0,0.35))', backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999, padding: 16, fontFamily: FONT }} onClick={onClose}>
<form style={{ background: 'var(--bg-card)', borderRadius: 16, width: '100%', maxWidth: 400, maxHeight: '90vh', overflow: 'auto', border: '1px solid var(--border-faint)' }} onClick={e => e.stopPropagation()} onSubmit={handleSubmit}> <form style={{ background: 'var(--bg-card)', borderRadius: 16, width: '100%', maxWidth: 400, maxHeight: '90vh', overflow: 'auto', border: '1px solid var(--border-faint)' }} onClick={e => e.stopPropagation()} onSubmit={handleSubmit}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 16px 12px', borderBottom: '1px solid var(--border-faint)' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 16px 12px', borderBottom: '1px solid var(--border-faint)' }}>
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-primary)', margin: 0 }}>{t('collab.polls.new')}</h3> <h3 style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', margin: 0 }}>{t('collab.polls.new')}</h3>
<button type="button" onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 2, display: 'flex' }}><X size={16} /></button> <button type="button" onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 2, display: 'flex' }}><X size={16} /></button>
</div> </div>
<div style={{ padding: '14px 16px 16px', display: 'flex', flexDirection: 'column', gap: 12 }}> <div style={{ padding: '14px 16px 16px', display: 'flex', flexDirection: 'column', gap: 12 }}>
{/* Question */} {/* Question */}
<div> <div>
<div style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }}>{t('collab.polls.question')}</div> <div style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }}>{t('collab.polls.question')}</div>
<input autoFocus value={question} onChange={e => setQuestion(e.target.value)} placeholder={t('collab.polls.questionPlaceholder') || 'Ask a question...'} style={{ width: '100%', border: '1px solid var(--border-primary)', borderRadius: 10, padding: '8px 12px', fontSize: 13, background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box' }} /> <input autoFocus value={question} onChange={e => setQuestion(e.target.value)} placeholder={t('collab.polls.questionPlaceholder') || 'Ask a question...'} style={{ width: '100%', border: '1px solid var(--border-primary)', borderRadius: 10, padding: '8px 12px', fontSize: 'calc(13px * var(--fs-scale-text, 1))', background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box' }} />
</div> </div>
{/* Options */} {/* Options */}
<div> <div>
<div style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }}>{t('collab.polls.options')}</div> <div style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }}>{t('collab.polls.options')}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{options.map((opt, i) => ( {options.map((opt, i) => (
<div key={i} style={{ display: 'flex', gap: 6, alignItems: 'center' }}> <div key={i} style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
<input value={opt} onChange={e => updateOption(i, e.target.value)} placeholder={`${t('collab.polls.option')} ${i + 1}`} <input value={opt} onChange={e => updateOption(i, e.target.value)} placeholder={`${t('collab.polls.option')} ${i + 1}`}
style={{ flex: 1, border: '1px solid var(--border-primary)', borderRadius: 10, padding: '8px 12px', fontSize: 13, background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }} /> style={{ flex: 1, border: '1px solid var(--border-primary)', borderRadius: 10, padding: '8px 12px', fontSize: 'calc(13px * var(--fs-scale-text, 1))', background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }} />
{options.length > 2 && ( {options.length > 2 && (
<button type="button" onClick={() => removeOption(i)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', display: 'flex', padding: 2 }}><X size={14} /></button> <button type="button" onClick={() => removeOption(i)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', display: 'flex', padding: 2 }}><X size={14} /></button>
)} )}
</div> </div>
))} ))}
<button type="button" onClick={addOption} style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '6px 12px', borderRadius: 10, border: '1px dashed var(--border-faint)', background: 'transparent', cursor: 'pointer', color: 'var(--text-faint)', fontSize: 12, fontFamily: FONT }}> <button type="button" onClick={addOption} style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '6px 12px', borderRadius: 10, border: '1px dashed var(--border-faint)', background: 'transparent', cursor: 'pointer', color: 'var(--text-faint)', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontFamily: FONT }}>
<Plus size={12} /> {t('collab.polls.addOption')} <Plus size={12} /> {t('collab.polls.addOption')}
</button> </button>
</div> </div>
@@ -126,13 +126,13 @@ function CreatePollModal({ onClose, onCreate, t }: CreatePollModalProps) {
}}> }}>
<div style={{ width: 16, height: 16, borderRadius: '50%', background: '#fff', transition: 'transform 0.2s', transform: multiChoice ? 'translateX(16px)' : 'translateX(0)' }} /> <div style={{ width: 16, height: 16, borderRadius: '50%', background: '#fff', transition: 'transform 0.2s', transform: multiChoice ? 'translateX(16px)' : 'translateX(0)' }} />
</div> </div>
<span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: FONT }}>{t('collab.polls.multiChoice')}</span> <span style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-muted)', fontFamily: FONT }}>{t('collab.polls.multiChoice')}</span>
</label> </label>
{/* Submit */} {/* Submit */}
<button type="submit" disabled={!canSubmit} style={{ <button type="submit" disabled={!canSubmit} style={{
width: '100%', borderRadius: 99, padding: '9px 14px', background: canSubmit ? 'var(--accent)' : 'var(--border-primary)', width: '100%', borderRadius: 99, padding: '9px 14px', background: canSubmit ? 'var(--accent)' : 'var(--border-primary)',
color: canSubmit ? 'var(--accent-text)' : 'var(--text-faint)', fontSize: 13, fontWeight: 600, border: 'none', cursor: canSubmit ? 'pointer' : 'default', fontFamily: FONT, color: canSubmit ? 'var(--accent-text)' : 'var(--text-faint)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, border: 'none', cursor: canSubmit ? 'pointer' : 'default', fontFamily: FONT,
}}> }}>
{submitting ? '...' : t('collab.polls.create')} {submitting ? '...' : t('collab.polls.create')}
</button> </button>
@@ -168,7 +168,7 @@ function VoterChip({ voter, offset }: VoterChipProps) {
style={{ style={{
width: 18, height: 18, borderRadius: '50%', background: 'var(--bg-tertiary)', width: 18, height: 18, borderRadius: '50%', background: 'var(--bg-tertiary)',
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 7, fontWeight: 700, color: 'var(--text-muted)', overflow: 'hidden', fontSize: 'calc(7px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-muted)', overflow: 'hidden',
border: '1.5px solid var(--bg-card)', marginLeft: offset ? -5 : 0, flexShrink: 0, border: '1.5px solid var(--bg-card)', marginLeft: offset ? -5 : 0, flexShrink: 0,
}}> }}>
{voter.avatar_url ? <img src={voter.avatar_url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : (voter.username || '?')[0].toUpperCase()} {voter.avatar_url ? <img src={voter.avatar_url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : (voter.username || '?')[0].toUpperCase()}
@@ -178,7 +178,7 @@ function VoterChip({ voter, offset }: VoterChipProps) {
position: 'fixed', top: pos.top, left: pos.left, transform: 'translate(-50%, -100%)', position: 'fixed', top: pos.top, left: pos.left, transform: 'translate(-50%, -100%)',
pointerEvents: 'none', zIndex: 10000, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 10000, whiteSpace: 'nowrap',
background: 'var(--bg-card)', color: 'var(--text-primary)', background: 'var(--bg-card)', color: 'var(--text-primary)',
fontSize: 11, fontWeight: 500, padding: '5px 10px', borderRadius: 8, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, padding: '5px 10px', borderRadius: 8,
boxShadow: '0 4px 12px rgba(0,0,0,0.15)', border: '1px solid var(--border-faint)', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', border: '1px solid var(--border-faint)',
}}> }}>
{voter.username} {voter.username}
@@ -217,26 +217,26 @@ function PollCard({ poll, currentUser, canEdit, onVote, onClose, onDelete, t }:
background: isClosed ? 'var(--bg-secondary)' : 'transparent', background: isClosed ? 'var(--bg-secondary)' : 'transparent',
}}> }}>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.35, wordBreak: 'break-word' }}> <div style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.35, wordBreak: 'break-word' }}>
{poll.question} {poll.question}
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 4, flexWrap: 'wrap' }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 4, flexWrap: 'wrap' }}>
{isClosed && ( {isClosed && (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', background: 'var(--bg-tertiary)', padding: '2px 7px', borderRadius: 99 }}> <span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', background: 'var(--bg-tertiary)', padding: '2px 7px', borderRadius: 99 }}>
<Lock size={8} /> {t('collab.polls.closed')} <Lock size={8} /> {t('collab.polls.closed')}
</span> </span>
)} )}
{remaining && !isClosed && ( {remaining && !isClosed && (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 9, fontWeight: 600, color: '#f59e0b', background: '#f59e0b18', padding: '2px 7px', borderRadius: 99 }}> <span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: '#f59e0b', background: '#f59e0b18', padding: '2px 7px', borderRadius: 99 }}>
<Clock size={8} /> {remaining} <Clock size={8} /> {remaining}
</span> </span>
)} )}
{poll.multi_choice && ( {poll.multi_choice && (
<span style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', background: 'var(--bg-tertiary)', padding: '2px 7px', borderRadius: 99 }}> <span style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', background: 'var(--bg-tertiary)', padding: '2px 7px', borderRadius: 99 }}>
{t('collab.polls.multiChoice')} {t('collab.polls.multiChoice')}
</span> </span>
)} )}
<span style={{ fontSize: 9, color: 'var(--text-faint)' }}> <span style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>
{total} {total === 1 ? 'vote' : 'votes'} {total} {total === 1 ? 'vote' : 'votes'}
</span> </span>
</div> </div>
@@ -303,7 +303,7 @@ function PollCard({ poll, currentUser, canEdit, onVote, onClose, onDelete, t }:
{/* Label */} {/* Label */}
<span style={{ <span style={{
flex: 1, fontSize: 13, fontWeight: myVote || isWinner ? 600 : 400, flex: 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: myVote || isWinner ? 600 : 400,
color: 'var(--text-primary)', position: 'relative', zIndex: 1, color: 'var(--text-primary)', position: 'relative', zIndex: 1,
}}> }}>
{typeof opt === 'string' ? opt : opt.text} {typeof opt === 'string' ? opt : opt.text}
@@ -321,7 +321,7 @@ function PollCard({ poll, currentUser, canEdit, onVote, onClose, onDelete, t }:
{/* Percentage */} {/* Percentage */}
{(hasVoted || isClosed) && ( {(hasVoted || isClosed) && (
<span style={{ <span style={{
fontSize: 12, fontWeight: 700, color: myVote ? '#007AFF' : 'var(--text-muted)', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 700, color: myVote ? '#007AFF' : 'var(--text-muted)',
position: 'relative', zIndex: 1, minWidth: 32, textAlign: 'right', position: 'relative', zIndex: 1, minWidth: 32, textAlign: 'right',
}}> }}>
{pct}% {pct}%
@@ -443,14 +443,14 @@ export default function CollabPolls({ tripId, currentUser }: CollabPollsProps) {
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', fontFamily: FONT }}> <div style={{ height: '100%', display: 'flex', flexDirection: 'column', fontFamily: FONT }}>
{/* Header */} {/* Header */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', flexShrink: 0 }}>
<h3 style={{ margin: 0, fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', display: 'flex', alignItems: 'center', gap: 7, letterSpacing: 0.3, textTransform: 'uppercase' }}> <h3 style={{ margin: 0, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)', display: 'flex', alignItems: 'center', gap: 7, letterSpacing: 0.3, textTransform: 'uppercase' }}>
<BarChart3 size={14} color="var(--text-faint)" /> <BarChart3 size={14} color="var(--text-faint)" />
{t('collab.polls.title')} {t('collab.polls.title')}
</h3> </h3>
{canEdit && ( {canEdit && (
<button onClick={() => setShowForm(true)} style={{ <button onClick={() => setShowForm(true)} style={{
display: 'inline-flex', alignItems: 'center', gap: 4, borderRadius: 99, padding: '6px 12px', display: 'inline-flex', alignItems: 'center', gap: 4, borderRadius: 99, padding: '6px 12px',
background: 'var(--accent)', color: 'var(--accent-text)', fontSize: 11, fontWeight: 600, background: 'var(--accent)', color: 'var(--accent-text)', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600,
fontFamily: FONT, border: 'none', cursor: 'pointer', fontFamily: FONT, border: 'none', cursor: 'pointer',
}}> }}>
<Plus size={12} /> {t('collab.polls.new')} <Plus size={12} /> {t('collab.polls.new')}
@@ -463,8 +463,8 @@ export default function CollabPolls({ tripId, currentUser }: CollabPollsProps) {
{polls.length === 0 ? ( {polls.length === 0 ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '48px 20px', textAlign: 'center', height: '100%' }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '48px 20px', textAlign: 'center', height: '100%' }}>
<BarChart3 size={36} color="var(--text-faint)" strokeWidth={1.3} style={{ marginBottom: 12 }} /> <BarChart3 size={36} color="var(--text-faint)" strokeWidth={1.3} style={{ marginBottom: 12 }} />
<div style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}>{t('collab.polls.empty')}</div> <div style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}>{t('collab.polls.empty')}</div>
<div style={{ fontSize: 12, color: 'var(--text-faint)' }}>{t('collab.polls.emptyHint')}</div> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>{t('collab.polls.emptyHint')}</div>
</div> </div>
) : ( ) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
@@ -474,7 +474,7 @@ export default function CollabPolls({ tripId, currentUser }: CollabPollsProps) {
{closedPolls.length > 0 && ( {closedPolls.length > 0 && (
<> <>
{activePolls.length > 0 && ( {activePolls.length > 0 && (
<div style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: 0.3, padding: '8px 0 2px' }}> <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: 0.3, padding: '8px 0 2px' }}>
{t('collab.polls.closedSection') || 'Closed'} {t('collab.polls.closedSection') || 'Closed'}
</div> </div>
)} )}
@@ -91,7 +91,7 @@ export default function WhatsNextWidget({ tripMembers = [] }: WhatsNextWidgetPro
padding: '10px 14px', display: 'flex', alignItems: 'center', gap: 7, flexShrink: 0, padding: '10px 14px', display: 'flex', alignItems: 'center', gap: 7, flexShrink: 0,
}}> }}>
<Sparkles size={14} color="var(--text-faint)" /> <Sparkles size={14} color="var(--text-faint)" />
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', letterSpacing: 0.3, textTransform: 'uppercase' }}> <span style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)', letterSpacing: 0.3, textTransform: 'uppercase' }}>
{t('collab.whatsNext.title') || "What's Next"} {t('collab.whatsNext.title') || "What's Next"}
</span> </span>
</div> </div>
@@ -101,8 +101,8 @@ export default function WhatsNextWidget({ tripMembers = [] }: WhatsNextWidgetPro
{upcoming.length === 0 ? ( {upcoming.length === 0 ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: '48px 20px', textAlign: 'center' }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: '48px 20px', textAlign: 'center' }}>
<Calendar size={36} color="var(--text-faint)" strokeWidth={1.3} style={{ marginBottom: 12 }} /> <Calendar size={36} color="var(--text-faint)" strokeWidth={1.3} style={{ marginBottom: 12 }} />
<div style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}>{t('collab.whatsNext.empty')}</div> <div style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}>{t('collab.whatsNext.empty')}</div>
<div style={{ fontSize: 12, color: 'var(--text-faint)' }}>{t('collab.whatsNext.emptyHint')}</div> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>{t('collab.whatsNext.emptyHint')}</div>
</div> </div>
) : ( ) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
@@ -114,7 +114,7 @@ export default function WhatsNextWidget({ tripMembers = [] }: WhatsNextWidgetPro
<React.Fragment key={item.id}> <React.Fragment key={item.id}>
{showDayHeader && ( {showDayHeader && (
<div style={{ <div style={{
fontSize: 10, fontWeight: 500, color: 'var(--text-faint)', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 500, color: 'var(--text-faint)',
textTransform: 'uppercase', letterSpacing: 0.5, textTransform: 'uppercase', letterSpacing: 0.5,
padding: idx === 0 ? '0 4px 4px' : '8px 4px 4px', padding: idx === 0 ? '0 4px 4px' : '8px 4px 4px',
}}> }}>
@@ -132,15 +132,15 @@ export default function WhatsNextWidget({ tripMembers = [] }: WhatsNextWidgetPro
> >
{/* Time column */} {/* Time column */}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minWidth: 44, flexShrink: 0 }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minWidth: 44, flexShrink: 0 }}>
<span style={{ fontSize: 11, fontWeight: 700, color: 'var(--text-primary)', whiteSpace: 'nowrap', lineHeight: 1 }}> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', whiteSpace: 'nowrap', lineHeight: 1 }}>
{item.time ? formatTime(item.time, is12h) : 'TBD'} {item.time ? formatTime(item.time, is12h) : 'TBD'}
</span> </span>
{item.endTime && ( {item.endTime && (
<> <>
<span style={{ fontSize: 7, color: 'var(--text-faint)', fontWeight: 600, letterSpacing: 0.3, margin: '2px 0', textTransform: 'uppercase' }}> <span style={{ fontSize: 'calc(7px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 600, letterSpacing: 0.3, margin: '2px 0', textTransform: 'uppercase' }}>
{t('collab.whatsNext.until') || 'bis'} {t('collab.whatsNext.until') || 'bis'}
</span> </span>
<span style={{ fontSize: 11, fontWeight: 700, color: 'var(--text-primary)', whiteSpace: 'nowrap', lineHeight: 1 }}> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', whiteSpace: 'nowrap', lineHeight: 1 }}>
{formatTime(item.endTime, is12h)} {formatTime(item.endTime, is12h)}
</span> </span>
</> </>
@@ -152,13 +152,13 @@ export default function WhatsNextWidget({ tripMembers = [] }: WhatsNextWidgetPro
{/* Details */} {/* Details */}
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)', lineHeight: 1.3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', lineHeight: 1.3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{item.name} {item.name}
</div> </div>
{item.address && ( {item.address && (
<div style={{ display: 'flex', alignItems: 'center', gap: 3, marginTop: 2 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 3, marginTop: 2 }}>
<MapPin size={9} color="var(--text-faint)" style={{ flexShrink: 0 }} /> <MapPin size={9} color="var(--text-faint)" style={{ flexShrink: 0 }} />
<span style={{ fontSize: 10, color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{item.address} {item.address}
</span> </span>
</div> </div>
@@ -175,7 +175,7 @@ export default function WhatsNextWidget({ tripMembers = [] }: WhatsNextWidgetPro
<div style={{ <div style={{
width: 16, height: 16, borderRadius: '50%', background: 'var(--bg-secondary)', width: 16, height: 16, borderRadius: '50%', background: 'var(--bg-secondary)',
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 7, fontWeight: 700, color: 'var(--text-muted)', fontSize: 'calc(7px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-muted)',
overflow: 'hidden', flexShrink: 0, overflow: 'hidden', flexShrink: 0,
}}> }}>
{p.avatar {p.avatar
@@ -183,7 +183,7 @@ export default function WhatsNextWidget({ tripMembers = [] }: WhatsNextWidgetPro
: p.username?.[0]?.toUpperCase() : p.username?.[0]?.toUpperCase()
} }
</div> </div>
<span style={{ fontSize: 10, fontWeight: 500, color: 'var(--text-muted)' }}>{p.username}</span> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 500, color: 'var(--text-muted)' }}>{p.username}</span>
</div> </div>
))} ))}
</div> </div>
@@ -17,8 +17,8 @@ export function AssignModal(S: FileManagerState) {
}} onClick={e => e.stopPropagation()}> }} onClick={e => e.stopPropagation()}>
<div style={{ padding: '16px 20px 12px', borderBottom: '1px solid var(--border-primary)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}> <div style={{ padding: '16px 20px 12px', borderBottom: '1px solid var(--border-primary)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 15, fontWeight: 600, color: 'var(--text-primary)' }}>{t('files.assignTitle')}</div> <div style={{ fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)' }}>{t('files.assignTitle')}</div>
<div style={{ fontSize: 12, color: 'var(--text-faint)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{files.find(f => f.id === assignFileId)?.original_name || ''} {files.find(f => f.id === assignFileId)?.original_name || ''}
</div> </div>
</div> </div>
@@ -27,7 +27,7 @@ export function AssignModal(S: FileManagerState) {
</button> </button>
</div> </div>
<div style={{ padding: '8px 12px 0' }}> <div style={{ padding: '8px 12px 0' }}>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-faint)', padding: '0 2px 4px', textTransform: 'uppercase', letterSpacing: 0.5 }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', padding: '0 2px 4px', textTransform: 'uppercase', letterSpacing: 0.5 }}>
{t('files.noteLabel') || 'Note'} {t('files.noteLabel') || 'Note'}
</div> </div>
<input <input
@@ -43,7 +43,7 @@ export function AssignModal(S: FileManagerState) {
}} }}
onKeyDown={e => { if (e.key === 'Enter') (e.target as HTMLInputElement).blur() }} onKeyDown={e => { if (e.key === 'Enter') (e.target as HTMLInputElement).blur() }}
style={{ style={{
width: '100%', padding: '7px 10px', fontSize: 13, borderRadius: 8, width: '100%', padding: '7px 10px', fontSize: 'calc(13px * var(--fs-scale-text, 1))', borderRadius: 8,
border: '1px solid var(--border-primary)', background: 'var(--bg-secondary)', border: '1px solid var(--border-primary)', background: 'var(--bg-secondary)',
color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none',
}} }}
@@ -91,7 +91,7 @@ export function AssignModal(S: FileManagerState) {
} }
}} style={{ }} style={{
width: '100%', textAlign: 'left', padding: '6px 10px 6px 20px', background: isLinked ? 'var(--bg-hover)' : 'none', width: '100%', textAlign: 'left', padding: '6px 10px 6px 20px', background: isLinked ? 'var(--bg-hover)' : 'none',
border: 'none', cursor: 'pointer', fontSize: 13, color: 'var(--text-primary)', border: 'none', cursor: 'pointer', fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-primary)',
borderRadius: 8, fontFamily: 'inherit', fontWeight: isLinked ? 600 : 400, borderRadius: 8, fontFamily: 'inherit', fontWeight: isLinked ? 600 : 400,
display: 'flex', alignItems: 'center', gap: 6, display: 'flex', alignItems: 'center', gap: 6,
}} }}
@@ -106,18 +106,18 @@ export function AssignModal(S: FileManagerState) {
const placesSection = places.length > 0 && ( const placesSection = places.length > 0 && (
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-faint)', padding: '8px 10px 4px', textTransform: 'uppercase', letterSpacing: 0.5 }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', padding: '8px 10px 4px', textTransform: 'uppercase', letterSpacing: 0.5 }}>
{t('files.assignPlace')} {t('files.assignPlace')}
</div> </div>
{dayGroups.map(({ day, dayPlaces }) => ( {dayGroups.map(({ day, dayPlaces }) => (
<div key={day.id}> <div key={day.id}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', padding: '8px 10px 2px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)', padding: '8px 10px 2px' }}>
<span>{day.title || t('dayplan.dayN', { n: day.day_number })}</span> <span>{day.title || t('dayplan.dayN', { n: day.day_number })}</span>
{(() => { {(() => {
const badge = day.date || (day.title ? t('dayplan.dayN', { n: day.day_number }) : null) const badge = day.date || (day.title ? t('dayplan.dayN', { n: day.day_number }) : null)
return badge ? ( return badge ? (
<span style={{ <span style={{
fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)',
background: 'var(--bg-tertiary)', padding: '1px 6px', borderRadius: 999, background: 'var(--bg-tertiary)', padding: '1px 6px', borderRadius: 999,
}}>{badge}</span> }}>{badge}</span>
) : null ) : null
@@ -128,7 +128,7 @@ export function AssignModal(S: FileManagerState) {
))} ))}
{unassigned.length > 0 && ( {unassigned.length > 0 && (
<div> <div>
{dayGroups.length > 0 && <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', padding: '8px 10px 2px' }}>{t('files.unassigned')}</div>} {dayGroups.length > 0 && <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)', padding: '8px 10px 2px' }}>{t('files.unassigned')}</div>}
{unassigned.map(placeBtn)} {unassigned.map(placeBtn)}
</div> </div>
)} )}
@@ -166,7 +166,7 @@ export function AssignModal(S: FileManagerState) {
} }
}} style={{ }} style={{
width: '100%', textAlign: 'left', padding: '6px 10px 6px 20px', background: isLinked ? 'var(--bg-hover)' : 'none', width: '100%', textAlign: 'left', padding: '6px 10px 6px 20px', background: isLinked ? 'var(--bg-hover)' : 'none',
border: 'none', cursor: 'pointer', fontSize: 13, color: 'var(--text-primary)', border: 'none', cursor: 'pointer', fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-primary)',
borderRadius: 8, fontFamily: 'inherit', fontWeight: isLinked ? 600 : 400, borderRadius: 8, fontFamily: 'inherit', fontWeight: isLinked ? 600 : 400,
display: 'flex', alignItems: 'center', gap: 6, display: 'flex', alignItems: 'center', gap: 6,
}} }}
@@ -183,7 +183,7 @@ export function AssignModal(S: FileManagerState) {
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
{bookingReservations.length > 0 && ( {bookingReservations.length > 0 && (
<> <>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-faint)', padding: '8px 10px 4px', textTransform: 'uppercase', letterSpacing: 0.5 }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', padding: '8px 10px 4px', textTransform: 'uppercase', letterSpacing: 0.5 }}>
{t('files.assignBooking')} {t('files.assignBooking')}
</div> </div>
{bookingReservations.map(reservationBtn)} {bookingReservations.map(reservationBtn)}
@@ -191,7 +191,7 @@ export function AssignModal(S: FileManagerState) {
)} )}
{transportReservations.length > 0 && ( {transportReservations.length > 0 && (
<> <>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-faint)', padding: '8px 10px 4px', textTransform: 'uppercase', letterSpacing: 0.5, marginTop: bookingReservations.length > 0 ? 4 : 0 }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', padding: '8px 10px 4px', textTransform: 'uppercase', letterSpacing: 0.5, marginTop: bookingReservations.length > 0 ? 4 : 0 }}>
{t('files.assignTransport')} {t('files.assignTransport')}
</div> </div>
{transportReservations.map(reservationBtn)} {transportReservations.map(reservationBtn)}
@@ -32,7 +32,7 @@ export function AvatarChip({ name, avatarUrl, size = 20 }: { name: string; avata
<div style={{ <div style={{
position: 'fixed', top: pos.top, left: pos.left, transform: 'translate(-50%, -100%)', position: 'fixed', top: pos.top, left: pos.left, transform: 'translate(-50%, -100%)',
background: 'var(--bg-elevated)', color: 'var(--text-primary)', background: 'var(--bg-elevated)', color: 'var(--text-primary)',
fontSize: 11, fontWeight: 500, padding: '3px 8px', borderRadius: 6, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, padding: '3px 8px', borderRadius: 6,
boxShadow: '0 2px 8px rgba(0,0,0,0.15)', whiteSpace: 'nowrap', zIndex: 9999, boxShadow: '0 2px 8px rgba(0,0,0,0.15)', whiteSpace: 'nowrap', zIndex: 9999,
pointerEvents: 'none', pointerEvents: 'none',
}}> }}>
@@ -22,15 +22,15 @@ export function FilesView(S: FileManagerState) {
<input {...getInputProps()} /> <input {...getInputProps()} />
<Upload size={24} style={{ margin: '0 auto 8px', color: isDragActive ? 'var(--text-secondary)' : 'var(--text-faint)', display: 'block' }} /> <Upload size={24} style={{ margin: '0 auto 8px', color: isDragActive ? 'var(--text-secondary)' : 'var(--text-faint)', display: 'block' }} />
{uploading ? ( {uploading ? (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, fontSize: 13, color: 'var(--text-secondary)' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-secondary)' }}>
<div style={{ width: 14, height: 14, border: '2px solid var(--text-secondary)', borderTopColor: 'transparent', borderRadius: '50%', animation: 'spin 0.8s linear infinite' }} /> <div style={{ width: 14, height: 14, border: '2px solid var(--text-secondary)', borderTopColor: 'transparent', borderRadius: '50%', animation: 'spin 0.8s linear infinite' }} />
{t('files.uploading')} {t('files.uploading')}
</div> </div>
) : ( ) : (
<> <>
<p style={{ fontSize: 13, color: 'var(--text-secondary)', fontWeight: 500, margin: 0 }}>{t('files.dropzone')}</p> <p style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-secondary)', fontWeight: 500, margin: 0 }}>{t('files.dropzone')}</p>
<p style={{ fontSize: 11.5, color: 'var(--text-faint)', marginTop: 3 }}>{t('files.dropzoneHint')}</p> <p style={{ fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginTop: 3 }}>{t('files.dropzoneHint')}</p>
<p style={{ fontSize: 10, color: 'var(--text-faint)', marginTop: 6, opacity: 0.7 }}> <p style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginTop: 6, opacity: 0.7 }}>
{(allowedFileTypes || 'jpg,jpeg,png,gif,webp,heic,pdf,doc,docx,xls,xlsx,txt,csv').toUpperCase().split(',').join(', ')} · Max 50 MB {(allowedFileTypes || 'jpg,jpeg,png,gif,webp,heic,pdf,doc,docx,xls,xlsx,txt,csv').toUpperCase().split(',').join(', ')} · Max 50 MB
</p> </p>
</> </>
@@ -48,14 +48,14 @@ export function FilesView(S: FileManagerState) {
...(files.some(f => f.note_id) ? [{ id: 'collab', label: t('files.filterCollab') || 'Collab' }] : []), ...(files.some(f => f.note_id) ? [{ id: 'collab', label: t('files.filterCollab') || 'Collab' }] : []),
].map(tab => ( ].map(tab => (
<button key={tab.id} onClick={() => setFilterType(tab.id)} style={{ <button key={tab.id} onClick={() => setFilterType(tab.id)} style={{
padding: '4px 12px', borderRadius: 99, border: 'none', cursor: 'pointer', fontSize: 12, padding: '4px 12px', borderRadius: 99, border: 'none', cursor: 'pointer', fontSize: 'calc(12px * var(--fs-scale-text, 1))',
fontFamily: 'inherit', transition: 'all 0.12s', fontFamily: 'inherit', transition: 'all 0.12s',
background: filterType === tab.id ? 'var(--accent)' : 'transparent', background: filterType === tab.id ? 'var(--accent)' : 'transparent',
color: filterType === tab.id ? 'var(--accent-text)' : 'var(--text-muted)', color: filterType === tab.id ? 'var(--accent-text)' : 'var(--text-muted)',
fontWeight: filterType === tab.id ? 600 : 400, fontWeight: filterType === tab.id ? 600 : 400,
}}>{tab.icon ? <tab.icon size={13} fill={filterType === tab.id ? '#facc15' : 'none'} color={filterType === tab.id ? '#facc15' : 'currentColor'} /> : tab.label}</button> }}>{tab.icon ? <tab.icon size={13} fill={filterType === tab.id ? '#facc15' : 'none'} color={filterType === tab.id ? '#facc15' : 'currentColor'} /> : tab.label}</button>
))} ))}
<span style={{ marginLeft: 'auto', fontSize: 11.5, color: 'var(--text-faint)', alignSelf: 'center' }}> <span style={{ marginLeft: 'auto', fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', alignSelf: 'center' }}>
{filteredFiles.length === 1 ? t('files.countSingular') : t('files.count', { count: filteredFiles.length })} {filteredFiles.length === 1 ? t('files.countSingular') : t('files.count', { count: filteredFiles.length })}
</span> </span>
</div> </div>
@@ -65,8 +65,8 @@ export function FilesView(S: FileManagerState) {
{filteredFiles.length === 0 ? ( {filteredFiles.length === 0 ? (
<div style={{ textAlign: 'center', padding: '60px 20px', color: 'var(--text-faint)' }}> <div style={{ textAlign: 'center', padding: '60px 20px', color: 'var(--text-faint)' }}>
<FileText size={40} style={{ color: 'var(--text-faint)', display: 'block', margin: '0 auto 12px' }} /> <FileText size={40} style={{ color: 'var(--text-faint)', display: 'block', margin: '0 auto 12px' }} />
<p style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-secondary)', margin: '0 0 4px' }}>{t('files.empty')}</p> <p style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-secondary)', margin: '0 0 4px' }}>{t('files.empty')}</p>
<p style={{ fontSize: 13, color: 'var(--text-faint)', margin: 0 }}>{t('files.emptyHint')}</p> <p style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', margin: 0 }}>{t('files.emptyHint')}</p>
</div> </div>
) : ( ) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
@@ -71,7 +71,7 @@ export function ImageLightbox({ files, initialIndex, onClose }: ImageLightboxPro
> >
{/* Header */} {/* Header */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', flexShrink: 0 }} onClick={e => e.stopPropagation()}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', flexShrink: 0 }} onClick={e => e.stopPropagation()}>
<span style={{ fontSize: 12, color: 'rgba(255,255,255,0.7)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}> <span style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'rgba(255,255,255,0.7)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>
{file.original_name} {file.original_name}
<span style={{ marginLeft: 8, color: 'rgba(255,255,255,0.4)' }}>{index + 1} / {files.length}</span> <span style={{ marginLeft: 8, color: 'rgba(255,255,255,0.4)' }}>{index + 1} / {files.length}</span>
</span> </span>
@@ -16,18 +16,18 @@ export function PdfPreviewModal(S: FileManagerState) {
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
> >
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', borderBottom: '1px solid var(--border-primary)', flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', borderBottom: '1px solid var(--border-primary)', flexShrink: 0 }}>
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{previewFile.original_name}</span> <span style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{previewFile.original_name}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
<button <button
onClick={() => openFileUrl(previewFile.url, previewFile.original_name).catch(() => toast.error(t('files.openError')))} onClick={() => openFileUrl(previewFile.url, previewFile.original_name).catch(() => toast.error(t('files.openError')))}
style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 12, color: 'var(--text-muted)', background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'none', padding: '4px 8px', borderRadius: 6, transition: 'color 0.15s' }} style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-muted)', background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'none', padding: '4px 8px', borderRadius: 6, transition: 'color 0.15s' }}
onMouseEnter={e => (e.currentTarget as HTMLElement).style.color = 'var(--text-primary)'} onMouseEnter={e => (e.currentTarget as HTMLElement).style.color = 'var(--text-primary)'}
onMouseLeave={e => (e.currentTarget as HTMLElement).style.color = 'var(--text-muted)'}> onMouseLeave={e => (e.currentTarget as HTMLElement).style.color = 'var(--text-muted)'}>
<ExternalLink size={13} /> {t('files.openTab')} <ExternalLink size={13} /> {t('files.openTab')}
</button> </button>
<button <button
onClick={() => triggerDownload(previewFile.url, previewFile.original_name)} onClick={() => triggerDownload(previewFile.url, previewFile.original_name)}
style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 12, color: 'var(--text-muted)', background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'none', padding: '4px 8px', borderRadius: 6, transition: 'color 0.15s' }} style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-muted)', background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'none', padding: '4px 8px', borderRadius: 6, transition: 'color 0.15s' }}
onMouseEnter={e => (e.currentTarget as HTMLElement).style.color = 'var(--text-primary)'} onMouseEnter={e => (e.currentTarget as HTMLElement).style.color = 'var(--text-primary)'}
onMouseLeave={e => (e.currentTarget as HTMLElement).style.color = 'var(--text-muted)'}> onMouseLeave={e => (e.currentTarget as HTMLElement).style.color = 'var(--text-muted)'}>
<Download size={13} /> {t('files.download') || 'Download'} <Download size={13} /> {t('files.download') || 'Download'}
@@ -49,7 +49,7 @@ export function FileRow(p: FileManagerState & { file: TripFile; isTrash?: boolea
const isPdf = file.mime_type === 'application/pdf' const isPdf = file.mime_type === 'application/pdf'
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%', background: isPdf ? '#ef44441a' : 'var(--bg-tertiary)' }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%', background: isPdf ? '#ef44441a' : 'var(--bg-tertiary)' }}>
<span style={{ fontSize: 9, fontWeight: 700, color: isPdf ? '#ef4444' : 'var(--text-muted)', letterSpacing: 0.3 }}>{ext}</span> <span style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 700, color: isPdf ? '#ef4444' : 'var(--text-muted)', letterSpacing: 0.3 }}>{ext}</span>
</div> </div>
) )
})() })()
@@ -65,19 +65,19 @@ export function FileRow(p: FileManagerState & { file: TripFile; isTrash?: boolea
{!isTrash && file.starred ? <Star size={12} fill="#facc15" color="#facc15" style={{ flexShrink: 0 }} /> : null} {!isTrash && file.starred ? <Star size={12} fill="#facc15" color="#facc15" style={{ flexShrink: 0 }} /> : null}
<span <span
onClick={() => !isTrash && openFile(file)} onClick={() => !isTrash && openFile(file)}
style={{ fontWeight: 500, fontSize: 13, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: isTrash ? 'default' : 'pointer' }} style={{ fontWeight: 500, fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: isTrash ? 'default' : 'pointer' }}
> >
{file.original_name} {file.original_name}
</span> </span>
</div> </div>
{file.description && ( {file.description && (
<p style={{ fontSize: 11.5, color: 'var(--text-faint)', margin: '2px 0 0', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{file.description}</p> <p style={{ fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', margin: '2px 0 0', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{file.description}</p>
)} )}
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 6, marginTop: 4 }}> <div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 6, marginTop: 4 }}>
{file.file_size && <span style={{ fontSize: 11, color: 'var(--text-faint)' }}>{formatSize(file.file_size)}</span>} {file.file_size && <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>{formatSize(file.file_size)}</span>}
<span style={{ fontSize: 11, color: 'var(--text-faint)' }}>{formatDateWithLocale(file.created_at, locale)}</span> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>{formatDateWithLocale(file.created_at, locale)}</span>
{linkedPlaces.map(p => ( {linkedPlaces.map(p => (
<SourceBadge key={p.id} icon={MapPin} label={`${t('files.sourcePlan')} · ${p.name}`} /> <SourceBadge key={p.id} icon={MapPin} label={`${t('files.sourcePlan')} · ${p.name}`} />
@@ -8,7 +8,7 @@ export function SourceBadge({ icon: Icon, label }: SourceBadgeProps) {
return ( return (
<span style={{ <span style={{
display: 'inline-flex', alignItems: 'center', gap: 4, display: 'inline-flex', alignItems: 'center', gap: 4,
fontSize: 10.5, color: '#4b5563', fontSize: 'calc(10.5px * var(--fs-scale-text, 1))', color: '#4b5563',
background: 'var(--bg-tertiary)', border: '1px solid var(--border-primary)', background: 'var(--bg-tertiary)', border: '1px solid var(--border-primary)',
borderRadius: 6, padding: '2px 7px', borderRadius: 6, padding: '2px 7px',
fontWeight: 500, maxWidth: '100%', overflow: 'hidden', fontWeight: 500, maxWidth: '100%', overflow: 'hidden',
@@ -10,7 +10,7 @@ export function FileManagerToolbar(S: FileManagerState) {
padding: '14px 16px 14px 22px', padding: '14px 16px 14px 22px',
display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap', display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap',
}}> }}>
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600, color: 'var(--text-primary)', letterSpacing: '-0.01em', flexShrink: 0 }}> <h2 style={{ margin: 0, fontSize: 'calc(18px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', letterSpacing: '-0.01em', flexShrink: 0 }}>
{showTrash ? (t('files.trash') || 'Trash') : t('files.title')} {showTrash ? (t('files.trash') || 'Trash') : t('files.title')}
</h2> </h2>
@@ -40,7 +40,7 @@ export function FileManagerToolbar(S: FileManagerState) {
style={{ style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6, display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '6px 12px', borderRadius: 99, fontSize: 13, whiteSpace: 'nowrap', padding: '6px 12px', borderRadius: 99, fontSize: 'calc(13px * var(--fs-scale-text, 1))', whiteSpace: 'nowrap',
background: active ? 'var(--bg-card)' : 'transparent', background: active ? 'var(--bg-card)' : 'transparent',
color: active ? 'var(--text-primary)' : 'var(--text-muted)', color: active ? 'var(--text-primary)' : 'var(--text-muted)',
fontWeight: active ? 500 : 400, fontWeight: active ? 500 : 400,
@@ -51,7 +51,7 @@ export function FileManagerToolbar(S: FileManagerState) {
{TabIcon ? <TabIcon size={13} fill={active ? '#facc15' : 'none'} color={active ? '#facc15' : 'currentColor'} /> : null} {TabIcon ? <TabIcon size={13} fill={active ? '#facc15' : 'none'} color={active ? '#facc15' : 'currentColor'} /> : null}
{'label' in tab && tab.label} {'label' in tab && tab.label}
<span style={{ <span style={{
fontSize: 10, fontWeight: 600, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600,
background: active ? 'var(--bg-tertiary)' : 'rgba(0,0,0,0.06)', background: active ? 'var(--bg-tertiary)' : 'rgba(0,0,0,0.06)',
color: 'var(--text-faint)', color: 'var(--text-faint)',
padding: '1px 6px', borderRadius: 99, minWidth: 16, textAlign: 'center', padding: '1px 6px', borderRadius: 99, minWidth: 16, textAlign: 'center',
@@ -66,7 +66,7 @@ export function FileManagerToolbar(S: FileManagerState) {
<button onClick={toggleTrash} style={{ <button onClick={toggleTrash} style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6, display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500, padding: '9px 14px', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
background: 'var(--accent)', color: 'var(--accent-text)', background: 'var(--accent)', color: 'var(--accent-text)',
flexShrink: 0, marginLeft: 'auto', flexShrink: 0, marginLeft: 'auto',
opacity: showTrash ? 1 : 0.88, opacity: showTrash ? 1 : 0.88,
@@ -10,7 +10,7 @@ export function TrashView(S: FileManagerState) {
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 12 }}> <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 12 }}>
<button onClick={handleEmptyTrash} style={{ <button onClick={handleEmptyTrash} style={{
padding: '5px 12px', borderRadius: 8, border: '1px solid #fecaca', padding: '5px 12px', borderRadius: 8, border: '1px solid #fecaca',
background: '#fef2f2', color: '#dc2626', fontSize: 12, fontWeight: 500, background: '#fef2f2', color: '#dc2626', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
{t('files.emptyTrash') || 'Empty Trash'} {t('files.emptyTrash') || 'Empty Trash'}
@@ -24,7 +24,7 @@ export function TrashView(S: FileManagerState) {
) : trashFiles.length === 0 ? ( ) : trashFiles.length === 0 ? (
<div style={{ textAlign: 'center', padding: '60px 20px', color: 'var(--text-faint)' }}> <div style={{ textAlign: 'center', padding: '60px 20px', color: 'var(--text-faint)' }}>
<Trash2 size={40} style={{ color: 'var(--text-faint)', display: 'block', margin: '0 auto 12px' }} /> <Trash2 size={40} style={{ color: 'var(--text-faint)', display: 'block', margin: '0 auto 12px' }} />
<p style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-secondary)', margin: '0 0 4px' }}>{t('files.trashEmpty') || 'Trash is empty'}</p> <p style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-secondary)', margin: '0 0 4px' }}>{t('files.trashEmpty') || 'Trash is empty'}</p>
</div> </div>
) : ( ) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
@@ -48,7 +48,7 @@ export default function JournalBody({ text, dark }: Props) {
<pre style={{ <pre style={{
background: dark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.04)', background: dark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.04)',
borderRadius: 8, padding: 14, overflowX: 'auto', borderRadius: 8, padding: 14, overflowX: 'auto',
fontSize: 13, fontFamily: 'monospace', margin: '12px 0', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'monospace', margin: '12px 0',
}}> }}>
<code>{children}</code> <code>{children}</code>
</pre> </pre>
+2 -2
View File
@@ -300,7 +300,7 @@ const JourneyMap = forwardRef<JourneyMapHandle, Props>(function JourneyMap(
border: `1px solid ${dark ? 'rgba(255,255,255,0.15)' : 'rgba(0,0,0,0.1)'}`, border: `1px solid ${dark ? 'rgba(255,255,255,0.15)' : 'rgba(0,0,0,0.1)'}`,
color: dark ? '#fff' : '#18181B', color: dark ? '#fff' : '#18181B',
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'pointer', fontSize: 16, fontWeight: 700, lineHeight: 1, cursor: 'pointer', fontSize: 'calc(16px * var(--fs-scale-text, 1))', fontWeight: 700, lineHeight: 1,
}} }}
>+</button> >+</button>
<button <button
@@ -312,7 +312,7 @@ const JourneyMap = forwardRef<JourneyMapHandle, Props>(function JourneyMap(
border: `1px solid ${dark ? 'rgba(255,255,255,0.15)' : 'rgba(0,0,0,0.1)'}`, border: `1px solid ${dark ? 'rgba(255,255,255,0.15)' : 'rgba(0,0,0,0.1)'}`,
color: dark ? '#fff' : '#18181B', color: dark ? '#fff' : '#18181B',
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'pointer', fontSize: 16, fontWeight: 700, lineHeight: 1, cursor: 'pointer', fontSize: 'calc(16px * var(--fs-scale-text, 1))', fontWeight: 700, lineHeight: 1,
}} }}
></button> ></button>
</div> </div>
@@ -81,7 +81,7 @@ export default function PhotoLightbox({ photos, startIndex = 0, onClose }: Props
> >
{/* Top bar */} {/* Top bar */}
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 10, display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 20px' }}> <div style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 10, display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 20px' }}>
<span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 13, fontWeight: 500 }}> <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500 }}>
{idx + 1} / {photos.length} {idx + 1} / {photos.length}
</span> </span>
<button onClick={onClose} style={{ <button onClick={onClose} style={{
@@ -137,7 +137,7 @@ export default function PhotoLightbox({ photos, startIndex = 0, onClose }: Props
{photo.caption && ( {photo.caption && (
<div style={{ position: 'absolute', bottom: 20, left: '50%', transform: 'translateX(-50%)', zIndex: 5, maxWidth: '70%', textAlign: 'center' }}> <div style={{ position: 'absolute', bottom: 20, left: '50%', transform: 'translateX(-50%)', zIndex: 5, maxWidth: '70%', textAlign: 'center' }}>
<p style={{ <p style={{
fontSize: 14, fontStyle: 'italic', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontStyle: 'italic',
color: 'rgba(255,255,255,0.75)', margin: 0, lineHeight: 1.5, color: 'rgba(255,255,255,0.75)', margin: 0, lineHeight: 1.5,
background: 'rgba(0,0,0,0.3)', backdropFilter: 'blur(8px)', background: 'rgba(0,0,0,0.3)', backdropFilter: 'blur(8px)',
padding: '6px 14px', borderRadius: 10, padding: '6px 14px', borderRadius: 10,
+13 -13
View File
@@ -287,12 +287,12 @@ export default function DemoBanner(): React.ReactElement | null {
{/* Header */} {/* Header */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
<img src="/icons/icon-dark.svg" alt="" style={{ width: 36, height: 36, borderRadius: 10 }} /> <img src="/icons/icon-dark.svg" alt="" style={{ width: 36, height: 36, borderRadius: 10 }} />
<h2 style={{ margin: 0, fontSize: 17, fontWeight: 700, color: '#111827', display: 'flex', alignItems: 'center', gap: 5 }}> <h2 style={{ margin: 0, fontSize: 'calc(17px * var(--fs-scale-text, 1))', fontWeight: 700, color: '#111827', display: 'flex', alignItems: 'center', gap: 5 }}>
{t.titleBefore}<img src="/text-dark.svg" alt="TREK" style={{ height: 18 }} />{t.titleAfter} {t.titleBefore}<img src="/text-dark.svg" alt="TREK" style={{ height: 18 }} />{t.titleAfter}
</h2> </h2>
</div> </div>
<p style={{ fontSize: 13, color: '#6b7280', lineHeight: 1.6, margin: '0 0 12px' }}> <p style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: '#6b7280', lineHeight: 1.6, margin: '0 0 12px' }}>
{t.description} {t.description}
</p> </p>
@@ -303,7 +303,7 @@ export default function DemoBanner(): React.ReactElement | null {
background: '#f0f9ff', border: '1px solid #bae6fd', borderRadius: 10, padding: '8px 10px', background: '#f0f9ff', border: '1px solid #bae6fd', borderRadius: 10, padding: '8px 10px',
}}> }}>
<Clock size={13} style={{ flexShrink: 0, color: '#0284c7' }} /> <Clock size={13} style={{ flexShrink: 0, color: '#0284c7' }} />
<span style={{ fontSize: 11, color: '#0369a1', fontWeight: 600 }}> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: '#0369a1', fontWeight: 600 }}>
{t.resetIn} {minutesLeft} {t.minutes} {t.resetIn} {minutesLeft} {t.minutes}
</span> </span>
</div> </div>
@@ -312,7 +312,7 @@ export default function DemoBanner(): React.ReactElement | null {
background: '#fffbeb', border: '1px solid #fde68a', borderRadius: 10, padding: '8px 10px', background: '#fffbeb', border: '1px solid #fde68a', borderRadius: 10, padding: '8px 10px',
}}> }}>
<Upload size={13} style={{ flexShrink: 0, color: '#b45309' }} /> <Upload size={13} style={{ flexShrink: 0, color: '#b45309' }} />
<span style={{ fontSize: 11, color: '#b45309' }}>{t.uploadNote}</span> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: '#b45309' }}>{t.uploadNote}</span>
</div> </div>
</div> </div>
@@ -323,15 +323,15 @@ export default function DemoBanner(): React.ReactElement | null {
}}> }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
<Map size={14} style={{ color: '#111827' }} /> <Map size={14} style={{ color: '#111827' }} />
<span style={{ fontSize: 12, fontWeight: 700, color: '#111827', display: 'flex', alignItems: 'center', gap: 4 }}> <span style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 700, color: '#111827', display: 'flex', alignItems: 'center', gap: 4 }}>
{t.whatIs} {t.whatIs}
</span> </span>
</div> </div>
<p style={{ fontSize: 12, color: '#64748b', lineHeight: 1.5, margin: 0 }}>{t.whatIsDesc}</p> <p style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: '#64748b', lineHeight: 1.5, margin: 0 }}>{t.whatIsDesc}</p>
</div> </div>
{/* Addons */} {/* Addons */}
<p style={{ fontSize: 10, fontWeight: 700, color: '#374151', margin: '0 0 8px', textTransform: 'uppercase', letterSpacing: '0.08em', display: 'flex', alignItems: 'center', gap: 6 }}> <p style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, color: '#374151', margin: '0 0 8px', textTransform: 'uppercase', letterSpacing: '0.08em', display: 'flex', alignItems: 'center', gap: 6 }}>
<Puzzle size={12} /> <Puzzle size={12} />
{t.addonsTitle} {t.addonsTitle}
</p> </p>
@@ -345,16 +345,16 @@ export default function DemoBanner(): React.ReactElement | null {
}}> }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 2 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 2 }}>
<Icon size={12} style={{ flexShrink: 0, color: '#111827' }} /> <Icon size={12} style={{ flexShrink: 0, color: '#111827' }} />
<span style={{ fontSize: 11, fontWeight: 700, color: '#111827' }}>{name}</span> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 700, color: '#111827' }}>{name}</span>
</div> </div>
<p style={{ fontSize: 10, color: '#94a3b8', margin: 0, lineHeight: 1.3, paddingLeft: 18 }}>{desc}</p> <p style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: '#94a3b8', margin: 0, lineHeight: 1.3, paddingLeft: 18 }}>{desc}</p>
</div> </div>
) )
})} })}
</div> </div>
{/* Full version features */} {/* Full version features */}
<p style={{ fontSize: 10, fontWeight: 700, color: '#374151', margin: '0 0 8px', textTransform: 'uppercase', letterSpacing: '0.08em', display: 'flex', alignItems: 'center', gap: 6 }}> <p style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, color: '#374151', margin: '0 0 8px', textTransform: 'uppercase', letterSpacing: '0.08em', display: 'flex', alignItems: 'center', gap: 6 }}>
<Shield size={12} /> <Shield size={12} />
{t.fullVersionTitle} {t.fullVersionTitle}
</p> </p>
@@ -362,7 +362,7 @@ export default function DemoBanner(): React.ReactElement | null {
{t.features.map((text, i) => { {t.features.map((text, i) => {
const Icon = featureIcons[i] const Icon = featureIcons[i]
return ( return (
<div key={text} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11, color: '#4b5563', padding: '4px 0' }}> <div key={text} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: '#4b5563', padding: '4px 0' }}>
<Icon size={13} style={{ flexShrink: 0, color: '#9ca3af' }} /> <Icon size={13} style={{ flexShrink: 0, color: '#9ca3af' }} />
<span>{text}</span> <span>{text}</span>
</div> </div>
@@ -377,7 +377,7 @@ export default function DemoBanner(): React.ReactElement | null {
position: 'sticky', bottom: 0, background: 'white', position: 'sticky', bottom: 0, background: 'white',
marginTop: 'auto', marginTop: 'auto',
}}> }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, color: '#9ca3af' }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: '#9ca3af' }}>
<Github size={13} /> <Github size={13} />
<span>{t.selfHost}</span> <span>{t.selfHost}</span>
<a href="https://github.com/mauriceboe/TREK" target="_blank" rel="noopener noreferrer" <a href="https://github.com/mauriceboe/TREK" target="_blank" rel="noopener noreferrer"
@@ -387,7 +387,7 @@ export default function DemoBanner(): React.ReactElement | null {
</div> </div>
<button onClick={() => setDismissed(true)} style={{ <button onClick={() => setDismissed(true)} style={{
background: '#111827', color: 'white', border: 'none', background: '#111827', color: 'white', border: 'none',
borderRadius: 10, padding: '8px 20px', fontSize: 12, borderRadius: 10, padding: '8px 20px', fontSize: 'calc(12px * var(--fs-scale-text, 1))',
fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
{t.close} {t.close}
@@ -55,7 +55,7 @@ export default function InAppNotificationBell(): React.ReactElement {
className="absolute -top-0.5 -right-0.5 flex items-center justify-center rounded-full text-white font-bold" className="absolute -top-0.5 -right-0.5 flex items-center justify-center rounded-full text-white font-bold"
style={{ style={{
background: '#ef4444', background: '#ef4444',
fontSize: 9, fontSize: 'calc(9px * var(--fs-scale-text, 1))',
minWidth: 14, minWidth: 14,
height: 14, height: 14,
padding: '0 3px', padding: '0 3px',
+2 -2
View File
@@ -154,7 +154,7 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }:
<Link key={tab.id} to={tab.path} <Link key={tab.id} to={tab.path}
className="flex items-center gap-1.5 transition-colors" className="flex items-center gap-1.5 transition-colors"
style={{ style={{
padding: '5px 16px', borderRadius: 9, fontSize: 13.5, fontWeight: 500, padding: '5px 16px', borderRadius: 9, fontSize: 'calc(13.5px * var(--fs-scale-text, 1))', fontWeight: 500,
color: isActive ? 'var(--text-primary)' : 'var(--text-muted)', color: isActive ? 'var(--text-primary)' : 'var(--text-muted)',
background: isActive ? 'var(--bg-card)' : 'transparent', background: isActive ? 'var(--bg-card)' : 'transparent',
boxShadow: isActive ? '0 1px 2px rgba(0,0,0,0.06), 0 2px 6px rgba(0,0,0,0.05)' : 'none', boxShadow: isActive ? '0 1px 2px rgba(0,0,0,0.06), 0 2px 6px rgba(0,0,0,0.05)' : 'none',
@@ -274,7 +274,7 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }:
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 5, background: 'var(--bg-tertiary)', borderRadius: 99, padding: '4px 12px' }}> <div style={{ display: 'inline-flex', alignItems: 'center', gap: 5, background: 'var(--bg-tertiary)', borderRadius: 99, padding: '4px 12px' }}>
<img src={dark ? '/text-light.svg' : '/text-dark.svg'} alt="TREK" style={{ height: 10, opacity: 0.5 }} /> <img src={dark ? '/text-light.svg' : '/text-dark.svg'} alt="TREK" style={{ height: 10, opacity: 0.5 }} />
<span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)' }}>v{appVersion}</span> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)' }}>v{appVersion}</span>
</div> </div>
<a href="https://discord.gg/NhZBDSd4qW" target="_blank" rel="noopener noreferrer" <a href="https://discord.gg/NhZBDSd4qW" target="_blank" rel="noopener noreferrer"
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24, borderRadius: 99, background: 'var(--bg-tertiary)', transition: 'background 0.15s' }} style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24, borderRadius: 99, background: 'var(--bg-tertiary)', transition: 'background 0.15s' }}
@@ -88,7 +88,7 @@ export default function OfflineBanner(): React.ReactElement | null {
padding: '6px 14px', padding: '6px 14px',
borderRadius: 999, borderRadius: 999,
boxShadow: '0 4px 16px rgba(0,0,0,0.18), 0 0 0 1px rgba(255,255,255,0.08)', boxShadow: '0 4px 16px rgba(0,0,0,0.18), 0 0 0 1px rgba(255,255,255,0.08)',
fontSize: 12, fontSize: 'calc(12px * var(--fs-scale-text, 1))',
fontWeight: 600, fontWeight: 600,
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
pointerEvents: 'none', pointerEvents: 'none',
@@ -82,7 +82,7 @@ export default function ApplyTemplateButton({ tripId, style, className }: ApplyT
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, width: '100%', display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '8px 12px', borderRadius: 8, border: 'none', cursor: 'pointer', padding: '8px 12px', borderRadius: 8, border: 'none', cursor: 'pointer',
background: 'transparent', fontFamily: 'inherit', fontSize: 12, color: 'var(--text-primary)', background: 'transparent', fontFamily: 'inherit', fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-primary)',
}} }}
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-tertiary)'} onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-tertiary)'}
onMouseLeave={e => e.currentTarget.style.background = 'transparent'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
@@ -90,7 +90,7 @@ export default function ApplyTemplateButton({ tripId, style, className }: ApplyT
<Package size={13} className="text-content-faint" /> <Package size={13} className="text-content-faint" />
<div style={{ flex: 1, textAlign: 'left' }}> <div style={{ flex: 1, textAlign: 'left' }}>
<div style={{ fontWeight: 600 }}>{tmpl.name}</div> <div style={{ fontWeight: 600 }}>{tmpl.name}</div>
<div style={{ fontSize: 10, color: 'var(--text-faint)' }}> <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>
{tmpl.item_count} {t('admin.packingTemplates.items')} {tmpl.item_count} {t('admin.packingTemplates.items')}
</div> </div>
</div> </div>
@@ -69,13 +69,13 @@ export function BagCard({ bag, bagItems, totalWeight, pct, tripId, tripMembers,
const isSelected = memberIds.includes(m.id) const isSelected = memberIds.includes(m.id)
return ( return (
<button key={m.id} onClick={() => { toggleMember(m.id); }} <button key={m.id} onClick={() => { toggleMember(m.id); }}
style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '6px 10px', borderRadius: 6, border: 'none', background: isSelected ? 'var(--bg-tertiary)' : 'transparent', cursor: 'pointer', fontSize: 11, color: 'var(--text-primary)', fontFamily: 'inherit' }} style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '6px 10px', borderRadius: 6, border: 'none', background: isSelected ? 'var(--bg-tertiary)' : 'transparent', cursor: 'pointer', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-primary)', fontFamily: 'inherit' }}
onMouseEnter={e => { if (!isSelected) e.currentTarget.style.background = 'var(--bg-secondary)' }} onMouseEnter={e => { if (!isSelected) e.currentTarget.style.background = 'var(--bg-secondary)' }}
onMouseLeave={e => { if (!isSelected) e.currentTarget.style.background = 'transparent' }}> onMouseLeave={e => { if (!isSelected) e.currentTarget.style.background = 'transparent' }}>
{m.avatar ? ( {m.avatar ? (
<img src={m.avatar} alt="" style={{ width: 20, height: 20, borderRadius: '50%', objectFit: 'cover' }} /> <img src={m.avatar} alt="" style={{ width: 20, height: 20, borderRadius: '50%', objectFit: 'cover' }} />
) : ( ) : (
<span style={{ width: 20, height: 20, borderRadius: '50%', background: 'var(--bg-tertiary)', fontSize: 10, fontWeight: 700, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-faint)' }}> <span style={{ width: 20, height: 20, borderRadius: '50%', background: 'var(--bg-tertiary)', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-faint)' }}>
{m.username[0].toUpperCase()} {m.username[0].toUpperCase()}
</span> </span>
)} )}
@@ -84,9 +84,9 @@ export function BagCard({ bag, bagItems, totalWeight, pct, tripId, tripMembers,
</button> </button>
) )
})} })}
{tripMembers.length === 0 && <div style={{ padding: '8px 10px', fontSize: 11, color: 'var(--text-faint)' }}>{t('packing.noMembers')}</div>} {tripMembers.length === 0 && <div style={{ padding: '8px 10px', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>{t('packing.noMembers')}</div>}
<div style={{ borderTop: '1px solid var(--border-secondary)', marginTop: 4, paddingTop: 4 }}> <div style={{ borderTop: '1px solid var(--border-secondary)', marginTop: 4, paddingTop: 4 }}>
<button onClick={() => setShowUserPicker(false)} style={{ width: '100%', padding: '6px 10px', borderRadius: 6, border: 'none', background: 'transparent', cursor: 'pointer', fontSize: 11, color: 'var(--text-faint)', fontFamily: 'inherit', textAlign: 'center' }}> <button onClick={() => setShowUserPicker(false)} style={{ width: '100%', padding: '6px 10px', borderRadius: 6, border: 'none', background: 'transparent', cursor: 'pointer', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontFamily: 'inherit', textAlign: 'center' }}>
{t('common.close')} {t('common.close')}
</button> </button>
</div> </div>
@@ -14,7 +14,7 @@ export function BagModal(S: PackingState) {
<div style={{ background: 'var(--bg-card)', borderRadius: 16, width: '100%', maxWidth: 360, maxHeight: 'calc(100vh - 80px)', overflow: 'auto', padding: 20, boxShadow: '0 16px 48px rgba(0,0,0,0.15)', flexShrink: 0 }} <div style={{ background: 'var(--bg-card)', borderRadius: 16, width: '100%', maxWidth: 360, maxHeight: 'calc(100vh - 80px)', overflow: 'auto', padding: 20, boxShadow: '0 16px 48px rgba(0,0,0,0.15)', flexShrink: 0 }}
onClick={e => e.stopPropagation()}> onClick={e => e.stopPropagation()}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 700, color: 'var(--text-primary)' }}>{t('packing.bags')}</h3> <h3 style={{ margin: 0, fontSize: 'calc(16px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)' }}>{t('packing.bags')}</h3>
<button onClick={() => setShowBagModal(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', display: 'flex' }}><X size={18} /></button> <button onClick={() => setShowBagModal(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', display: 'flex' }}><X size={18} /></button>
</div> </div>
@@ -37,19 +37,19 @@ export function BagModal(S: PackingState) {
<div style={{ marginBottom: 16, opacity: 0.6 }}> <div style={{ marginBottom: 16, opacity: 0.6 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
<span style={{ width: 12, height: 12, borderRadius: '50%', border: '2px dashed var(--border-primary)', flexShrink: 0 }} /> <span style={{ width: 12, height: 12, borderRadius: '50%', border: '2px dashed var(--border-primary)', flexShrink: 0 }} />
<span style={{ flex: 1, fontSize: 14, fontWeight: 600, color: 'var(--text-faint)' }}>{t('packing.noBag')}</span> <span style={{ flex: 1, fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)' }}>{t('packing.noBag')}</span>
<span style={{ fontSize: 13, color: 'var(--text-faint)' }}> <span style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>
{unassignedWeight >= 1000 ? `${(unassignedWeight / 1000).toFixed(1)} kg` : `${unassignedWeight} g`} {unassignedWeight >= 1000 ? `${(unassignedWeight / 1000).toFixed(1)} kg` : `${unassignedWeight} g`}
</span> </span>
</div> </div>
<div style={{ fontSize: 11, color: 'var(--text-faint)' }}>{unassigned.length} {t('admin.packingTemplates.items')}</div> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>{unassigned.length} {t('admin.packingTemplates.items')}</div>
</div> </div>
) )
})()} })()}
{/* Total */} {/* Total */}
<div style={{ borderTop: '1px solid var(--border-secondary)', paddingTop: 12, marginTop: 8 }}> <div style={{ borderTop: '1px solid var(--border-secondary)', paddingTop: 12, marginTop: 8 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 14, fontWeight: 700, color: 'var(--text-primary)' }}> <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)' }}>
<span>{t('packing.totalWeight')}</span> <span>{t('packing.totalWeight')}</span>
<span>{(() => { const w = items.reduce((s, i) => s + itemWeight(i), 0); return w >= 1000 ? `${(w / 1000).toFixed(1)} kg` : `${w} g` })()}</span> <span>{(() => { const w = items.reduce((s, i) => s + itemWeight(i), 0); return w >= 1000 ? `${(w / 1000).toFixed(1)} kg` : `${w} g` })()}</span>
</div> </div>
@@ -61,7 +61,7 @@ export function BagModal(S: PackingState) {
<input autoFocus value={newBagName} onChange={e => setNewBagName(e.target.value)} <input autoFocus value={newBagName} onChange={e => setNewBagName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleCreateBag(); if (e.key === 'Escape') { setShowAddBag(false); setNewBagName('') } }} onKeyDown={e => { if (e.key === 'Enter') handleCreateBag(); if (e.key === 'Escape') { setShowAddBag(false); setNewBagName('') } }}
placeholder={t('packing.bagName')} placeholder={t('packing.bagName')}
style={{ flex: 1, padding: '8px 12px', borderRadius: 10, border: '1px solid var(--border-primary)', fontSize: 13, fontFamily: 'inherit', outline: 'none' }} /> style={{ flex: 1, padding: '8px 12px', borderRadius: 10, border: '1px solid var(--border-primary)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'inherit', outline: 'none' }} />
<button onClick={handleCreateBag} disabled={!newBagName.trim()} <button onClick={handleCreateBag} disabled={!newBagName.trim()}
style={{ padding: '8px 12px', borderRadius: 10, border: 'none', background: newBagName.trim() ? 'var(--text-primary)' : 'var(--border-primary)', color: 'var(--bg-primary)', cursor: newBagName.trim() ? 'pointer' : 'default', display: 'flex', alignItems: 'center' }}> style={{ padding: '8px 12px', borderRadius: 10, border: 'none', background: newBagName.trim() ? 'var(--text-primary)' : 'var(--border-primary)', color: 'var(--bg-primary)', cursor: newBagName.trim() ? 'pointer' : 'default', display: 'flex', alignItems: 'center' }}>
<Plus size={14} /> <Plus size={14} />
@@ -69,7 +69,7 @@ export function BagModal(S: PackingState) {
</div> </div>
) : ( ) : (
<button onClick={() => setShowAddBag(true)} <button onClick={() => setShowAddBag(true)}
style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 14, padding: '9px 14px', borderRadius: 10, border: '1px dashed var(--border-primary)', background: 'none', cursor: 'pointer', fontSize: 13, color: 'var(--text-faint)', fontFamily: 'inherit', width: '100%', transition: 'all 0.15s' }} style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 14, padding: '9px 14px', borderRadius: 10, border: '1px dashed var(--border-primary)', background: 'none', cursor: 'pointer', fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontFamily: 'inherit', width: '100%', transition: 'all 0.15s' }}
onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--text-muted)'; e.currentTarget.style.color = 'var(--text-secondary)' }} onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--text-muted)'; e.currentTarget.style.color = 'var(--text-secondary)' }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.color = 'var(--text-faint)' }}> onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.color = 'var(--text-faint)' }}>
<Plus size={14} /> {t('packing.addBag')} <Plus size={14} /> {t('packing.addBag')}
@@ -10,7 +10,7 @@ export function BagSidebar(S: PackingState) {
} = S } = S
return ( return (
<div className="hidden xl:block" style={{ width: 260, borderLeft: '1px solid var(--border-secondary)', overflowY: 'auto', padding: 16, flexShrink: 0 }}> <div className="hidden xl:block" style={{ width: 260, borderLeft: '1px solid var(--border-secondary)', overflowY: 'auto', padding: 16, flexShrink: 0 }}>
<div style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-faint)', marginBottom: 12 }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-faint)', marginBottom: 12 }}>
{t('packing.bags')} {t('packing.bags')}
</div> </div>
@@ -33,19 +33,19 @@ export function BagSidebar(S: PackingState) {
<div style={{ marginBottom: 14, opacity: 0.6 }}> <div style={{ marginBottom: 14, opacity: 0.6 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
<span style={{ width: 10, height: 10, borderRadius: '50%', border: '2px dashed var(--border-primary)', flexShrink: 0 }} /> <span style={{ width: 10, height: 10, borderRadius: '50%', border: '2px dashed var(--border-primary)', flexShrink: 0 }} />
<span style={{ flex: 1, fontSize: 12, fontWeight: 600, color: 'var(--text-faint)' }}>{t('packing.noBag')}</span> <span style={{ flex: 1, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)' }}>{t('packing.noBag')}</span>
<span style={{ fontSize: 11, color: 'var(--text-faint)' }}> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>
{unassignedWeight >= 1000 ? `${(unassignedWeight / 1000).toFixed(1)} kg` : `${unassignedWeight} g`} {unassignedWeight >= 1000 ? `${(unassignedWeight / 1000).toFixed(1)} kg` : `${unassignedWeight} g`}
</span> </span>
</div> </div>
<div style={{ fontSize: 10, color: 'var(--text-faint)' }}>{unassigned.length} {t('admin.packingTemplates.items')}</div> <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>{unassigned.length} {t('admin.packingTemplates.items')}</div>
</div> </div>
) )
})()} })()}
{/* Total */} {/* Total */}
<div style={{ borderTop: '1px solid var(--border-secondary)', paddingTop: 10, marginTop: 6 }}> <div style={{ borderTop: '1px solid var(--border-secondary)', paddingTop: 10, marginTop: 6 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, fontWeight: 700, color: 'var(--text-primary)' }}> <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)' }}>
<span>{t('packing.totalWeight')}</span> <span>{t('packing.totalWeight')}</span>
<span>{(() => { const w = items.reduce((s, i) => s + itemWeight(i), 0); return w >= 1000 ? `${(w / 1000).toFixed(1)} kg` : `${w} g` })()}</span> <span>{(() => { const w = items.reduce((s, i) => s + itemWeight(i), 0); return w >= 1000 ? `${(w / 1000).toFixed(1)} kg` : `${w} g` })()}</span>
</div> </div>
@@ -57,14 +57,14 @@ export function BagSidebar(S: PackingState) {
<input autoFocus value={newBagName} onChange={e => setNewBagName(e.target.value)} <input autoFocus value={newBagName} onChange={e => setNewBagName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleCreateBag(); if (e.key === 'Escape') { setShowAddBag(false); setNewBagName('') } }} onKeyDown={e => { if (e.key === 'Enter') handleCreateBag(); if (e.key === 'Escape') { setShowAddBag(false); setNewBagName('') } }}
placeholder={t('packing.bagName')} placeholder={t('packing.bagName')}
style={{ flex: 1, padding: '5px 8px', borderRadius: 8, border: '1px solid var(--border-primary)', fontSize: 11, fontFamily: 'inherit', outline: 'none' }} /> style={{ flex: 1, padding: '5px 8px', borderRadius: 8, border: '1px solid var(--border-primary)', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontFamily: 'inherit', outline: 'none' }} />
<button onClick={handleCreateBag} style={{ padding: '4px 8px', borderRadius: 8, border: 'none', background: 'var(--text-primary)', color: 'var(--bg-primary)', cursor: 'pointer', display: 'flex', alignItems: 'center' }}> <button onClick={handleCreateBag} style={{ padding: '4px 8px', borderRadius: 8, border: 'none', background: 'var(--text-primary)', color: 'var(--bg-primary)', cursor: 'pointer', display: 'flex', alignItems: 'center' }}>
<Plus size={12} /> <Plus size={12} />
</button> </button>
</div> </div>
) : ( ) : (
<button onClick={() => setShowAddBag(true)} <button onClick={() => setShowAddBag(true)}
style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 12, padding: '5px 8px', borderRadius: 8, border: '1px dashed var(--border-primary)', background: 'none', cursor: 'pointer', fontSize: 11, color: 'var(--text-faint)', fontFamily: 'inherit', width: '100%' }}> style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 12, padding: '5px 8px', borderRadius: 8, border: '1px dashed var(--border-primary)', background: 'none', cursor: 'pointer', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontFamily: 'inherit', width: '100%' }}>
<Plus size={11} /> {t('packing.addBag')} <Plus size={11} /> {t('packing.addBag')}
</button> </button>
))} ))}
@@ -99,10 +99,10 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
onChange={e => setEditKatName(e.target.value)} onChange={e => setEditKatName(e.target.value)}
onBlur={handleSaveKatName} onBlur={handleSaveKatName}
onKeyDown={e => { if (e.key === 'Enter') handleSaveKatName(); if (e.key === 'Escape') { setEditingName(false); setEditKatName(kategorie) } }} onKeyDown={e => { if (e.key === 'Enter') handleSaveKatName(); if (e.key === 'Escape') { setEditingName(false); setEditKatName(kategorie) } }}
style={{ flex: 1, fontSize: 12.5, fontWeight: 600, border: 'none', borderBottom: '2px solid var(--text-primary)', outline: 'none', background: 'transparent', fontFamily: 'inherit', color: 'var(--text-primary)', padding: '0 2px' }} style={{ flex: 1, fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontWeight: 600, border: 'none', borderBottom: '2px solid var(--text-primary)', outline: 'none', background: 'transparent', fontFamily: 'inherit', color: 'var(--text-primary)', padding: '0 2px' }}
/> />
) : ( ) : (
<span style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.04em' }}> <span style={{ fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>
{kategorie} {kategorie}
</span> </span>
)} )}
@@ -118,7 +118,7 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
width: 22, height: 22, borderRadius: '50%', flexShrink: 0, cursor: canEdit ? 'pointer' : 'default', width: 22, height: 22, borderRadius: '50%', flexShrink: 0, cursor: canEdit ? 'pointer' : 'default',
background: `hsl(${a.username.charCodeAt(0) * 37 % 360}, 55%, 55%)`, background: `hsl(${a.username.charCodeAt(0) * 37 % 360}, 55%, 55%)`,
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 10, fontWeight: 700, color: 'white', textTransform: 'uppercase', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'white', textTransform: 'uppercase',
border: '2px solid var(--bg-card)', transition: 'opacity 0.15s', border: '2px solid var(--bg-card)', transition: 'opacity 0.15s',
}} }}
> >
@@ -128,7 +128,7 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
position: 'absolute', top: '100%', left: '50%', transform: 'translateX(-50%)', position: 'absolute', top: '100%', left: '50%', transform: 'translateX(-50%)',
marginTop: 6, padding: '3px 8px', borderRadius: 6, zIndex: 60, marginTop: 6, padding: '3px 8px', borderRadius: 6, zIndex: 60,
background: 'var(--text-primary)', color: 'var(--bg-primary)', background: 'var(--text-primary)', color: 'var(--bg-primary)',
fontSize: 10, fontWeight: 600, whiteSpace: 'nowrap', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, whiteSpace: 'nowrap',
pointerEvents: 'none', opacity: 0, transition: 'opacity 0.15s', pointerEvents: 'none', opacity: 0, transition: 'opacity 0.15s',
}}> }}>
{a.username} {a.username}
@@ -168,7 +168,7 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
display: 'flex', alignItems: 'center', gap: 8, width: '100%', display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '6px 10px', borderRadius: 8, border: 'none', cursor: 'pointer', padding: '6px 10px', borderRadius: 8, border: 'none', cursor: 'pointer',
background: isAssigned ? 'var(--bg-hover)' : 'transparent', background: isAssigned ? 'var(--bg-hover)' : 'transparent',
fontFamily: 'inherit', fontSize: 12, color: 'var(--text-primary)', fontFamily: 'inherit', fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-primary)',
transition: 'background 0.1s', transition: 'background 0.1s',
}} }}
onMouseEnter={e => { if (!isAssigned) e.currentTarget.style.background = 'var(--bg-tertiary)' }} onMouseEnter={e => { if (!isAssigned) e.currentTarget.style.background = 'var(--bg-tertiary)' }}
@@ -178,7 +178,7 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
width: 20, height: 20, borderRadius: '50%', flexShrink: 0, width: 20, height: 20, borderRadius: '50%', flexShrink: 0,
background: `hsl(${m.username.charCodeAt(0) * 37 % 360}, 55%, 55%)`, background: `hsl(${m.username.charCodeAt(0) * 37 % 360}, 55%, 55%)`,
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 10, fontWeight: 700, color: 'white', textTransform: 'uppercase', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'white', textTransform: 'uppercase',
}}> }}>
{m.username[0]} {m.username[0]}
</div> </div>
@@ -188,7 +188,7 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
) )
})} })}
{tripMembers.length === 0 && ( {tripMembers.length === 0 && (
<div style={{ padding: '8px 10px', fontSize: 11, color: 'var(--text-faint)' }}>{t('packing.noMembers')}</div> <div style={{ padding: '8px 10px', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>{t('packing.noMembers')}</div>
)} )}
</div> </div>
)} )}
@@ -197,7 +197,7 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
</div> </div>
<span style={{ <span style={{
fontSize: 11, fontWeight: 600, padding: '1px 8px', borderRadius: 99, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, padding: '1px 8px', borderRadius: 99,
background: alleAbgehakt ? 'rgba(22,163,74,0.12)' : 'var(--bg-tertiary)', background: alleAbgehakt ? 'rgba(22,163,74,0.12)' : 'var(--bg-tertiary)',
color: alleAbgehakt ? '#16a34a' : 'var(--text-muted)', color: alleAbgehakt ? '#16a34a' : 'var(--text-muted)',
}}> }}>
@@ -251,7 +251,7 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
if (e.key === 'Escape') { setShowAddItem(false); setNewItemName('') } if (e.key === 'Escape') { setShowAddItem(false); setNewItemName('') }
}} }}
placeholder={t('packing.addItemPlaceholder')} placeholder={t('packing.addItemPlaceholder')}
style={{ flex: 1, padding: '6px 10px', borderRadius: 8, border: '1px solid var(--border-primary)', fontSize: 12.5, fontFamily: 'inherit', outline: 'none', color: 'var(--text-primary)', background: 'var(--bg-input)' }} style={{ flex: 1, padding: '6px 10px', borderRadius: 8, border: '1px solid var(--border-primary)', fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontFamily: 'inherit', outline: 'none', color: 'var(--text-primary)', background: 'var(--bg-input)' }}
/> />
<button onClick={() => { if (newItemName.trim()) { onAddItem(kategorie, newItemName.trim()); setNewItemName(''); setTimeout(() => addItemRef.current?.focus(), 30) } }} <button onClick={() => { if (newItemName.trim()) { onAddItem(kategorie, newItemName.trim()); setNewItemName(''); setTimeout(() => addItemRef.current?.focus(), 30) } }}
disabled={!newItemName.trim()} disabled={!newItemName.trim()}
@@ -265,7 +265,7 @@ export function KategorieGruppe({ kategorie, items, tripId, allCategories, onRen
</div> </div>
) : ( ) : (
<button onClick={() => { setShowAddItem(true); setTimeout(() => addItemRef.current?.focus(), 30) }} <button onClick={() => { setShowAddItem(true); setTimeout(() => addItemRef.current?.focus(), 30) }}
style={{ display: 'flex', alignItems: 'center', gap: 5, padding: '5px 10px', margin: '2px 4px', borderRadius: 8, border: 'none', background: 'none', cursor: 'pointer', fontSize: 12, color: 'var(--text-faint)', fontFamily: 'inherit' }} style={{ display: 'flex', alignItems: 'center', gap: 5, padding: '5px 10px', margin: '2px 4px', borderRadius: 8, border: 'none', background: 'none', cursor: 'pointer', fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontFamily: 'inherit' }}
onMouseEnter={e => e.currentTarget.style.color = 'var(--text-secondary)'} onMouseEnter={e => e.currentTarget.style.color = 'var(--text-secondary)'}
onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}> onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}>
<Plus size={12} /> {t('packing.addItem')} <Plus size={12} /> {t('packing.addItem')}
@@ -289,7 +289,7 @@ function MenuItem({ icon, label, onClick, danger = false }: MenuItemProps) {
<button onClick={onClick} style={{ <button onClick={onClick} style={{
display: 'flex', alignItems: 'center', gap: 8, width: '100%', display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '7px 10px', background: 'none', border: 'none', cursor: 'pointer', padding: '7px 10px', background: 'none', border: 'none', cursor: 'pointer',
fontSize: 12.5, fontFamily: 'inherit', borderRadius: 7, textAlign: 'left', fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontFamily: 'inherit', borderRadius: 7, textAlign: 'left',
color: danger ? '#ef4444' : 'var(--text-secondary)', color: danger ? '#ef4444' : 'var(--text-secondary)',
}} }}
onMouseEnter={e => e.currentTarget.style.background = danger ? '#fef2f2' : 'var(--bg-tertiary)'} onMouseEnter={e => e.currentTarget.style.background = danger ? '#fef2f2' : 'var(--bg-tertiary)'}
@@ -7,7 +7,7 @@ export function PackingFilterTabs({ items, filter, setFilter, t }: PackingState)
{[['alle', t('packing.filterAll')], ['offen', t('packing.filterOpen')], ['erledigt', t('packing.filterDone')]].map(([id, label]) => ( {[['alle', t('packing.filterAll')], ['offen', t('packing.filterOpen')], ['erledigt', t('packing.filterDone')]].map(([id, label]) => (
<button key={id} onClick={() => setFilter(id)} style={{ <button key={id} onClick={() => setFilter(id)} style={{
padding: '4px 12px', borderRadius: 99, border: 'none', cursor: 'pointer', padding: '4px 12px', borderRadius: 99, border: 'none', cursor: 'pointer',
fontSize: 12, fontFamily: 'inherit', fontWeight: filter === id ? 600 : 400, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontFamily: 'inherit', fontWeight: filter === id ? 600 : 400,
background: filter === id ? 'var(--text-primary)' : 'transparent', background: filter === id ? 'var(--text-primary)' : 'transparent',
color: filter === id ? 'var(--bg-primary)' : 'var(--text-muted)', color: filter === id ? 'var(--bg-primary)' : 'var(--text-muted)',
}}>{label}</button> }}>{label}</button>
@@ -17,9 +17,9 @@ export function PackingHeader(S: PackingState) {
<div style={{ display: 'flex', alignItems: inlineHeader ? 'flex-start' : 'center', justifyContent: 'space-between', gap: 14 }}> <div style={{ display: 'flex', alignItems: inlineHeader ? 'flex-start' : 'center', justifyContent: 'space-between', gap: 14 }}>
{inlineHeader ? ( {inlineHeader ? (
<div> <div>
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 700, color: 'var(--text-primary)' }}>{t('packing.title')}</h2> <h2 style={{ margin: 0, fontSize: 'calc(18px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)' }}>{t('packing.title')}</h2>
{items.length > 0 && ( {items.length > 0 && (
<p style={{ margin: '2px 0 0', fontSize: 12.5, color: 'var(--text-faint)' }}> <p style={{ margin: '2px 0 0', fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>
{t('packing.progress', { packed: abgehakt, total: items.length, percent: fortschritt })} {t('packing.progress', { packed: abgehakt, total: items.length, percent: fortschritt })}
</p> </p>
)} )}
@@ -34,7 +34,7 @@ export function PackingHeader(S: PackingState) {
onChange={e => setSaveTemplateName(e.target.value)} onChange={e => setSaveTemplateName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleSaveAsTemplate(); if (e.key === 'Escape') { setShowSaveTemplate(false); setSaveTemplateName('') } }} onKeyDown={e => { if (e.key === 'Enter') handleSaveAsTemplate(); if (e.key === 'Escape') { setShowSaveTemplate(false); setSaveTemplateName('') } }}
placeholder={t('packing.templateName')} placeholder={t('packing.templateName')}
style={{ fontSize: 12, padding: '5px 10px', borderRadius: 99, border: '1px solid var(--border-primary)', outline: 'none', fontFamily: 'inherit', width: 140, background: 'var(--bg-card)', color: 'var(--text-primary)' }} style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', padding: '5px 10px', borderRadius: 99, border: '1px solid var(--border-primary)', outline: 'none', fontFamily: 'inherit', width: 140, background: 'var(--bg-card)', color: 'var(--text-primary)' }}
/> />
<button onClick={handleSaveAsTemplate} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: '#10b981' }}><Check size={14} /></button> <button onClick={handleSaveAsTemplate} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: '#10b981' }}><Check size={14} /></button>
<button onClick={() => { setShowSaveTemplate(false); setSaveTemplateName('') }} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: 'var(--text-faint)' }}><X size={14} /></button> <button onClick={() => { setShowSaveTemplate(false); setSaveTemplateName('') }} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: 'var(--text-faint)' }}><X size={14} /></button>
@@ -43,7 +43,7 @@ export function PackingHeader(S: PackingState) {
{inlineHeader && canEdit && ( {inlineHeader && canEdit && (
<button onClick={() => setShowImportModal(true)} style={{ <button onClick={() => setShowImportModal(true)} style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '5px 11px', borderRadius: 99, display: 'flex', alignItems: 'center', gap: 5, padding: '5px 11px', borderRadius: 99,
border: '1px solid var(--border-primary)', fontSize: 12, fontWeight: 500, cursor: 'pointer', border: '1px solid var(--border-primary)', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer',
fontFamily: 'inherit', background: 'var(--bg-card)', color: 'var(--text-muted)', fontFamily: 'inherit', background: 'var(--bg-card)', color: 'var(--text-muted)',
}}> }}>
<Upload size={12} /> <span className="hidden sm:inline">{t('packing.import')}</span> <Upload size={12} /> <span className="hidden sm:inline">{t('packing.import')}</span>
@@ -51,7 +51,7 @@ export function PackingHeader(S: PackingState) {
)} )}
{inlineHeader && canEdit && abgehakt > 0 && ( {inlineHeader && canEdit && abgehakt > 0 && (
<button onClick={handleClearChecked} style={{ <button onClick={handleClearChecked} style={{
fontSize: 11.5, padding: '5px 10px', borderRadius: 99, border: '1px solid rgba(239,68,68,0.3)', fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', padding: '5px 10px', borderRadius: 99, border: '1px solid rgba(239,68,68,0.3)',
background: 'rgba(239,68,68,0.1)', color: '#ef4444', cursor: 'pointer', fontFamily: 'inherit', background: 'rgba(239,68,68,0.1)', color: '#ef4444', cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
<span className="hidden sm:inline">{t('packing.clearChecked', { count: abgehakt })}</span> <span className="hidden sm:inline">{t('packing.clearChecked', { count: abgehakt })}</span>
@@ -62,7 +62,7 @@ export function PackingHeader(S: PackingState) {
<div ref={templateDropdownRef} style={{ position: 'relative' }}> <div ref={templateDropdownRef} style={{ position: 'relative' }}>
<button onClick={() => setShowTemplateDropdown(v => !v)} disabled={applyingTemplate} style={{ <button onClick={() => setShowTemplateDropdown(v => !v)} disabled={applyingTemplate} style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '5px 11px', borderRadius: 99, display: 'flex', alignItems: 'center', gap: 5, padding: '5px 11px', borderRadius: 99,
border: '1px solid', fontSize: 12, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit', border: '1px solid', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit',
background: showTemplateDropdown ? 'var(--text-primary)' : 'var(--bg-card)', background: showTemplateDropdown ? 'var(--text-primary)' : 'var(--bg-card)',
borderColor: showTemplateDropdown ? 'var(--text-primary)' : 'var(--border-primary)', borderColor: showTemplateDropdown ? 'var(--text-primary)' : 'var(--border-primary)',
color: showTemplateDropdown ? 'var(--bg-primary)' : 'var(--text-muted)', color: showTemplateDropdown ? 'var(--bg-primary)' : 'var(--text-muted)',
@@ -80,7 +80,7 @@ export function PackingHeader(S: PackingState) {
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, width: '100%', display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '8px 12px', borderRadius: 8, border: 'none', cursor: 'pointer', padding: '8px 12px', borderRadius: 8, border: 'none', cursor: 'pointer',
background: 'transparent', fontFamily: 'inherit', fontSize: 12, color: 'var(--text-primary)', background: 'transparent', fontFamily: 'inherit', fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-primary)',
transition: 'background 0.1s', transition: 'background 0.1s',
}} }}
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-tertiary)'} onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-tertiary)'}
@@ -89,7 +89,7 @@ export function PackingHeader(S: PackingState) {
<Package size={13} className="text-content-faint" /> <Package size={13} className="text-content-faint" />
<div style={{ flex: 1, textAlign: 'left' }}> <div style={{ flex: 1, textAlign: 'left' }}>
<div style={{ fontWeight: 600 }}>{tmpl.name}</div> <div style={{ fontWeight: 600 }}>{tmpl.name}</div>
<div style={{ fontSize: 10, color: 'var(--text-faint)' }}>{tmpl.item_count} {t('admin.packingTemplates.items')}</div> <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>{tmpl.item_count} {t('admin.packingTemplates.items')}</div>
</div> </div>
</button> </button>
))} ))}
@@ -100,7 +100,7 @@ export function PackingHeader(S: PackingState) {
{inlineHeader && canEdit && isAdmin && items.length > 0 && !showSaveTemplate && ( {inlineHeader && canEdit && isAdmin && items.length > 0 && !showSaveTemplate && (
<button onClick={() => setShowSaveTemplate(true)} style={{ <button onClick={() => setShowSaveTemplate(true)} style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '5px 11px', borderRadius: 99, display: 'flex', alignItems: 'center', gap: 5, padding: '5px 11px', borderRadius: 99,
border: '1px solid var(--border-primary)', fontSize: 12, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit', border: '1px solid var(--border-primary)', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit',
background: 'var(--bg-card)', color: 'var(--text-muted)', background: 'var(--bg-card)', color: 'var(--text-muted)',
}}> }}>
<FolderPlus size={12} /> <span className="hidden sm:inline">{t('packing.saveAsTemplate')}</span> <FolderPlus size={12} /> <span className="hidden sm:inline">{t('packing.saveAsTemplate')}</span>
@@ -110,7 +110,7 @@ export function PackingHeader(S: PackingState) {
<button onClick={() => setShowBagModal(true)} className="xl:!hidden" <button onClick={() => setShowBagModal(true)} className="xl:!hidden"
style={{ style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '5px 11px', borderRadius: 99, display: 'flex', alignItems: 'center', gap: 5, padding: '5px 11px', borderRadius: 99,
border: '1px solid', fontSize: 12, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit', border: '1px solid', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit',
background: showBagModal ? 'var(--text-primary)' : 'var(--bg-card)', background: showBagModal ? 'var(--text-primary)' : 'var(--bg-card)',
borderColor: showBagModal ? 'var(--text-primary)' : 'var(--border-primary)', borderColor: showBagModal ? 'var(--text-primary)' : 'var(--border-primary)',
color: showBagModal ? 'var(--bg-primary)' : 'var(--text-muted)', color: showBagModal ? 'var(--bg-primary)' : 'var(--text-muted)',
@@ -127,7 +127,7 @@ export function PackingHeader(S: PackingState) {
{fortschritt === 100 ? ( {fortschritt === 100 ? (
<div style={{ <div style={{
display: 'flex', alignItems: 'center', gap: 8, display: 'flex', alignItems: 'center', gap: 8,
fontSize: 16, fontWeight: 700, color: '#10b981', fontSize: 'calc(16px * var(--fs-scale-text, 1))', fontWeight: 700, color: '#10b981',
letterSpacing: '-0.01em', flexShrink: 0, letterSpacing: '-0.01em', flexShrink: 0,
}}> }}>
<CheckCheck size={18} strokeWidth={2.5} /> <CheckCheck size={18} strokeWidth={2.5} />
@@ -137,17 +137,17 @@ export function PackingHeader(S: PackingState) {
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8, flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, flexShrink: 0 }}>
<div style={{ display: 'flex', alignItems: 'baseline' }}> <div style={{ display: 'flex', alignItems: 'baseline' }}>
<span style={{ <span style={{
fontSize: 22, fontWeight: 700, color: 'var(--text-primary)', fontSize: 'calc(22px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)',
fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em', fontVariantNumeric: 'tabular-nums', letterSpacing: '-0.02em',
lineHeight: 1, lineHeight: 1,
}}>{abgehakt}</span> }}>{abgehakt}</span>
<span style={{ <span style={{
fontSize: 14, fontWeight: 500, color: 'var(--text-faint)', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500, color: 'var(--text-faint)',
fontVariantNumeric: 'tabular-nums', lineHeight: 1, marginLeft: 1, fontVariantNumeric: 'tabular-nums', lineHeight: 1, marginLeft: 1,
}}>/{items.length}</span> }}>/{items.length}</span>
</div> </div>
<span style={{ <span style={{
fontSize: 11, fontWeight: 600, padding: '2px 7px', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, padding: '2px 7px',
borderRadius: 99, background: 'var(--bg-tertiary)', borderRadius: 99, background: 'var(--bg-tertiary)',
color: 'var(--text-muted)', color: 'var(--text-muted)',
fontVariantNumeric: 'tabular-nums', fontVariantNumeric: 'tabular-nums',
@@ -195,7 +195,7 @@ export function PackingHeader(S: PackingState) {
type="text" value={newCatName} onChange={e => setNewCatName(e.target.value)} type="text" value={newCatName} onChange={e => setNewCatName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleAddNewCategory(); if (e.key === 'Escape') { setAddingCategory(false); setNewCatName('') } }} onKeyDown={e => { if (e.key === 'Enter') handleAddNewCategory(); if (e.key === 'Escape') { setAddingCategory(false); setNewCatName('') } }}
placeholder={t('packing.newCategoryPlaceholder')} placeholder={t('packing.newCategoryPlaceholder')}
style={{ flex: 1, padding: '8px 12px', borderRadius: 10, border: '1px solid var(--border-primary)', fontSize: 13.5, fontFamily: 'inherit', outline: 'none', color: 'var(--text-primary)' }} style={{ flex: 1, padding: '8px 12px', borderRadius: 10, border: '1px solid var(--border-primary)', fontSize: 'calc(13.5px * var(--fs-scale-text, 1))', fontFamily: 'inherit', outline: 'none', color: 'var(--text-primary)' }}
/> />
<button onClick={handleAddNewCategory} disabled={!newCatName.trim()} <button onClick={handleAddNewCategory} disabled={!newCatName.trim()}
style={{ padding: '8px 12px', borderRadius: 10, border: 'none', background: newCatName.trim() ? 'var(--text-primary)' : 'var(--border-primary)', color: 'var(--bg-primary)', cursor: newCatName.trim() ? 'pointer' : 'default', display: 'flex', alignItems: 'center' }}> style={{ padding: '8px 12px', borderRadius: 10, border: 'none', background: newCatName.trim() ? 'var(--text-primary)' : 'var(--border-primary)', color: 'var(--bg-primary)', cursor: newCatName.trim() ? 'pointer' : 'default', display: 'flex', alignItems: 'center' }}>
@@ -208,7 +208,7 @@ export function PackingHeader(S: PackingState) {
</div> </div>
) : ( ) : (
<button onClick={() => setAddingCategory(true)} <button onClick={() => setAddingCategory(true)}
style={{ display: 'flex', alignItems: 'center', gap: 6, width: '100%', padding: '9px 14px', borderRadius: 10, border: '1px dashed var(--border-primary)', background: 'none', cursor: 'pointer', fontSize: 13, color: 'var(--text-faint)', fontFamily: 'inherit', transition: 'all 0.15s' }} style={{ display: 'flex', alignItems: 'center', gap: 6, width: '100%', padding: '9px 14px', borderRadius: 10, border: '1px dashed var(--border-primary)', background: 'none', cursor: 'pointer', fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontFamily: 'inherit', transition: 'all 0.15s' }}
onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--text-muted)'; e.currentTarget.style.color = 'var(--text-secondary)' }} onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--text-muted)'; e.currentTarget.style.color = 'var(--text-secondary)' }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.color = 'var(--text-faint)' }}> onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.color = 'var(--text-faint)' }}>
<FolderPlus size={14} /> {t('packing.addCategory')} <FolderPlus size={14} /> {t('packing.addCategory')}
@@ -15,11 +15,11 @@ export function BulkImportModal(S: PackingState) {
boxShadow: '0 16px 48px rgba(0,0,0,0.22)', padding: '22px 22px 18px', boxShadow: '0 16px 48px rgba(0,0,0,0.22)', padding: '22px 22px 18px',
display: 'flex', flexDirection: 'column', gap: 14, display: 'flex', flexDirection: 'column', gap: 14,
}} onClick={e => e.stopPropagation()}> }} onClick={e => e.stopPropagation()}>
<div style={{ fontSize: 15, fontWeight: 600, color: 'var(--text-primary)' }}>{t('packing.importTitle')}</div> <div style={{ fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)' }}>{t('packing.importTitle')}</div>
<div style={{ fontSize: 12, color: 'var(--text-faint)', lineHeight: 1.5 }}>{t('packing.importHint')}</div> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', lineHeight: 1.5 }}>{t('packing.importHint')}</div>
<div style={{ display: 'flex', border: '1px solid var(--border-primary)', borderRadius: 10, overflow: 'hidden', background: 'var(--bg-input)' }}> <div style={{ display: 'flex', border: '1px solid var(--border-primary)', borderRadius: 10, overflow: 'hidden', background: 'var(--bg-input)' }}>
<div style={{ <div style={{
padding: '10px 0', fontSize: 13, fontFamily: 'monospace', lineHeight: 1.5, padding: '10px 0', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'monospace', lineHeight: 1.5,
color: 'var(--text-faint)', textAlign: 'right', userSelect: 'none', color: 'var(--text-faint)', textAlign: 'right', userSelect: 'none',
background: 'var(--bg-hover)', borderRight: '1px solid var(--border-faint)', background: 'var(--bg-hover)', borderRight: '1px solid var(--border-faint)',
minWidth: 32, flexShrink: 0, minWidth: 32, flexShrink: 0,
@@ -34,7 +34,7 @@ export function BulkImportModal(S: PackingState) {
rows={10} rows={10}
placeholder={t('packing.importPlaceholder')} placeholder={t('packing.importPlaceholder')}
style={{ style={{
flex: 1, border: 'none', padding: '10px 12px', fontSize: 13, fontFamily: 'monospace', flex: 1, border: 'none', padding: '10px 12px', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'monospace',
outline: 'none', boxSizing: 'border-box', color: 'var(--text-primary)', outline: 'none', boxSizing: 'border-box', color: 'var(--text-primary)',
background: 'transparent', resize: 'vertical', lineHeight: 1.5, background: 'transparent', resize: 'vertical', lineHeight: 1.5,
}} }}
@@ -46,18 +46,18 @@ export function BulkImportModal(S: PackingState) {
<button onClick={() => csvInputRef.current?.click()} style={{ <button onClick={() => csvInputRef.current?.click()} style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '5px 10px', display: 'flex', alignItems: 'center', gap: 5, padding: '5px 10px',
border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none', border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none',
fontSize: 11, color: 'var(--text-faint)', cursor: 'pointer', fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
<Upload size={11} /> {t('packing.importCsv')} <Upload size={11} /> {t('packing.importCsv')}
</button> </button>
</div> </div>
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
<button onClick={() => setShowImportModal(false)} style={{ <button onClick={() => setShowImportModal(false)} style={{
fontSize: 12, background: 'none', border: '1px solid var(--border-primary)', fontSize: 'calc(12px * var(--fs-scale-text, 1))', background: 'none', border: '1px solid var(--border-primary)',
borderRadius: 8, padding: '6px 14px', cursor: 'pointer', color: 'var(--text-muted)', fontFamily: 'inherit', borderRadius: 8, padding: '6px 14px', cursor: 'pointer', color: 'var(--text-muted)', fontFamily: 'inherit',
}}>{t('common.cancel')}</button> }}>{t('common.cancel')}</button>
<button onClick={handleBulkImport} disabled={!importText.trim()} style={{ <button onClick={handleBulkImport} disabled={!importText.trim()} style={{
fontSize: 12, background: 'var(--accent)', color: 'var(--accent-text)', fontSize: 'calc(12px * var(--fs-scale-text, 1))', background: 'var(--accent)', color: 'var(--accent-text)',
border: 'none', borderRadius: 8, padding: '6px 16px', cursor: 'pointer', fontWeight: 600, border: 'none', borderRadius: 8, padding: '6px 16px', cursor: 'pointer', fontWeight: 600,
fontFamily: 'inherit', opacity: importText.trim() ? 1 : 0.5, fontFamily: 'inherit', opacity: importText.trim() ? 1 : 0.5,
}}>{t('packing.importAction', { count: parseImportLines(importText).length })}</button> }}>{t('packing.importAction', { count: parseImportLines(importText).length })}</button>
@@ -97,13 +97,13 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange, onDel
onChange={e => setEditName(e.target.value)} onChange={e => setEditName(e.target.value)}
onBlur={handleSaveName} onBlur={handleSaveName}
onKeyDown={e => { if (e.key === 'Enter') handleSaveName(); if (e.key === 'Escape') { setEditing(false); setEditName(isPlaceholder ? '' : item.name) } }} onKeyDown={e => { if (e.key === 'Enter') handleSaveName(); if (e.key === 'Escape') { setEditing(false); setEditName(isPlaceholder ? '' : item.name) } }}
style={{ flex: 1, fontSize: 13.5, padding: '2px 8px', borderRadius: 6, border: '1px solid var(--border-primary)', outline: 'none', fontFamily: 'inherit' }} style={{ flex: 1, fontSize: 'calc(13.5px * var(--fs-scale-text, 1))', padding: '2px 8px', borderRadius: 6, border: '1px solid var(--border-primary)', outline: 'none', fontFamily: 'inherit' }}
/> />
) : ( ) : (
<span <span
onClick={() => canEdit && !item.checked && setEditing(true)} onClick={() => canEdit && !item.checked && setEditing(true)}
style={{ style={{
flex: 1, fontSize: 13.5, flex: 1, fontSize: 'calc(13.5px * var(--fs-scale-text, 1))',
cursor: !canEdit || item.checked ? 'default' : 'text', cursor: !canEdit || item.checked ? 'default' : 'text',
color: isPlaceholder ? 'var(--text-faint)' : (item.checked ? 'var(--text-faint)' : 'var(--text-primary)'), color: isPlaceholder ? 'var(--text-faint)' : (item.checked ? 'var(--text-faint)' : 'var(--text-primary)'),
transition: 'color 200ms cubic-bezier(0.23,1,0.32,1)', transition: 'color 200ms cubic-bezier(0.23,1,0.32,1)',
@@ -132,9 +132,9 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange, onDel
try { await updatePackingItem(tripId, item.id, { weight_grams: v }) } catch { toast.error(t('packing.toast.saveError')) } try { await updatePackingItem(tripId, item.id, { weight_grams: v }) } catch { toast.error(t('packing.toast.saveError')) }
}} }}
placeholder="—" placeholder="—"
style={{ width: 36, border: 'none', fontSize: 12, textAlign: 'right', fontFamily: 'inherit', outline: 'none', color: 'var(--text-secondary)', background: 'transparent', padding: 0 }} style={{ width: 36, border: 'none', fontSize: 'calc(12px * var(--fs-scale-text, 1))', textAlign: 'right', fontFamily: 'inherit', outline: 'none', color: 'var(--text-secondary)', background: 'transparent', padding: 0 }}
/> />
<span style={{ fontSize: 10, color: 'var(--text-faint)', userSelect: 'none' }}>g</span> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', userSelect: 'none' }}>g</span>
</div> </div>
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<button <button
@@ -155,7 +155,7 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange, onDel
}}> }}>
{item.bag_id && ( {item.bag_id && (
<button onClick={async () => { setShowBagPicker(false); try { await updatePackingItem(tripId, item.id, { bag_id: null }) } catch { toast.error(t('packing.toast.saveError')) } }} <button onClick={async () => { setShowBagPicker(false); try { await updatePackingItem(tripId, item.id, { bag_id: null }) } catch { toast.error(t('packing.toast.saveError')) } }}
style={{ display: 'flex', alignItems: 'center', gap: 7, width: '100%', padding: '6px 10px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, fontFamily: 'inherit', color: 'var(--text-faint)', borderRadius: 7 }}> style={{ display: 'flex', alignItems: 'center', gap: 7, width: '100%', padding: '6px 10px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontFamily: 'inherit', color: 'var(--text-faint)', borderRadius: 7 }}>
<span style={{ width: 10, height: 10, borderRadius: '50%', border: '2px dashed var(--border-primary)' }} /> <span style={{ width: 10, height: 10, borderRadius: '50%', border: '2px dashed var(--border-primary)' }} />
{t('packing.noBag')} {t('packing.noBag')}
</button> </button>
@@ -165,7 +165,7 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange, onDel
style={{ style={{
display: 'flex', alignItems: 'center', gap: 7, width: '100%', padding: '6px 10px', display: 'flex', alignItems: 'center', gap: 7, width: '100%', padding: '6px 10px',
background: item.bag_id === b.id ? 'var(--bg-tertiary)' : 'none', background: item.bag_id === b.id ? 'var(--bg-tertiary)' : 'none',
border: 'none', cursor: 'pointer', fontSize: 12, fontFamily: 'inherit', color: 'var(--text-secondary)', borderRadius: 7, border: 'none', cursor: 'pointer', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontFamily: 'inherit', color: 'var(--text-secondary)', borderRadius: 7,
}} }}
onMouseEnter={e => { if (item.bag_id !== b.id) e.currentTarget.style.background = 'var(--bg-tertiary)' }} onMouseEnter={e => { if (item.bag_id !== b.id) e.currentTarget.style.background = 'var(--bg-tertiary)' }}
onMouseLeave={e => { if (item.bag_id !== b.id) e.currentTarget.style.background = 'none' }}> onMouseLeave={e => { if (item.bag_id !== b.id) e.currentTarget.style.background = 'none' }}>
@@ -187,7 +187,7 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange, onDel
if (e.key === 'Escape') { setBagInlineCreate(false); setBagInlineName('') } if (e.key === 'Escape') { setBagInlineCreate(false); setBagInlineName('') }
}} }}
placeholder={t('packing.bagName')} placeholder={t('packing.bagName')}
style={{ flex: 1, padding: '4px 8px', borderRadius: 6, border: '1px solid var(--border-primary)', fontSize: 11, fontFamily: 'inherit', outline: 'none' }} /> style={{ flex: 1, padding: '4px 8px', borderRadius: 6, border: '1px solid var(--border-primary)', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontFamily: 'inherit', outline: 'none' }} />
<button onClick={async () => { <button onClick={async () => {
if (bagInlineName.trim()) { if (bagInlineName.trim()) {
const newBag = await onCreateBag(bagInlineName.trim()) const newBag = await onCreateBag(bagInlineName.trim())
@@ -201,7 +201,7 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange, onDel
</div> </div>
) : ( ) : (
<button onClick={() => setBagInlineCreate(true)} <button onClick={() => setBagInlineCreate(true)}
style={{ display: 'flex', alignItems: 'center', gap: 5, width: '100%', padding: '5px 6px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 11, fontFamily: 'inherit', color: 'var(--text-faint)', borderRadius: 7 }} style={{ display: 'flex', alignItems: 'center', gap: 5, width: '100%', padding: '5px 6px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontFamily: 'inherit', color: 'var(--text-faint)', borderRadius: 7 }}
onMouseEnter={e => e.currentTarget.style.color = 'var(--text-secondary)'} onMouseEnter={e => e.currentTarget.style.color = 'var(--text-secondary)'}
onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}> onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}>
<Plus size={11} /> {t('packing.addBag')} <Plus size={11} /> {t('packing.addBag')}
@@ -220,7 +220,7 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange, onDel
<button <button
onClick={() => setShowCatPicker(p => !p)} onClick={() => setShowCatPicker(p => !p)}
title={t('packing.changeCategory')} title={t('packing.changeCategory')}
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '3px 5px', borderRadius: 6, display: 'flex', alignItems: 'center', color: 'var(--text-faint)', fontSize: 10, gap: 2 }} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '3px 5px', borderRadius: 6, display: 'flex', alignItems: 'center', color: 'var(--text-faint)', fontSize: 'calc(10px * var(--fs-scale-text, 1))', gap: 2 }}
> >
<span style={{ width: 7, height: 7, borderRadius: '50%', background: katColor(item.category || t('packing.defaultCategory'), categories), display: 'inline-block' }} /> <span style={{ width: 7, height: 7, borderRadius: '50%', background: katColor(item.category || t('packing.defaultCategory'), categories), display: 'inline-block' }} />
</button> </button>
@@ -234,7 +234,7 @@ export function ArtikelZeile({ item, tripId, categories, onCategoryChange, onDel
<button key={cat} onClick={() => handleCatChange(cat)} style={{ <button key={cat} onClick={() => handleCatChange(cat)} style={{
display: 'flex', alignItems: 'center', gap: 7, width: '100%', display: 'flex', alignItems: 'center', gap: 7, width: '100%',
padding: '6px 10px', background: cat === (item.category || t('packing.defaultCategory')) ? 'var(--bg-tertiary)' : 'none', padding: '6px 10px', background: cat === (item.category || t('packing.defaultCategory')) ? 'var(--bg-tertiary)' : 'none',
border: 'none', cursor: 'pointer', fontSize: 12.5, fontFamily: 'inherit', border: 'none', cursor: 'pointer', fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontFamily: 'inherit',
color: 'var(--text-secondary)', borderRadius: 7, textAlign: 'left', color: 'var(--text-secondary)', borderRadius: 7, textAlign: 'left',
}}> }}>
<span style={{ width: 8, height: 8, borderRadius: '50%', background: katColor(cat, categories), flexShrink: 0 }} /> <span style={{ width: 8, height: 8, borderRadius: '50%', background: katColor(cat, categories), flexShrink: 0 }} />
@@ -13,12 +13,12 @@ export function PackingList(S: PackingState) {
{items.length === 0 ? ( {items.length === 0 ? (
<div style={{ textAlign: 'center', padding: '60px 20px' }}> <div style={{ textAlign: 'center', padding: '60px 20px' }}>
<Luggage size={40} style={{ color: 'var(--text-faint)', display: 'block', margin: '0 auto 10px' }} /> <Luggage size={40} style={{ color: 'var(--text-faint)', display: 'block', margin: '0 auto 10px' }} />
<p style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-secondary)', margin: '0 0 4px' }}>{t('packing.emptyTitle')}</p> <p style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-secondary)', margin: '0 0 4px' }}>{t('packing.emptyTitle')}</p>
<p style={{ fontSize: 13, color: 'var(--text-faint)', margin: 0 }}>{t('packing.emptyHint')}</p> <p style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', margin: 0 }}>{t('packing.emptyHint')}</p>
</div> </div>
) : Object.keys(gruppiert).length === 0 ? ( ) : Object.keys(gruppiert).length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px 20px', color: 'var(--text-faint)' }}> <div style={{ textAlign: 'center', padding: '40px 20px', color: 'var(--text-faint)' }}>
<p style={{ fontSize: 13, margin: 0 }}>{t('packing.emptyFiltered')}</p> <p style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', margin: 0 }}>{t('packing.emptyFiltered')}</p>
</div> </div>
) : ( ) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 md:grid-cols-2 gap-2">
@@ -18,9 +18,9 @@ export function QuantityInput({ value, onSave }: { value: number; onSave: (qty:
onChange={e => setLocal(e.target.value.replace(/\D/g, ''))} onChange={e => setLocal(e.target.value.replace(/\D/g, ''))}
onBlur={commit} onBlur={commit}
onKeyDown={e => { if (e.key === 'Enter') { commit(); (e.target as HTMLInputElement).blur() } }} onKeyDown={e => { if (e.key === 'Enter') { commit(); (e.target as HTMLInputElement).blur() } }}
style={{ width: 24, border: 'none', outline: 'none', background: 'transparent', fontSize: 12, textAlign: 'right', fontFamily: 'inherit', color: 'var(--text-secondary)', padding: 0 }} style={{ width: 24, border: 'none', outline: 'none', background: 'transparent', fontSize: 'calc(12px * var(--fs-scale-text, 1))', textAlign: 'right', fontFamily: 'inherit', color: 'var(--text-secondary)', padding: 0 }}
/> />
<span style={{ fontSize: 10, color: 'var(--text-faint)', fontWeight: 500 }}>x</span> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 500 }}>x</span>
</div> </div>
) )
} }
@@ -161,13 +161,13 @@ export default function AirTrailImportModal({ isOpen, onClose, tripId, pushUndo
</span> </span>
<Plane size={15} color="#3b82f6" style={{ flexShrink: 0 }} /> <Plane size={15} color="#3b82f6" style={{ flexShrink: 0 }} />
<span style={{ flex: 1, minWidth: 0 }}> <span style={{ flex: 1, minWidth: 0 }}>
<span style={{ display: 'block', fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{label}</span> <span style={{ display: 'block', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{label}</span>
<span style={{ display: 'block', fontSize: 11, color: 'var(--text-muted)' }}> <span style={{ display: 'block', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-muted)' }}>
{f.fromCode ?? f.fromName ?? '?'} {f.toCode ?? f.toName ?? '?'}{f.date ? ` · ${fmtDate(f.date, locale)}` : ''} {f.fromCode ?? f.fromName ?? '?'} {f.toCode ?? f.toName ?? '?'}{f.date ? ` · ${fmtDate(f.date, locale)}` : ''}
</span> </span>
</span> </span>
{already && ( {already && (
<span style={{ flexShrink: 0, fontSize: 10, fontWeight: 600, color: 'var(--text-faint)' }}> <span style={{ flexShrink: 0, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)' }}>
{t('reservations.airtrail.alreadyImported')} {t('reservations.airtrail.alreadyImported')}
</span> </span>
)} )}
@@ -192,7 +192,7 @@ export default function AirTrailImportModal({ isOpen, onClose, tripId, pushUndo
> >
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14 }}>
<Plane size={16} color="#3b82f6" /> <Plane size={16} color="#3b82f6" />
<div style={{ flex: 1, fontSize: 15, fontWeight: 700, color: 'var(--text-primary)' }}> <div style={{ flex: 1, fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)' }}>
{t('reservations.airtrail.title')} {t('reservations.airtrail.title')}
</div> </div>
<button onClick={handleClose} className="bg-transparent text-content-faint" style={{ border: 'none', cursor: 'pointer', padding: 4, borderRadius: 6, display: 'flex', alignItems: 'center' }}> <button onClick={handleClose} className="bg-transparent text-content-faint" style={{ border: 'none', cursor: 'pointer', padding: 4, borderRadius: 6, display: 'flex', alignItems: 'center' }}>
@@ -202,20 +202,20 @@ export default function AirTrailImportModal({ isOpen, onClose, tripId, pushUndo
<div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}> <div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
{loading && ( {loading && (
<div className="text-content-faint" style={{ fontSize: 13, textAlign: 'center', padding: '24px 0' }}> <div className="text-content-faint" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', textAlign: 'center', padding: '24px 0' }}>
{t('common.loading')} {t('common.loading')}
</div> </div>
)} )}
{!loading && flights.length === 0 && !error && ( {!loading && flights.length === 0 && !error && (
<div className="text-content-faint" style={{ fontSize: 13, textAlign: 'center', padding: '24px 0' }}> <div className="text-content-faint" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', textAlign: 'center', padding: '24px 0' }}>
{t('reservations.airtrail.empty')} {t('reservations.airtrail.empty')}
</div> </div>
)} )}
{!loading && during.length > 0 && ( {!loading && during.length > 0 && (
<> <>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-primary)', margin: '2px 0 8px' }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', margin: '2px 0 8px' }}>
{t('reservations.airtrail.duringTrip')} {t('reservations.airtrail.duringTrip')}
</div> </div>
{during.map(renderFlight)} {during.map(renderFlight)}
@@ -224,7 +224,7 @@ export default function AirTrailImportModal({ isOpen, onClose, tripId, pushUndo
{!loading && others.length > 0 && ( {!loading && others.length > 0 && (
<> <>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-faint)', margin: `${during.length > 0 ? 14 : 2}px 0 8px` }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-faint)', margin: `${during.length > 0 ? 14 : 2}px 0 8px` }}>
{t('reservations.airtrail.otherFlights')} {t('reservations.airtrail.otherFlights')}
</div> </div>
{others.map(renderFlight)} {others.map(renderFlight)}
@@ -232,7 +232,7 @@ export default function AirTrailImportModal({ isOpen, onClose, tripId, pushUndo
)} )}
{error && ( {error && (
<div className="bg-[rgba(239,68,68,0.08)] text-[#b91c1c]" style={{ border: '1px solid rgba(239,68,68,0.35)', borderRadius: 10, padding: '8px 10px', fontSize: 12, whiteSpace: 'pre-wrap', marginTop: 8 }}> <div className="bg-[rgba(239,68,68,0.08)] text-[#b91c1c]" style={{ border: '1px solid rgba(239,68,68,0.35)', borderRadius: 10, padding: '8px 10px', fontSize: 'calc(12px * var(--fs-scale-text, 1))', whiteSpace: 'pre-wrap', marginTop: 8 }}>
{error} {error}
</div> </div>
)} )}
@@ -241,7 +241,7 @@ export default function AirTrailImportModal({ isOpen, onClose, tripId, pushUndo
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 14, paddingTop: 14, borderTop: '1px solid var(--border-faint)' }}> <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 14, paddingTop: 14, borderTop: '1px solid var(--border-faint)' }}>
<button <button
onClick={handleClose} onClick={handleClose}
style={{ padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)', background: 'none', color: 'var(--text-primary)', fontSize: 13, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit' }} style={{ padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)', background: 'none', color: 'var(--text-primary)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit' }}
> >
{t('common.cancel')} {t('common.cancel')}
</button> </button>
@@ -249,7 +249,7 @@ export default function AirTrailImportModal({ isOpen, onClose, tripId, pushUndo
onClick={handleImport} onClick={handleImport}
disabled={selectableCount === 0 || importing} disabled={selectableCount === 0 || importing}
className={selectableCount > 0 && !importing ? 'bg-accent text-accent-text' : 'bg-surface-tertiary text-content-faint'} className={selectableCount > 0 && !importing ? 'bg-accent text-accent-text' : 'bg-surface-tertiary text-content-faint'}
style={{ padding: '8px 16px', borderRadius: 10, border: 'none', fontSize: 13, fontWeight: 500, cursor: selectableCount > 0 && !importing ? 'pointer' : 'default', fontFamily: 'inherit' }} style={{ padding: '8px 16px', borderRadius: 10, border: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: selectableCount > 0 && !importing ? 'pointer' : 'default', fontFamily: 'inherit' }}
> >
{importing ? t('common.loading') : t('reservations.airtrail.importCta', { count: selectableCount })} {importing ? t('common.loading') : t('reservations.airtrail.importCta', { count: selectableCount })}
</button> </button>
@@ -115,7 +115,7 @@ export default function AirportSelect({ value, onChange, placeholder, style }: P
onFocus={() => setOpen(true)} onFocus={() => setOpen(true)}
onKeyDown={onKey} onKeyDown={onKey}
className="bg-transparent text-content" className="bg-transparent text-content"
style={{ flex: 1, minWidth: 0, border: 'none', outline: 'none', fontSize: 13 }} style={{ flex: 1, minWidth: 0, border: 'none', outline: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}
/> />
{value && ( {value && (
<button type="button" onClick={clear} className="bg-transparent text-content-faint" style={{ border: 'none', padding: 2, cursor: 'pointer', display: 'flex' }} aria-label="Clear"> <button type="button" onClick={clear} className="bg-transparent text-content-faint" style={{ border: 'none', padding: 2, cursor: 'pointer', display: 'flex' }} aria-label="Clear">
@@ -127,7 +127,7 @@ export default function AirportSelect({ value, onChange, placeholder, style }: P
{open && (loading || results.length > 0) && ( {open && (loading || results.length > 0) && (
<div className="bg-surface-card" style={{ position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, border: '1px solid var(--border-primary)', borderRadius: 10, boxShadow: '0 8px 24px rgba(0,0,0,0.18)', maxHeight: 260, overflowY: 'auto', zIndex: 1000 }}> <div className="bg-surface-card" style={{ position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, border: '1px solid var(--border-primary)', borderRadius: 10, boxShadow: '0 8px 24px rgba(0,0,0,0.18)', maxHeight: 260, overflowY: 'auto', zIndex: 1000 }}>
{loading && results.length === 0 && ( {loading && results.length === 0 && (
<div className="text-content-faint" style={{ padding: 10, fontSize: 12 }}>{t('common.loading')}</div> <div className="text-content-faint" style={{ padding: 10, fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>{t('common.loading')}</div>
)} )}
{results.map((a, i) => ( {results.map((a, i) => (
<button <button
@@ -142,10 +142,10 @@ export default function AirportSelect({ value, onChange, placeholder, style }: P
fontFamily: 'inherit', fontFamily: 'inherit',
}} }}
> >
<span className="text-content-muted" style={{ fontFamily: 'ui-monospace, SFMono-Regular, monospace', fontSize: 11, fontWeight: 700, minWidth: 32 }}>{a.iata}</span> <span className="text-content-muted" style={{ fontFamily: 'ui-monospace, SFMono-Regular, monospace', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 700, minWidth: 32 }}>{a.iata}</span>
<span style={{ flex: 1, minWidth: 0 }}> <span style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.city || a.name}</div> <div style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.city || a.name}</div>
<div className="text-content-faint" style={{ fontSize: 11, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.name}{a.country ? ` · ${displayCountry(a.country)}` : ''}</div> <div className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.name}{a.country ? ` · ${displayCountry(a.country)}` : ''}</div>
</span> </span>
</button> </button>
))} ))}
@@ -39,10 +39,10 @@ export function BookingCostsSection({ reservationId, pendingExpense, onCreate, o
<div className="bg-surface-secondary border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 10 }}> <div className="bg-surface-secondary border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 10 }}>
<span style={{ width: 26, height: 26, borderRadius: 7, display: 'grid', placeItems: 'center', background: meta.color + '22', color: meta.color, flexShrink: 0 }}><Icon size={14} /></span> <span style={{ width: 26, height: 26, borderRadius: 7, display: 'grid', placeItems: 'center', background: meta.color + '22', color: meta.color, flexShrink: 0 }}><Icon size={14} /></span>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div className="text-content" style={{ fontSize: 14, fontWeight: 600 }}>{t(meta.labelKey)}</div> <div className="text-content" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t(meta.labelKey)}</div>
<div className="text-content-faint" style={{ fontSize: 12 }}>{t('reservations.createExpenseHint')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>{t('reservations.createExpenseHint')}</div>
</div> </div>
<span className="text-content" style={{ fontSize: 14, fontWeight: 700, flexShrink: 0 }}>{formatMoney(pendingExpense.total_price, pendingExpense.currency || base, locale)}</span> <span className="text-content" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700, flexShrink: 0 }}>{formatMoney(pendingExpense.total_price, pendingExpense.currency || base, locale)}</span>
</div> </div>
</div> </div>
) )
@@ -57,10 +57,10 @@ export function BookingCostsSection({ reservationId, pendingExpense, onCreate, o
<div className="bg-surface-secondary border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 10 }}> <div className="bg-surface-secondary border border-edge" style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 10 }}>
<span style={{ width: 26, height: 26, borderRadius: 7, display: 'grid', placeItems: 'center', background: meta.color + '22', color: meta.color, flexShrink: 0 }}><Icon size={14} /></span> <span style={{ width: 26, height: 26, borderRadius: 7, display: 'grid', placeItems: 'center', background: meta.color + '22', color: meta.color, flexShrink: 0 }}><Icon size={14} /></span>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div className="text-content" style={{ fontSize: 14, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{linked.name}</div> <div className="text-content" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{linked.name}</div>
<div className="text-content-faint" style={{ fontSize: 12 }}>{t(meta.labelKey)}</div> <div className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>{t(meta.labelKey)}</div>
</div> </div>
<span className="text-content" style={{ fontSize: 14, fontWeight: 700, flexShrink: 0 }}>{formatMoney(linked.total_price, linked.currency || base, locale)}</span> <span className="text-content" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700, flexShrink: 0 }}>{formatMoney(linked.total_price, linked.currency || base, locale)}</span>
<button type="button" onClick={() => onEdit(linked)} title={t('common.edit')} className="text-content-muted border border-edge bg-surface-card" style={{ display: 'inline-flex', padding: 7, borderRadius: 8, cursor: 'pointer' }}><Pencil size={13} /></button> <button type="button" onClick={() => onEdit(linked)} title={t('common.edit')} className="text-content-muted border border-edge bg-surface-card" style={{ display: 'inline-flex', padding: 7, borderRadius: 8, cursor: 'pointer' }}><Pencil size={13} /></button>
<button type="button" onClick={() => onRemove(linked)} title={t('reservations.removeExpense')} className="text-content-muted border border-edge bg-surface-card" style={{ display: 'inline-flex', padding: 7, borderRadius: 8, cursor: 'pointer' }}><Trash2 size={13} /></button> <button type="button" onClick={() => onRemove(linked)} title={t('reservations.removeExpense')} className="text-content-muted border border-edge bg-surface-card" style={{ display: 'inline-flex', padding: 7, borderRadius: 8, cursor: 'pointer' }}><Trash2 size={13} /></button>
</div> </div>
@@ -73,10 +73,10 @@ export function BookingCostsSection({ reservationId, pendingExpense, onCreate, o
<label className={labelCls}>{t('reservations.costsLabel')}</label> <label className={labelCls}>{t('reservations.costsLabel')}</label>
<button type="button" onClick={onCreate} <button type="button" onClick={onCreate}
className="bg-surface-secondary border border-edge text-content" className="bg-surface-secondary border border-edge text-content"
style={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, padding: '11px 13px', borderRadius: 10, fontSize: 13.5, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}> style={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, padding: '11px 13px', borderRadius: 10, fontSize: 'calc(13.5px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
<Plus size={15} /> {t('reservations.createExpense')} <Plus size={15} /> {t('reservations.createExpense')}
</button> </button>
<div className="text-content-faint" style={{ fontSize: 11, marginTop: 6 }}>{t('reservations.createExpenseHint')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: 6 }}>{t('reservations.createExpenseHint')}</div>
</div> </div>
) )
} }
@@ -127,7 +127,7 @@ export default function BookingImportModal({ isOpen, onClose, tripId }: BookingI
> >
{/* Header */} {/* Header */}
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14 }}>
<div style={{ flex: 1, fontSize: 15, fontWeight: 700, color: 'var(--text-primary)' }}> <div style={{ flex: 1, fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)' }}>
{t('reservations.import.title')} {t('reservations.import.title')}
</div> </div>
<button onClick={handleClose} className="bg-transparent text-content-faint" style={{ border: 'none', cursor: 'pointer', padding: 4, borderRadius: 6, display: 'flex', alignItems: 'center' }}> <button onClick={handleClose} className="bg-transparent text-content-faint" style={{ border: 'none', cursor: 'pointer', padding: 4, borderRadius: 6, display: 'flex', alignItems: 'center' }}>
@@ -136,7 +136,7 @@ export default function BookingImportModal({ isOpen, onClose, tripId }: BookingI
</div> </div>
<div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}> <div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
<div style={{ fontSize: 12, color: 'var(--text-faint)', marginBottom: 14, lineHeight: 1.45 }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginBottom: 14, lineHeight: 1.45 }}>
{t('reservations.import.acceptedFormats')} {t('reservations.import.acceptedFormats')}
</div> </div>
@@ -160,7 +160,7 @@ export default function BookingImportModal({ isOpen, onClose, tripId }: BookingI
width: '100%', minHeight: 100, borderRadius: 12, width: '100%', minHeight: 100, borderRadius: 12,
border: `2px dashed ${isDragOver ? 'var(--accent)' : 'var(--border-primary)'}`, border: `2px dashed ${isDragOver ? 'var(--accent)' : 'var(--border-primary)'}`,
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
gap: 6, fontSize: 13, fontWeight: 500, cursor: 'pointer', gap: 6, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer',
marginBottom: 12, padding: 16, boxSizing: 'border-box', marginBottom: 12, padding: 16, boxSizing: 'border-box',
transition: 'border-color 0.15s, background 0.15s', transition: 'border-color 0.15s, background 0.15s',
}} }}
@@ -176,7 +176,7 @@ export default function BookingImportModal({ isOpen, onClose, tripId }: BookingI
</div> </div>
{error && ( {error && (
<div className="bg-[rgba(239,68,68,0.08)] text-[#b91c1c]" style={{ border: '1px solid rgba(239,68,68,0.35)', borderRadius: 10, padding: '8px 10px', fontSize: 12, whiteSpace: 'pre-wrap', marginTop: 8 }}> <div className="bg-[rgba(239,68,68,0.08)] text-[#b91c1c]" style={{ border: '1px solid rgba(239,68,68,0.35)', borderRadius: 10, padding: '8px 10px', fontSize: 'calc(12px * var(--fs-scale-text, 1))', whiteSpace: 'pre-wrap', marginTop: 8 }}>
{error} {error}
</div> </div>
)} )}
@@ -186,7 +186,7 @@ export default function BookingImportModal({ isOpen, onClose, tripId }: BookingI
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 14, paddingTop: 14, borderTop: '1px solid var(--border-faint)' }}> <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 14, paddingTop: 14, borderTop: '1px solid var(--border-faint)' }}>
<button <button
onClick={handleClose} onClick={handleClose}
style={{ padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)', background: 'none', color: 'var(--text-primary)', fontSize: 13, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit' }} style={{ padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)', background: 'none', color: 'var(--text-primary)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit' }}
> >
{t('common.cancel')} {t('common.cancel')}
</button> </button>
@@ -194,7 +194,7 @@ export default function BookingImportModal({ isOpen, onClose, tripId }: BookingI
onClick={handleParse} onClick={handleParse}
disabled={files.length === 0 || loading} disabled={files.length === 0 || loading}
className={files.length > 0 && !loading ? 'bg-accent text-accent-text' : 'bg-surface-tertiary text-content-faint'} className={files.length > 0 && !loading ? 'bg-accent text-accent-text' : 'bg-surface-tertiary text-content-faint'}
style={{ padding: '8px 16px', borderRadius: 10, border: 'none', fontSize: 13, fontWeight: 500, cursor: files.length > 0 && !loading ? 'pointer' : 'default', fontFamily: 'inherit' }} style={{ padding: '8px 16px', borderRadius: 10, border: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: files.length > 0 && !loading ? 'pointer' : 'default', fontFamily: 'inherit' }}
> >
{loading ? t('reservations.import.parsing') : t('common.import')} {loading ? t('reservations.import.parsing') : t('common.import')}
</button> </button>
@@ -137,7 +137,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
{day.title || t('planner.dayN', { n: (days.indexOf(day) + 1) || '?' })} {day.title || t('planner.dayN', { n: (days.indexOf(day) + 1) || '?' })}
{collapsed && formattedDate && <span className="text-content-muted" style={{ fontWeight: 500, marginLeft: 8 }}>{formattedDate}</span>} {collapsed && formattedDate && <span className="text-content-muted" style={{ fontWeight: 500, marginLeft: 8 }}>{formattedDate}</span>}
</div> </div>
{!collapsed && formattedDate && <div className="text-content-muted" style={{ fontSize: 12, marginTop: 1 }}>{formattedDate}</div>} {!collapsed && formattedDate && <div className="text-content-muted" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', marginTop: 1 }}>{formattedDate}</div>}
</div> </div>
<button onClick={(e) => { e.stopPropagation(); toggleCollapse() }} title={collapsed ? t('common.expand') : t('common.collapse')} <button onClick={(e) => { e.stopPropagation(); toggleCollapse() }} title={collapsed ? t('common.expand') : t('common.collapse')}
className="bg-surface-secondary" className="bg-surface-secondary"
@@ -159,7 +159,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
{/* ── Weather ── */} {/* ── Weather ── */}
{day.date && lat && lng && ( {day.date && lat && lng && (
loading ? ( loading ? (
<div style={{ textAlign: 'center', padding: 16, color: 'var(--text-faint)', fontSize: 12 }}> <div style={{ textAlign: 'center', padding: 16, color: 'var(--text-faint)', fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>
<div style={{ width: 18, height: 18, border: '2px solid var(--border-primary)', borderTopColor: 'var(--text-primary)', borderRadius: '50%', animation: 'spin 0.8s linear infinite', margin: '0 auto 6px' }} /> <div style={{ width: 18, height: 18, border: '2px solid var(--border-primary)', borderTopColor: 'var(--text-primary)', borderRadius: '50%', animation: 'spin 0.8s linear infinite', margin: '0 auto 6px' }} />
</div> </div>
) : weather ? ( ) : weather ? (
@@ -170,16 +170,16 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
<WIcon main={weather.main} size={20} /> <WIcon main={weather.main} size={20} />
</div> </div>
<div style={{ flex: 1, display: 'flex', alignItems: 'baseline', gap: 6, flexWrap: 'wrap' }}> <div style={{ flex: 1, display: 'flex', alignItems: 'baseline', gap: 6, flexWrap: 'wrap' }}>
<span style={{ fontSize: 20, fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1 }}> <span style={{ fontSize: 'calc(20px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1 }}>
{weather.type === 'climate' ? 'Ø ' : ''}{cTemp(weather.temp, isFahrenheit)}{unit} {weather.type === 'climate' ? 'Ø ' : ''}{cTemp(weather.temp, isFahrenheit)}{unit}
</span> </span>
{weather.temp_max != null && ( {weather.temp_max != null && (
<span style={{ fontSize: 12, color: 'var(--text-faint)' }}> <span style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>
{cTemp(weather.temp_min, isFahrenheit)}° / {cTemp(weather.temp_max, isFahrenheit)}° {cTemp(weather.temp_min, isFahrenheit)}° / {cTemp(weather.temp_max, isFahrenheit)}°
</span> </span>
)} )}
{weather.description && ( {weather.description && (
<span style={{ fontSize: 12, color: 'var(--text-muted)', textTransform: 'capitalize' }}>{weather.description}</span> <span style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-muted)', textTransform: 'capitalize' }}>{weather.description}</span>
)} )}
</div> </div>
</div> </div>
@@ -209,11 +209,11 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
width: 44, padding: '5px 2px', borderRadius: 8, width: 44, padding: '5px 2px', borderRadius: 8,
background: h.precipitation_probability > 50 ? 'rgba(59,130,246,0.07)' : 'transparent', background: h.precipitation_probability > 50 ? 'rgba(59,130,246,0.07)' : 'transparent',
}}> }}>
<span style={{ fontSize: 9, color: 'var(--text-faint)', fontWeight: 500 }}>{String(h.hour).padStart(2, '0')}</span> <span style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 500 }}>{String(h.hour).padStart(2, '0')}</span>
<WIcon main={h.main} size={12} /> <WIcon main={h.main} size={12} />
<span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-primary)' }}>{cTemp(h.temp, isFahrenheit)}°</span> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)' }}>{cTemp(h.temp, isFahrenheit)}°</span>
{h.precipitation_probability > 0 && ( {h.precipitation_probability > 0 && (
<span style={{ fontSize: 8, color: '#3b82f6', fontWeight: 500 }}>{h.precipitation_probability}%</span> <span style={{ fontSize: 'calc(8px * var(--fs-scale-text, 1))', color: '#3b82f6', fontWeight: 500 }}>{h.precipitation_probability}%</span>
)} )}
</div> </div>
))} ))}
@@ -222,11 +222,11 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
)} )}
{weather.type === 'climate' && ( {weather.type === 'climate' && (
<div style={{ fontSize: 10, color: 'var(--text-faint)', marginTop: 6, fontStyle: 'italic' }}>{t('day.climateHint')}</div> <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginTop: 6, fontStyle: 'italic' }}>{t('day.climateHint')}</div>
)} )}
</div> </div>
) : ( ) : (
<div style={{ fontSize: 12, color: 'var(--text-faint)', textAlign: 'center', padding: 8 }}>{t('day.noWeather')}</div> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', textAlign: 'center', padding: 8 }}>{t('day.noWeather')}</div>
) )
)} )}
@@ -242,7 +242,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
return ( return (
<div style={{ marginBottom: 0 }}> <div style={{ marginBottom: 0 }}>
{day.date && lat && lng && <div style={{ height: 1, background: 'var(--border-faint)', margin: '12px 0' }} />} {day.date && lat && lng && <div style={{ height: 1, background: 'var(--border-faint)', margin: '12px 0' }} />}
<div className="text-content-faint" style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6 }}>{t('day.reservations')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6 }}>{t('day.reservations')}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{dayReservations.map(r => { {dayReservations.map(r => {
const linkedAssignment = dayAssignments.find(a => a.id === r.assignment_id) const linkedAssignment = dayAssignments.find(a => a.id === r.assignment_id)
@@ -251,15 +251,15 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
<div key={r.id} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 8, background: confirmed ? 'rgba(22,163,74,0.06)' : 'rgba(217,119,6,0.06)', border: `1px solid ${confirmed ? 'rgba(22,163,74,0.15)' : 'rgba(217,119,6,0.15)'}` }}> <div key={r.id} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 8, background: confirmed ? 'rgba(22,163,74,0.06)' : 'rgba(217,119,6,0.06)', border: `1px solid ${confirmed ? 'rgba(22,163,74,0.15)' : 'rgba(217,119,6,0.15)'}` }}>
{(() => { const TIcon = RES_TYPE_ICONS[r.type] || FileText; return <TIcon size={12} style={{ color: RES_TYPE_COLORS[r.type] || 'var(--text-faint)', flexShrink: 0 }} /> })()} {(() => { const TIcon = RES_TYPE_ICONS[r.type] || FileText; return <TIcon size={12} style={{ color: RES_TYPE_COLORS[r.type] || 'var(--text-faint)', flexShrink: 0 }} /> })()}
<div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 6, overflow: 'hidden' }}> <div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 6, overflow: 'hidden' }}>
<span style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-primary)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.title}</span> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.title}</span>
{linkedAssignment?.place && <span style={{ fontSize: 9, color: 'var(--text-faint)', whiteSpace: 'nowrap' }}>· {linkedAssignment.place.name}</span>} {linkedAssignment?.place && <span style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', whiteSpace: 'nowrap' }}>· {linkedAssignment.place.name}</span>}
</div> </div>
{(() => { {(() => {
const { time: startTime } = splitReservationDateTime(r.reservation_time) const { time: startTime } = splitReservationDateTime(r.reservation_time)
const { time: endTime } = splitReservationDateTime(r.reservation_end_time) const { time: endTime } = splitReservationDateTime(r.reservation_end_time)
if (!startTime && !endTime) return null if (!startTime && !endTime) return null
return ( return (
<span style={{ fontSize: 10, color: 'var(--text-muted)', whiteSpace: 'nowrap', flexShrink: 0 }}> <span style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-muted)', whiteSpace: 'nowrap', flexShrink: 0 }}>
{startTime ? formatTime12(startTime, is12h) : ''} {startTime ? formatTime12(startTime, is12h) : ''}
{endTime ? ` ${formatTime12(endTime, is12h)}` : ''} {endTime ? ` ${formatTime12(endTime, is12h)}` : ''}
</span> </span>
@@ -278,7 +278,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
{/* ── Accommodation ── */} {/* ── Accommodation ── */}
<div> <div>
<div className="text-content-faint" style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 8 }}>{t('day.accommodation')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 8 }}>{t('day.accommodation')}</div>
<AccommodationList dayAccommodations={dayAccommodations} day={day} reservations={reservations} <AccommodationList dayAccommodations={dayAccommodations} day={day} reservations={reservations}
canEditDays={canEditDays} fmtTime={fmtTime} blurCodes={blurCodes} t={t} canEditDays={canEditDays} fmtTime={fmtTime} blurCodes={blurCodes} t={t}
@@ -307,7 +307,7 @@ interface ChipProps {
function Chip({ icon: Icon, value }: ChipProps) { function Chip({ icon: Icon, value }: ChipProps) {
return ( return (
<div className="bg-surface-secondary text-content-muted" style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '4px 8px', borderRadius: 8, fontSize: 11 }}> <div className="bg-surface-secondary text-content-muted" style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '4px 8px', borderRadius: 8, fontSize: 'calc(11px * var(--fs-scale-text, 1))' }}>
<Icon size={11} style={{ flexShrink: 0, opacity: 0.6 }} /> <Icon size={11} style={{ flexShrink: 0, opacity: 0.6 }} />
<span style={{ fontWeight: 500 }}>{value}</span> <span style={{ fontWeight: 500 }}>{value}</span>
</div> </div>
@@ -347,7 +347,7 @@ function InfoChip({ icon: Icon, label, value, placeholder, onEdit, type }: InfoC
> >
<Icon size={11} style={{ color: 'var(--text-faint)', flexShrink: 0 }} /> <Icon size={11} style={{ color: 'var(--text-faint)', flexShrink: 0 }} />
<div style={{ minWidth: 0 }}> <div style={{ minWidth: 0 }}>
<div style={{ fontSize: 8, color: 'var(--text-faint)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.04em', lineHeight: 1 }}>{label}</div> <div style={{ fontSize: 'calc(8px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.04em', lineHeight: 1 }}>{label}</div>
{editing ? ( {editing ? (
<input <input
ref={inputRef} ref={inputRef}
@@ -359,12 +359,12 @@ function InfoChip({ icon: Icon, label, value, placeholder, onEdit, type }: InfoC
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
style={{ style={{
border: 'none', outline: 'none', background: 'none', padding: 0, margin: 0, border: 'none', outline: 'none', background: 'none', padding: 0, margin: 0,
fontSize: 11, fontWeight: 600, color: 'var(--text-primary)', fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', fontFamily: 'inherit',
width: type === 'time' ? 50 : '100%', lineHeight: 1.3, width: type === 'time' ? 50 : '100%', lineHeight: 1.3,
}} }}
/> />
) : ( ) : (
<div style={{ fontSize: 11, fontWeight: 600, color: value ? 'var(--text-primary)' : 'var(--text-faint)', lineHeight: 1.3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: value ? 'var(--text-primary)' : 'var(--text-faint)', lineHeight: 1.3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{value || placeholder} {value || placeholder}
</div> </div>
)} )}
@@ -398,7 +398,7 @@ function AccommodationList({ dayAccommodations, day, reservations, canEditDays,
<div style={{ padding: '4px 12px 0', display: 'flex', alignItems: 'center', gap: 4 }}> <div style={{ padding: '4px 12px 0', display: 'flex', alignItems: 'center', gap: 4 }}>
{isCheckInDay && <LogIn size={9} style={{ color: '#22c55e' }} />} {isCheckInDay && <LogIn size={9} style={{ color: '#22c55e' }} />}
{isCheckOutDay && !isCheckInDay && <LogOut size={9} style={{ color: '#ef4444' }} />} {isCheckOutDay && !isCheckInDay && <LogOut size={9} style={{ color: '#ef4444' }} />}
<span style={{ fontSize: 9, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: isCheckOutDay && !isCheckInDay ? '#ef4444' : '#22c55e' }}>{dayLabel}</span> <span style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: isCheckOutDay && !isCheckInDay ? '#ef4444' : '#22c55e' }}>{dayLabel}</span>
</div> </div>
)} )}
{/* Hotel header */} {/* Hotel header */}
@@ -411,8 +411,8 @@ function AccommodationList({ dayAccommodations, day, reservations, canEditDays,
)} )}
</div> </div>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{acc.place_name}</div> <div style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{acc.place_name}</div>
{acc.place_address && <div style={{ fontSize: 10, color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{acc.place_address}</div>} {acc.place_address && <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{acc.place_address}</div>}
</div> </div>
{canEditDays && <button onClick={() => { setAccommodation(acc); setHotelForm({ check_in: acc.check_in || '', check_in_end: acc.check_in_end || '', check_out: acc.check_out || '', confirmation: acc.confirmation || '', place_id: acc.place_id }); setHotelDayRange({ start: acc.start_day_id, end: acc.end_day_id }); setShowHotelPicker('edit') }} {canEditDays && <button onClick={() => { setAccommodation(acc); setHotelForm({ check_in: acc.check_in || '', check_in_end: acc.check_in_end || '', check_out: acc.check_out || '', confirmation: acc.confirmation || '', place_id: acc.place_id }); setHotelDayRange({ start: acc.start_day_id, end: acc.end_day_id }); setShowHotelPicker('edit') }}
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 3, flexShrink: 0 }}> style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 3, flexShrink: 0 }}>
@@ -426,26 +426,26 @@ function AccommodationList({ dayAccommodations, day, reservations, canEditDays,
<div style={{ display: 'flex', gap: 0, margin: '0 12px 8px', borderRadius: 10, overflow: 'hidden', border: '1px solid var(--border-faint)' }}> <div style={{ display: 'flex', gap: 0, margin: '0 12px 8px', borderRadius: 10, overflow: 'hidden', border: '1px solid var(--border-faint)' }}>
{acc.check_in && ( {acc.check_in && (
<div style={{ flex: 1, padding: '8px 10px', borderRight: '1px solid var(--border-faint)', textAlign: 'center' }}> <div style={{ flex: 1, padding: '8px 10px', borderRight: '1px solid var(--border-faint)', textAlign: 'center' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.2 }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.2 }}>
{fmtTime(acc.check_in)}{acc.check_in_end ? ` ${fmtTime(acc.check_in_end)}` : ''} {fmtTime(acc.check_in)}{acc.check_in_end ? ` ${fmtTime(acc.check_in_end)}` : ''}
</div> </div>
<div style={{ fontSize: 9, color: 'var(--text-faint)', fontWeight: 500, marginTop: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3 }}> <div style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 500, marginTop: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3 }}>
<LogIn size={8} /> {t('day.checkIn')} <LogIn size={8} /> {t('day.checkIn')}
</div> </div>
</div> </div>
)} )}
{acc.check_out && ( {acc.check_out && (
<div style={{ flex: 1, padding: '8px 10px', borderRight: acc.confirmation ? '1px solid var(--border-faint)' : 'none', textAlign: 'center' }}> <div style={{ flex: 1, padding: '8px 10px', borderRight: acc.confirmation ? '1px solid var(--border-faint)' : 'none', textAlign: 'center' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.2 }}>{fmtTime(acc.check_out)}</div> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.2 }}>{fmtTime(acc.check_out)}</div>
<div style={{ fontSize: 9, color: 'var(--text-faint)', fontWeight: 500, marginTop: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3 }}> <div style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 500, marginTop: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3 }}>
<LogOut size={8} /> {t('day.checkOut')} <LogOut size={8} /> {t('day.checkOut')}
</div> </div>
</div> </div>
)} )}
{acc.confirmation && ( {acc.confirmation && (
<div style={{ flex: 1, padding: '8px 10px', textAlign: 'center' }}> <div style={{ flex: 1, padding: '8px 10px', textAlign: 'center' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.2 }}>{acc.confirmation}</div> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1.2 }}>{acc.confirmation}</div>
<div style={{ fontSize: 9, color: 'var(--text-faint)', fontWeight: 500, marginTop: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3 }}> <div style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 500, marginTop: 2, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3 }}>
<Hash size={8} /> {t('day.confirmation')} <Hash size={8} /> {t('day.confirmation')}
</div> </div>
</div> </div>
@@ -456,8 +456,8 @@ function AccommodationList({ dayAccommodations, day, reservations, canEditDays,
<div style={{ margin: '0 12px 8px', padding: '6px 10px', borderRadius: 8, background: confirmed ? 'rgba(22,163,74,0.06)' : 'rgba(217,119,6,0.06)', border: `1px solid ${confirmed ? 'rgba(22,163,74,0.15)' : 'rgba(217,119,6,0.15)'}`, display: 'flex', alignItems: 'center', gap: 8 }}> <div style={{ margin: '0 12px 8px', padding: '6px 10px', borderRadius: 8, background: confirmed ? 'rgba(22,163,74,0.06)' : 'rgba(217,119,6,0.06)', border: `1px solid ${confirmed ? 'rgba(22,163,74,0.15)' : 'rgba(217,119,6,0.15)'}`, display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: confirmed ? '#16a34a' : '#d97706', flexShrink: 0 }} /> <div style={{ width: 6, height: 6, borderRadius: '50%', background: confirmed ? '#16a34a' : '#d97706', flexShrink: 0 }} />
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{linked.title}</div> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{linked.title}</div>
<div style={{ fontSize: 9, color: 'var(--text-faint)', display: 'flex', gap: 6, marginTop: 1 }}> <div style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', display: 'flex', gap: 6, marginTop: 1 }}>
<span>{confirmed ? t('reservations.confirmed') : t('reservations.pending')}</span> <span>{confirmed ? t('reservations.confirmed') : t('reservations.pending')}</span>
{linked.confirmation_number && <span {linked.confirmation_number && <span
onMouseEnter={e => { if (blurCodes) e.currentTarget.style.filter = 'none' }} onMouseEnter={e => { if (blurCodes) e.currentTarget.style.filter = 'none' }}
@@ -476,7 +476,7 @@ function AccommodationList({ dayAccommodations, day, reservations, canEditDays,
{canEditDays && <button onClick={() => setShowHotelPicker(true)} style={{ {canEditDays && <button onClick={() => setShowHotelPicker(true)} style={{
width: '100%', padding: 8, border: '1.5px dashed var(--border-primary)', borderRadius: 10, width: '100%', padding: 8, border: '1.5px dashed var(--border-primary)', borderRadius: 10,
background: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, background: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
fontSize: 10, color: 'var(--text-faint)', fontFamily: 'inherit', fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontFamily: 'inherit',
}}> }}>
<Hotel size={10} /> {t('day.addAccommodation')} <Hotel size={10} /> {t('day.addAccommodation')}
</button>} </button>}
@@ -485,7 +485,7 @@ function AccommodationList({ dayAccommodations, day, reservations, canEditDays,
canEditDays ? <button onClick={() => setShowHotelPicker(true)} style={{ canEditDays ? <button onClick={() => setShowHotelPicker(true)} style={{
width: '100%', padding: 10, border: '1.5px dashed var(--border-primary)', borderRadius: 10, width: '100%', padding: 10, border: '1.5px dashed var(--border-primary)', borderRadius: 10,
background: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, background: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
fontSize: 11, color: 'var(--text-faint)', fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontFamily: 'inherit',
}}> }}>
<Hotel size={12} /> {t('day.addAccommodation')} <Hotel size={12} /> {t('day.addAccommodation')}
</button> : null </button> : null
@@ -512,7 +512,7 @@ function HotelPickerModal({ showHotelPicker, setShowHotelPicker, font, t, hotelD
{/* Popup Header */} {/* Popup Header */}
<div style={{ padding: '16px 18px 12px', borderBottom: '1px solid var(--border-faint)', display: 'flex', alignItems: 'center', gap: 10 }}> <div style={{ padding: '16px 18px 12px', borderBottom: '1px solid var(--border-faint)', display: 'flex', alignItems: 'center', gap: 10 }}>
<Hotel size={16} style={{ color: 'var(--text-primary)' }} /> <Hotel size={16} style={{ color: 'var(--text-primary)' }} />
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-primary)', flex: 1 }}>{showHotelPicker === 'edit' ? t('day.editAccommodation') : t('day.addAccommodation')}</span> <span style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', flex: 1 }}>{showHotelPicker === 'edit' ? t('day.editAccommodation') : t('day.addAccommodation')}</span>
<button onClick={() => setShowHotelPicker(false)} style={{ background: 'var(--bg-secondary)', border: 'none', borderRadius: 8, width: 28, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}> <button onClick={() => setShowHotelPicker(false)} style={{ background: 'var(--bg-secondary)', border: 'none', borderRadius: 8, width: 28, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}>
<X size={12} style={{ color: 'var(--text-muted)' }} /> <X size={12} style={{ color: 'var(--text-muted)' }} />
</button> </button>
@@ -520,7 +520,7 @@ function HotelPickerModal({ showHotelPicker, setShowHotelPicker, font, t, hotelD
{/* Day Range */} {/* Day Range */}
<div style={{ padding: '12px 18px', borderBottom: '1px solid var(--border-faint)', background: 'var(--bg-secondary)' }}> <div style={{ padding: '12px 18px', borderBottom: '1px solid var(--border-faint)', background: 'var(--bg-secondary)' }}>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-faint)', marginBottom: 8, textTransform: 'uppercase', letterSpacing: '0.04em' }}>{t('day.hotelDayRange')}</div> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', marginBottom: 8, textTransform: 'uppercase', letterSpacing: '0.04em' }}>{t('day.hotelDayRange')}</div>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}> <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<CustomSelect <CustomSelect
@@ -536,7 +536,7 @@ function HotelPickerModal({ showHotelPicker, setShowHotelPicker, font, t, hotelD
size="sm" size="sm"
/> />
</div> </div>
<span style={{ fontSize: 11, color: 'var(--text-faint)', flexShrink: 0 }}></span> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', flexShrink: 0 }}></span>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<CustomSelect <CustomSelect
value={hotelDayRange.end} value={hotelDayRange.end}
@@ -552,7 +552,7 @@ function HotelPickerModal({ showHotelPicker, setShowHotelPicker, font, t, hotelD
/> />
</div> </div>
<button onClick={() => setHotelDayRange({ start: days[0]?.id, end: days[days.length - 1]?.id })} style={{ <button onClick={() => setHotelDayRange({ start: days[0]?.id, end: days[days.length - 1]?.id })} style={{
padding: '6px 14px', borderRadius: 8, border: 'none', fontSize: 11, fontWeight: 600, cursor: 'pointer', flexShrink: 0, padding: '6px 14px', borderRadius: 8, border: 'none', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', flexShrink: 0,
background: hotelDayRange.start === days[0]?.id && hotelDayRange.end === days[days.length - 1]?.id ? 'var(--text-primary)' : 'var(--bg-card)', background: hotelDayRange.start === days[0]?.id && hotelDayRange.end === days[days.length - 1]?.id ? 'var(--text-primary)' : 'var(--bg-card)',
color: hotelDayRange.start === days[0]?.id && hotelDayRange.end === days[days.length - 1]?.id ? 'var(--bg-card)' : 'var(--text-muted)', color: hotelDayRange.start === days[0]?.id && hotelDayRange.end === days[days.length - 1]?.id ? 'var(--bg-card)' : 'var(--text-muted)',
}}> }}>
@@ -564,21 +564,21 @@ function HotelPickerModal({ showHotelPicker, setShowHotelPicker, font, t, hotelD
{/* Check-in / Check-out / Confirmation */} {/* Check-in / Check-out / Confirmation */}
<div style={{ padding: '10px 18px', borderBottom: '1px solid var(--border-faint)', display: 'flex', gap: 8, flexWrap: 'wrap' }}> <div style={{ padding: '10px 18px', borderBottom: '1px solid var(--border-faint)', display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<div style={{ flex: 1, minWidth: 80 }}> <div style={{ flex: 1, minWidth: 80 }}>
<label style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkIn')}</label> <label style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkIn')}</label>
<CustomTimePicker value={hotelForm.check_in} onChange={v => setHotelForm(f => ({ ...f, check_in: v }))} placeholder="14:00" /> <CustomTimePicker value={hotelForm.check_in} onChange={v => setHotelForm(f => ({ ...f, check_in: v }))} placeholder="14:00" />
</div> </div>
<div style={{ flex: 1, minWidth: 80 }}> <div style={{ flex: 1, minWidth: 80 }}>
<label style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkInUntil')}</label> <label style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkInUntil')}</label>
<CustomTimePicker value={hotelForm.check_in_end} onChange={v => setHotelForm(f => ({ ...f, check_in_end: v }))} placeholder="22:00" /> <CustomTimePicker value={hotelForm.check_in_end} onChange={v => setHotelForm(f => ({ ...f, check_in_end: v }))} placeholder="22:00" />
</div> </div>
<div style={{ flex: 1, minWidth: 80 }}> <div style={{ flex: 1, minWidth: 80 }}>
<label style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkOut')}</label> <label style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.checkOut')}</label>
<CustomTimePicker value={hotelForm.check_out} onChange={v => setHotelForm(f => ({ ...f, check_out: v }))} placeholder="11:00" /> <CustomTimePicker value={hotelForm.check_out} onChange={v => setHotelForm(f => ({ ...f, check_out: v }))} placeholder="11:00" />
</div> </div>
<div style={{ flex: 2, minWidth: 120 }}> <div style={{ flex: 2, minWidth: 120 }}>
<label style={{ fontSize: 9, fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.confirmation')}</label> <label style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '0.04em', display: 'block', marginBottom: 3 }}>{t('day.confirmation')}</label>
<input type="text" value={hotelForm.confirmation} onChange={e => setHotelForm(f => ({ ...f, confirmation: e.target.value }))} <input type="text" value={hotelForm.confirmation} onChange={e => setHotelForm(f => ({ ...f, confirmation: e.target.value }))}
placeholder="ABC-12345" style={{ width: '100%', padding: '8px 10px', borderRadius: 10, border: '1px solid var(--border-primary)', background: 'var(--bg-card)', color: 'var(--text-primary)', fontSize: 13, fontFamily: 'inherit', boxSizing: 'border-box', height: 38 }} /> placeholder="ABC-12345" style={{ width: '100%', padding: '8px 10px', borderRadius: 10, border: '1px solid var(--border-primary)', background: 'var(--bg-card)', color: 'var(--text-primary)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'inherit', boxSizing: 'border-box', height: 38 }} />
</div> </div>
</div> </div>
@@ -586,14 +586,14 @@ function HotelPickerModal({ showHotelPicker, setShowHotelPicker, font, t, hotelD
{categories.length > 0 && ( {categories.length > 0 && (
<div style={{ padding: '8px 18px', borderBottom: '1px solid var(--border-faint)', display: 'flex', gap: 4, flexWrap: 'wrap' }}> <div style={{ padding: '8px 18px', borderBottom: '1px solid var(--border-faint)', display: 'flex', gap: 4, flexWrap: 'wrap' }}>
<button onClick={() => setHotelCategoryFilter('')} style={{ <button onClick={() => setHotelCategoryFilter('')} style={{
padding: '3px 10px', borderRadius: 6, border: 'none', fontSize: 10, fontWeight: 600, cursor: 'pointer', padding: '3px 10px', borderRadius: 6, border: 'none', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer',
background: !hotelCategoryFilter ? 'var(--text-primary)' : 'var(--bg-secondary)', background: !hotelCategoryFilter ? 'var(--text-primary)' : 'var(--bg-secondary)',
color: !hotelCategoryFilter ? 'var(--bg-card)' : 'var(--text-muted)', color: !hotelCategoryFilter ? 'var(--bg-card)' : 'var(--text-muted)',
}}>{t('day.allDays')}</button> }}>{t('day.allDays')}</button>
{categories.map(c => ( {categories.map(c => (
<button key={c.id} onClick={() => setHotelCategoryFilter(c.id)} style={{ <button key={c.id} onClick={() => setHotelCategoryFilter(c.id)} style={{
padding: '3px 10px', borderRadius: 6, border: 'none', fontSize: 10, fontWeight: 600, cursor: 'pointer', padding: '3px 10px', borderRadius: 6, border: 'none', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer',
background: hotelCategoryFilter === c.id ? c.color || 'var(--text-primary)' : 'var(--bg-secondary)', background: hotelCategoryFilter === c.id ? c.color || 'var(--text-primary)' : 'var(--bg-secondary)',
color: hotelCategoryFilter === c.id ? '#fff' : 'var(--text-muted)', color: hotelCategoryFilter === c.id ? '#fff' : 'var(--text-muted)',
}}>{c.name}</button> }}>{c.name}</button>
@@ -606,7 +606,7 @@ function HotelPickerModal({ showHotelPicker, setShowHotelPicker, font, t, hotelD
{(() => { {(() => {
const filtered = hotelCategoryFilter ? places.filter(p => p.category_id === hotelCategoryFilter) : places const filtered = hotelCategoryFilter ? places.filter(p => p.category_id === hotelCategoryFilter) : places
return filtered.length === 0 ? ( return filtered.length === 0 ? (
<div style={{ padding: 20, textAlign: 'center', fontSize: 12, color: 'var(--text-faint)' }}>{t('day.noPlacesForHotel')}</div> <div style={{ padding: 20, textAlign: 'center', fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>{t('day.noPlacesForHotel')}</div>
) : filtered.map(p => ( ) : filtered.map(p => (
<button key={p.id} onClick={() => handleSelectPlace(p.id)} style={{ <button key={p.id} onClick={() => handleSelectPlace(p.id)} style={{
display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '10px 18px', display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '10px 18px',
@@ -628,8 +628,8 @@ function HotelPickerModal({ showHotelPicker, setShowHotelPicker, font, t, hotelD
)} )}
</div> </div>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</div> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</div>
{p.address && <div style={{ fontSize: 10, color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.address}</div>} {p.address && <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.address}</div>}
</div> </div>
</button> </button>
)) ))
@@ -638,7 +638,7 @@ function HotelPickerModal({ showHotelPicker, setShowHotelPicker, font, t, hotelD
{/* Save / Cancel */} {/* Save / Cancel */}
<div style={{ padding: '12px 18px', borderTop: '1px solid var(--border-faint)', display: 'flex', justifyContent: 'flex-end', gap: 8 }}> <div style={{ padding: '12px 18px', borderTop: '1px solid var(--border-faint)', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
<button onClick={() => setShowHotelPicker(false)} style={{ padding: '7px 16px', borderRadius: 8, border: '1px solid var(--border-primary)', background: 'none', fontSize: 12, cursor: 'pointer', fontFamily: 'inherit', color: 'var(--text-muted)' }}> <button onClick={() => setShowHotelPicker(false)} style={{ padding: '7px 16px', borderRadius: 8, border: '1px solid var(--border-primary)', background: 'none', fontSize: 'calc(12px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit', color: 'var(--text-muted)' }}>
{t('common.cancel')} {t('common.cancel')}
</button> </button>
<button onClick={async () => { <button onClick={async () => {
@@ -670,7 +670,7 @@ function HotelPickerModal({ showHotelPicker, setShowHotelPicker, font, t, hotelD
await handleSaveAccommodation() await handleSaveAccommodation()
} }
}} disabled={!hotelForm.place_id} style={{ }} disabled={!hotelForm.place_id} style={{
padding: '7px 20px', borderRadius: 8, border: 'none', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', padding: '7px 20px', borderRadius: 8, border: 'none', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit',
background: hotelForm.place_id ? 'var(--text-primary)' : 'var(--bg-tertiary)', background: hotelForm.place_id ? 'var(--text-primary)' : 'var(--bg-tertiary)',
color: hotelForm.place_id ? 'var(--bg-card)' : 'var(--text-faint)', color: hotelForm.place_id ? 'var(--bg-card)' : 'var(--text-faint)',
}}> }}>
@@ -1300,7 +1300,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
color: isSelected ? 'var(--accent-text)' : 'var(--text-muted)', color: isSelected ? 'var(--accent-text)' : 'var(--text-muted)',
display: 'flex', flexDirection: 'column', alignItems: 'center', overflow: 'hidden', display: 'flex', flexDirection: 'column', alignItems: 'center', overflow: 'hidden',
}}> }}>
<div style={{ width: '100%', height: 26, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, fontWeight: 700 }}> <div style={{ width: '100%', height: 26, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 700 }}>
{index + 1} {index + 1}
</div> </div>
{hasWeather && ( {hasWeather && (
@@ -1326,20 +1326,20 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
style={{ style={{
width: '100%', border: 'none', outline: 'none', width: '100%', border: 'none', outline: 'none',
fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)',
background: 'transparent', padding: 0, fontFamily: 'inherit', background: 'transparent', padding: 0, fontFamily: 'inherit',
borderBottom: '1.5px solid var(--text-primary)', borderBottom: '1.5px solid var(--text-primary)',
}} }}
/> />
) : (<> ) : (<>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flexShrink: 1, minWidth: 0 }}> <span style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flexShrink: 1, minWidth: 0 }}>
{day.title || t('dayplan.dayN', { n: index + 1 })} {day.title || t('dayplan.dayN', { n: index + 1 })}
</span> </span>
{formattedDate && ( {formattedDate && (
<> <>
<span style={{ flexShrink: 0, width: 1, height: 11, background: 'var(--border-primary)' }} /> <span style={{ flexShrink: 0, width: 1, height: 11, background: 'var(--border-primary)' }} />
<span style={{ flexShrink: 0, fontSize: 11, fontWeight: 400, color: 'var(--text-faint)', whiteSpace: 'nowrap' }}> <span style={{ flexShrink: 0, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 400, color: 'var(--text-faint)', whiteSpace: 'nowrap' }}>
{formattedDate} {formattedDate}
</span> </span>
</> </>
@@ -1374,7 +1374,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
return ( return (
<span key={acc.id} onClick={e => { e.stopPropagation(); if ((acc as any).place_id) onPlaceClick((acc as any).place_id) }} className="bg-surface-hover" style={{ display: 'inline-flex', alignItems: 'center', gap: 4, flexShrink: 1, minWidth: 0, cursor: (acc as any).place_id ? 'pointer' : 'default', borderRadius: 7, padding: '2px 7px 2px 6px' }}> <span key={acc.id} onClick={e => { e.stopPropagation(); if ((acc as any).place_id) onPlaceClick((acc as any).place_id) }} className="bg-surface-hover" style={{ display: 'inline-flex', alignItems: 'center', gap: 4, flexShrink: 1, minWidth: 0, cursor: (acc as any).place_id ? 'pointer' : 'default', borderRadius: 7, padding: '2px 7px 2px 6px' }}>
<Hotel size={11} strokeWidth={1.8} style={{ color: iconColor, flexShrink: 0 }} /> <Hotel size={11} strokeWidth={1.8} style={{ color: iconColor, flexShrink: 0 }} />
<span className="text-content-muted" style={{ fontSize: 10.5, fontWeight: 400, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{(acc as any).place_name || (acc as any).reservation_title}</span> <span className="text-content-muted" style={{ fontSize: 'calc(10.5px * var(--fs-scale-text, 1))', fontWeight: 400, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{(acc as any).place_name || (acc as any).reservation_title}</span>
</span> </span>
) )
}) })
@@ -1386,7 +1386,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
return activeRentals.map(r => ( return activeRentals.map(r => (
<span key={`rental-${r.id}`} onClick={e => { e.stopPropagation(); setTransportDetail(r) }} className="bg-surface-hover" style={{ display: 'inline-flex', alignItems: 'center', gap: 4, flexShrink: 1, minWidth: 0, cursor: 'pointer', borderRadius: 7, padding: '2px 7px 2px 6px' }}> <span key={`rental-${r.id}`} onClick={e => { e.stopPropagation(); setTransportDetail(r) }} className="bg-surface-hover" style={{ display: 'inline-flex', alignItems: 'center', gap: 4, flexShrink: 1, minWidth: 0, cursor: 'pointer', borderRadius: 7, padding: '2px 7px 2px 6px' }}>
<Car size={11} strokeWidth={1.8} className="text-content-faint" style={{ flexShrink: 0 }} /> <Car size={11} strokeWidth={1.8} className="text-content-faint" style={{ flexShrink: 0 }} />
<span className="text-content-muted" style={{ fontSize: 10.5, fontWeight: 400, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{r.title}</span> <span className="text-content-muted" style={{ fontSize: 'calc(10.5px * var(--fs-scale-text, 1))', fontWeight: 400, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{r.title}</span>
</span> </span>
)) ))
})()} })()}
@@ -1395,7 +1395,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
)} )}
{cost && ( {cost && (
<div style={{ marginTop: 2 }}> <div style={{ marginTop: 2 }}>
<span className="text-[#059669]" style={{ fontSize: 11 }}>{cost}</span> <span className="text-[#059669]" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))' }}>{cost}</span>
</div> </div>
)} )}
</div> </div>
@@ -1506,7 +1506,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
border: dragOverDayId === day.id ? '2px dashed rgba(17,24,39,0.2)' : '2px dashed transparent', border: dragOverDayId === day.id ? '2px dashed rgba(17,24,39,0.2)' : '2px dashed transparent',
}} }}
> >
<span className="text-content-faint" style={{ fontSize: 12 }}>{t('dayplan.emptyDay')}</span> <span className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>{t('dayplan.emptyDay')}</span>
</div> </div>
) : ( ) : (
merged.map((item, idx) => { merged.map((item, idx) => {
@@ -1684,7 +1684,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
position: 'absolute', left: '100%', top: '50%', transform: 'translateY(-50%)', position: 'absolute', left: '100%', top: '50%', transform: 'translateY(-50%)',
marginLeft: 8, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 50, marginLeft: 8, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 50,
background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)', background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)',
fontSize: 11, fontWeight: 500, padding: '5px 10px', borderRadius: 8, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, padding: '5px 10px', borderRadius: 8,
boxShadow: '0 4px 12px rgba(0,0,0,0.15)', border: '1px solid var(--border-faint, #e5e7eb)', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', border: '1px solid var(--border-faint, #e5e7eb)',
}}> }}>
{lockedIds.has(assignment.id) {lockedIds.has(assignment.id)
@@ -1699,18 +1699,18 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
const CatIcon = getCategoryIcon(cat.icon) const CatIcon = getCategoryIcon(cat.icon)
return <span title={cat.name} style={{ display: 'inline-flex', flexShrink: 0 }}><CatIcon size={10} strokeWidth={2} color={cat.color || 'var(--text-muted)'} /></span> return <span title={cat.name} style={{ display: 'inline-flex', flexShrink: 0 }}><CatIcon size={10} strokeWidth={2} color={cat.color || 'var(--text-muted)'} /></span>
})()} })()}
<span style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.2 }}> <span style={{ fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.2 }}>
{place.name} {place.name}
</span> </span>
{place.place_time && ( {place.place_time && (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, flexShrink: 0, fontSize: 10, color: 'var(--text-faint)', fontWeight: 400, marginLeft: 6 }}> <span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, flexShrink: 0, fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 400, marginLeft: 6 }}>
<Clock size={9} strokeWidth={2} /> <Clock size={9} strokeWidth={2} />
{formatTime(place.place_time, locale, timeFormat)}{place.end_time ? ` ${formatTime(place.end_time, locale, timeFormat)}` : ''} {formatTime(place.place_time, locale, timeFormat)}{place.end_time ? ` ${formatTime(place.end_time, locale, timeFormat)}` : ''}
</span> </span>
)} )}
</div> </div>
{(place.description || place.address || cat?.name) && ( {(place.description || place.address || cat?.name) && (
<div className="collab-note-md" style={{ marginTop: 2, fontSize: 10, color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.2, maxHeight: '1.2em' }}> <div className="collab-note-md" style={{ marginTop: 2, fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.2, maxHeight: '1.2em' }}>
<Markdown remarkPlugins={[remarkGfm]}>{place.description || place.address || cat?.name || ''}</Markdown> <Markdown remarkPlugins={[remarkGfm]}>{place.description || place.address || cat?.name || ''}</Markdown>
</div> </div>
)} )}
@@ -1722,7 +1722,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
const active = hasEndpoints ? visibleConnectionIds.includes(res.id) : false const active = hasEndpoints ? visibleConnectionIds.includes(res.id) : false
return ( return (
<div style={{ marginTop: 3, display: 'inline-flex', alignItems: 'center', gap: 4 }}> <div style={{ marginTop: 3, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
<div className={confirmed ? 'bg-[rgba(22,163,74,0.1)] text-[#16a34a]' : 'bg-[rgba(217,119,6,0.1)] text-[#d97706]'} style={{ display: 'inline-flex', alignItems: 'center', gap: 3, padding: '1px 6px', borderRadius: 5, fontSize: 9, fontWeight: 600, <div className={confirmed ? 'bg-[rgba(22,163,74,0.1)] text-[#16a34a]' : 'bg-[rgba(217,119,6,0.1)] text-[#d97706]'} style={{ display: 'inline-flex', alignItems: 'center', gap: 3, padding: '1px 6px', borderRadius: 5, fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600,
}}> }}>
{(() => { const RI = RES_ICONS[res.type] || Ticket; return <RI size={8} /> })()} {(() => { const RI = RES_ICONS[res.type] || Ticket; return <RI size={8} /> })()}
<span className="hidden sm:inline">{confirmed ? t('planner.resConfirmed') : t('planner.resPending')}</span> <span className="hidden sm:inline">{confirmed ? t('planner.resConfirmed') : t('planner.resPending')}</span>
@@ -1797,7 +1797,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
{assignment.participants.slice(0, 5).map((p, pi) => ( {assignment.participants.slice(0, 5).map((p, pi) => (
<div key={p.user_id} className="bg-surface-tertiary text-content-muted" style={{ <div key={p.user_id} className="bg-surface-tertiary text-content-muted" style={{
width: 16, height: 16, borderRadius: '50%', border: '1.5px solid var(--bg-card)', width: 16, height: 16, borderRadius: '50%', border: '1.5px solid var(--bg-card)',
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 'calc(7px * var(--fs-scale-text, 1))', fontWeight: 700,
marginLeft: pi > 0 ? -4 : 0, flexShrink: 0, marginLeft: pi > 0 ? -4 : 0, flexShrink: 0,
overflow: 'hidden', overflow: 'hidden',
}}> }}>
@@ -1805,7 +1805,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
</div> </div>
))} ))}
{assignment.participants.length > 5 && ( {assignment.participants.length > 5 && (
<span className="text-content-faint" style={{ fontSize: 8, marginLeft: 2 }}>+{assignment.participants.length - 5}</span> <span className="text-content-faint" style={{ fontSize: 'calc(8px * var(--fs-scale-text, 1))', marginLeft: 2 }}>+{assignment.participants.length - 5}</span>
)} )}
</div> </div>
)} )}
@@ -1835,7 +1835,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: 3, gap: 3,
fontSize: 10, fontSize: 'calc(10px * var(--fs-scale-text, 1))',
fontWeight: 500, fontWeight: 500,
color: 'var(--text-muted)', color: 'var(--text-muted)',
fontFamily: 'inherit', fontFamily: 'inherit',
@@ -1968,13 +1968,13 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
{spanLabel && ( {spanLabel && (
<span style={{ <span style={{
fontSize: 9, fontWeight: 700, padding: '1px 5px', borderRadius: 4, flexShrink: 0, fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 700, padding: '1px 5px', borderRadius: 4, flexShrink: 0,
background: `${color}20`, color: color, textTransform: 'uppercase', letterSpacing: '0.03em', background: `${color}20`, color: color, textTransform: 'uppercase', letterSpacing: '0.03em',
}}> }}>
{spanLabel} {spanLabel}
</span> </span>
)} )}
<span style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <span style={{ fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{res.title} {res.title}
</span> </span>
{(() => { {(() => {
@@ -1982,7 +1982,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
const { time: endTime } = splitReservationDateTime(res.reservation_end_time) const { time: endTime } = splitReservationDateTime(res.reservation_end_time)
if (!dispTime && !endTime) return null if (!dispTime && !endTime) return null
return ( return (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, flexShrink: 0, fontSize: 10, color: 'var(--text-faint)', fontWeight: 400, marginLeft: 6 }}> <span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, flexShrink: 0, fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 400, marginLeft: 6 }}>
<Clock size={9} strokeWidth={2} /> <Clock size={9} strokeWidth={2} />
{dispTime ? formatTime(dispTime, locale, timeFormat) : ''} {dispTime ? formatTime(dispTime, locale, timeFormat) : ''}
{spanPhase === 'single' && endTime ? ` ${formatTime(endTime, locale, timeFormat)}` : ''} {spanPhase === 'single' && endTime ? ` ${formatTime(endTime, locale, timeFormat)}` : ''}
@@ -1993,7 +1993,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
})()} })()}
</div> </div>
{subtitle && ( {subtitle && (
<div style={{ fontSize: 10, color: 'var(--text-faint)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{subtitle} {subtitle}
</div> </div>
)} )}
@@ -2110,11 +2110,11 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
<NoteIcon size={13} strokeWidth={1.8} color="var(--text-muted)" /> <NoteIcon size={13} strokeWidth={1.8} color="var(--text-muted)" />
</div> </div>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<span style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--text-primary)', wordBreak: 'break-word' }}> <span style={{ fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', fontWeight: 500, color: 'var(--text-primary)', wordBreak: 'break-word' }}>
{note.text} {note.text}
</span> </span>
{note.time && ( {note.time && (
<div className="collab-note-md" style={{ fontSize: 10.5, fontWeight: 400, color: 'var(--text-faint)', lineHeight: '1.3', marginTop: 2, wordBreak: 'break-word' }}><Markdown remarkPlugins={[remarkGfm]}>{note.time}</Markdown></div> <div className="collab-note-md" style={{ fontSize: 'calc(10.5px * var(--fs-scale-text, 1))', fontWeight: 400, color: 'var(--text-faint)', lineHeight: '1.3', marginTop: 2, wordBreak: 'break-word' }}><Markdown remarkPlugins={[remarkGfm]}>{note.time}</Markdown></div>
)} )}
</div> </div>
{canEditDays && <div className="note-edit-buttons" style={{ display: 'flex', gap: 1, flexShrink: 0, opacity: 0, transition: 'opacity 0.15s' }}> {canEditDays && <div className="note-edit-buttons" style={{ display: 'flex', gap: 1, flexShrink: 0, opacity: 0, transition: 'opacity 0.15s' }}>
@@ -2185,7 +2185,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
className={routeShown ? 'bg-accent text-accent-text' : 'bg-transparent text-content-secondary'} className={routeShown ? 'bg-accent text-accent-text' : 'bg-transparent text-content-secondary'}
style={{ style={{
flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
padding: '6px 0', fontSize: 11, fontWeight: 600, borderRadius: 8, padding: '6px 0', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, borderRadius: 8,
border: routeShown ? 'none' : '1px solid var(--border-faint)', border: routeShown ? 'none' : '1px solid var(--border-faint)',
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}} }}
@@ -2217,7 +2217,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
</button> </button>
<button onClick={() => handleOptimize(day.id)} className="bg-surface-hover text-content-secondary" style={{ <button onClick={() => handleOptimize(day.id)} className="bg-surface-hover text-content-secondary" style={{
flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
padding: '6px 0', fontSize: 11, fontWeight: 500, borderRadius: 8, border: 'none', padding: '6px 0', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, borderRadius: 8, border: 'none',
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
<RotateCcw size={12} strokeWidth={2} /> <RotateCcw size={12} strokeWidth={2} />
@@ -2245,7 +2245,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
</div> </div>
</div> </div>
{isSelected && routeInfo && ( {isSelected && routeInfo && (
<div className="text-content-secondary bg-surface-hover" style={{ display: 'flex', justifyContent: 'center', gap: 12, fontSize: 12, borderRadius: 8, padding: '5px 10px' }}> <div className="text-content-secondary bg-surface-hover" style={{ display: 'flex', justifyContent: 'center', gap: 12, fontSize: 'calc(12px * var(--fs-scale-text, 1))', borderRadius: 8, padding: '5px 10px' }}>
<span>{routeInfo.distance}</span> <span>{routeInfo.distance}</span>
<span className="text-content-faint">·</span> <span className="text-content-faint">·</span>
<span>{routeInfo.duration}</span> <span>{routeInfo.duration}</span>
@@ -10,8 +10,8 @@ export function DayPlanSidebarFooter({ totalCost, currency, t }: DayPlanSidebarF
if (!(totalCost > 0)) return null if (!(totalCost > 0)) return null
return ( return (
<div className="border-t border-edge-faint" style={{ flexShrink: 0, padding: '10px 16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div className="border-t border-edge-faint" style={{ flexShrink: 0, padding: '10px 16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span className="text-content-faint" style={{ fontSize: 11 }}>{t('dayplan.totalCost')}</span> <span className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))' }}>{t('dayplan.totalCost')}</span>
<span className="text-content" style={{ fontSize: 13, fontWeight: 600 }}>{totalCost.toFixed(currencyDecimals(currency))} {currency}</span> <span className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{totalCost.toFixed(currencyDecimals(currency))} {currency}</span>
</div> </div>
) )
} }
@@ -31,7 +31,7 @@ export function MobileAddPlaceButton({ dayId, places, assignments, onAssign, onA
padding: '10px 0', borderRadius: 12, padding: '10px 0', borderRadius: 12,
border: '1.5px dashed var(--border-primary)', border: '1.5px dashed var(--border-primary)',
background: 'transparent', color: 'var(--text-muted)', background: 'transparent', color: 'var(--text-muted)',
fontSize: 12, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer',
}} }}
> >
<Plus size={14} /> <Plus size={14} />
@@ -45,7 +45,7 @@ export function MobileAddPlaceButton({ dayId, places, assignments, onAssign, onA
value={search} value={search}
onChange={e => setSearch(e.target.value)} onChange={e => setSearch(e.target.value)}
placeholder={t('dayplan.mobile.searchPlaces')} placeholder={t('dayplan.mobile.searchPlaces')}
style={{ flex: 1, border: 'none', outline: 'none', background: 'transparent', fontSize: 13, fontFamily: 'inherit', color: 'var(--text-primary)' }} style={{ flex: 1, border: 'none', outline: 'none', background: 'transparent', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'inherit', color: 'var(--text-primary)' }}
/> />
<button onClick={() => { setOpen(false); setSearch('') }} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: 'var(--text-faint)' }}> <button onClick={() => { setOpen(false); setSearch('') }} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: 'var(--text-faint)' }}>
<X size={14} /> <X size={14} />
@@ -53,7 +53,7 @@ export function MobileAddPlaceButton({ dayId, places, assignments, onAssign, onA
</div> </div>
<div style={{ maxHeight: 200, overflowY: 'auto' }}> <div style={{ maxHeight: 200, overflowY: 'auto' }}>
{filtered.length === 0 && ( {filtered.length === 0 && (
<div style={{ padding: '16px 12px', textAlign: 'center', fontSize: 12, color: 'var(--text-faint)' }}> <div style={{ padding: '16px 12px', textAlign: 'center', fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>
{available.length === 0 ? t('dayplan.mobile.allAssigned') : t('dayplan.mobile.noMatch')} {available.length === 0 ? t('dayplan.mobile.allAssigned') : t('dayplan.mobile.noMatch')}
</div> </div>
)} )}
@@ -72,7 +72,7 @@ export function MobileAddPlaceButton({ dayId, places, assignments, onAssign, onA
}} }}
> >
<MapPin size={13} style={{ color: 'var(--text-faint)', flexShrink: 0 }} /> <MapPin size={13} style={{ color: 'var(--text-faint)', flexShrink: 0 }} />
<span style={{ fontSize: 13, fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</span> <span style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</span>
</button> </button>
))} ))}
</div> </div>
@@ -83,7 +83,7 @@ export function MobileAddPlaceButton({ dayId, places, assignments, onAssign, onA
width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
padding: '10px 0', borderTop: '1px solid var(--border-faint)', padding: '10px 0', borderTop: '1px solid var(--border-faint)',
background: 'transparent', border: 'none', color: 'var(--text-muted)', background: 'transparent', border: 'none', color: 'var(--text-muted)',
fontSize: 12, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer',
}} }}
> >
<Plus size={13} /> <Plus size={13} />
@@ -31,7 +31,7 @@ export function DayPlanSidebarNoteModal({ noteUi, setNoteUi, noteInputRef, cance
boxShadow: '0 16px 48px rgba(0,0,0,0.22)', padding: '22px 22px 18px', boxShadow: '0 16px 48px rgba(0,0,0,0.22)', padding: '22px 22px 18px',
display: 'flex', flexDirection: 'column', gap: 12, display: 'flex', flexDirection: 'column', gap: 12,
}} onClick={e => e.stopPropagation()}> }} onClick={e => e.stopPropagation()}>
<div className="text-content" style={{ fontSize: 14, fontWeight: 600 }}> <div className="text-content" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600 }}>
{ui.mode === 'add' ? t('dayplan.noteAdd') : t('dayplan.noteEdit')} {ui.mode === 'add' ? t('dayplan.noteAdd') : t('dayplan.noteEdit')}
</div> </div>
{/* Icon-Auswahl */} {/* Icon-Auswahl */}
@@ -54,7 +54,7 @@ export function DayPlanSidebarNoteModal({ noteUi, setNoteUi, noteInputRef, cance
placeholder={t('dayplan.noteTitle') + ' *'} placeholder={t('dayplan.noteTitle') + ' *'}
required required
className="text-content" className="text-content"
style={{ fontSize: 13, fontWeight: 500, border: `1px solid ${!ui.text?.trim() ? 'var(--border-primary)' : 'var(--border-primary)'}`, borderRadius: 8, padding: '8px 10px', fontFamily: 'inherit', outline: 'none', width: '100%', boxSizing: 'border-box' }} style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, border: `1px solid ${!ui.text?.trim() ? 'var(--border-primary)' : 'var(--border-primary)'}`, borderRadius: 8, padding: '8px 10px', fontFamily: 'inherit', outline: 'none', width: '100%', boxSizing: 'border-box' }}
/> />
<textarea <textarea
value={ui.time} value={ui.time}
@@ -64,12 +64,12 @@ export function DayPlanSidebarNoteModal({ noteUi, setNoteUi, noteInputRef, cance
onKeyDown={e => { if (e.key === 'Escape') cancelNote(Number(dayId)) }} onKeyDown={e => { if (e.key === 'Escape') cancelNote(Number(dayId)) }}
placeholder={t('dayplan.noteSubtitle')} placeholder={t('dayplan.noteSubtitle')}
className="text-content" className="text-content"
style={{ fontSize: 12, border: '1px solid var(--border-primary)', borderRadius: 8, padding: '7px 10px', fontFamily: 'inherit', outline: 'none', width: '100%', boxSizing: 'border-box', resize: 'none', lineHeight: 1.4 }} style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', border: '1px solid var(--border-primary)', borderRadius: 8, padding: '7px 10px', fontFamily: 'inherit', outline: 'none', width: '100%', boxSizing: 'border-box', resize: 'none', lineHeight: 1.4 }}
/> />
<div className={(ui.time?.length || 0) >= 240 ? 'text-[#d97706]' : 'text-content-faint'} style={{ textAlign: 'right', fontSize: 11, marginTop: -2 }}>{ui.time?.length || 0}/250</div> <div className={(ui.time?.length || 0) >= 240 ? 'text-[#d97706]' : 'text-content-faint'} style={{ textAlign: 'right', fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: -2 }}>{ui.time?.length || 0}/250</div>
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}> <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
<button onClick={() => cancelNote(Number(dayId))} className="text-content-muted" style={{ fontSize: 12, background: 'none', border: '1px solid var(--border-primary)', borderRadius: 8, padding: '6px 14px', cursor: 'pointer', fontFamily: 'inherit' }}>{t('common.cancel')}</button> <button onClick={() => cancelNote(Number(dayId))} className="text-content-muted" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', background: 'none', border: '1px solid var(--border-primary)', borderRadius: 8, padding: '6px 14px', cursor: 'pointer', fontFamily: 'inherit' }}>{t('common.cancel')}</button>
<button onClick={() => saveNote(Number(dayId))} disabled={!ui.text?.trim()} className={!ui.text?.trim() ? 'bg-[var(--border-primary)] text-content-faint' : 'bg-accent text-accent-text'} style={{ fontSize: 12, border: 'none', borderRadius: 8, padding: '6px 16px', cursor: !ui.text?.trim() ? 'not-allowed' : 'pointer', fontWeight: 600, fontFamily: 'inherit', transition: 'background 0.15s, color 0.15s' }}> <button onClick={() => saveNote(Number(dayId))} disabled={!ui.text?.trim()} className={!ui.text?.trim() ? 'bg-[var(--border-primary)] text-content-faint' : 'bg-accent text-accent-text'} style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', border: 'none', borderRadius: 8, padding: '6px 16px', cursor: !ui.text?.trim() ? 'not-allowed' : 'pointer', fontWeight: 600, fontFamily: 'inherit', transition: 'background 0.15s, color 0.15s' }}>
{ui.mode === 'add' ? t('common.add') : t('common.save')} {ui.mode === 'add' ? t('common.add') : t('common.save')}
</button> </button>
</div> </div>
@@ -7,7 +7,7 @@ export function RouteConnector({ seg, profile }: { seg: RouteSegment; profile: '
const Icon = driving ? Car : Footprints const Icon = driving ? Car : Footprints
const line = { flex: 1, height: 1, minHeight: 1, alignSelf: 'center', background: 'var(--border-primary)' } const line = { flex: 1, height: 1, minHeight: 1, alignSelf: 'center', background: 'var(--border-primary)' }
return ( return (
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '3px 14px', fontSize: 10.5, color: 'var(--text-faint)', lineHeight: 1.2 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '3px 14px', fontSize: 'calc(10.5px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', lineHeight: 1.2 }}>
<div style={line} /> <div style={line} />
<div style={{ display: 'flex', alignItems: 'center', gap: 4, flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 4, flexShrink: 0 }}>
<Icon size={11} strokeWidth={2} /> <Icon size={11} strokeWidth={2} />
@@ -43,13 +43,13 @@ export function HotelRouteConnector({
const hotelRow = ( const hotelRow = (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, padding: '0 14px', minWidth: 0 }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, padding: '0 14px', minWidth: 0 }}>
<Hotel size={12} strokeWidth={1.8} style={{ flexShrink: 0, color: 'var(--text-muted)' }} /> <Hotel size={12} strokeWidth={1.8} style={{ flexShrink: 0, color: 'var(--text-muted)' }} />
<span style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.2 }}> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.2 }}>
{name} {name}
</span> </span>
</div> </div>
) )
const travelRow = ( const travelRow = (
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '3px 14px', fontSize: 10.5, color: 'var(--text-faint)', lineHeight: 1.2 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '3px 14px', fontSize: 'calc(10.5px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', lineHeight: 1.2 }}>
<div style={line} /> <div style={line} />
<div style={{ display: 'flex', alignItems: 'center', gap: 4, flexShrink: 0 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 4, flexShrink: 0 }}>
<Icon size={11} strokeWidth={2} /> <Icon size={11} strokeWidth={2} />
@@ -39,20 +39,20 @@ export function DayPlanSidebarTimeConfirmModal({ timeConfirm, setTimeConfirm, co
}}> }}>
<Clock size={18} strokeWidth={1.8} color="#ef4444" /> <Clock size={18} strokeWidth={1.8} color="#ef4444" />
</div> </div>
<div className="text-content" style={{ fontSize: 14, fontWeight: 600 }}> <div className="text-content" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600 }}>
{t('dayplan.confirmRemoveTimeTitle')} {t('dayplan.confirmRemoveTimeTitle')}
</div> </div>
</div> </div>
<div className="text-content-secondary" style={{ fontSize: 12.5, lineHeight: 1.5 }}> <div className="text-content-secondary" style={{ fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', lineHeight: 1.5 }}>
{t('dayplan.confirmRemoveTimeBody', { time: timeConfirm.time })} {t('dayplan.confirmRemoveTimeBody', { time: timeConfirm.time })}
</div> </div>
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 4 }}> <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 4 }}>
<button onClick={() => setTimeConfirm(null)} className="text-content-muted" style={{ <button onClick={() => setTimeConfirm(null)} className="text-content-muted" style={{
fontSize: 12, background: 'none', border: '1px solid var(--border-primary)', fontSize: 'calc(12px * var(--fs-scale-text, 1))', background: 'none', border: '1px solid var(--border-primary)',
borderRadius: 8, padding: '6px 14px', cursor: 'pointer', fontFamily: 'inherit', borderRadius: 8, padding: '6px 14px', cursor: 'pointer', fontFamily: 'inherit',
}}>{t('common.cancel')}</button> }}>{t('common.cancel')}</button>
<button onClick={confirmTimeRemoval} className="bg-[#ef4444] text-white" style={{ <button onClick={confirmTimeRemoval} className="bg-[#ef4444] text-white" style={{
fontSize: 12, fontSize: 'calc(12px * var(--fs-scale-text, 1))',
border: 'none', borderRadius: 8, padding: '6px 16px', cursor: 'pointer', fontWeight: 600, fontFamily: 'inherit', border: 'none', borderRadius: 8, padding: '6px 16px', cursor: 'pointer', fontWeight: 600, fontFamily: 'inherit',
}}>{t('common.confirm')}</button> }}>{t('common.confirm')}</button>
</div> </div>
@@ -63,7 +63,7 @@ export function DayPlanSidebarToolbar({
style={{ style={{
display: 'flex', alignItems: 'center', gap: 5, display: 'flex', alignItems: 'center', gap: 5,
padding: '5px 10px', borderRadius: 8, border: 'none', padding: '5px 10px', borderRadius: 8, border: 'none',
fontSize: 11, fontWeight: 500, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}} }}
> >
@@ -75,7 +75,7 @@ export function DayPlanSidebarToolbar({
position: 'absolute', top: 'calc(100% + 6px)', right: 0, position: 'absolute', top: 'calc(100% + 6px)', right: 0,
whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 200, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 200,
background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)', background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)',
fontSize: 11, fontWeight: 500, padding: '5px 10px', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, padding: '5px 10px',
borderRadius: 8, boxShadow: '0 4px 12px rgba(0,0,0,0.15)', borderRadius: 8, boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
border: '1px solid var(--border-faint, #e5e7eb)', border: '1px solid var(--border-faint, #e5e7eb)',
}}> }}>
@@ -106,7 +106,7 @@ export function DayPlanSidebarToolbar({
display: 'flex', alignItems: 'center', gap: 5, display: 'flex', alignItems: 'center', gap: 5,
padding: '5px 10px', borderRadius: 8, padding: '5px 10px', borderRadius: 8,
border: '1px solid var(--border-primary)', background: 'none', border: '1px solid var(--border-primary)', background: 'none',
color: 'var(--text-muted)', fontSize: 11, fontWeight: 500, color: 'var(--text-muted)', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}} }}
> >
@@ -118,7 +118,7 @@ export function DayPlanSidebarToolbar({
position: 'absolute', top: 'calc(100% + 6px)', right: 0, position: 'absolute', top: 'calc(100% + 6px)', right: 0,
whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 200, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 200,
background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)', background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)',
fontSize: 11, fontWeight: 500, padding: '5px 10px', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, padding: '5px 10px',
borderRadius: 8, boxShadow: '0 4px 12px rgba(0,0,0,0.15)', borderRadius: 8, boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
border: '1px solid var(--border-faint, #e5e7eb)', border: '1px solid var(--border-faint, #e5e7eb)',
}}> }}>
@@ -195,7 +195,7 @@ export function DayPlanSidebarToolbar({
position: 'absolute', top: 'calc(100% + 6px)', right: 0, position: 'absolute', top: 'calc(100% + 6px)', right: 0,
whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 200, whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 200,
background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)', background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)',
fontSize: 11, fontWeight: 500, padding: '5px 10px', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, padding: '5px 10px',
borderRadius: 8, boxShadow: '0 4px 12px rgba(0,0,0,0.15)', borderRadius: 8, boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
border: '1px solid var(--border-faint, #e5e7eb)', border: '1px solid var(--border-faint, #e5e7eb)',
}}> }}>
@@ -67,8 +67,8 @@ export function DayPlanSidebarTransportDetailModal({
<TransportIcon size={18} strokeWidth={1.8} color={color} /> <TransportIcon size={18} strokeWidth={1.8} color={color} />
</div> </div>
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<div className="text-content" style={{ fontSize: 15, fontWeight: 600 }}>{res.title}</div> <div className="text-content" style={{ fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{res.title}</div>
<div className="text-content-faint" style={{ fontSize: 11, marginTop: 2 }}> <div className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: 2 }}>
{(() => { {(() => {
const { date, time } = splitReservationDateTime(res.reservation_time) const { date, time } = splitReservationDateTime(res.reservation_time)
const { time: endTime } = splitReservationDateTime(res.reservation_end_time) const { time: endTime } = splitReservationDateTime(res.reservation_end_time)
@@ -85,7 +85,7 @@ export function DayPlanSidebarTransportDetailModal({
</div> </div>
</div> </div>
<div className={res.status === 'confirmed' ? 'bg-[rgba(22,163,74,0.1)] text-[#16a34a]' : 'bg-[rgba(217,119,6,0.1)] text-[#d97706]'} style={{ <div className={res.status === 'confirmed' ? 'bg-[rgba(22,163,74,0.1)] text-[#16a34a]' : 'bg-[rgba(217,119,6,0.1)] text-[#d97706]'} style={{
padding: '3px 8px', borderRadius: 6, fontSize: 10, fontWeight: 600, padding: '3px 8px', borderRadius: 6, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600,
}}> }}>
{(res.status === 'confirmed' ? t('planner.resConfirmed') : t('planner.resPending')).replace(/\s*·\s*$/, '')} {(res.status === 'confirmed' ? t('planner.resConfirmed') : t('planner.resPending')).replace(/\s*·\s*$/, '')}
</div> </div>
@@ -98,14 +98,14 @@ export function DayPlanSidebarTransportDetailModal({
const shouldBlur = f.sensitive && useSettingsStore.getState().settings.blur_booking_codes const shouldBlur = f.sensitive && useSettingsStore.getState().settings.blur_booking_codes
return ( return (
<div key={i} className="bg-surface-tertiary" style={{ padding: '8px 10px', borderRadius: 8 }}> <div key={i} className="bg-surface-tertiary" style={{ padding: '8px 10px', borderRadius: 8 }}>
<div className="text-content-faint" style={{ fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 3 }}>{f.label}</div> <div className="text-content-faint" style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 3 }}>{f.label}</div>
<div <div
onMouseEnter={e => { if (shouldBlur) e.currentTarget.style.filter = 'none' }} onMouseEnter={e => { if (shouldBlur) e.currentTarget.style.filter = 'none' }}
onMouseLeave={e => { if (shouldBlur) e.currentTarget.style.filter = 'blur(5px)' }} onMouseLeave={e => { if (shouldBlur) e.currentTarget.style.filter = 'blur(5px)' }}
onClick={e => { if (shouldBlur) { const el = e.currentTarget; el.style.filter = el.style.filter === 'none' ? 'blur(5px)' : 'none' } }} onClick={e => { if (shouldBlur) { const el = e.currentTarget; el.style.filter = el.style.filter === 'none' ? 'blur(5px)' : 'none' } }}
className="text-content" className="text-content"
style={{ style={{
fontSize: 12, fontWeight: 500, wordBreak: 'break-word', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500, wordBreak: 'break-word',
filter: shouldBlur ? 'blur(5px)' : 'none', transition: 'filter 0.2s', filter: shouldBlur ? 'blur(5px)' : 'none', transition: 'filter 0.2s',
cursor: shouldBlur ? 'pointer' : 'default', cursor: shouldBlur ? 'pointer' : 'default',
userSelect: shouldBlur ? 'none' : 'auto', userSelect: shouldBlur ? 'none' : 'auto',
@@ -120,8 +120,8 @@ export function DayPlanSidebarTransportDetailModal({
{/* Notizen */} {/* Notizen */}
{res.notes && ( {res.notes && (
<div className="bg-surface-tertiary" style={{ padding: '8px 10px', borderRadius: 8 }}> <div className="bg-surface-tertiary" style={{ padding: '8px 10px', borderRadius: 8 }}>
<div className="text-content-faint" style={{ fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 3 }}>{t('reservations.notes')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 3 }}>{t('reservations.notes')}</div>
<div className="collab-note-md text-content" style={{ fontSize: 12, wordBreak: 'break-word', overflowWrap: 'anywhere' }}><Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{res.notes}</Markdown></div> <div className="collab-note-md text-content" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', wordBreak: 'break-word', overflowWrap: 'anywhere' }}><Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{res.notes}</Markdown></div>
</div> </div>
)} )}
@@ -136,7 +136,7 @@ export function DayPlanSidebarTransportDetailModal({
if (resFiles.length === 0) return null if (resFiles.length === 0) return null
return ( return (
<div> <div>
<div className="text-content-faint" style={{ fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 6 }}>{t('files.title')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 6 }}>{t('files.title')}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{resFiles.map(f => ( {resFiles.map(f => (
<div key={f.id} <div key={f.id}
@@ -151,7 +151,7 @@ export function DayPlanSidebarTransportDetailModal({
onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-tertiary)'} onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-tertiary)'}
> >
<FileText size={14} className="text-content-muted" style={{ flexShrink: 0 }} /> <FileText size={14} className="text-content-muted" style={{ flexShrink: 0 }} />
<span className="text-content" style={{ flex: 1, fontSize: 12, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <span className="text-content" style={{ flex: 1, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{f.original_name} {f.original_name}
</span> </span>
<ExternalLink size={11} className="text-content-faint" style={{ flexShrink: 0 }} /> <ExternalLink size={11} className="text-content-faint" style={{ flexShrink: 0 }} />
@@ -165,7 +165,7 @@ export function DayPlanSidebarTransportDetailModal({
{/* Schließen */} {/* Schließen */}
<div style={{ textAlign: 'right' }}> <div style={{ textAlign: 'right' }}>
<button onClick={() => setTransportDetail(null)} className="bg-accent text-accent-text" style={{ <button onClick={() => setTransportDetail(null)} className="bg-accent text-accent-text" style={{
fontSize: 12, fontSize: 'calc(12px * var(--fs-scale-text, 1))',
border: 'none', borderRadius: 8, padding: '6px 16px', cursor: 'pointer', fontWeight: 600, fontFamily: 'inherit', border: 'none', borderRadius: 8, padding: '6px 16px', cursor: 'pointer', fontWeight: 600, fontFamily: 'inherit',
}}> }}>
{t('common.close')} {t('common.close')}
@@ -226,10 +226,10 @@ export default function FileImportModal({ isOpen, onClose, tripId, pushUndo, ini
className="bg-surface-card" className="bg-surface-card"
style={{ borderRadius: 16, width: '100%', maxWidth: 520, padding: 24, boxShadow: '0 8px 32px rgba(0,0,0,0.2)', fontFamily: "var(--font-system)" }} style={{ borderRadius: 16, width: '100%', maxWidth: 520, padding: 24, boxShadow: '0 8px 32px rgba(0,0,0,0.2)', fontFamily: "var(--font-system)" }}
> >
<div style={{ fontSize: 15, fontWeight: 700, color: 'var(--text-primary)', marginBottom: 6 }}> <div style={{ fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', marginBottom: 6 }}>
{t('places.importFile')} {t('places.importFile')}
</div> </div>
<div style={{ fontSize: 12, color: 'var(--text-faint)', marginBottom: 14, lineHeight: 1.45 }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginBottom: 14, lineHeight: 1.45 }}>
{t('places.importFileHint')} {t('places.importFileHint')}
</div> </div>
@@ -259,7 +259,7 @@ export default function FileImportModal({ isOpen, onClose, tripId, pushUndo, ini
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
gap: 6, gap: 6,
fontSize: 13, fontSize: 'calc(13px * var(--fs-scale-text, 1))',
fontWeight: 500, fontWeight: 500,
cursor: 'pointer', cursor: 'pointer',
marginBottom: 12, marginBottom: 12,
@@ -281,7 +281,7 @@ export default function FileImportModal({ isOpen, onClose, tripId, pushUndo, ini
{isGpx && ( {isGpx && (
<div style={{ marginBottom: 12 }}> <div style={{ marginBottom: 12 }}>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.05em' }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.05em' }}>
{t('places.gpxImportTypes')} {t('places.gpxImportTypes')}
</div> </div>
{(['waypoints', 'routes', 'tracks'] as const).map(key => ( {(['waypoints', 'routes', 'tracks'] as const).map(key => (
@@ -293,20 +293,20 @@ export default function FileImportModal({ isOpen, onClose, tripId, pushUndo, ini
}}> }}>
{gpxOpts[key] && <svg width="10" height="10" viewBox="0 0 10 10"><polyline points="1.5,5 4,7.5 8.5,2" stroke="white" strokeWidth="1.8" fill="none" strokeLinecap="round" strokeLinejoin="round" /></svg>} {gpxOpts[key] && <svg width="10" height="10" viewBox="0 0 10 10"><polyline points="1.5,5 4,7.5 8.5,2" stroke="white" strokeWidth="1.8" fill="none" strokeLinecap="round" strokeLinejoin="round" /></svg>}
</div> </div>
<span style={{ fontSize: 12, color: 'var(--text-primary)', userSelect: 'none' }}> <span style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-primary)', userSelect: 'none' }}>
{t(key === 'waypoints' ? 'places.gpxImportWaypoints' : key === 'routes' ? 'places.gpxImportRoutes' : 'places.gpxImportTracks')} {t(key === 'waypoints' ? 'places.gpxImportWaypoints' : key === 'routes' ? 'places.gpxImportRoutes' : 'places.gpxImportTracks')}
</span> </span>
</label> </label>
))} ))}
{gpxNoneSelected && ( {gpxNoneSelected && (
<div className="text-[#b45309]" style={{ fontSize: 11, marginTop: 4 }}>{t('places.gpxImportNoneSelected')}</div> <div className="text-[#b45309]" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: 4 }}>{t('places.gpxImportNoneSelected')}</div>
)} )}
</div> </div>
)} )}
{isKml && ( {isKml && (
<div style={{ marginBottom: 12 }}> <div style={{ marginBottom: 12 }}>
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.05em' }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.05em' }}>
{t('places.kmlImportTypes')} {t('places.kmlImportTypes')}
</div> </div>
{(['points', 'paths'] as const).map(key => ( {(['points', 'paths'] as const).map(key => (
@@ -318,13 +318,13 @@ export default function FileImportModal({ isOpen, onClose, tripId, pushUndo, ini
}}> }}>
{kmlOpts[key] && <svg width="10" height="10" viewBox="0 0 10 10"><polyline points="1.5,5 4,7.5 8.5,2" stroke="white" strokeWidth="1.8" fill="none" strokeLinecap="round" strokeLinejoin="round" /></svg>} {kmlOpts[key] && <svg width="10" height="10" viewBox="0 0 10 10"><polyline points="1.5,5 4,7.5 8.5,2" stroke="white" strokeWidth="1.8" fill="none" strokeLinecap="round" strokeLinejoin="round" /></svg>}
</div> </div>
<span style={{ fontSize: 12, color: 'var(--text-primary)', userSelect: 'none' }}> <span style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-primary)', userSelect: 'none' }}>
{t(key === 'points' ? 'places.kmlImportPoints' : 'places.kmlImportPaths')} {t(key === 'points' ? 'places.kmlImportPoints' : 'places.kmlImportPaths')}
</span> </span>
</label> </label>
))} ))}
{kmlNoneSelected && ( {kmlNoneSelected && (
<div className="text-[#b45309]" style={{ fontSize: 11, marginTop: 4 }}>{t('places.kmlImportNoneSelected')}</div> <div className="text-[#b45309]" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: 4 }}>{t('places.kmlImportNoneSelected')}</div>
)} )}
</div> </div>
)} )}
@@ -334,7 +334,7 @@ export default function FileImportModal({ isOpen, onClose, tripId, pushUndo, ini
border: '1px solid var(--border-primary)', borderRadius: 10, border: '1px solid var(--border-primary)', borderRadius: 10,
background: 'var(--bg-tertiary)', padding: 10, marginBottom: 10, background: 'var(--bg-tertiary)', padding: 10, marginBottom: 10,
}}> }}>
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-muted)' }}>
{t('places.kmlKmzSummaryValues', { {t('places.kmlKmzSummaryValues', {
total: summary.totalPlacemarks, total: summary.totalPlacemarks,
created: summary.createdCount, created: summary.createdCount,
@@ -342,7 +342,7 @@ export default function FileImportModal({ isOpen, onClose, tripId, pushUndo, ini
})} })}
</div> </div>
{summary.warnings?.length > 0 && ( {summary.warnings?.length > 0 && (
<div className="text-[#b45309]" style={{ marginTop: 8, fontSize: 12, whiteSpace: 'pre-wrap' }}> <div className="text-[#b45309]" style={{ marginTop: 8, fontSize: 'calc(12px * var(--fs-scale-text, 1))', whiteSpace: 'pre-wrap' }}>
{summary.warnings.join('\n')} {summary.warnings.join('\n')}
</div> </div>
)} )}
@@ -353,7 +353,7 @@ export default function FileImportModal({ isOpen, onClose, tripId, pushUndo, ini
<div className="bg-[rgba(239,68,68,0.08)] text-[#b91c1c]" style={{ <div className="bg-[rgba(239,68,68,0.08)] text-[#b91c1c]" style={{
border: '1px solid rgba(239,68,68,0.35)', borderRadius: 10, border: '1px solid rgba(239,68,68,0.35)', borderRadius: 10,
padding: '8px 10px', padding: '8px 10px',
fontSize: 12, whiteSpace: 'pre-wrap', marginBottom: 10, fontSize: 'calc(12px * var(--fs-scale-text, 1))', whiteSpace: 'pre-wrap', marginBottom: 10,
}}> }}>
{error} {error}
</div> </div>
@@ -364,7 +364,7 @@ export default function FileImportModal({ isOpen, onClose, tripId, pushUndo, ini
onClick={handleClose} onClick={handleClose}
style={{ style={{
padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)', padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)',
background: 'none', color: 'var(--text-primary)', fontSize: 13, fontWeight: 500, background: 'none', color: 'var(--text-primary)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}} }}
> >
@@ -376,7 +376,7 @@ export default function FileImportModal({ isOpen, onClose, tripId, pushUndo, ini
className={canImport ? 'bg-accent text-accent-text' : 'bg-surface-tertiary text-content-faint'} className={canImport ? 'bg-accent text-accent-text' : 'bg-surface-tertiary text-content-faint'}
style={{ style={{
padding: '8px 16px', borderRadius: 10, border: 'none', padding: '8px 16px', borderRadius: 10, border: 'none',
fontSize: 13, fontWeight: 500, cursor: canImport ? 'pointer' : 'default', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: canImport ? 'pointer' : 'default',
fontFamily: 'inherit', fontFamily: 'inherit',
}} }}
> >
@@ -98,7 +98,7 @@ export default function LocationSelect({ value, onChange, placeholder, style }:
onFocus={() => setOpen(true)} onFocus={() => setOpen(true)}
onKeyDown={onKey} onKeyDown={onKey}
className="bg-transparent text-content" className="bg-transparent text-content"
style={{ flex: 1, minWidth: 0, border: 'none', outline: 'none', fontSize: 13 }} style={{ flex: 1, minWidth: 0, border: 'none', outline: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}
/> />
{value && ( {value && (
<button type="button" onClick={clear} className="bg-transparent text-content-faint" style={{ border: 'none', padding: 2, cursor: 'pointer', display: 'flex' }} aria-label="Clear"> <button type="button" onClick={clear} className="bg-transparent text-content-faint" style={{ border: 'none', padding: 2, cursor: 'pointer', display: 'flex' }} aria-label="Clear">
@@ -110,7 +110,7 @@ export default function LocationSelect({ value, onChange, placeholder, style }:
{open && (loading || results.length > 0) && ( {open && (loading || results.length > 0) && (
<div className="bg-surface-card" style={{ position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, border: '1px solid var(--border-primary)', borderRadius: 10, boxShadow: '0 8px 24px rgba(0,0,0,0.18)', maxHeight: 260, overflowY: 'auto', zIndex: 1000 }}> <div className="bg-surface-card" style={{ position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, border: '1px solid var(--border-primary)', borderRadius: 10, boxShadow: '0 8px 24px rgba(0,0,0,0.18)', maxHeight: 260, overflowY: 'auto', zIndex: 1000 }}>
{loading && results.length === 0 && ( {loading && results.length === 0 && (
<div className="text-content-faint" style={{ padding: 10, fontSize: 12 }}>{t('common.loading')}</div> <div className="text-content-faint" style={{ padding: 10, fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>{t('common.loading')}</div>
)} )}
{results.map((r, i) => ( {results.map((r, i) => (
<button <button
@@ -127,9 +127,9 @@ export default function LocationSelect({ value, onChange, placeholder, style }:
> >
<MapPin size={12} className="text-content-faint" style={{ marginTop: 2, flexShrink: 0 }} /> <MapPin size={12} className="text-content-faint" style={{ marginTop: 2, flexShrink: 0 }} />
<span style={{ flex: 1, minWidth: 0 }}> <span style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.name || r.address}</div> <div style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.name || r.address}</div>
{r.address && r.name !== r.address && ( {r.address && r.name !== r.address && (
<div className="text-content-faint" style={{ fontSize: 11, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.address}</div> <div className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.address}</div>
)} )}
</span> </span>
</button> </button>
@@ -253,7 +253,7 @@ export default function PlaceInspector({
<div style={{ display: 'flex', gap: 12 }}> <div style={{ display: 'flex', gap: 12 }}>
<a href={`tel:${place.phone || googleDetails.phone}`} <a href={`tel:${place.phone || googleDetails.phone}`}
className="text-content" className="text-content"
style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 12, textDecoration: 'none' }}> style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 'calc(12px * var(--fs-scale-text, 1))', textDecoration: 'none' }}>
<Phone size={12} /> {place.phone || googleDetails.phone} <Phone size={12} /> {place.phone || googleDetails.phone}
</a> </a>
</div> </div>
@@ -261,14 +261,14 @@ export default function PlaceInspector({
{/* Description / Summary */} {/* Description / Summary */}
{(place.description || googleDetails?.summary) && ( {(place.description || googleDetails?.summary) && (
<div className="collab-note-md bg-surface-hover text-content-muted" style={{ borderRadius: 10, overflow: 'hidden', flexShrink: 0, fontSize: 12, lineHeight: '1.5', padding: '8px 12px', wordBreak: 'break-word', overflowWrap: 'anywhere' }}> <div className="collab-note-md bg-surface-hover text-content-muted" style={{ borderRadius: 10, overflow: 'hidden', flexShrink: 0, fontSize: 'calc(12px * var(--fs-scale-text, 1))', lineHeight: '1.5', padding: '8px 12px', wordBreak: 'break-word', overflowWrap: 'anywhere' }}>
<Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{place.description || googleDetails?.summary || ''}</Markdown> <Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{place.description || googleDetails?.summary || ''}</Markdown>
</div> </div>
)} )}
{/* Notes */} {/* Notes */}
{place.notes && ( {place.notes && (
<div className="collab-note-md bg-surface-hover text-content-muted" style={{ borderRadius: 10, overflow: 'hidden', flexShrink: 0, fontSize: 12, lineHeight: '1.5', padding: '8px 12px', wordBreak: 'break-word', overflowWrap: 'anywhere' }}> <div className="collab-note-md bg-surface-hover text-content-muted" style={{ borderRadius: 10, overflow: 'hidden', flexShrink: 0, fontSize: 'calc(12px * var(--fs-scale-text, 1))', lineHeight: '1.5', padding: '8px 12px', wordBreak: 'break-word', overflowWrap: 'anywhere' }}>
<Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{place.notes}</Markdown> <Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{place.notes}</Markdown>
</div> </div>
)} )}
@@ -323,7 +323,7 @@ interface ChipProps {
function Chip({ icon, text, color = 'var(--text-secondary)', bg = 'var(--bg-hover)' }: ChipProps) { function Chip({ icon, text, color = 'var(--text-secondary)', bg = 'var(--bg-hover)' }: ChipProps) {
return ( return (
<div style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '3px 9px', borderRadius: 99, background: bg, color, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '3px 9px', borderRadius: 99, background: bg, color, fontSize: 'calc(12px * var(--fs-scale-text, 1))', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 }}>
<span style={{ flexShrink: 0, display: 'flex' }}>{icon}</span> <span style={{ flexShrink: 0, display: 'flex' }}>{icon}</span>
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{text}</span> <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{text}</span>
</div> </div>
@@ -364,7 +364,7 @@ function ActionButton({ onClick, variant, icon, label }: ActionButtonProps) {
style={{ style={{
display: 'flex', alignItems: 'center', gap: 5, display: 'flex', alignItems: 'center', gap: 5,
padding: '6px 12px', borderRadius: 10, minHeight: 30, padding: '6px 12px', borderRadius: 10, minHeight: 30,
fontSize: 12, fontWeight: 500, cursor: 'pointer', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer',
fontFamily: 'inherit', transition: 'background 0.15s, opacity 0.15s', fontFamily: 'inherit', transition: 'background 0.15s, opacity 0.15s',
background: s.background, color: s.color, border: s.border, background: s.background, color: s.color, border: s.border,
}} }}
@@ -419,7 +419,7 @@ function ParticipantsBox({ tripMembers, participantIds, allJoined, onSetParticip
return ( return (
<div style={{ borderRadius: 12, border: '1px solid var(--border-faint)', padding: '8px 10px' }}> <div style={{ borderRadius: 12, border: '1px solid var(--border-faint)', padding: '8px 10px' }}>
<div className="text-content-faint" style={{ fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 }}> <div className="text-content-faint" style={{ fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em', marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 }}>
<Users size={10} /> {t('inspector.participants')} <Users size={10} /> {t('inspector.participants')}
</div> </div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, alignItems: 'center' }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, alignItems: 'center' }}>
@@ -435,13 +435,13 @@ function ParticipantsBox({ tripMembers, participantIds, allJoined, onSetParticip
style={{ style={{
display: 'flex', alignItems: 'center', gap: 4, padding: '2px 7px 2px 3px', borderRadius: 99, display: 'flex', alignItems: 'center', gap: 4, padding: '2px 7px 2px 3px', borderRadius: 99,
border: `1.5px solid ${isHovered && canRemove ? 'rgba(239,68,68,0.4)' : 'var(--accent)'}`, border: `1.5px solid ${isHovered && canRemove ? 'rgba(239,68,68,0.4)' : 'var(--accent)'}`,
fontSize: 10, fontWeight: 500, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: canRemove ? 'pointer' : 'default', cursor: canRemove ? 'pointer' : 'default',
transition: 'all 0.15s', transition: 'all 0.15s',
}}> }}>
<div className="bg-surface-tertiary text-content-muted" style={{ <div className="bg-surface-tertiary text-content-muted" style={{
width: 16, height: 16, borderRadius: '50%', width: 16, height: 16, borderRadius: '50%',
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 'calc(7px * var(--fs-scale-text, 1))', fontWeight: 700,
overflow: 'hidden', flexShrink: 0, overflow: 'hidden', flexShrink: 0,
}}> }}>
{(member.avatar_url || member.avatar) ? <img src={member.avatar_url || `/uploads/avatars/${member.avatar}`} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : member.username?.[0]?.toUpperCase()} {(member.avatar_url || member.avatar) ? <img src={member.avatar_url || `/uploads/avatars/${member.avatar}`} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : member.username?.[0]?.toUpperCase()}
@@ -457,7 +457,7 @@ function ParticipantsBox({ tripMembers, participantIds, allJoined, onSetParticip
<button onClick={() => setShowAdd(!showAdd)} className="text-content-faint" style={{ <button onClick={() => setShowAdd(!showAdd)} className="text-content-faint" style={{
width: 22, height: 22, borderRadius: '50%', border: '1.5px dashed var(--border-primary)', width: 22, height: 22, borderRadius: '50%', border: '1.5px dashed var(--border-primary)',
background: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 12, transition: 'all 0.12s', fontSize: 'calc(12px * var(--fs-scale-text, 1))', transition: 'all 0.12s',
}} }}
onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--text-muted)'; e.currentTarget.style.color = 'var(--text-primary)' }} onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--text-muted)'; e.currentTarget.style.color = 'var(--text-primary)' }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.color = 'var(--text-faint)' }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.color = 'var(--text-faint)' }}
@@ -473,7 +473,7 @@ function ParticipantsBox({ tripMembers, participantIds, allJoined, onSetParticip
<button key={member.id} onClick={() => handleAdd(member.id)} className="text-content" style={{ <button key={member.id} onClick={() => handleAdd(member.id)} className="text-content" style={{
display: 'flex', alignItems: 'center', gap: 6, width: '100%', padding: '5px 8px', display: 'flex', alignItems: 'center', gap: 6, width: '100%', padding: '5px 8px',
borderRadius: 6, border: 'none', background: 'none', cursor: 'pointer', borderRadius: 6, border: 'none', background: 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 11, textAlign: 'left', fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', textAlign: 'left',
transition: 'background 0.1s', transition: 'background 0.1s',
}} }}
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'} onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'}
@@ -481,7 +481,7 @@ function ParticipantsBox({ tripMembers, participantIds, allJoined, onSetParticip
> >
<div className="bg-surface-tertiary text-content-muted" style={{ <div className="bg-surface-tertiary text-content-muted" style={{
width: 18, height: 18, borderRadius: '50%', width: 18, height: 18, borderRadius: '50%',
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 8, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 'calc(8px * var(--fs-scale-text, 1))', fontWeight: 700,
overflow: 'hidden', flexShrink: 0, overflow: 'hidden', flexShrink: 0,
}}> }}>
{(member.avatar_url || member.avatar) ? <img src={member.avatar_url || `/uploads/avatars/${member.avatar}`} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : member.username?.[0]?.toUpperCase()} {(member.avatar_url || member.avatar) ? <img src={member.avatar_url || `/uploads/avatars/${member.avatar}`} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : member.username?.[0]?.toUpperCase()}
@@ -514,7 +514,7 @@ function PlaceInspectorHeader({ openNow, place, category, t, editingName, nameIn
{openNow !== null && ( {openNow !== null && (
<span style={{ <span style={{
position: 'absolute', bottom: -7, left: '50%', transform: 'translateX(-50%)', position: 'absolute', bottom: -7, left: '50%', transform: 'translateX(-50%)',
fontSize: 9, fontWeight: 500, letterSpacing: '0.02em', fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 500, letterSpacing: '0.02em',
color: 'white', color: 'white',
background: openNow ? '#16a34a' : '#dc2626', background: openNow ? '#16a34a' : '#dc2626',
padding: '1.5px 7px', borderRadius: 99, padding: '1.5px 7px', borderRadius: 99,
@@ -535,13 +535,13 @@ function PlaceInspectorHeader({ openNow, place, category, t, editingName, nameIn
onBlur={commitNameEdit} onBlur={commitNameEdit}
onKeyDown={handleNameKeyDown} onKeyDown={handleNameKeyDown}
className="text-content bg-surface-secondary" className="text-content bg-surface-secondary"
style={{ fontWeight: 600, fontSize: 15, lineHeight: '1.3', border: '1px solid var(--border-primary)', borderRadius: 6, padding: '1px 6px', fontFamily: 'inherit', outline: 'none', width: '100%' }} style={{ fontWeight: 600, fontSize: 'calc(15px * var(--fs-scale-text, 1))', lineHeight: '1.3', border: '1px solid var(--border-primary)', borderRadius: 6, padding: '1px 6px', fontFamily: 'inherit', outline: 'none', width: '100%' }}
/> />
) : ( ) : (
<span <span
onDoubleClick={startNameEdit} onDoubleClick={startNameEdit}
className="text-content" className="text-content"
style={{ fontWeight: 600, fontSize: 15, lineHeight: '1.3', cursor: onUpdatePlace ? 'text' : 'default' }} style={{ fontWeight: 600, fontSize: 'calc(15px * var(--fs-scale-text, 1))', lineHeight: '1.3', cursor: onUpdatePlace ? 'text' : 'default' }}
>{place.name}</span> >{place.name}</span>
)} )}
{category && (() => { {category && (() => {
@@ -549,7 +549,7 @@ function PlaceInspectorHeader({ openNow, place, category, t, editingName, nameIn
return ( return (
<span style={{ <span style={{
display: 'inline-flex', alignItems: 'center', gap: 4, display: 'inline-flex', alignItems: 'center', gap: 4,
fontSize: 11, fontWeight: 500, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500,
color: category.color || '#6b7280', color: category.color || '#6b7280',
background: category.color ? `${category.color}18` : 'rgba(0,0,0,0.06)', background: category.color ? `${category.color}18` : 'rgba(0,0,0,0.06)',
border: `1px solid ${category.color ? `${category.color}30` : 'transparent'}`, border: `1px solid ${category.color ? `${category.color}30` : 'transparent'}`,
@@ -564,17 +564,17 @@ function PlaceInspectorHeader({ openNow, place, category, t, editingName, nameIn
{place.address && ( {place.address && (
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 4, marginTop: 6 }}> <div style={{ display: 'flex', alignItems: 'flex-start', gap: 4, marginTop: 6 }}>
<MapPin size={11} color="var(--text-faint)" style={{ flexShrink: 0, marginTop: 2 }} /> <MapPin size={11} color="var(--text-faint)" style={{ flexShrink: 0, marginTop: 2 }} />
<span className="text-content-muted" style={{ fontSize: 12, lineHeight: '1.4', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{place.address}</span> <span className="text-content-muted" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', lineHeight: '1.4', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{place.address}</span>
</div> </div>
)} )}
{place.place_time && ( {place.place_time && (
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 3 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 3 }}>
<Clock size={10} color="var(--text-faint)" style={{ flexShrink: 0 }} /> <Clock size={10} color="var(--text-faint)" style={{ flexShrink: 0 }} />
<span className="text-content-muted" style={{ fontSize: 12 }}>{formatTime(place.place_time, locale, timeFormat)}{place.end_time ? ` ${formatTime(place.end_time, locale, timeFormat)}` : ''}</span> <span className="text-content-muted" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>{formatTime(place.place_time, locale, timeFormat)}{place.end_time ? ` ${formatTime(place.end_time, locale, timeFormat)}` : ''}</span>
</div> </div>
)} )}
{place.lat && place.lng && ( {place.lat && place.lng && (
<div className="hidden sm:block text-content-faint" style={{ fontSize: 11, marginTop: 4, fontVariantNumeric: 'tabular-nums' }}> <div className="hidden sm:block text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: 4, fontVariantNumeric: 'tabular-nums' }}>
{Number(place.lat).toFixed(6)}, {Number(place.lng).toFixed(6)} {Number(place.lat).toFixed(6)}, {Number(place.lng).toFixed(6)}
</div> </div>
)} )}
@@ -613,9 +613,9 @@ function PlaceReservationParticipants({ selectedAssignmentId, reservations, assi
<div style={{ borderRadius: 12, overflow: 'hidden', border: `1px solid ${confirmed ? 'rgba(22,163,74,0.2)' : 'rgba(217,119,6,0.2)'}` }}> <div style={{ borderRadius: 12, overflow: 'hidden', border: `1px solid ${confirmed ? 'rgba(22,163,74,0.2)' : 'rgba(217,119,6,0.2)'}` }}>
<div className={confirmed ? 'bg-[rgba(22,163,74,0.08)]' : 'bg-[rgba(217,119,6,0.08)]'} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px' }}> <div className={confirmed ? 'bg-[rgba(22,163,74,0.08)]' : 'bg-[rgba(217,119,6,0.08)]'} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px' }}>
<div className={confirmed ? 'bg-[#16a34a]' : 'bg-[#d97706]'} style={{ width: 6, height: 6, borderRadius: '50%', flexShrink: 0 }} /> <div className={confirmed ? 'bg-[#16a34a]' : 'bg-[#d97706]'} style={{ width: 6, height: 6, borderRadius: '50%', flexShrink: 0 }} />
<span className={confirmed ? 'text-[#16a34a]' : 'text-[#d97706]'} style={{ fontSize: 10, fontWeight: 700 }}>{confirmed ? t('reservations.confirmed') : t('reservations.pending')}</span> <span className={confirmed ? 'text-[#16a34a]' : 'text-[#d97706]'} style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700 }}>{confirmed ? t('reservations.confirmed') : t('reservations.pending')}</span>
<span style={{ flex: 1 }} /> <span style={{ flex: 1 }} />
<span className="text-content" style={{ fontSize: 11, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{res.title}</span> <span className="text-content" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{res.title}</span>
</div> </div>
<div style={{ padding: '6px 10px', display: 'flex', gap: 12, flexWrap: 'wrap' }}> <div style={{ padding: '6px 10px', display: 'flex', gap: 12, flexWrap: 'wrap' }}>
{(() => { {(() => {
@@ -625,14 +625,14 @@ function PlaceReservationParticipants({ selectedAssignmentId, reservations, assi
<> <>
{date && ( {date && (
<div> <div>
<div className="text-content-faint" style={{ fontSize: 8, fontWeight: 600, textTransform: 'uppercase' }}>{t('reservations.date')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(8px * var(--fs-scale-text, 1))', fontWeight: 600, textTransform: 'uppercase' }}>{t('reservations.date')}</div>
<div className="text-content" style={{ fontSize: 10, fontWeight: 500, marginTop: 1 }}>{new Date(date + 'T00:00:00Z').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })}</div> <div className="text-content" style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 500, marginTop: 1 }}>{new Date(date + 'T00:00:00Z').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })}</div>
</div> </div>
)} )}
{(startTime || endTime) && ( {(startTime || endTime) && (
<div> <div>
<div className="text-content-faint" style={{ fontSize: 8, fontWeight: 600, textTransform: 'uppercase' }}>{t('reservations.time')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(8px * var(--fs-scale-text, 1))', fontWeight: 600, textTransform: 'uppercase' }}>{t('reservations.time')}</div>
<div className="text-content" style={{ fontSize: 10, fontWeight: 500, marginTop: 1 }}> <div className="text-content" style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 500, marginTop: 1 }}>
{startTime ? formatTime(startTime, locale, timeFormat) : ''} {startTime ? formatTime(startTime, locale, timeFormat) : ''}
{endTime ? ` ${formatTime(endTime, locale, timeFormat)}` : ''} {endTime ? ` ${formatTime(endTime, locale, timeFormat)}` : ''}
</div> </div>
@@ -643,12 +643,12 @@ function PlaceReservationParticipants({ selectedAssignmentId, reservations, assi
})()} })()}
{res.confirmation_number && ( {res.confirmation_number && (
<div> <div>
<div className="text-content-faint" style={{ fontSize: 8, fontWeight: 600, textTransform: 'uppercase' }}>{t('reservations.confirmationCode')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(8px * var(--fs-scale-text, 1))', fontWeight: 600, textTransform: 'uppercase' }}>{t('reservations.confirmationCode')}</div>
<div className="text-content" style={{ fontSize: 10, fontWeight: 500, marginTop: 1 }}>{res.confirmation_number}</div> <div className="text-content" style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 500, marginTop: 1 }}>{res.confirmation_number}</div>
</div> </div>
)} )}
</div> </div>
{res.notes && <div className="collab-note-md text-content-faint" style={{ padding: '0 10px 6px', fontSize: 10, lineHeight: 1.4, wordBreak: 'break-word', overflowWrap: 'anywhere' }}><Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{res.notes}</Markdown></div>} {res.notes && <div className="collab-note-md text-content-faint" style={{ padding: '0 10px 6px', fontSize: 'calc(10px * var(--fs-scale-text, 1))', lineHeight: 1.4, wordBreak: 'break-word', overflowWrap: 'anywhere' }}><Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{res.notes}</Markdown></div>}
{(() => { {(() => {
const meta = typeof res.metadata === 'string' ? JSON.parse(res.metadata || '{}') : (res.metadata || {}) const meta = typeof res.metadata === 'string' ? JSON.parse(res.metadata || '{}') : (res.metadata || {})
if (!meta || Object.keys(meta).length === 0) return null if (!meta || Object.keys(meta).length === 0) return null
@@ -661,7 +661,7 @@ function PlaceReservationParticipants({ selectedAssignmentId, reservations, assi
if (meta.check_in_time) parts.push(`Check-in ${meta.check_in_time}`) if (meta.check_in_time) parts.push(`Check-in ${meta.check_in_time}`)
if (meta.check_out_time) parts.push(`Check-out ${meta.check_out_time}`) if (meta.check_out_time) parts.push(`Check-out ${meta.check_out_time}`)
if (parts.length === 0) return null if (parts.length === 0) return null
return <div className="text-content-muted" style={{ padding: '0 10px 6px', fontSize: 10, fontWeight: 500 }}>{parts.join(' · ')}</div> return <div className="text-content-muted" style={{ padding: '0 10px 6px', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 500 }}>{parts.join(' · ')}</div>
})()} })()}
</div> </div>
) )
@@ -702,7 +702,7 @@ function PlaceExtras({ openingHours, weekdayIndex, hoursExpanded, setHoursExpand
> >
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<Clock size={13} color="#9ca3af" /> <Clock size={13} color="#9ca3af" />
<span className="text-content-secondary" style={{ fontSize: 12, fontWeight: 500 }}> <span className="text-content-secondary" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500 }}>
{hoursExpanded ? t('inspector.openingHours') : (convertHoursLine(openingHours[weekdayIndex] || '', timeFormat) || t('inspector.showHours'))} {hoursExpanded ? t('inspector.openingHours') : (convertHoursLine(openingHours[weekdayIndex] || '', timeFormat) || t('inspector.showHours'))}
</span> </span>
</div> </div>
@@ -712,7 +712,7 @@ function PlaceExtras({ openingHours, weekdayIndex, hoursExpanded, setHoursExpand
<div style={{ padding: '0 12px 10px' }}> <div style={{ padding: '0 12px 10px' }}>
{openingHours.map((line, i) => ( {openingHours.map((line, i) => (
<div key={i} className={i === weekdayIndex ? 'text-content' : 'text-content-muted'} style={{ <div key={i} className={i === weekdayIndex ? 'text-content' : 'text-content-muted'} style={{
fontSize: 12, fontSize: 'calc(12px * var(--fs-scale-text, 1))',
fontWeight: i === weekdayIndex ? 600 : 400, fontWeight: i === weekdayIndex ? 600 : 400,
padding: '2px 0', padding: '2px 0',
}}>{convertHoursLine(line, timeFormat)}</div> }}>{convertHoursLine(line, timeFormat)}</div>
@@ -775,24 +775,24 @@ function PlaceExtras({ openingHours, weekdayIndex, hoursExpanded, setHoursExpand
<div className="bg-surface-hover" style={{ borderRadius: 10, padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 8 }}> <div className="bg-surface-hover" style={{ borderRadius: 10, padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<TrendingUp size={13} color="#9ca3af" /> <TrendingUp size={13} color="#9ca3af" />
<span className="text-content-secondary" style={{ fontSize: 12, fontWeight: 500 }}>{t('inspector.trackStats')}</span> <span className="text-content-secondary" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500 }}>{t('inspector.trackStats')}</span>
</div> </div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
<div className="text-content" style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 12, fontWeight: 600 }}> <div className="text-content" style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600 }}>
<MapPin size={12} color="#3b82f6" /> <MapPin size={12} color="#3b82f6" />
{formatDistance(distKm, distanceUnit)} {formatDistance(distKm, distanceUnit)}
</div> </div>
{hasEle && ( {hasEle && (
<> <>
<div className="text-content" style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 12, fontWeight: 600 }}> <div className="text-content" style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600 }}>
<Mountain size={12} color="#22c55e" /> <Mountain size={12} color="#22c55e" />
{formatElevation(maxEle, distanceUnit)} {formatElevation(maxEle, distanceUnit)}
</div> </div>
<div className="text-content" style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 12, fontWeight: 600 }}> <div className="text-content" style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600 }}>
<Mountain size={12} color="#ef4444" /> <Mountain size={12} color="#ef4444" />
{formatElevation(minEle, distanceUnit)} {formatElevation(minEle, distanceUnit)}
</div> </div>
<div className="text-content-muted" style={{ fontSize: 12 }}> <div className="text-content-muted" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>
{formatElevation(totalUp, distanceUnit)} &nbsp;{formatElevation(totalDown, distanceUnit)} {formatElevation(totalUp, distanceUnit)} &nbsp;{formatElevation(totalDown, distanceUnit)}
</div> </div>
</> </>
@@ -824,16 +824,16 @@ function PlaceExtras({ openingHours, weekdayIndex, hoursExpanded, setHoursExpand
style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 6, background: 'none', border: 'none', cursor: 'pointer', padding: 0, fontFamily: 'inherit', textAlign: 'left' }} style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 6, background: 'none', border: 'none', cursor: 'pointer', padding: 0, fontFamily: 'inherit', textAlign: 'left' }}
> >
<FileText size={13} color="#9ca3af" /> <FileText size={13} color="#9ca3af" />
<span className="text-content-secondary" style={{ fontSize: 12, fontWeight: 500 }}> <span className="text-content-secondary" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500 }}>
{placeFiles.length > 0 ? t('inspector.filesCount', { count: placeFiles.length }) : t('inspector.files')} {placeFiles.length > 0 ? t('inspector.filesCount', { count: placeFiles.length }) : t('inspector.files')}
</span> </span>
{filesExpanded ? <ChevronUp size={12} color="#9ca3af" /> : <ChevronDown size={12} color="#9ca3af" />} {filesExpanded ? <ChevronUp size={12} color="#9ca3af" /> : <ChevronDown size={12} color="#9ca3af" />}
</button> </button>
{onFileUpload && ( {onFileUpload && (
<label className="text-content-muted bg-surface-tertiary" style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 4, fontSize: 11, padding: '2px 6px', borderRadius: 6 }}> <label className="text-content-muted bg-surface-tertiary" style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 4, fontSize: 'calc(11px * var(--fs-scale-text, 1))', padding: '2px 6px', borderRadius: 6 }}>
<input ref={fileInputRef} type="file" multiple style={{ display: 'none' }} onChange={handleFileUpload} /> <input ref={fileInputRef} type="file" multiple style={{ display: 'none' }} onChange={handleFileUpload} />
{isUploading ? ( {isUploading ? (
<span style={{ fontSize: 11 }}></span> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))' }}></span>
) : ( ) : (
<><Upload size={11} strokeWidth={2} /> {t('common.upload')}</> <><Upload size={11} strokeWidth={2} /> {t('common.upload')}</>
)} )}
@@ -845,8 +845,8 @@ function PlaceExtras({ openingHours, weekdayIndex, hoursExpanded, setHoursExpand
{placeFiles.map(f => ( {placeFiles.map(f => (
<button key={f.id} onClick={() => openFile(f.url).catch(() => {})} style={{ display: 'flex', alignItems: 'center', gap: 8, textDecoration: 'none', cursor: 'pointer', background: 'none', border: 'none', width: '100%', textAlign: 'left' }}> <button key={f.id} onClick={() => openFile(f.url).catch(() => {})} style={{ display: 'flex', alignItems: 'center', gap: 8, textDecoration: 'none', cursor: 'pointer', background: 'none', border: 'none', width: '100%', textAlign: 'left' }}>
{(f.mime_type || '').startsWith('image/') ? <FileImage size={12} color="#6b7280" /> : <File size={12} color="#6b7280" />} {(f.mime_type || '').startsWith('image/') ? <FileImage size={12} color="#6b7280" /> : <File size={12} color="#6b7280" />}
<span className="text-content-secondary" style={{ fontSize: 12, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.original_name}</span> <span className="text-content-secondary" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.original_name}</span>
{f.file_size && <span className="text-content-faint" style={{ fontSize: 11, flexShrink: 0 }}>{formatFileSize(f.file_size)}</span>} {f.file_size && <span className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', flexShrink: 0 }}>{formatFileSize(f.file_size)}</span>}
</button> </button>
))} ))}
</div> </div>
@@ -34,7 +34,7 @@ const PlacesSidebar = React.memo(function PlacesSidebar(props: PlacesSidebarProp
<PlacesSelectionBar {...S} /> <PlacesSelectionBar {...S} />
) : ( ) : (
<div style={{ padding: '6px 16px', flexShrink: 0 }}> <div style={{ padding: '6px 16px', flexShrink: 0 }}>
<span className="text-content-faint" style={{ fontSize: 11 }}>{filtered.length === 1 ? t('places.countSingular') : t('places.count', { count: filtered.length })}</span> <span className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))' }}>{filtered.length === 1 ? t('places.countSingular') : t('places.count', { count: filtered.length })}</span>
</div> </div>
)} )}
@@ -14,7 +14,7 @@ export function PlacesDropOverlay({ t }: SidebarState) {
gap: 10, pointerEvents: 'none', gap: 10, pointerEvents: 'none',
}}> }}>
<Upload size={28} strokeWidth={1.5} color="var(--accent)" /> <Upload size={28} strokeWidth={1.5} color="var(--accent)" />
<span className="text-accent" style={{ fontSize: 13, fontWeight: 600 }}>{t('places.sidebarDrop')}</span> <span className="text-accent" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('places.sidebarDrop')}</span>
</div> </div>
) )
} }
@@ -34,7 +34,7 @@ export function PlacesHeader(S: SidebarState) {
style={{ style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
width: '100%', padding: '8px 12px', borderRadius: 12, border: 'none', width: '100%', padding: '8px 12px', borderRadius: 12, border: 'none',
fontSize: 13, fontWeight: 500, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', marginBottom: 10, cursor: 'pointer', fontFamily: 'inherit', marginBottom: 10,
}} }}
> >
@@ -48,7 +48,7 @@ export function PlacesHeader(S: SidebarState) {
style={{ style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
flex: 1, padding: '5px 12px', borderRadius: 8, flex: 1, padding: '5px 12px', borderRadius: 8,
background: 'none', fontSize: 11, fontWeight: 500, background: 'none', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}} }}
> >
@@ -60,7 +60,7 @@ export function PlacesHeader(S: SidebarState) {
style={{ style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
flex: 1, padding: '5px 12px', borderRadius: 8, flex: 1, padding: '5px 12px', borderRadius: 8,
background: 'none', fontSize: 11, fontWeight: 500, background: 'none', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}} }}
> >
@@ -105,14 +105,14 @@ export function PlacesHeader(S: SidebarState) {
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 5, display: 'inline-flex', alignItems: 'center', gap: 5,
padding: '4px 9px', borderRadius: 99, padding: '4px 9px', borderRadius: 99,
fontSize: 11, fontWeight: 500, whiteSpace: 'nowrap', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, whiteSpace: 'nowrap',
boxShadow: active ? 'none' : '0 1px 2px rgba(0,0,0,0.06)', boxShadow: active ? 'none' : '0 1px 2px rgba(0,0,0,0.06)',
transition: 'background 0.15s, color 0.15s, box-shadow 0.15s', transition: 'background 0.15s, color 0.15s, box-shadow 0.15s',
}} }}
> >
{f.label} {f.label}
<span className={active ? 'text-accent-text' : 'text-content-faint'} style={{ <span className={active ? 'text-accent-text' : 'text-content-faint'} style={{
fontSize: 9, fontWeight: 600, lineHeight: 1, fontSize: 'calc(9px * var(--fs-scale-text, 1))', fontWeight: 600, lineHeight: 1,
background: active ? 'color-mix(in srgb, var(--accent-text) 22%, transparent)' : 'var(--bg-tertiary)', background: active ? 'color-mix(in srgb, var(--accent-text) 22%, transparent)' : 'var(--bg-tertiary)',
padding: '1px 5px', borderRadius: 99, minWidth: 14, textAlign: 'center', padding: '1px 5px', borderRadius: 99, minWidth: 14, textAlign: 'center',
}}> }}>
@@ -136,7 +136,7 @@ export function PlacesHeader(S: SidebarState) {
className="bg-surface-tertiary text-content" className="bg-surface-tertiary text-content"
style={{ style={{
width: '100%', padding: '7px 30px 7px 30px', borderRadius: 10, width: '100%', padding: '7px 30px 7px 30px', borderRadius: 10,
border: 'none', fontSize: 12, border: 'none', fontSize: 'calc(12px * var(--fs-scale-text, 1))',
outline: 'none', fontFamily: 'inherit', boxSizing: 'border-box', outline: 'none', fontFamily: 'inherit', boxSizing: 'border-box',
}} }}
/> />
@@ -159,7 +159,7 @@ export function PlacesHeader(S: SidebarState) {
<button onClick={() => setCatDropOpen(v => !v)} className="bg-surface-card text-content" style={{ <button onClick={() => setCatDropOpen(v => !v)} className="bg-surface-card text-content" style={{
flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', justifyContent: 'space-between', flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '6px 10px', borderRadius: 8, border: '1px solid var(--border-primary)', padding: '6px 10px', borderRadius: 8, border: '1px solid var(--border-primary)',
fontSize: 12, fontSize: 'calc(12px * var(--fs-scale-text, 1))',
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{label}</span> <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{label}</span>
@@ -213,7 +213,7 @@ export function PlacesHeader(S: SidebarState) {
<button key={c.id} onClick={() => toggleCategoryFilter(String(c.id))} className={`text-content ${active ? 'bg-surface-hover' : 'bg-transparent'}`} style={{ <button key={c.id} onClick={() => toggleCategoryFilter(String(c.id))} className={`text-content ${active ? 'bg-surface-hover' : 'bg-transparent'}`} style={{
display: 'flex', alignItems: 'center', gap: 8, width: '100%', display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer', padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 12, fontFamily: 'inherit', fontSize: 'calc(12px * var(--fs-scale-text, 1))',
textAlign: 'left', textAlign: 'left',
}}> }}>
<div style={{ <div style={{
@@ -235,7 +235,7 @@ export function PlacesHeader(S: SidebarState) {
<button onClick={() => toggleCategoryFilter('uncategorized')} className={`text-content-muted ${active ? 'bg-surface-hover' : 'bg-transparent'}`} style={{ <button onClick={() => toggleCategoryFilter('uncategorized')} className={`text-content-muted ${active ? 'bg-surface-hover' : 'bg-transparent'}`} style={{
display: 'flex', alignItems: 'center', gap: 8, width: '100%', display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer', padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 12, fontFamily: 'inherit', fontSize: 'calc(12px * var(--fs-scale-text, 1))',
textAlign: 'left', borderTop: '1px solid var(--border-faint)', marginTop: 2, textAlign: 'left', borderTop: '1px solid var(--border-faint)', marginTop: 2,
}}> }}>
<div className={active ? 'bg-[var(--text-faint)]' : 'bg-transparent'} style={{ <div className={active ? 'bg-[var(--text-faint)]' : 'bg-transparent'} style={{
@@ -254,7 +254,7 @@ export function PlacesHeader(S: SidebarState) {
<button onClick={() => { setCategoryFiltersLocal(new Set()); onCategoryFilterChange?.(new Set()) }} className="bg-transparent text-content-faint" style={{ <button onClick={() => { setCategoryFiltersLocal(new Set()); onCategoryFilterChange?.(new Set()) }} className="bg-transparent text-content-faint" style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4,
width: '100%', padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer', width: '100%', padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 11, fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))',
marginTop: 2, borderTop: '1px solid var(--border-faint)', marginTop: 2, borderTop: '1px solid var(--border-faint)',
}}> }}>
<X size={10} /> {t('places.clearFilter')} <X size={10} /> {t('places.clearFilter')}
@@ -11,10 +11,10 @@ export function PlacesList(S: SidebarState) {
<div className="trek-stagger" style={{ flex: 1, overflowY: 'auto', minHeight: 0 }} ref={scrollContainerRef} onScroll={(e) => onScrollTopChange?.((e.currentTarget as HTMLElement).scrollTop)}> <div className="trek-stagger" style={{ flex: 1, overflowY: 'auto', minHeight: 0 }} ref={scrollContainerRef} onScroll={(e) => onScrollTopChange?.((e.currentTarget as HTMLElement).scrollTop)}>
{filtered.length === 0 ? ( {filtered.length === 0 ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '40px 16px', gap: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '40px 16px', gap: 8 }}>
<span className="text-content-faint" style={{ fontSize: 13 }}> <span className="text-content-faint" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>
{filter === 'unplanned' ? t('places.allPlanned') : t('places.noneFound')} {filter === 'unplanned' ? t('places.allPlanned') : t('places.noneFound')}
</span> </span>
{canEditPlaces && <button onClick={onAddPlace} className="text-content" style={{ fontSize: 12, background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'underline', fontFamily: 'inherit' }}> {canEditPlaces && <button onClick={onAddPlace} className="text-content" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'underline', fontFamily: 'inherit' }}>
{t('places.addPlace')} {t('places.addPlace')}
</button>} </button>}
</div> </div>
@@ -19,7 +19,7 @@ export function ListImportModal(S: SidebarState) {
className="bg-surface-card" className="bg-surface-card"
style={{ borderRadius: 16, width: '100%', maxWidth: 440, padding: 24, boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }} style={{ borderRadius: 16, width: '100%', maxWidth: 440, padding: 24, boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }}
> >
<div className="text-content" style={{ fontSize: 15, fontWeight: 700, marginBottom: 4 }}> <div className="text-content" style={{ fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 700, marginBottom: 4 }}>
{t('places.importList')} {t('places.importList')}
</div> </div>
{hasMultipleListImportProviders && ( {hasMultipleListImportProviders && (
@@ -31,7 +31,7 @@ export function ListImportModal(S: SidebarState) {
className={listImportProvider === provider ? 'bg-accent text-accent-text' : 'bg-surface-tertiary text-content-muted'} className={listImportProvider === provider ? 'bg-accent text-accent-text' : 'bg-surface-tertiary text-content-muted'}
style={{ style={{
padding: '6px 10px', borderRadius: 20, border: 'none', cursor: 'pointer', padding: '6px 10px', borderRadius: 20, border: 'none', cursor: 'pointer',
fontSize: 11, fontWeight: 600, fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, fontFamily: 'inherit',
}} }}
> >
{provider === 'google' ? t('places.importGoogleList') : t('places.importNaverList')} {provider === 'google' ? t('places.importGoogleList') : t('places.importNaverList')}
@@ -39,7 +39,7 @@ export function ListImportModal(S: SidebarState) {
))} ))}
</div> </div>
)} )}
<div className="text-content-faint" style={{ fontSize: 12, marginBottom: 16 }}> <div className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', marginBottom: 16 }}>
{t(listImportProvider === 'google' ? 'places.googleListHint' : 'places.naverListHint')} {t(listImportProvider === 'google' ? 'places.googleListHint' : 'places.naverListHint')}
</div> </div>
<input <input
@@ -53,15 +53,15 @@ export function ListImportModal(S: SidebarState) {
style={{ style={{
width: '100%', padding: '10px 14px', borderRadius: 10, width: '100%', padding: '10px 14px', borderRadius: 10,
border: '1px solid var(--border-primary)', border: '1px solid var(--border-primary)',
fontSize: 13, outline: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', outline: 'none',
fontFamily: 'inherit', boxSizing: 'border-box', fontFamily: 'inherit', boxSizing: 'border-box',
}} }}
/> />
{canEnrichImport && ( {canEnrichImport && (
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginTop: 12 }}> <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginTop: 12 }}>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div className="text-content" style={{ fontSize: 12, fontWeight: 600 }}>{t('places.enrichOnImport')}</div> <div className="text-content" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('places.enrichOnImport')}</div>
<div className="text-content-faint" style={{ fontSize: 12, marginTop: 2 }}>{t('places.enrichOnImportHint')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', marginTop: 2 }}>{t('places.enrichOnImportHint')}</div>
</div> </div>
<ToggleSwitch on={listImportEnrich} onToggle={() => setListImportEnrich(!listImportEnrich)} /> <ToggleSwitch on={listImportEnrich} onToggle={() => setListImportEnrich(!listImportEnrich)} />
</div> </div>
@@ -72,7 +72,7 @@ export function ListImportModal(S: SidebarState) {
className="text-content" className="text-content"
style={{ style={{
padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)', padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)',
background: 'none', fontSize: 13, fontWeight: 500, background: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}} }}
> >
@@ -84,7 +84,7 @@ export function ListImportModal(S: SidebarState) {
className={!listImportUrl.trim() || listImportLoading ? 'bg-surface-tertiary text-content-faint' : 'bg-accent text-accent-text'} className={!listImportUrl.trim() || listImportLoading ? 'bg-surface-tertiary text-content-faint' : 'bg-accent text-accent-text'}
style={{ style={{
padding: '8px 16px', borderRadius: 10, border: 'none', padding: '8px 16px', borderRadius: 10, border: 'none',
fontSize: 13, fontWeight: 500, cursor: !listImportUrl.trim() || listImportLoading ? 'default' : 'pointer', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: !listImportUrl.trim() || listImportLoading ? 'default' : 'pointer',
fontFamily: 'inherit', fontFamily: 'inherit',
}} }}
> >
@@ -18,15 +18,15 @@ export function MobileDayPickerSheet(S: SidebarState) {
style={{ borderRadius: '20px 20px 0 0', width: '100%', maxWidth: 500, maxHeight: '70vh', display: 'flex', flexDirection: 'column', overflow: 'hidden', paddingBottom: 'var(--bottom-nav-h)' }} style={{ borderRadius: '20px 20px 0 0', width: '100%', maxWidth: 500, maxHeight: '70vh', display: 'flex', flexDirection: 'column', overflow: 'hidden', paddingBottom: 'var(--bottom-nav-h)' }}
> >
<div style={{ padding: '16px 20px 12px', borderBottom: '1px solid var(--border-secondary)' }}> <div style={{ padding: '16px 20px 12px', borderBottom: '1px solid var(--border-secondary)' }}>
<div className="text-content" style={{ fontSize: 15, fontWeight: 700 }}>{dayPickerPlace.name}</div> <div className="text-content" style={{ fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 700 }}>{dayPickerPlace.name}</div>
{dayPickerPlace.address && <div className="text-content-faint" style={{ fontSize: 12, marginTop: 2 }}>{dayPickerPlace.address}</div>} {dayPickerPlace.address && <div className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', marginTop: 2 }}>{dayPickerPlace.address}</div>}
</div> </div>
<div style={{ overflowY: 'auto', padding: '8px 12px' }}> <div style={{ overflowY: 'auto', padding: '8px 12px' }}>
{/* View details */} {/* View details */}
<button <button
onClick={() => { onPlaceClick(dayPickerPlace.id); setDayPickerPlace(null); setMobileShowDays(false) }} onClick={() => { onPlaceClick(dayPickerPlace.id); setDayPickerPlace(null); setMobileShowDays(false) }}
className="bg-transparent text-content" className="bg-transparent text-content"
style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left', fontSize: 14 }} style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left', fontSize: 'calc(14px * var(--fs-scale-text, 1))' }}
> >
<Eye size={18} color="var(--text-muted)" /> {t('places.viewDetails')} <Eye size={18} color="var(--text-muted)" /> {t('places.viewDetails')}
</button> </button>
@@ -35,7 +35,7 @@ export function MobileDayPickerSheet(S: SidebarState) {
<button <button
onClick={() => { onEditPlace(dayPickerPlace); setDayPickerPlace(null); setMobileShowDays(false) }} onClick={() => { onEditPlace(dayPickerPlace); setDayPickerPlace(null); setMobileShowDays(false) }}
className="bg-transparent text-content" className="bg-transparent text-content"
style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left', fontSize: 14 }} style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left', fontSize: 'calc(14px * var(--fs-scale-text, 1))' }}
> >
<Pencil size={18} color="var(--text-muted)" /> {t('common.edit')} <Pencil size={18} color="var(--text-muted)" /> {t('common.edit')}
</button> </button>
@@ -46,7 +46,7 @@ export function MobileDayPickerSheet(S: SidebarState) {
<button <button
onClick={() => setMobileShowDays(v => !v)} onClick={() => setMobileShowDays(v => !v)}
className="bg-transparent text-content" className="bg-transparent text-content"
style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left', fontSize: 14 }} style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left', fontSize: 'calc(14px * var(--fs-scale-text, 1))' }}
> >
<CalendarDays size={18} color="var(--text-muted)" /> {t('places.assignToDay')} <CalendarDays size={18} color="var(--text-muted)" /> {t('places.assignToDay')}
<ChevronDown size={14} style={{ marginLeft: 'auto', color: 'var(--text-faint)', transform: mobileShowDays ? 'rotate(180deg)' : 'none', transition: 'transform 0.15s' }} /> <ChevronDown size={14} style={{ marginLeft: 'auto', color: 'var(--text-faint)', transform: mobileShowDays ? 'rotate(180deg)' : 'none', transition: 'transform 0.15s' }} />
@@ -59,10 +59,10 @@ export function MobileDayPickerSheet(S: SidebarState) {
onClick={() => { onAssignToDay(dayPickerPlace.id, day.id); setDayPickerPlace(null); setMobileShowDays(false) }} onClick={() => { onAssignToDay(dayPickerPlace.id, day.id); setDayPickerPlace(null); setMobileShowDays(false) }}
style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '10px 14px', borderRadius: 10, border: 'none', cursor: 'pointer', background: 'transparent', fontFamily: 'inherit', textAlign: 'left' }} style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '10px 14px', borderRadius: 10, border: 'none', cursor: 'pointer', background: 'transparent', fontFamily: 'inherit', textAlign: 'left' }}
> >
<div className="bg-surface-tertiary text-content" style={{ width: 28, height: 28, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 700, flexShrink: 0 }}>{i + 1}</div> <div className="bg-surface-tertiary text-content" style={{ width: 28, height: 28, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 700, flexShrink: 0 }}>{i + 1}</div>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div className="text-content" style={{ fontSize: 13, fontWeight: 500 }}>{day.title || t('dayplan.dayN', { n: i + 1 })}</div> <div className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500 }}>{day.title || t('dayplan.dayN', { n: i + 1 })}</div>
{day.date && <div className="text-content-faint" style={{ fontSize: 11 }}>{new Date(day.date + 'T00:00:00Z').toLocaleDateString(undefined, { timeZone: 'UTC' })}</div>} {day.date && <div className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))' }}>{new Date(day.date + 'T00:00:00Z').toLocaleDateString(undefined, { timeZone: 'UTC' })}</div>}
</div> </div>
{(assignments[String(day.id)] || []).some(a => a.place?.id === dayPickerPlace.id) && <Check size={14} color="var(--text-faint)" />} {(assignments[String(day.id)] || []).some(a => a.place?.id === dayPickerPlace.id) && <Check size={14} color="var(--text-faint)" />}
</button> </button>
@@ -75,7 +75,7 @@ export function MobileDayPickerSheet(S: SidebarState) {
{canEditPlaces && ( {canEditPlaces && (
<button <button
onClick={() => { onDeletePlace(dayPickerPlace.id); setDayPickerPlace(null); setMobileShowDays(false) }} onClick={() => { onDeletePlace(dayPickerPlace.id); setDayPickerPlace(null); setMobileShowDays(false) }}
style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', background: 'transparent', fontFamily: 'inherit', textAlign: 'left', fontSize: 14, color: '#ef4444' }} style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', padding: '12px 14px', borderRadius: 12, border: 'none', cursor: 'pointer', background: 'transparent', fontFamily: 'inherit', textAlign: 'left', fontSize: 'calc(14px * var(--fs-scale-text, 1))', color: '#ef4444' }}
> >
<Trash2 size={18} /> {t('common.delete')} <Trash2 size={18} /> {t('common.delete')}
</button> </button>
@@ -82,13 +82,13 @@ export const MemoPlaceRow = React.memo(function MemoPlaceRow({
const CatIcon = getCategoryIcon(cat.icon) const CatIcon = getCategoryIcon(cat.icon)
return <span title={cat.name} style={{ display: 'inline-flex', flexShrink: 0 }}><CatIcon size={11} strokeWidth={2} color={cat.color || '#6366f1'} /></span> return <span title={cat.name} style={{ display: 'inline-flex', flexShrink: 0 }}><CatIcon size={11} strokeWidth={2} color={cat.color || '#6366f1'} /></span>
})()} })()}
<span className="text-content" style={{ fontSize: 13, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.2 }}> <span className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.2 }}>
{place.name} {place.name}
</span> </span>
</div> </div>
{(place.description || place.address || cat?.name) && ( {(place.description || place.address || cat?.name) && (
<div style={{ marginTop: 2 }}> <div style={{ marginTop: 2 }}>
<span className="text-content-faint" style={{ fontSize: 11, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'block', lineHeight: 1.2 }}> <span className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'block', lineHeight: 1.2 }}>
{place.description || place.address || cat?.name} {place.description || place.address || cat?.name}
</span> </span>
</div> </div>
@@ -8,7 +8,7 @@ export function PlacesSelectionBar(S: SidebarState) {
<div style={{ <div style={{
margin: '6px 16px', padding: '5px 8px 5px 10px', borderRadius: 8, margin: '6px 16px', padding: '5px 8px 5px 10px', borderRadius: 8,
background: 'color-mix(in srgb, var(--accent) 10%, transparent)', background: 'color-mix(in srgb, var(--accent) 10%, transparent)',
display: 'flex', alignItems: 'center', gap: 4, flexShrink: 0, fontSize: 11, display: 'flex', alignItems: 'center', gap: 4, flexShrink: 0, fontSize: 'calc(11px * var(--fs-scale-text, 1))',
}}> }}>
<span className="text-accent" style={{ flex: 1, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}> <span className="text-accent" style={{ flex: 1, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{t('places.selectionCount', { count: selectedIds.size })} {t('places.selectionCount', { count: selectedIds.size })}
@@ -349,10 +349,10 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
size="2xl" size="2xl"
footer={ footer={
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}> <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
<button type="button" onClick={onClose} className="text-content-muted" style={{ padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)', background: 'none', fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' }}> <button type="button" onClick={onClose} className="text-content-muted" style={{ padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)', background: 'none', fontSize: 'calc(12px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit' }}>
{t('common.cancel')} {t('common.cancel')}
</button> </button>
<button type="button" onClick={handleSubmit} disabled={isSaving || !form.title.trim() || isEndBeforeStart} className="bg-[var(--text-primary)] text-[var(--bg-primary)]" style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', opacity: isSaving || !form.title.trim() || isEndBeforeStart ? 0.5 : 1 }}> <button type="button" onClick={handleSubmit} disabled={isSaving || !form.title.trim() || isEndBeforeStart} className="bg-[var(--text-primary)] text-[var(--bg-primary)]" style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', opacity: isSaving || !form.title.trim() || isEndBeforeStart ? 0.5 : 1 }}>
{isSaving ? t('common.saving') : reservation ? t('common.update') : t('common.add')} {isSaving ? t('common.saving') : reservation ? t('common.update') : t('common.add')}
</button> </button>
</div> </div>
@@ -368,7 +368,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
<button key={value} type="button" onClick={() => set('type', value)} className={form.type === value ? 'bg-[var(--text-primary)] text-[var(--bg-primary)]' : 'bg-surface-card text-content-muted'} style={{ <button key={value} type="button" onClick={() => set('type', value)} className={form.type === value ? 'bg-[var(--text-primary)] text-[var(--bg-primary)]' : 'bg-surface-card text-content-muted'} style={{
display: 'flex', alignItems: 'center', gap: 4, display: 'flex', alignItems: 'center', gap: 4,
padding: '5px 10px', borderRadius: 99, border: '1px solid', padding: '5px 10px', borderRadius: 99, border: '1px solid',
fontSize: 11, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.12s', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.12s',
borderColor: form.type === value ? 'var(--text-primary)' : 'var(--border-primary)', borderColor: form.type === value ? 'var(--text-primary)' : 'var(--border-primary)',
}}> }}>
<Icon size={11} /> {t(labelKey)} <Icon size={11} /> {t(labelKey)}
@@ -457,7 +457,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
</div> </div>
</div> </div>
{isEndBeforeStart && ( {isEndBeforeStart && (
<div className="text-[#ef4444]" style={{ fontSize: 11, marginTop: -6 }}>{t('reservations.validation.endBeforeStart')}</div> <div className="text-[#ef4444]" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: -6 }}>{t('reservations.validation.endBeforeStart')}</div>
)} )}
</> </>
)} )}
@@ -606,7 +606,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
{attachedFiles.map(f => ( {attachedFiles.map(f => (
<div key={f.id} className="bg-surface-secondary" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 8 }}> <div key={f.id} className="bg-surface-secondary" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 8 }}>
<FileText size={12} className="text-content-muted" style={{ flexShrink: 0 }} /> <FileText size={12} className="text-content-muted" style={{ flexShrink: 0 }} />
<span className="text-content-secondary" style={{ flex: 1, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.original_name}</span> <span className="text-content-secondary" style={{ flex: 1, fontSize: 'calc(12px * var(--fs-scale-text, 1))', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.original_name}</span>
<a href="#" onClick={(e) => { e.preventDefault(); openFile(f.url).catch(() => {}) }} className="text-content-faint" style={{ display: 'flex', flexShrink: 0, cursor: 'pointer' }}><ExternalLink size={11} /></a> <a href="#" onClick={(e) => { e.preventDefault(); openFile(f.url).catch(() => {}) }} className="text-content-faint" style={{ display: 'flex', flexShrink: 0, cursor: 'pointer' }}><ExternalLink size={11} /></a>
<button type="button" onClick={async () => { <button type="button" onClick={async () => {
if (f.reservation_id === reservation?.id) { if (f.reservation_id === reservation?.id) {
@@ -627,7 +627,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
{pendingFiles.map((f, i) => ( {pendingFiles.map((f, i) => (
<div key={i} className="bg-surface-secondary" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 8 }}> <div key={i} className="bg-surface-secondary" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 8 }}>
<FileText size={12} className="text-content-muted" style={{ flexShrink: 0 }} /> <FileText size={12} className="text-content-muted" style={{ flexShrink: 0 }} />
<span className="text-content-secondary" style={{ flex: 1, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.name}</span> <span className="text-content-secondary" style={{ flex: 1, fontSize: 'calc(12px * var(--fs-scale-text, 1))', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.name}</span>
<button type="button" onClick={() => setPendingFiles(prev => prev.filter((_, j) => j !== i))} <button type="button" onClick={() => setPendingFiles(prev => prev.filter((_, j) => j !== i))}
className="text-content-faint" style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', padding: 0, flexShrink: 0 }}> className="text-content-faint" style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', padding: 0, flexShrink: 0 }}>
<X size={11} /> <X size={11} />
@@ -639,7 +639,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
{onFileUpload && <button type="button" onClick={() => fileInputRef.current?.click()} disabled={uploadingFile} className="text-content-faint" style={{ {onFileUpload && <button type="button" onClick={() => fileInputRef.current?.click()} disabled={uploadingFile} className="text-content-faint" style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '6px 10px', display: 'flex', alignItems: 'center', gap: 5, padding: '6px 10px',
border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none', border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none',
fontSize: 11, cursor: uploadingFile ? 'default' : 'pointer', fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', cursor: uploadingFile ? 'default' : 'pointer', fontFamily: 'inherit',
}}> }}>
<Paperclip size={11} /> <Paperclip size={11} />
{uploadingFile ? t('reservations.uploading') : t('reservations.attachFile')} {uploadingFile ? t('reservations.uploading') : t('reservations.attachFile')}
@@ -649,7 +649,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
<button type="button" onClick={() => setShowFilePicker(v => !v)} className="text-content-faint" style={{ <button type="button" onClick={() => setShowFilePicker(v => !v)} className="text-content-faint" style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '6px 10px', display: 'flex', alignItems: 'center', gap: 5, padding: '6px 10px',
border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none', border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none',
fontSize: 11, cursor: 'pointer', fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
<Link2 size={11} /> {t('reservations.linkExisting')} <Link2 size={11} /> {t('reservations.linkExisting')}
</button> </button>
@@ -671,7 +671,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
className="text-content-secondary" className="text-content-secondary"
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '6px 10px', display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '6px 10px',
background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, fontFamily: 'inherit', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontFamily: 'inherit',
borderRadius: 7, textAlign: 'left', borderRadius: 7, textAlign: 'left',
}} }}
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-tertiary)'} onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-tertiary)'}
@@ -129,7 +129,7 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
<span>{name}</span> <span>{name}</span>
{badge && ( {badge && (
<span className="text-content-faint bg-surface-secondary" style={{ <span className="text-content-faint bg-surface-secondary" style={{
fontSize: 10, fontWeight: 600, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600,
padding: '1px 6px', borderRadius: 999, padding: '1px 6px', borderRadius: 999,
}}>{badge}</span> }}>{badge}</span>
)} )}
@@ -156,14 +156,14 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 10, minWidth: 0, flexWrap: 'wrap' }}> <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10, minWidth: 0, flexWrap: 'wrap' }}>
<span className={confirmed ? 'text-[#16a34a]' : 'text-[#d97706]'} style={{ <span className={confirmed ? 'text-[#16a34a]' : 'text-[#d97706]'} style={{
display: 'inline-flex', alignItems: 'center', gap: 6, display: 'inline-flex', alignItems: 'center', gap: 6,
fontSize: 12, fontWeight: 600, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600,
}}> }}>
<span className={confirmed ? 'bg-[#16a34a]' : 'bg-[#d97706]'} style={{ width: 7, height: 7, borderRadius: '50%', flexShrink: 0 }} /> <span className={confirmed ? 'bg-[#16a34a]' : 'bg-[#d97706]'} style={{ width: 7, height: 7, borderRadius: '50%', flexShrink: 0 }} />
{confirmed ? t('reservations.confirmed') : t('reservations.pending')} {confirmed ? t('reservations.confirmed') : t('reservations.pending')}
</span> </span>
<span className="text-content-muted bg-surface-secondary" style={{ <span className="text-content-muted bg-surface-secondary" style={{
display: 'inline-flex', alignItems: 'center', gap: 5, display: 'inline-flex', alignItems: 'center', gap: 5,
fontSize: 12, fontSize: 'calc(12px * var(--fs-scale-text, 1))',
padding: '3px 8px', borderRadius: 6, padding: '3px 8px', borderRadius: 6,
}}> }}>
<TypeIcon size={12} style={{ color: typeInfo.color }} /> <TypeIcon size={12} style={{ color: typeInfo.color }} />
@@ -172,7 +172,7 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
{r.needs_review ? ( {r.needs_review ? (
<span className="text-[#b45309] bg-[rgba(245,158,11,0.12)]" style={{ <span className="text-[#b45309] bg-[rgba(245,158,11,0.12)]" style={{
display: 'inline-flex', alignItems: 'center', gap: 4, display: 'inline-flex', alignItems: 'center', gap: 4,
fontSize: 11, fontWeight: 600, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600,
padding: '3px 8px', borderRadius: 6, padding: '3px 8px', borderRadius: 6,
}} title={t('reservations.needsReviewHint')}> }} title={t('reservations.needsReviewHint')}>
<AlertCircle size={11} /> <AlertCircle size={11} />
@@ -182,7 +182,7 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
{r.external_source === 'airtrail' ? ( {r.external_source === 'airtrail' ? (
<span <span
className={r.sync_enabled ? 'text-[#2563eb] bg-[rgba(59,130,246,0.12)]' : 'text-content-faint bg-surface-tertiary'} className={r.sync_enabled ? 'text-[#2563eb] bg-[rgba(59,130,246,0.12)]' : 'text-content-faint bg-surface-tertiary'}
style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 11, fontWeight: 600, padding: '3px 8px', borderRadius: 6 }} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, padding: '3px 8px', borderRadius: 6 }}
title={r.sync_enabled ? t('reservations.airtrail.syncedHint') : t('reservations.airtrail.notSyncedHint')} title={r.sync_enabled ? t('reservations.airtrail.syncedHint') : t('reservations.airtrail.notSyncedHint')}
> >
<Plane size={11} /> <Plane size={11} />
@@ -192,7 +192,7 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: 2 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<span className="text-content" style={{ <span className="text-content" style={{
fontSize: 13, fontWeight: 600, marginRight: 6, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, marginRight: 6,
maxWidth: 140, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 140, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
}}>{r.title}</span> }}>{r.title}</span>
{canEdit && ( {canEdit && (
@@ -269,7 +269,7 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
onClick={() => blurCodes && setCodeRevealed(v => !v)} onClick={() => blurCodes && setCodeRevealed(v => !v)}
className={`${fieldValueClass} text-center`} className={`${fieldValueClass} text-center`}
style={{ style={{
fontFamily: '"SF Mono", "JetBrains Mono", Menlo, monospace', fontSize: 12.5, fontFamily: '"SF Mono", "JetBrains Mono", Menlo, monospace', fontSize: 'calc(12.5px * var(--fs-scale-text, 1))',
filter: blurCodes && !codeRevealed ? 'blur(5px)' : 'none', filter: blurCodes && !codeRevealed ? 'blur(5px)' : 'none',
cursor: blurCodes ? 'pointer' : 'default', cursor: blurCodes ? 'pointer' : 'default',
transition: 'filter 0.2s', transition: 'filter 0.2s',
@@ -288,7 +288,7 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
<div className="bg-surface-tertiary text-content" style={{ <div className="bg-surface-tertiary text-content" style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
padding: '8px 12px', borderRadius: 10, padding: '8px 12px', borderRadius: 10,
fontSize: 12.5, flexWrap: 'wrap', fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', flexWrap: 'wrap',
}}> }}>
{eps.map((ep, i) => ( {eps.map((ep, i) => (
<span key={i} style={{ display: 'inline-flex', alignItems: 'center', gap: 8, minWidth: 0 }}> <span key={i} style={{ display: 'inline-flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
@@ -379,7 +379,7 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
{attachedFiles.map(f => ( {attachedFiles.map(f => (
<a key={f.id} href="#" onClick={(e) => { e.preventDefault(); openFile(f.url).catch(() => {}) }} style={{ display: 'flex', alignItems: 'center', gap: 5, textDecoration: 'none', cursor: 'pointer' }}> <a key={f.id} href="#" onClick={(e) => { e.preventDefault(); openFile(f.url).catch(() => {}) }} style={{ display: 'flex', alignItems: 'center', gap: 5, textDecoration: 'none', cursor: 'pointer' }}>
<FileText size={11} className="text-content-faint" style={{ flexShrink: 0 }} /> <FileText size={11} className="text-content-faint" style={{ flexShrink: 0 }} />
<span style={{ fontSize: 12, color: 'var(--text-muted)', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.original_name}</span> <span style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-muted)', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.original_name}</span>
</a> </a>
))} ))}
</div> </div>
@@ -406,20 +406,20 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
}}> }}>
<Trash2 size={18} strokeWidth={1.8} color="#ef4444" /> <Trash2 size={18} strokeWidth={1.8} color="#ef4444" />
</div> </div>
<div className="text-content" style={{ fontSize: 14, fontWeight: 600 }}> <div className="text-content" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600 }}>
{t('reservations.confirm.deleteTitle')} {t('reservations.confirm.deleteTitle')}
</div> </div>
</div> </div>
<div className="text-content-secondary" style={{ fontSize: 12.5, lineHeight: 1.5 }}> <div className="text-content-secondary" style={{ fontSize: 'calc(12.5px * var(--fs-scale-text, 1))', lineHeight: 1.5 }}>
{t('reservations.confirm.deleteBody', { name: r.title })} {t('reservations.confirm.deleteBody', { name: r.title })}
</div> </div>
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 4 }}> <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 4 }}>
<button onClick={() => setShowDeleteConfirm(false)} className="text-content-muted" style={{ <button onClick={() => setShowDeleteConfirm(false)} className="text-content-muted" style={{
fontSize: 12, background: 'none', border: '1px solid var(--border-primary)', fontSize: 'calc(12px * var(--fs-scale-text, 1))', background: 'none', border: '1px solid var(--border-primary)',
borderRadius: 8, padding: '6px 14px', cursor: 'pointer', fontFamily: 'inherit', borderRadius: 8, padding: '6px 14px', cursor: 'pointer', fontFamily: 'inherit',
}}>{t('common.cancel')}</button> }}>{t('common.cancel')}</button>
<button onClick={handleDelete} className="bg-[#ef4444] text-white" style={{ <button onClick={handleDelete} className="bg-[#ef4444] text-white" style={{
fontSize: 12, fontSize: 'calc(12px * var(--fs-scale-text, 1))',
border: 'none', borderRadius: 8, padding: '6px 16px', cursor: 'pointer', fontWeight: 600, fontFamily: 'inherit', border: 'none', borderRadius: 8, padding: '6px 16px', cursor: 'pointer', fontWeight: 600, fontFamily: 'inherit',
}}>{t('common.confirm')}</button> }}>{t('common.confirm')}</button>
</div> </div>
@@ -459,9 +459,9 @@ function Section({ title, count, children, defaultOpen = true, accent, storageKe
userSelect: 'none', userSelect: 'none',
}}> }}>
{open ? <ChevronDown size={14} style={{ color: 'var(--text-faint)' }} /> : <ChevronRight size={14} style={{ color: 'var(--text-faint)' }} />} {open ? <ChevronDown size={14} style={{ color: 'var(--text-faint)' }} /> : <ChevronRight size={14} style={{ color: 'var(--text-faint)' }} />}
<span className="text-content-muted" style={{ fontWeight: 600, fontSize: 12, textTransform: 'uppercase', letterSpacing: '0.08em' }}>{title}</span> <span className="text-content-muted" style={{ fontWeight: 600, fontSize: 'calc(12px * var(--fs-scale-text, 1))', textTransform: 'uppercase', letterSpacing: '0.08em' }}>{title}</span>
<span className="bg-surface-tertiary text-content-faint" style={{ <span className="bg-surface-tertiary text-content-faint" style={{
fontSize: 11, fontWeight: 600, padding: '2px 7px', borderRadius: 99, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, padding: '2px 7px', borderRadius: 99,
minWidth: 20, textAlign: 'center', minWidth: 20, textAlign: 'center',
}}>{count}</span> }}>{count}</span>
</button> </button>
@@ -542,7 +542,7 @@ export default function ReservationsPanel({ tripId, reservations, days, assignme
padding: '14px 16px 14px 22px', padding: '14px 16px 14px 22px',
display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap', display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap',
}}> }}>
<h2 className="text-content" style={{ margin: 0, fontSize: 18, fontWeight: 600, letterSpacing: '-0.01em', flexShrink: 0 }}> <h2 className="text-content" style={{ margin: 0, fontSize: 'calc(18px * var(--fs-scale-text, 1))', fontWeight: 600, letterSpacing: '-0.01em', flexShrink: 0 }}>
{t(titleKey)} {t(titleKey)}
</h2> </h2>
@@ -556,7 +556,7 @@ export default function ReservationsPanel({ tripId, reservations, days, assignme
style={{ style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6, display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '6px 12px', borderRadius: 99, fontSize: 13, whiteSpace: 'nowrap', padding: '6px 12px', borderRadius: 99, fontSize: 'calc(13px * var(--fs-scale-text, 1))', whiteSpace: 'nowrap',
fontWeight: typeFilters.size === 0 ? 500 : 400, fontWeight: typeFilters.size === 0 ? 500 : 400,
boxShadow: typeFilters.size === 0 ? '0 1px 2px rgba(0,0,0,0.06)' : 'none', boxShadow: typeFilters.size === 0 ? '0 1px 2px rgba(0,0,0,0.06)' : 'none',
transition: 'all 0.15s ease', transition: 'all 0.15s ease',
@@ -564,7 +564,7 @@ export default function ReservationsPanel({ tripId, reservations, days, assignme
> >
{t('common.all')} {t('common.all')}
<span className={`text-content-faint ${typeFilters.size === 0 ? 'bg-surface-tertiary' : 'bg-[rgba(0,0,0,0.06)]'}`} style={{ <span className={`text-content-faint ${typeFilters.size === 0 ? 'bg-surface-tertiary' : 'bg-[rgba(0,0,0,0.06)]'}`} style={{
fontSize: 10, fontWeight: 600, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600,
padding: '1px 6px', borderRadius: 99, minWidth: 16, textAlign: 'center', padding: '1px 6px', borderRadius: 99, minWidth: 16, textAlign: 'center',
}}>{reservations.length}</span> }}>{reservations.length}</span>
</button> </button>
@@ -579,7 +579,7 @@ export default function ReservationsPanel({ tripId, reservations, days, assignme
style={{ style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6, display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '6px 12px', borderRadius: 99, fontSize: 13, whiteSpace: 'nowrap', padding: '6px 12px', borderRadius: 99, fontSize: 'calc(13px * var(--fs-scale-text, 1))', whiteSpace: 'nowrap',
fontWeight: active ? 500 : 400, fontWeight: active ? 500 : 400,
boxShadow: active ? '0 1px 2px rgba(0,0,0,0.06)' : 'none', boxShadow: active ? '0 1px 2px rgba(0,0,0,0.06)' : 'none',
transition: 'all 0.15s ease', transition: 'all 0.15s ease',
@@ -588,7 +588,7 @@ export default function ReservationsPanel({ tripId, reservations, days, assignme
<Icon size={13} style={{ color: active ? opt.color : 'var(--text-faint)' }} /> <Icon size={13} style={{ color: active ? opt.color : 'var(--text-faint)' }} />
{t(opt.labelKey)} {t(opt.labelKey)}
<span className={`text-content-faint ${active ? 'bg-surface-tertiary' : 'bg-[rgba(0,0,0,0.06)]'}`} style={{ <span className={`text-content-faint ${active ? 'bg-surface-tertiary' : 'bg-[rgba(0,0,0,0.06)]'}`} style={{
fontSize: 10, fontWeight: 600, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600,
padding: '1px 6px', borderRadius: 99, minWidth: 16, textAlign: 'center', padding: '1px 6px', borderRadius: 99, minWidth: 16, textAlign: 'center',
}}>{typeCounts[opt.value] || 0}</span> }}>{typeCounts[opt.value] || 0}</span>
</button> </button>
@@ -604,7 +604,7 @@ export default function ReservationsPanel({ tripId, reservations, days, assignme
<button onClick={onImport} className="bg-surface-card text-content" style={{ <button onClick={onImport} className="bg-surface-card text-content" style={{
appearance: 'none', border: '1px solid var(--border-primary)', cursor: 'pointer', fontFamily: 'inherit', appearance: 'none', border: '1px solid var(--border-primary)', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6, display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '8px 13px', borderRadius: 10, fontSize: 13, fontWeight: 500, padding: '8px 13px', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
transition: 'opacity 0.15s ease', transition: 'opacity 0.15s ease',
}} }}
onMouseEnter={e => e.currentTarget.style.opacity = '0.75'} onMouseEnter={e => e.currentTarget.style.opacity = '0.75'}
@@ -619,7 +619,7 @@ export default function ReservationsPanel({ tripId, reservations, days, assignme
<button onClick={onAirTrailImport} className="bg-surface-secondary text-content" style={{ <button onClick={onAirTrailImport} className="bg-surface-secondary text-content" style={{
appearance: 'none', border: '1px solid var(--border-primary)', cursor: 'pointer', fontFamily: 'inherit', appearance: 'none', border: '1px solid var(--border-primary)', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6, display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '8px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500, boxSizing: 'border-box', padding: '8px 14px', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, boxSizing: 'border-box',
transition: 'opacity 0.15s ease', transition: 'opacity 0.15s ease',
}} }}
onMouseEnter={e => e.currentTarget.style.opacity = '0.75'} onMouseEnter={e => e.currentTarget.style.opacity = '0.75'}
@@ -633,7 +633,7 @@ export default function ReservationsPanel({ tripId, reservations, days, assignme
<button onClick={onAdd} className="bg-accent text-accent-text" style={{ <button onClick={onAdd} className="bg-accent text-accent-text" style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6, display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500, padding: '9px 14px', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
transition: 'opacity 0.15s ease', transition: 'opacity 0.15s ease',
}} }}
onMouseEnter={e => e.currentTarget.style.opacity = '0.88'} onMouseEnter={e => e.currentTarget.style.opacity = '0.88'}
@@ -652,12 +652,12 @@ export default function ReservationsPanel({ tripId, reservations, days, assignme
{total === 0 && reservations.length === 0 ? ( {total === 0 && reservations.length === 0 ? (
<div style={{ textAlign: 'center', padding: '60px 20px' }}> <div style={{ textAlign: 'center', padding: '60px 20px' }}>
<BookMarked size={36} className="text-content-faint" style={{ display: 'block', margin: '0 auto 12px' }} /> <BookMarked size={36} className="text-content-faint" style={{ display: 'block', margin: '0 auto 12px' }} />
<p className="text-content-secondary" style={{ fontSize: 14, fontWeight: 600, margin: '0 0 4px' }}>{t('reservations.empty')}</p> <p className="text-content-secondary" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600, margin: '0 0 4px' }}>{t('reservations.empty')}</p>
<p className="text-content-faint" style={{ fontSize: 12, margin: 0 }}>{t('reservations.emptyHint')}</p> <p className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', margin: 0 }}>{t('reservations.emptyHint')}</p>
</div> </div>
) : total === 0 ? ( ) : total === 0 ? (
<div style={{ textAlign: 'center', padding: '60px 20px' }}> <div style={{ textAlign: 'center', padding: '60px 20px' }}>
<p className="text-content-faint" style={{ fontSize: 13 }}>{t('places.noneFound')}</p> <p className="text-content-faint" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>{t('places.noneFound')}</p>
</div> </div>
) : ( ) : (
<> <>
@@ -443,10 +443,10 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
size="2xl" size="2xl"
footer={ footer={
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}> <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
<button type="button" onClick={onClose} className="text-content-muted" style={{ padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)', background: 'none', fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' }}> <button type="button" onClick={onClose} className="text-content-muted" style={{ padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)', background: 'none', fontSize: 'calc(12px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit' }}>
{t('common.cancel')} {t('common.cancel')}
</button> </button>
<button type="button" onClick={handleSubmit} disabled={isSaving || !form.title.trim()} className="bg-[var(--text-primary)] text-[var(--bg-primary)]" style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', opacity: isSaving || !form.title.trim() ? 0.5 : 1 }}> <button type="button" onClick={handleSubmit} disabled={isSaving || !form.title.trim()} className="bg-[var(--text-primary)] text-[var(--bg-primary)]" style={{ padding: '8px 20px', borderRadius: 10, border: 'none', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', opacity: isSaving || !form.title.trim() ? 0.5 : 1 }}>
{isSaving ? t('common.saving') : reservation ? t('common.update') : t('common.add')} {isSaving ? t('common.saving') : reservation ? t('common.update') : t('common.add')}
</button> </button>
</div> </div>
@@ -462,7 +462,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
<button key={value} type="button" onClick={() => set('type', value)} className={form.type === value ? 'bg-[var(--text-primary)] text-[var(--bg-primary)]' : 'bg-surface-card text-content-muted'} style={{ <button key={value} type="button" onClick={() => set('type', value)} className={form.type === value ? 'bg-[var(--text-primary)] text-[var(--bg-primary)]' : 'bg-surface-card text-content-muted'} style={{
display: 'flex', alignItems: 'center', gap: 4, display: 'flex', alignItems: 'center', gap: 4,
padding: '5px 10px', borderRadius: 99, border: '1px solid', padding: '5px 10px', borderRadius: 99, border: '1px solid',
fontSize: 11, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.12s', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.12s',
borderColor: form.type === value ? 'var(--text-primary)' : 'var(--border-primary)', borderColor: form.type === value ? 'var(--text-primary)' : 'var(--border-primary)',
}}> }}>
<Icon size={11} /> {t(labelKey)} <Icon size={11} /> {t(labelKey)}
@@ -491,7 +491,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
<div key={i} style={{ display: 'flex', flexDirection: 'column', gap: 6 }}> <div key={i} style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<div className="bg-surface-card" style={{ border: '1px solid var(--border-primary)', borderRadius: 10, padding: 10, display: 'flex', flexDirection: 'column', gap: 8 }}> <div className="bg-surface-card" style={{ border: '1px solid var(--border-primary)', borderRadius: 10, padding: 10, display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span className="text-content-faint" style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.03em', flexShrink: 0 }}>{roleLabel}</span> <span className="text-content-faint" style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.03em', flexShrink: 0 }}>{roleLabel}</span>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<AirportSelect value={wp.airport} onChange={a => updateWp({ airport: a || null })} /> <AirportSelect value={wp.airport} onChange={a => updateWp({ airport: a || null })} />
</div> </div>
@@ -514,7 +514,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
{wp.airport && ( {wp.airport && (
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<label className={labelClass}>{t('reservations.meta.arrivalTimezone')}</label> <label className={labelClass}>{t('reservations.meta.arrivalTimezone')}</label>
<div className={inputClass} style={{ padding: '8px 12px', color: 'var(--text-muted)', fontSize: 12, background: 'var(--bg-tertiary)' }}>{wp.airport.tz}</div> <div className={inputClass} style={{ padding: '8px 12px', color: 'var(--text-muted)', fontSize: 'calc(12px * var(--fs-scale-text, 1))', background: 'var(--bg-tertiary)' }}>{wp.airport.tz}</div>
</div> </div>
)} )}
</div> </div>
@@ -533,7 +533,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
{wp.airport && ( {wp.airport && (
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<label className={labelClass}>{t('reservations.meta.departureTimezone')}</label> <label className={labelClass}>{t('reservations.meta.departureTimezone')}</label>
<div className={inputClass} style={{ padding: '8px 12px', color: 'var(--text-muted)', fontSize: 12, background: 'var(--bg-tertiary)' }}>{wp.airport.tz}</div> <div className={inputClass} style={{ padding: '8px 12px', color: 'var(--text-muted)', fontSize: 'calc(12px * var(--fs-scale-text, 1))', background: 'var(--bg-tertiary)' }}>{wp.airport.tz}</div>
</div> </div>
)} )}
</div> </div>
@@ -556,7 +556,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
</div> </div>
{!isLast && ( {!isLast && (
<button type="button" onClick={() => setWaypoints(prev => [...prev.slice(0, i + 1), emptyWaypoint(prev[i]?.depDayId || ''), ...prev.slice(i + 1)])} <button type="button" onClick={() => setWaypoints(prev => [...prev.slice(0, i + 1), emptyWaypoint(prev[i]?.depDayId || ''), ...prev.slice(i + 1)])}
className="text-content-faint hover:text-content-secondary" style={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, padding: '6px 10px', border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none', fontSize: 11, cursor: 'pointer', fontFamily: 'inherit' }}> className="text-content-faint hover:text-content-secondary" style={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, padding: '6px 10px', border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none', fontSize: 'calc(11px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit' }}>
<Plus size={12} /> {t('reservations.layover.addStop')} <Plus size={12} /> {t('reservations.layover.addStop')}
</button> </button>
)} )}
@@ -661,7 +661,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
{attachedFiles.map(f => ( {attachedFiles.map(f => (
<div key={f.id} className="bg-surface-secondary" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 8 }}> <div key={f.id} className="bg-surface-secondary" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 8 }}>
<FileText size={12} className="text-content-muted" style={{ flexShrink: 0 }} /> <FileText size={12} className="text-content-muted" style={{ flexShrink: 0 }} />
<span className="text-content-secondary" style={{ flex: 1, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.original_name}</span> <span className="text-content-secondary" style={{ flex: 1, fontSize: 'calc(12px * var(--fs-scale-text, 1))', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.original_name}</span>
<a href="#" onClick={(e) => { e.preventDefault(); openFile(f.url).catch(() => {}) }} className="text-content-faint" style={{ display: 'flex', flexShrink: 0, cursor: 'pointer' }}><ExternalLink size={11} /></a> <a href="#" onClick={(e) => { e.preventDefault(); openFile(f.url).catch(() => {}) }} className="text-content-faint" style={{ display: 'flex', flexShrink: 0, cursor: 'pointer' }}><ExternalLink size={11} /></a>
<button type="button" onClick={async () => { <button type="button" onClick={async () => {
if (f.reservation_id === reservation?.id) { if (f.reservation_id === reservation?.id) {
@@ -682,7 +682,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
{pendingFiles.map((f, i) => ( {pendingFiles.map((f, i) => (
<div key={i} className="bg-surface-secondary" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 8 }}> <div key={i} className="bg-surface-secondary" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderRadius: 8 }}>
<FileText size={12} className="text-content-muted" style={{ flexShrink: 0 }} /> <FileText size={12} className="text-content-muted" style={{ flexShrink: 0 }} />
<span className="text-content-secondary" style={{ flex: 1, fontSize: 12, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.name}</span> <span className="text-content-secondary" style={{ flex: 1, fontSize: 'calc(12px * var(--fs-scale-text, 1))', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.name}</span>
<button type="button" onClick={() => setPendingFiles(prev => prev.filter((_, j) => j !== i))} <button type="button" onClick={() => setPendingFiles(prev => prev.filter((_, j) => j !== i))}
className="text-content-faint" style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', padding: 0, flexShrink: 0 }}> className="text-content-faint" style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', padding: 0, flexShrink: 0 }}>
<X size={11} /> <X size={11} />
@@ -694,7 +694,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
{onFileUpload && <button type="button" onClick={() => fileInputRef.current?.click()} disabled={uploadingFile} className="text-content-faint" style={{ {onFileUpload && <button type="button" onClick={() => fileInputRef.current?.click()} disabled={uploadingFile} className="text-content-faint" style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '6px 10px', display: 'flex', alignItems: 'center', gap: 5, padding: '6px 10px',
border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none', border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none',
fontSize: 11, cursor: uploadingFile ? 'default' : 'pointer', fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', cursor: uploadingFile ? 'default' : 'pointer', fontFamily: 'inherit',
}}> }}>
<Paperclip size={11} /> <Paperclip size={11} />
{uploadingFile ? t('reservations.uploading') : t('reservations.attachFile')} {uploadingFile ? t('reservations.uploading') : t('reservations.attachFile')}
@@ -704,7 +704,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
<button type="button" onClick={() => setShowFilePicker(v => !v)} className="text-content-faint" style={{ <button type="button" onClick={() => setShowFilePicker(v => !v)} className="text-content-faint" style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '6px 10px', display: 'flex', alignItems: 'center', gap: 5, padding: '6px 10px',
border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none', border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none',
fontSize: 11, cursor: 'pointer', fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-text, 1))', cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
<Link2 size={11} /> {t('reservations.linkExisting')} <Link2 size={11} /> {t('reservations.linkExisting')}
</button> </button>
@@ -726,7 +726,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
className="text-content-secondary" className="text-content-secondary"
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '6px 10px', display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '6px 10px',
background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, fontFamily: 'inherit', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontFamily: 'inherit',
borderRadius: 7, textAlign: 'left', borderRadius: 7, textAlign: 'left',
}} }}
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-tertiary)'} onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-tertiary)'}
+3 -3
View File
@@ -239,14 +239,14 @@ export default function AboutTab({ appVersion }: Props): React.ReactElement {
50% { transform: scale(1.15); } 50% { transform: scale(1.15); }
} }
`}</style> `}</style>
<p className="text-content-secondary" style={{ fontSize: 13, lineHeight: 1.6, marginBottom: 6, marginTop: -4 }}> <p className="text-content-secondary" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', lineHeight: 1.6, marginBottom: 6, marginTop: -4 }}>
{t('settings.about.description')} {t('settings.about.description')}
</p> </p>
<p className="text-content-faint" style={{ fontSize: 12, lineHeight: 1.6, marginBottom: 16 }}> <p className="text-content-faint" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', lineHeight: 1.6, marginBottom: 16 }}>
{t('settings.about.madeWith')}{' '} {t('settings.about.madeWith')}{' '}
<Heart size={11} fill="#991b1b" stroke="#991b1b" style={{ display: 'inline-block', verticalAlign: '-1px', animation: 'heartPulse 1.5s ease-in-out infinite' }} /> <Heart size={11} fill="#991b1b" stroke="#991b1b" style={{ display: 'inline-block', verticalAlign: '-1px', animation: 'heartPulse 1.5s ease-in-out infinite' }} />
{' '}{t('settings.about.madeBy')}{' '} {' '}{t('settings.about.madeBy')}{' '}
<span className="text-content-faint bg-surface-tertiary" style={{ display: 'inline-flex', alignItems: 'center', borderRadius: 99, padding: '1px 7px', fontSize: 10, fontWeight: 600, verticalAlign: '1px' }}>v{appVersion}</span> <span className="text-content-faint bg-surface-tertiary" style={{ display: 'inline-flex', alignItems: 'center', borderRadius: 99, padding: '1px 7px', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, verticalAlign: '1px' }}>v{appVersion}</span>
</p> </p>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
+10 -10
View File
@@ -408,7 +408,7 @@ export default function AccountTab(): React.ReactElement {
<div className="bg-surface-hover text-content-secondary" style={{ <div className="bg-surface-hover text-content-secondary" style={{
width: 64, height: 64, borderRadius: '50%', width: 64, height: 64, borderRadius: '50%',
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 24, fontWeight: 700, fontSize: 'calc(24px * var(--fs-scale-text, 1))', fontWeight: 700,
}}> }}>
{user?.username?.charAt(0).toUpperCase()} {user?.username?.charAt(0).toUpperCase()}
</div> </div>
@@ -453,7 +453,7 @@ export default function AccountTab(): React.ReactElement {
{(user as UserWithOidc)?.oidc_issuer && ( {(user as UserWithOidc)?.oidc_issuer && (
<span className="bg-[#dbeafe] text-[#1d4ed8]" style={{ <span className="bg-[#dbeafe] text-[#1d4ed8]" style={{
display: 'inline-flex', alignItems: 'center', gap: 4, display: 'inline-flex', alignItems: 'center', gap: 4,
fontSize: 10, fontWeight: 500, padding: '1px 8px', borderRadius: 99, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 500, padding: '1px 8px', borderRadius: 99,
marginLeft: 6, marginLeft: 6,
}}> }}>
SSO SSO
@@ -461,7 +461,7 @@ export default function AccountTab(): React.ReactElement {
)} )}
</div> </div>
{(user as UserWithOidc)?.oidc_issuer && ( {(user as UserWithOidc)?.oidc_issuer && (
<p className="text-content-faint" style={{ fontSize: 11, marginTop: -2 }}> <p className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginTop: -2 }}>
{t('settings.oidcLinked')} {(user as UserWithOidc).oidc_issuer!.replace('https://', '').replace(/\/+$/, '')} {t('settings.oidcLinked')} {(user as UserWithOidc).oidc_issuer!.replace('https://', '').replace(/\/+$/, '')}
</p> </p>
)} )}
@@ -516,9 +516,9 @@ export default function AccountTab(): React.ReactElement {
<div className="bg-[#fef3c7]" style={{ width: 36, height: 36, borderRadius: 10, display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <div className="bg-[#fef3c7]" style={{ width: 36, height: 36, borderRadius: 10, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Shield size={18} className="text-[#d97706]" /> <Shield size={18} className="text-[#d97706]" />
</div> </div>
<h3 className="text-content" style={{ margin: 0, fontSize: 16, fontWeight: 700 }}>{t('settings.deleteBlockedTitle')}</h3> <h3 className="text-content" style={{ margin: 0, fontSize: 'calc(16px * var(--fs-scale-text, 1))', fontWeight: 700 }}>{t('settings.deleteBlockedTitle')}</h3>
</div> </div>
<p className="text-content-muted" style={{ fontSize: 13, lineHeight: 1.6, margin: '0 0 20px' }}> <p className="text-content-muted" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', lineHeight: 1.6, margin: '0 0 20px' }}>
{t('settings.deleteBlockedMessage')} {t('settings.deleteBlockedMessage')}
</p> </p>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
@@ -526,7 +526,7 @@ export default function AccountTab(): React.ReactElement {
onClick={() => setShowDeleteConfirm(false)} onClick={() => setShowDeleteConfirm(false)}
className="border border-edge bg-surface-card text-content-secondary" className="border border-edge bg-surface-card text-content-secondary"
style={{ style={{
padding: '8px 16px', borderRadius: 8, fontSize: 13, fontWeight: 500, padding: '8px 16px', borderRadius: 8, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}} }}
> >
@@ -552,9 +552,9 @@ export default function AccountTab(): React.ReactElement {
<div className="bg-[#fef2f2]" style={{ width: 36, height: 36, borderRadius: 10, display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <div className="bg-[#fef2f2]" style={{ width: 36, height: 36, borderRadius: 10, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Trash2 size={18} className="text-[#ef4444]" /> <Trash2 size={18} className="text-[#ef4444]" />
</div> </div>
<h3 className="text-content" style={{ margin: 0, fontSize: 16, fontWeight: 700 }}>{t('settings.deleteAccountTitle')}</h3> <h3 className="text-content" style={{ margin: 0, fontSize: 'calc(16px * var(--fs-scale-text, 1))', fontWeight: 700 }}>{t('settings.deleteAccountTitle')}</h3>
</div> </div>
<p className="text-content-muted" style={{ fontSize: 13, lineHeight: 1.6, margin: '0 0 20px' }}> <p className="text-content-muted" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', lineHeight: 1.6, margin: '0 0 20px' }}>
{t('settings.deleteAccountWarning')} {t('settings.deleteAccountWarning')}
</p> </p>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}> <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
@@ -562,7 +562,7 @@ export default function AccountTab(): React.ReactElement {
onClick={() => setShowDeleteConfirm(false)} onClick={() => setShowDeleteConfirm(false)}
className="border border-edge bg-surface-card text-content-secondary" className="border border-edge bg-surface-card text-content-secondary"
style={{ style={{
padding: '8px 16px', borderRadius: 8, fontSize: 13, fontWeight: 500, padding: '8px 16px', borderRadius: 8, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}} }}
> >
@@ -581,7 +581,7 @@ export default function AccountTab(): React.ReactElement {
}} }}
className="bg-[#ef4444] text-white" className="bg-[#ef4444] text-white"
style={{ style={{
padding: '8px 16px', borderRadius: 8, fontSize: 13, fontWeight: 600, padding: '8px 16px', borderRadius: 8, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600,
border: 'none', border: 'none',
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}} }}
@@ -66,7 +66,7 @@ export default function DisplaySettingsTab(): React.ReactElement {
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 20px', borderRadius: 10, cursor: 'pointer', padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 14, fontWeight: 500, fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500,
border: settings.language === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)', border: settings.language === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: settings.language === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)', background: settings.language === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
@@ -90,7 +90,7 @@ export default function DisplaySettingsTab(): React.ReactElement {
padding: '10px 14px', borderRadius: 10, padding: '10px 14px', borderRadius: 10,
border: '2px solid var(--border-primary)', border: '2px solid var(--border-primary)',
background: 'var(--bg-card)', color: 'var(--text-primary)', background: 'var(--bg-card)', color: 'var(--text-primary)',
fontSize: 14, fontWeight: 500, fontFamily: 'inherit', cursor: 'pointer', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500, fontFamily: 'inherit', cursor: 'pointer',
}} }}
> >
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{current?.label}</span> <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{current?.label}</span>
@@ -119,7 +119,7 @@ export default function DisplaySettingsTab(): React.ReactElement {
display: 'flex', alignItems: 'center', gap: 8, width: '100%', display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '9px 12px', borderRadius: 6, border: 'none', cursor: 'pointer', padding: '9px 12px', borderRadius: 6, border: 'none', cursor: 'pointer',
background: active ? 'var(--bg-hover)' : 'transparent', background: active ? 'var(--bg-hover)' : 'transparent',
fontFamily: 'inherit', fontSize: 14, color: 'var(--text-primary)', fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-text, 1))', color: 'var(--text-primary)',
textAlign: 'left', fontWeight: active ? 600 : 500, textAlign: 'left', fontWeight: active ? 600 : 500,
}} }}
> >
@@ -151,7 +151,7 @@ export default function DisplaySettingsTab(): React.ReactElement {
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 20px', borderRadius: 10, cursor: 'pointer', padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 14, fontWeight: 500, fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500,
border: tempUnit === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)', border: tempUnit === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: tempUnit === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)', background: tempUnit === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
@@ -182,7 +182,7 @@ export default function DisplaySettingsTab(): React.ReactElement {
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 20px', borderRadius: 10, cursor: 'pointer', padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 14, fontWeight: 500, fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500,
border: distanceUnit === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)', border: distanceUnit === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: distanceUnit === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)', background: distanceUnit === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
@@ -212,7 +212,7 @@ export default function DisplaySettingsTab(): React.ReactElement {
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 20px', borderRadius: 10, cursor: 'pointer', padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 14, fontWeight: 500, fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500,
border: settings.time_format === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)', border: settings.time_format === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: settings.time_format === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)', background: settings.time_format === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
@@ -243,7 +243,7 @@ export default function DisplaySettingsTab(): React.ReactElement {
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 20px', borderRadius: 10, cursor: 'pointer', padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 14, fontWeight: 500, fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500,
border: (settings.map_booking_labels !== false) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)', border: (settings.map_booking_labels !== false) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: (settings.map_booking_labels !== false) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)', background: (settings.map_booking_labels !== false) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
@@ -274,7 +274,7 @@ export default function DisplaySettingsTab(): React.ReactElement {
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 20px', borderRadius: 10, cursor: 'pointer', padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 14, fontWeight: 500, fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500,
border: (settings.map_poi_pill_enabled !== false) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)', border: (settings.map_poi_pill_enabled !== false) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: (settings.map_poi_pill_enabled !== false) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)', background: (settings.map_poi_pill_enabled !== false) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
@@ -305,7 +305,7 @@ export default function DisplaySettingsTab(): React.ReactElement {
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 20px', borderRadius: 10, cursor: 'pointer', padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 14, fontWeight: 500, fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500,
border: (!!settings.blur_booking_codes) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)', border: (!!settings.blur_booking_codes) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: (!!settings.blur_booking_codes) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)', background: (!!settings.blur_booking_codes) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
@@ -335,7 +335,7 @@ export default function DisplaySettingsTab(): React.ReactElement {
style={{ style={{
display: 'flex', alignItems: 'center', gap: 8, display: 'flex', alignItems: 'center', gap: 8,
padding: '10px 20px', borderRadius: 10, cursor: 'pointer', padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
fontFamily: 'inherit', fontSize: 14, fontWeight: 500, fontFamily: 'inherit', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 500,
border: (settings.optimize_from_accommodation !== false) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)', border: (settings.optimize_from_accommodation !== false) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
background: (settings.optimize_from_accommodation !== false) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)', background: (settings.optimize_from_accommodation !== false) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
@@ -171,11 +171,11 @@ export default function NotificationsTab(): React.ReactElement {
} }
const renderContent = () => { const renderContent = () => {
if (!matrix) return <p style={{ fontSize: 12, color: 'var(--text-faint)', fontStyle: 'italic' }}>{t('common.loading')}</p> if (!matrix) return <p style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontStyle: 'italic' }}>{t('common.loading')}</p>
if (visibleChannels.length === 0) { if (visibleChannels.length === 0) {
return ( return (
<p style={{ fontSize: 12, color: 'var(--text-faint)', fontStyle: 'italic' }}> <p style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontStyle: 'italic' }}>
{t('settings.notificationPreferences.noChannels')} {t('settings.notificationPreferences.noChannels')}
</p> </p>
) )
@@ -183,32 +183,32 @@ export default function NotificationsTab(): React.ReactElement {
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
{saving && <p style={{ fontSize: 11, color: 'var(--text-faint)', marginBottom: 8 }}>{t('common.saving')}</p>} {saving && <p style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginBottom: 8 }}>{t('common.saving')}</p>}
{matrix.available_channels.webhook && ( {matrix.available_channels.webhook && (
<div style={{ marginBottom: 16, padding: '12px', background: 'var(--bg-secondary)', borderRadius: 8, border: '1px solid var(--border-primary)' }}> <div style={{ marginBottom: 16, padding: '12px', background: 'var(--bg-secondary)', borderRadius: 8, border: '1px solid var(--border-primary)' }}>
<label style={{ display: 'block', fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}> <label style={{ display: 'block', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}>
{t('settings.webhookUrl.label')} {t('settings.webhookUrl.label')}
</label> </label>
<p style={{ fontSize: 11, color: 'var(--text-faint)', marginBottom: 8 }}>{t('settings.webhookUrl.hint')}</p> <p style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginBottom: 8 }}>{t('settings.webhookUrl.hint')}</p>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}> <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<input <input
type="text" type="text"
value={webhookUrl} value={webhookUrl}
onChange={e => setWebhookUrl(e.target.value)} onChange={e => setWebhookUrl(e.target.value)}
placeholder={webhookIsSet ? '••••••••' : t('settings.webhookUrl.placeholder')} placeholder={webhookIsSet ? '••••••••' : t('settings.webhookUrl.placeholder')}
style={{ flex: 1, fontSize: 13, padding: '6px 10px', border: '1px solid var(--border-primary)', borderRadius: 6, background: 'var(--bg-primary)', color: 'var(--text-primary)' }} style={{ flex: 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', padding: '6px 10px', border: '1px solid var(--border-primary)', borderRadius: 6, background: 'var(--bg-primary)', color: 'var(--text-primary)' }}
/> />
<button <button
onClick={saveWebhookUrl} onClick={saveWebhookUrl}
disabled={webhookSaving} disabled={webhookSaving}
style={{ fontSize: 12, padding: '6px 12px', background: 'var(--text-primary)', color: 'var(--bg-primary)', border: 'none', borderRadius: 6, cursor: webhookSaving ? 'not-allowed' : 'pointer', opacity: webhookSaving ? 0.6 : 1 }} style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', padding: '6px 12px', background: 'var(--text-primary)', color: 'var(--bg-primary)', border: 'none', borderRadius: 6, cursor: webhookSaving ? 'not-allowed' : 'pointer', opacity: webhookSaving ? 0.6 : 1 }}
> >
{t('common.save')} {t('common.save')}
</button> </button>
<button <button
onClick={testWebhookUrl} onClick={testWebhookUrl}
disabled={(!webhookUrl && !webhookIsSet) || webhookTesting} disabled={(!webhookUrl && !webhookIsSet) || webhookTesting}
style={{ fontSize: 12, padding: '6px 12px', background: 'transparent', color: 'var(--text-secondary)', border: '1px solid var(--border-primary)', borderRadius: 6, cursor: ((!webhookUrl && !webhookIsSet) || webhookTesting) ? 'not-allowed' : 'pointer', opacity: ((!webhookUrl && !webhookIsSet) || webhookTesting) ? 0.5 : 1 }} style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', padding: '6px 12px', background: 'transparent', color: 'var(--text-secondary)', border: '1px solid var(--border-primary)', borderRadius: 6, cursor: ((!webhookUrl && !webhookIsSet) || webhookTesting) ? 'not-allowed' : 'pointer', opacity: ((!webhookUrl && !webhookIsSet) || webhookTesting) ? 0.5 : 1 }}
> >
{t('settings.webhookUrl.test')} {t('settings.webhookUrl.test')}
</button> </button>
@@ -217,18 +217,18 @@ export default function NotificationsTab(): React.ReactElement {
)} )}
{matrix.available_channels.ntfy && ( {matrix.available_channels.ntfy && (
<div style={{ marginBottom: 16, padding: '12px', background: 'var(--bg-secondary)', borderRadius: 8, border: '1px solid var(--border-primary)' }}> <div style={{ marginBottom: 16, padding: '12px', background: 'var(--bg-secondary)', borderRadius: 8, border: '1px solid var(--border-primary)' }}>
<label style={{ display: 'block', fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}> <label style={{ display: 'block', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}>
{t('settings.ntfyUrl.topicLabel')} {t('settings.ntfyUrl.topicLabel')}
</label> </label>
<p style={{ fontSize: 11, color: 'var(--text-faint)', marginBottom: 8 }}>{t('settings.ntfyUrl.hint')}</p> <p style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginBottom: 8 }}>{t('settings.ntfyUrl.hint')}</p>
<input <input
type="text" type="text"
value={ntfyTopic} value={ntfyTopic}
onChange={e => setNtfyTopic(e.target.value)} onChange={e => setNtfyTopic(e.target.value)}
placeholder={t('settings.ntfyUrl.topicPlaceholder')} placeholder={t('settings.ntfyUrl.topicPlaceholder')}
style={{ width: '100%', boxSizing: 'border-box', fontSize: 13, padding: '6px 10px', border: '1px solid var(--border-primary)', borderRadius: 6, background: 'var(--bg-primary)', color: 'var(--text-primary)', marginBottom: 6 }} style={{ width: '100%', boxSizing: 'border-box', fontSize: 'calc(13px * var(--fs-scale-text, 1))', padding: '6px 10px', border: '1px solid var(--border-primary)', borderRadius: 6, background: 'var(--bg-primary)', color: 'var(--text-primary)', marginBottom: 6 }}
/> />
<label style={{ display: 'block', fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}> <label style={{ display: 'block', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}>
{t('settings.ntfyUrl.serverLabel')} {t('settings.ntfyUrl.serverLabel')}
</label> </label>
<input <input
@@ -236,24 +236,24 @@ export default function NotificationsTab(): React.ReactElement {
value={ntfyServer} value={ntfyServer}
onChange={e => setNtfyServer(e.target.value)} onChange={e => setNtfyServer(e.target.value)}
placeholder={matrix.defaults?.ntfyServer || t('settings.ntfyUrl.serverPlaceholder')} placeholder={matrix.defaults?.ntfyServer || t('settings.ntfyUrl.serverPlaceholder')}
style={{ width: '100%', boxSizing: 'border-box', fontSize: 13, padding: '6px 10px', border: '1px solid var(--border-primary)', borderRadius: 6, background: 'var(--bg-primary)', color: 'var(--text-primary)', marginBottom: 6 }} style={{ width: '100%', boxSizing: 'border-box', fontSize: 'calc(13px * var(--fs-scale-text, 1))', padding: '6px 10px', border: '1px solid var(--border-primary)', borderRadius: 6, background: 'var(--bg-primary)', color: 'var(--text-primary)', marginBottom: 6 }}
/> />
<label style={{ display: 'block', fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}> <label style={{ display: 'block', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}>
{t('settings.ntfyUrl.tokenLabel')} {t('settings.ntfyUrl.tokenLabel')}
</label> </label>
<p style={{ fontSize: 11, color: 'var(--text-faint)', marginBottom: 4 }}>{t('settings.ntfyUrl.tokenHint')}</p> <p style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginBottom: 4 }}>{t('settings.ntfyUrl.tokenHint')}</p>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}> <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<input <input
type="password" type="password"
value={ntfyToken} value={ntfyToken}
onChange={e => setNtfyToken(e.target.value)} onChange={e => setNtfyToken(e.target.value)}
placeholder={ntfyTokenIsSet ? '••••••••' : ''} placeholder={ntfyTokenIsSet ? '••••••••' : ''}
style={{ flex: 1, fontSize: 13, padding: '6px 10px', border: '1px solid var(--border-primary)', borderRadius: 6, background: 'var(--bg-primary)', color: 'var(--text-primary)' }} style={{ flex: 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', padding: '6px 10px', border: '1px solid var(--border-primary)', borderRadius: 6, background: 'var(--bg-primary)', color: 'var(--text-primary)' }}
/> />
{ntfyTokenIsSet && ( {ntfyTokenIsSet && (
<button <button
onClick={clearNtfyToken} onClick={clearNtfyToken}
style={{ fontSize: 12, padding: '6px 12px', background: 'transparent', color: 'var(--color-danger, #e53e3e)', border: '1px solid var(--color-danger, #e53e3e)', borderRadius: 6, cursor: 'pointer' }} style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', padding: '6px 12px', background: 'transparent', color: 'var(--color-danger, #e53e3e)', border: '1px solid var(--color-danger, #e53e3e)', borderRadius: 6, cursor: 'pointer' }}
> >
{t('common.clear')} {t('common.clear')}
</button> </button>
@@ -261,14 +261,14 @@ export default function NotificationsTab(): React.ReactElement {
<button <button
onClick={saveNtfySettings} onClick={saveNtfySettings}
disabled={ntfySaving} disabled={ntfySaving}
style={{ fontSize: 12, padding: '6px 12px', background: 'var(--text-primary)', color: 'var(--bg-primary)', border: 'none', borderRadius: 6, cursor: ntfySaving ? 'not-allowed' : 'pointer', opacity: ntfySaving ? 0.6 : 1 }} style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', padding: '6px 12px', background: 'var(--text-primary)', color: 'var(--bg-primary)', border: 'none', borderRadius: 6, cursor: ntfySaving ? 'not-allowed' : 'pointer', opacity: ntfySaving ? 0.6 : 1 }}
> >
{t('common.save')} {t('common.save')}
</button> </button>
<button <button
onClick={testNtfySettings} onClick={testNtfySettings}
disabled={!ntfyTopic || ntfyTesting} disabled={!ntfyTopic || ntfyTesting}
style={{ fontSize: 12, padding: '6px 12px', background: 'transparent', color: 'var(--text-secondary)', border: '1px solid var(--border-primary)', borderRadius: 6, cursor: (!ntfyTopic || ntfyTesting) ? 'not-allowed' : 'pointer', opacity: (!ntfyTopic || ntfyTesting) ? 0.5 : 1 }} style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', padding: '6px 12px', background: 'transparent', color: 'var(--text-secondary)', border: '1px solid var(--border-primary)', borderRadius: 6, cursor: (!ntfyTopic || ntfyTesting) ? 'not-allowed' : 'pointer', opacity: (!ntfyTopic || ntfyTesting) ? 0.5 : 1 }}
> >
{t('settings.ntfyUrl.test')} {t('settings.ntfyUrl.test')}
</button> </button>
@@ -279,7 +279,7 @@ export default function NotificationsTab(): React.ReactElement {
<div style={{ display: 'grid', gridTemplateColumns: `1fr ${visibleChannels.map(() => '64px').join(' ')}`, gap: 4, paddingBottom: 6, marginBottom: 4, borderBottom: '1px solid var(--border-primary)' }}> <div style={{ display: 'grid', gridTemplateColumns: `1fr ${visibleChannels.map(() => '64px').join(' ')}`, gap: 4, paddingBottom: 6, marginBottom: 4, borderBottom: '1px solid var(--border-primary)' }}>
<span /> <span />
{visibleChannels.map(ch => ( {visibleChannels.map(ch => (
<span key={ch} style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-faint)', textAlign: 'center', textTransform: 'uppercase', letterSpacing: '0.04em' }}> <span key={ch} style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', textAlign: 'center', textTransform: 'uppercase', letterSpacing: '0.04em' }}>
{t(CHANNEL_LABEL_KEYS[ch]) || ch} {t(CHANNEL_LABEL_KEYS[ch]) || ch}
</span> </span>
))} ))}
@@ -291,12 +291,12 @@ export default function NotificationsTab(): React.ReactElement {
if (relevantChannels.length === 0) return null if (relevantChannels.length === 0) return null
return ( return (
<div key={eventType} style={{ display: 'grid', gridTemplateColumns: `1fr ${visibleChannels.map(() => '64px').join(' ')}`, gap: 4, alignItems: 'center', padding: '6px 0', borderBottom: '1px solid var(--border-primary)' }}> <div key={eventType} style={{ display: 'grid', gridTemplateColumns: `1fr ${visibleChannels.map(() => '64px').join(' ')}`, gap: 4, alignItems: 'center', padding: '6px 0', borderBottom: '1px solid var(--border-primary)' }}>
<span style={{ fontSize: 13, color: 'var(--text-primary)' }}> <span style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-primary)' }}>
{t(EVENT_LABEL_KEYS[eventType]) || eventType} {t(EVENT_LABEL_KEYS[eventType]) || eventType}
</span> </span>
{visibleChannels.map(ch => { {visibleChannels.map(ch => {
if (!implementedForEvent.includes(ch)) { if (!implementedForEvent.includes(ch)) {
return <span key={ch} style={{ textAlign: 'center', color: 'var(--text-faint)', fontSize: 14 }}></span> return <span key={ch} style={{ textAlign: 'center', color: 'var(--text-faint)', fontSize: 'calc(14px * var(--fs-scale-text, 1))' }}></span>
} }
const isOn = matrix.preferences[eventType]?.[ch] ?? true const isOn = matrix.preferences[eventType]?.[ch] ?? true
return ( return (
@@ -101,7 +101,7 @@ export default function OfflineTab(): React.ReactElement {
display: 'flex', alignItems: 'center', gap: 6, padding: '8px 14px', display: 'flex', alignItems: 'center', gap: 6, padding: '8px 14px',
borderRadius: 8, borderRadius: 8,
cursor: syncing || !navigator.onLine ? 'not-allowed' : 'pointer', cursor: syncing || !navigator.onLine ? 'not-allowed' : 'pointer',
fontSize: 13, fontWeight: 500, opacity: !navigator.onLine ? 0.5 : 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, opacity: !navigator.onLine ? 0.5 : 1,
}} }}
> >
<RefreshCw size={14} style={syncing ? { animation: 'spin 1s linear infinite' } : {}} /> <RefreshCw size={14} style={syncing ? { animation: 'spin 1s linear infinite' } : {}} />
@@ -116,7 +116,7 @@ export default function OfflineTab(): React.ReactElement {
display: 'flex', alignItems: 'center', gap: 6, padding: '8px 14px', display: 'flex', alignItems: 'center', gap: 6, padding: '8px 14px',
borderRadius: 8, borderRadius: 8,
cursor: clearing || rows.length === 0 ? 'not-allowed' : 'pointer', cursor: clearing || rows.length === 0 ? 'not-allowed' : 'pointer',
fontSize: 13, fontWeight: 500, opacity: rows.length === 0 ? 0.5 : 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, opacity: rows.length === 0 ? 0.5 : 1,
}} }}
> >
<Trash2 size={14} /> <Trash2 size={14} />
@@ -126,9 +126,9 @@ export default function OfflineTab(): React.ReactElement {
{/* Cached trip list */} {/* Cached trip list */}
{loading ? ( {loading ? (
<p className="text-content-muted" style={{ fontSize: 13 }}>Loading</p> <p className="text-content-muted" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>Loading</p>
) : rows.length === 0 ? ( ) : rows.length === 0 ? (
<p className="text-content-muted" style={{ fontSize: 13 }}> <p className="text-content-muted" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))' }}>
No trips cached yet. Connect to internet to sync. No trips cached yet. Connect to internet to sync.
</p> </p>
) : ( ) : (
@@ -143,17 +143,17 @@ export default function OfflineTab(): React.ReactElement {
}} }}
> >
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span className="text-content" style={{ fontWeight: 600, fontSize: 14 }}> <span className="text-content" style={{ fontWeight: 600, fontSize: 'calc(14px * var(--fs-scale-text, 1))' }}>
{trip.title} {trip.title}
</span> </span>
<span className="text-content-muted" style={{ fontSize: 11 }}> <span className="text-content-muted" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))' }}>
<Wifi size={10} style={{ display: 'inline', marginRight: 3 }} /> <Wifi size={10} style={{ display: 'inline', marginRight: 3 }} />
{meta.lastSyncedAt {meta.lastSyncedAt
? new Date(meta.lastSyncedAt).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }) ? new Date(meta.lastSyncedAt).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
: '—'} : '—'}
</span> </span>
</div> </div>
<span className="text-content-muted" style={{ fontSize: 12 }}> <span className="text-content-muted" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))' }}>
{formatDate(trip.start_date)} {formatDate(trip.end_date)} {formatDate(trip.start_date)} {formatDate(trip.end_date)}
{' · '} {' · '}
{placeCount} place{placeCount !== 1 ? 's' : ''} {placeCount} place{placeCount !== 1 ? 's' : ''}
@@ -175,9 +175,9 @@ function Stat({ label, value, danger }: { label: string; value: number; danger?:
padding: '8px 14px', borderRadius: 8, padding: '8px 14px', borderRadius: 8,
minWidth: 100, minWidth: 100,
}}> }}>
<div style={{ fontSize: 20, fontWeight: 700, color: danger ? '#ef4444' : undefined }} <div style={{ fontSize: 'calc(20px * var(--fs-scale-text, 1))', fontWeight: 700, color: danger ? '#ef4444' : undefined }}
className={danger ? undefined : 'text-content'}>{value}</div> className={danger ? undefined : 'text-content'}>{value}</div>
<div className="text-content-muted" style={{ fontSize: 11 }}>{label}</div> <div className="text-content-muted" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))' }}>{label}</div>
</div> </div>
) )
} }
+28 -28
View File
@@ -38,7 +38,7 @@ export default function TodoListPanel({ tripId, items, addItemSignal = 0 }: { tr
style={{ style={{
display: 'flex', alignItems: 'center', justifyContent: isMobile ? 'center' : 'flex-start', display: 'flex', alignItems: 'center', justifyContent: isMobile ? 'center' : 'flex-start',
gap: isMobile ? 0 : 8, width: '100%', padding: isMobile ? '8px 0' : '7px 12px', gap: isMobile ? 0 : 8, width: '100%', padding: isMobile ? '8px 0' : '7px 12px',
border: 'none', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, border: 'none', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', fontSize: 'calc(13px * var(--fs-scale-text, 1))',
background: filter === id ? 'var(--bg-hover)' : 'transparent', background: filter === id ? 'var(--bg-hover)' : 'transparent',
color: filter === id ? 'var(--text-primary)' : 'var(--text-secondary)', color: filter === id ? 'var(--text-primary)' : 'var(--text-secondary)',
fontWeight: filter === id ? 600 : 400, transition: 'all 0.1s', fontWeight: filter === id ? 600 : 400, transition: 'all 0.1s',
@@ -53,12 +53,12 @@ export default function TodoListPanel({ tripId, items, addItemSignal = 0 }: { tr
)} )}
{!isMobile && <span style={{ flex: 1, textAlign: 'left' }}>{label}</span>} {!isMobile && <span style={{ flex: 1, textAlign: 'left' }}>{label}</span>}
{!isMobile && count > 0 && ( {!isMobile && count > 0 && (
<span style={{ fontSize: 11, color: 'var(--text-faint)', background: 'var(--bg-hover)', borderRadius: 10, padding: '1px 7px', minWidth: 20, textAlign: 'center' }}> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', background: 'var(--bg-hover)', borderRadius: 10, padding: '1px 7px', minWidth: 20, textAlign: 'center' }}>
{count} {count}
</span> </span>
)} )}
{isMobile && count > 0 && ( {isMobile && count > 0 && (
<span style={{ position: 'absolute', top: 2, right: 2, fontSize: 8, fontWeight: 700, color: 'var(--bg-primary)', background: 'var(--text-faint)', borderRadius: '50%', width: 14, height: 14, display: 'flex', alignItems: 'center', justifyContent: 'center' }}> <span style={{ position: 'absolute', top: 2, right: 2, fontSize: 'calc(8px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--bg-primary)', background: 'var(--text-faint)', borderRadius: '50%', width: 14, height: 14, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{count} {count}
</span> </span>
)} )}
@@ -91,20 +91,20 @@ export default function TodoListPanel({ tripId, items, addItemSignal = 0 }: { tr
boxShadow: '0 1px 2px rgba(0,0,0,0.02)', boxShadow: '0 1px 2px rgba(0,0,0,0.02)',
}}> }}>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginBottom: 8 }}> <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginBottom: 8 }}>
<span style={{ fontSize: 18, fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1, letterSpacing: '-0.02em' }}> <span style={{ fontSize: 'calc(18px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1, letterSpacing: '-0.02em' }}>
{totalCount > 0 ? Math.round((doneCount / totalCount) * 100) : 0}% {totalCount > 0 ? Math.round((doneCount / totalCount) * 100) : 0}%
</span> </span>
</div> </div>
<div style={{ height: 4, background: 'var(--border-faint)', borderRadius: 2, overflow: 'hidden', marginBottom: 6 }}> <div style={{ height: 4, background: 'var(--border-faint)', borderRadius: 2, overflow: 'hidden', marginBottom: 6 }}>
<div style={{ height: '100%', width: totalCount > 0 ? `${Math.round((doneCount / totalCount) * 100)}%` : '0%', background: '#22c55e', borderRadius: 2, transition: 'width 0.3s' }} /> <div style={{ height: '100%', width: totalCount > 0 ? `${Math.round((doneCount / totalCount) * 100)}%` : '0%', background: '#22c55e', borderRadius: 2, transition: 'width 0.3s' }} />
</div> </div>
<div style={{ fontSize: 11, color: 'var(--text-faint)' }}> <div style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)' }}>
{doneCount} / {totalCount} {t('todo.completed')} {doneCount} / {totalCount} {t('todo.completed')}
</div> </div>
</div>} </div>}
{/* Smart filters */} {/* Smart filters */}
{!isMobile && <div style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', padding: '8px 12px 4px', textTransform: 'uppercase', letterSpacing: '0.05em' }}> {!isMobile && <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', padding: '8px 12px 4px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
{t('todo.sidebar.tasks')} {t('todo.sidebar.tasks')}
</div>} </div>}
<SidebarItem id="all" icon={Inbox} label={t('todo.filter.all')} count={items.filter(i => !i.checked).length} /> <SidebarItem id="all" icon={Inbox} label={t('todo.filter.all')} count={items.filter(i => !i.checked).length} />
@@ -113,7 +113,7 @@ export default function TodoListPanel({ tripId, items, addItemSignal = 0 }: { tr
<SidebarItem id="done" icon={CheckCheck} label={t('todo.filter.done')} count={doneCount} /> <SidebarItem id="done" icon={CheckCheck} label={t('todo.filter.done')} count={doneCount} />
{/* Sort by */} {/* Sort by */}
{!isMobile && <div style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', padding: '16px 12px 4px', textTransform: 'uppercase', letterSpacing: '0.05em' }}> {!isMobile && <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', padding: '16px 12px 4px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
{t('todo.sidebar.sortBy')} {t('todo.sidebar.sortBy')}
</div>} </div>}
<button onClick={() => setSortByPrio(v => !v)} <button onClick={() => setSortByPrio(v => !v)}
@@ -121,7 +121,7 @@ export default function TodoListPanel({ tripId, items, addItemSignal = 0 }: { tr
style={{ style={{
display: 'flex', alignItems: 'center', justifyContent: isMobile ? 'center' : 'flex-start', display: 'flex', alignItems: 'center', justifyContent: isMobile ? 'center' : 'flex-start',
gap: isMobile ? 0 : 8, width: '100%', padding: isMobile ? '8px 0' : '7px 12px', gap: isMobile ? 0 : 8, width: '100%', padding: isMobile ? '8px 0' : '7px 12px',
border: 'none', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, border: 'none', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', fontSize: 'calc(13px * var(--fs-scale-text, 1))',
background: sortByPrio ? '#f59e0b12' : 'transparent', background: sortByPrio ? '#f59e0b12' : 'transparent',
color: sortByPrio ? '#f59e0b' : 'var(--text-secondary)', color: sortByPrio ? '#f59e0b' : 'var(--text-secondary)',
fontWeight: sortByPrio ? 600 : 400, transition: 'all 0.1s', fontWeight: sortByPrio ? 600 : 400, transition: 'all 0.1s',
@@ -133,7 +133,7 @@ export default function TodoListPanel({ tripId, items, addItemSignal = 0 }: { tr
</button> </button>
{/* Categories */} {/* Categories */}
{!isMobile && <div style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', padding: '16px 12px 4px', textTransform: 'uppercase', letterSpacing: '0.05em' }}> {!isMobile && <div style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', padding: '16px 12px 4px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
{t('todo.sidebar.categories')} {t('todo.sidebar.categories')}
</div>} </div>}
{isMobile && <div style={{ height: 1, background: 'var(--border-faint)', margin: '8px 4px' }} />} {isMobile && <div style={{ height: 1, background: 'var(--border-faint)', margin: '8px 4px' }} />}
@@ -147,13 +147,13 @@ export default function TodoListPanel({ tripId, items, addItemSignal = 0 }: { tr
<input autoFocus value={newCategoryName} onChange={e => setNewCategoryName(e.target.value)} <input autoFocus value={newCategoryName} onChange={e => setNewCategoryName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') addCategory(); if (e.key === 'Escape') { setAddingCategory(false); setNewCategoryName('') } }} onKeyDown={e => { if (e.key === 'Enter') addCategory(); if (e.key === 'Escape') { setAddingCategory(false); setNewCategoryName('') } }}
placeholder={t('todo.newCategory')} placeholder={t('todo.newCategory')}
style={{ flex: 1, fontSize: 12, padding: '4px 6px', border: '1px solid var(--border-primary)', borderRadius: 5, background: 'var(--bg-hover)', color: 'var(--text-primary)', fontFamily: 'inherit', minWidth: 0 }} /> style={{ flex: 1, fontSize: 'calc(12px * var(--fs-scale-text, 1))', padding: '4px 6px', border: '1px solid var(--border-primary)', borderRadius: 5, background: 'var(--bg-hover)', color: 'var(--text-primary)', fontFamily: 'inherit', minWidth: 0 }} />
<button onClick={addCategory} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#22c55e', padding: 2 }}><Check size={13} /></button> <button onClick={addCategory} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#22c55e', padding: 2 }}><Check size={13} /></button>
</div> </div>
) : ( ) : (
<button onClick={() => setAddingCategory(true)} <button onClick={() => setAddingCategory(true)}
title={isMobile ? t('todo.addCategory') : undefined} title={isMobile ? t('todo.addCategory') : undefined}
style={{ display: 'flex', alignItems: 'center', justifyContent: isMobile ? 'center' : 'flex-start', gap: isMobile ? 0 : 6, padding: isMobile ? '8px 0' : '7px 12px', fontSize: 12, color: 'var(--text-faint)', background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', width: '100%', textAlign: 'left' }}> style={{ display: 'flex', alignItems: 'center', justifyContent: isMobile ? 'center' : 'flex-start', gap: isMobile ? 0 : 6, padding: isMobile ? '8px 0' : '7px 12px', fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', width: '100%', textAlign: 'left' }}>
<Plus size={isMobile ? 18 : 13} /> {!isMobile && t('todo.addCategory')} <Plus size={isMobile ? 18 : 13} /> {!isMobile && t('todo.addCategory')}
</button> </button>
) )
@@ -165,10 +165,10 @@ export default function TodoListPanel({ tripId, items, addItemSignal = 0 }: { tr
{/* Header */} {/* Header */}
<div style={{ padding: '16px 20px 12px', borderBottom: '1px solid var(--border-faint)' }}> <div style={{ padding: '16px 20px 12px', borderBottom: '1px solid var(--border-faint)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<h2 style={{ margin: 0, fontSize: 22, fontWeight: 700, color: 'var(--text-primary)', letterSpacing: '-0.02em' }}> <h2 style={{ margin: 0, fontSize: 'calc(22px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', letterSpacing: '-0.02em' }}>
{filterTitle} {filterTitle}
</h2> </h2>
<span style={{ fontSize: 13, color: 'var(--text-faint)', background: 'var(--bg-hover)', borderRadius: 6, padding: '2px 8px', fontWeight: 600 }}> <span style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', background: 'var(--bg-hover)', borderRadius: 6, padding: '2px 8px', fontWeight: 600 }}>
{filtered.length} {filtered.length}
</span> </span>
</div> </div>
@@ -318,7 +318,7 @@ function DetailPane({ item, tripId, categories, members, onClose }: {
const labelClass = 'block text-xs font-medium text-content-secondary mb-1' const labelClass = 'block text-xs font-medium text-content-secondary mb-1'
const inputStyle: React.CSSProperties = { const inputStyle: React.CSSProperties = {
width: '100%', fontSize: 13, padding: '8px 10px', border: '1px solid var(--border-primary)', width: '100%', fontSize: 'calc(13px * var(--fs-scale-text, 1))', padding: '8px 10px', border: '1px solid var(--border-primary)',
borderRadius: 8, background: 'var(--bg-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', borderRadius: 8, background: 'var(--bg-primary)', color: 'var(--text-primary)', fontFamily: 'inherit',
} }
@@ -329,7 +329,7 @@ function DetailPane({ item, tripId, categories, members, onClose }: {
}}> }}>
{/* Header */} {/* Header */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 20px 12px', borderBottom: '1px solid var(--border-faint)' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 20px 12px', borderBottom: '1px solid var(--border-faint)' }}>
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-primary)' }}>{t('todo.detail.title')}</span> <span style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)' }}>{t('todo.detail.title')}</span>
<button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 4 }}> <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 4 }}>
<X size={16} /> <X size={16} />
</button> </button>
@@ -340,7 +340,7 @@ function DetailPane({ item, tripId, categories, members, onClose }: {
{/* Name */} {/* Name */}
<div> <div>
<input value={name} onChange={e => setName(e.target.value)} disabled={!canEdit} <input value={name} onChange={e => setName(e.target.value)} disabled={!canEdit}
style={{ ...inputStyle, fontSize: 15, fontWeight: 600, border: 'none', padding: '4px 0', background: 'transparent' }} style={{ ...inputStyle, fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 600, border: 'none', padding: '4px 0', background: 'transparent' }}
placeholder={t('todo.namePlaceholder')} /> placeholder={t('todo.namePlaceholder')} />
</div> </div>
@@ -362,7 +362,7 @@ function DetailPane({ item, tripId, categories, members, onClose }: {
return ( return (
<button key={p} onClick={() => canEdit && setPriority(p)} <button key={p} onClick={() => canEdit && setPriority(p)}
style={{ style={{
flex: 1, padding: '6px 0', borderRadius: 6, fontSize: 11, fontWeight: 600, cursor: canEdit ? 'pointer' : 'default', flex: 1, padding: '6px 0', borderRadius: 6, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: canEdit ? 'pointer' : 'default',
fontFamily: 'inherit', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4, fontFamily: 'inherit', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4,
border: `1px solid ${isActive && cfg ? cfg.color + '40' : 'var(--border-primary)'}`, border: `1px solid ${isActive && cfg ? cfg.color + '40' : 'var(--border-primary)'}`,
background: isActive && cfg ? cfg.color + '12' : 'transparent', background: isActive && cfg ? cfg.color + '12' : 'transparent',
@@ -387,7 +387,7 @@ function DetailPane({ item, tripId, categories, members, onClose }: {
onChange={e => setCategory(e.target.value)} onChange={e => setCategory(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') setAddingCategoryInline(false); if (e.key === 'Escape') { setCategory(''); setAddingCategoryInline(false) } }} onKeyDown={e => { if (e.key === 'Enter') setAddingCategoryInline(false); if (e.key === 'Escape') { setCategory(''); setAddingCategoryInline(false) } }}
placeholder={t('todo.newCategory')} placeholder={t('todo.newCategory')}
style={{ flex: 1, fontSize: 13, padding: '8px 10px', border: '1px solid var(--border-primary)', borderRadius: 8, background: 'var(--bg-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }} style={{ flex: 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', padding: '8px 10px', border: '1px solid var(--border-primary)', borderRadius: 8, background: 'var(--bg-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }}
/> />
<button type="button" onClick={() => setAddingCategoryInline(false)} <button type="button" onClick={() => setAddingCategoryInline(false)}
style={{ background: 'var(--bg-hover)', border: '1px solid var(--border-primary)', borderRadius: 8, padding: '0 10px', cursor: 'pointer', color: 'var(--text-primary)' }}> style={{ background: 'var(--bg-hover)', border: '1px solid var(--border-primary)', borderRadius: 8, padding: '0 10px', cursor: 'pointer', color: 'var(--text-primary)' }}>
@@ -450,7 +450,7 @@ function DetailPane({ item, tripId, categories, members, onClose }: {
icon: m.avatar ? ( icon: m.avatar ? (
<img src={`/uploads/avatars/${m.avatar}`} style={{ width: 18, height: 18, borderRadius: '50%', objectFit: 'cover' as const }} alt="" /> <img src={`/uploads/avatars/${m.avatar}`} style={{ width: 18, height: 18, borderRadius: '50%', objectFit: 'cover' as const }} alt="" />
) : ( ) : (
<span style={{ width: 18, height: 18, borderRadius: '50%', background: 'var(--border-primary)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, color: 'var(--text-faint)', fontWeight: 600 }}> <span style={{ width: 18, height: 18, borderRadius: '50%', background: 'var(--border-primary)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 600 }}>
{m.username.charAt(0).toUpperCase()} {m.username.charAt(0).toUpperCase()}
</span> </span>
), ),
@@ -468,7 +468,7 @@ function DetailPane({ item, tripId, categories, members, onClose }: {
<div style={{ padding: '12px 20px', borderTop: '1px solid var(--border-faint)', display: 'flex', gap: 8 }}> <div style={{ padding: '12px 20px', borderTop: '1px solid var(--border-faint)', display: 'flex', gap: 8 }}>
<button onClick={handleDelete} <button onClick={handleDelete}
style={{ style={{
flex: 1, padding: '9px 16px', borderRadius: 8, fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', flex: 1, padding: '9px 16px', borderRadius: 8, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit',
border: '1px solid var(--border-primary)', background: 'transparent', color: 'var(--text-secondary)', border: '1px solid var(--border-primary)', background: 'transparent', color: 'var(--text-secondary)',
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
}}> }}>
@@ -477,7 +477,7 @@ function DetailPane({ item, tripId, categories, members, onClose }: {
</button> </button>
<button onClick={save} disabled={!hasChanges || saving} <button onClick={save} disabled={!hasChanges || saving}
style={{ style={{
flex: 1, padding: '9px 16px', borderRadius: 8, fontSize: 12, fontWeight: 600, cursor: hasChanges ? 'pointer' : 'default', fontFamily: 'inherit', flex: 1, padding: '9px 16px', borderRadius: 8, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: hasChanges ? 'pointer' : 'default', fontFamily: 'inherit',
border: 'none', background: hasChanges ? 'var(--text-primary)' : 'var(--border-faint)', border: 'none', background: hasChanges ? 'var(--text-primary)' : 'var(--border-faint)',
color: hasChanges ? 'var(--bg-primary)' : 'var(--text-faint)', color: hasChanges ? 'var(--bg-primary)' : 'var(--text-faint)',
transition: 'all 0.15s', transition: 'all 0.15s',
@@ -532,7 +532,7 @@ function NewTaskPane({ tripId, categories, members, defaultCategory, onCreated,
display: 'flex', flexDirection: 'column', background: 'var(--bg-primary)', display: 'flex', flexDirection: 'column', background: 'var(--bg-primary)',
}}> }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 20px 12px', borderBottom: '1px solid var(--border-faint)' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 20px 12px', borderBottom: '1px solid var(--border-faint)' }}>
<span style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-primary)' }}>{t('todo.newItem')}</span> <span style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)' }}>{t('todo.newItem')}</span>
<button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 4 }}> <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 4 }}>
<X size={16} /> <X size={16} />
</button> </button>
@@ -542,7 +542,7 @@ function NewTaskPane({ tripId, categories, members, defaultCategory, onCreated,
<div> <div>
<input autoFocus value={name} onChange={e => setName(e.target.value)} <input autoFocus value={name} onChange={e => setName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter' && name.trim()) create() }} onKeyDown={e => { if (e.key === 'Enter' && name.trim()) create() }}
style={{ width: '100%', fontSize: 15, fontWeight: 600, border: 'none', padding: '4px 0', background: 'transparent', color: 'var(--text-primary)', outline: 'none', fontFamily: 'inherit' }} style={{ width: '100%', fontSize: 'calc(15px * var(--fs-scale-text, 1))', fontWeight: 600, border: 'none', padding: '4px 0', background: 'transparent', color: 'var(--text-primary)', outline: 'none', fontFamily: 'inherit' }}
placeholder={t('todo.namePlaceholder')} /> placeholder={t('todo.namePlaceholder')} />
</div> </div>
@@ -550,7 +550,7 @@ function NewTaskPane({ tripId, categories, members, defaultCategory, onCreated,
<label className={labelClass}>{t('todo.detail.description')}</label> <label className={labelClass}>{t('todo.detail.description')}</label>
<textarea value={desc} onChange={e => setDesc(e.target.value)} rows={4} <textarea value={desc} onChange={e => setDesc(e.target.value)} rows={4}
placeholder={t('todo.descriptionPlaceholder')} placeholder={t('todo.descriptionPlaceholder')}
style={{ width: '100%', fontSize: 13, padding: '8px 10px', border: '1px solid var(--border-primary)', borderRadius: 8, background: 'var(--bg-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', resize: 'vertical', minHeight: 80 }} /> style={{ width: '100%', fontSize: 'calc(13px * var(--fs-scale-text, 1))', padding: '8px 10px', border: '1px solid var(--border-primary)', borderRadius: 8, background: 'var(--bg-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', resize: 'vertical', minHeight: 80 }} />
</div> </div>
<div> <div>
@@ -563,7 +563,7 @@ function NewTaskPane({ tripId, categories, members, defaultCategory, onCreated,
onChange={e => setCategory(e.target.value)} onChange={e => setCategory(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') setAddingCategoryInline(false); if (e.key === 'Escape') { setCategory(''); setAddingCategoryInline(false) } }} onKeyDown={e => { if (e.key === 'Enter') setAddingCategoryInline(false); if (e.key === 'Escape') { setCategory(''); setAddingCategoryInline(false) } }}
placeholder={t('todo.newCategory')} placeholder={t('todo.newCategory')}
style={{ flex: 1, fontSize: 13, padding: '8px 10px', border: '1px solid var(--border-primary)', borderRadius: 8, background: 'var(--bg-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }} style={{ flex: 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', padding: '8px 10px', border: '1px solid var(--border-primary)', borderRadius: 8, background: 'var(--bg-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }}
/> />
<button type="button" onClick={() => setAddingCategoryInline(false)} <button type="button" onClick={() => setAddingCategoryInline(false)}
style={{ background: 'var(--bg-hover)', border: '1px solid var(--border-primary)', borderRadius: 8, padding: '0 10px', cursor: 'pointer', color: 'var(--text-primary)' }}> style={{ background: 'var(--bg-hover)', border: '1px solid var(--border-primary)', borderRadius: 8, padding: '0 10px', cursor: 'pointer', color: 'var(--text-primary)' }}>
@@ -609,7 +609,7 @@ function NewTaskPane({ tripId, categories, members, defaultCategory, onCreated,
return ( return (
<button key={p} onClick={() => setPriority(p)} <button key={p} onClick={() => setPriority(p)}
style={{ style={{
flex: 1, padding: '6px 0', borderRadius: 6, fontSize: 11, fontWeight: 600, cursor: 'pointer', flex: 1, padding: '6px 0', borderRadius: 6, fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer',
fontFamily: 'inherit', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4, fontFamily: 'inherit', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4,
border: `1px solid ${isActive && cfg ? cfg.color + '40' : 'var(--border-primary)'}`, border: `1px solid ${isActive && cfg ? cfg.color + '40' : 'var(--border-primary)'}`,
background: isActive && cfg ? cfg.color + '12' : 'transparent', background: isActive && cfg ? cfg.color + '12' : 'transparent',
@@ -640,7 +640,7 @@ function NewTaskPane({ tripId, categories, members, defaultCategory, onCreated,
icon: m.avatar ? ( icon: m.avatar ? (
<img src={`/uploads/avatars/${m.avatar}`} style={{ width: 18, height: 18, borderRadius: '50%', objectFit: 'cover' as const }} alt="" /> <img src={`/uploads/avatars/${m.avatar}`} style={{ width: 18, height: 18, borderRadius: '50%', objectFit: 'cover' as const }} alt="" />
) : ( ) : (
<span style={{ width: 18, height: 18, borderRadius: '50%', background: 'var(--border-primary)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, color: 'var(--text-faint)', fontWeight: 600 }}> <span style={{ width: 18, height: 18, borderRadius: '50%', background: 'var(--border-primary)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 'calc(10px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 600 }}>
{m.username.charAt(0).toUpperCase()} {m.username.charAt(0).toUpperCase()}
</span> </span>
), ),
@@ -655,7 +655,7 @@ function NewTaskPane({ tripId, categories, members, defaultCategory, onCreated,
<div style={{ padding: '12px 20px', borderTop: '1px solid var(--border-faint)' }}> <div style={{ padding: '12px 20px', borderTop: '1px solid var(--border-faint)' }}>
<button onClick={create} disabled={!name.trim() || saving} <button onClick={create} disabled={!name.trim() || saving}
style={{ style={{
width: '100%', padding: '9px 16px', borderRadius: 8, fontSize: 12, fontWeight: 600, cursor: name.trim() ? 'pointer' : 'default', fontFamily: 'inherit', width: '100%', padding: '9px 16px', borderRadius: 8, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: name.trim() ? 'pointer' : 'default', fontFamily: 'inherit',
border: 'none', background: name.trim() ? 'var(--text-primary)' : 'var(--border-faint)', border: 'none', background: name.trim() ? 'var(--text-primary)' : 'var(--border-faint)',
color: name.trim() ? 'var(--bg-primary)' : 'var(--text-faint)', transition: 'all 0.15s', color: name.trim() ? 'var(--bg-primary)' : 'var(--text-faint)', transition: 'all 0.15s',
}}> }}>
+7 -7
View File
@@ -42,7 +42,7 @@ export default function TodoRow({ item, members, categories, today, isSelected,
{/* Content */} {/* Content */}
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ <div style={{
fontSize: 14, color: done ? 'var(--text-faint)' : 'var(--text-primary)', fontSize: 'calc(14px * var(--fs-scale-text, 1))', color: done ? 'var(--text-faint)' : 'var(--text-primary)',
textDecoration: done ? 'line-through' : 'none', lineHeight: 1.4, textDecoration: done ? 'line-through' : 'none', lineHeight: 1.4,
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
}}> }}>
@@ -50,7 +50,7 @@ export default function TodoRow({ item, members, categories, today, isSelected,
</div> </div>
{/* Description preview */} {/* Description preview */}
{item.description && ( {item.description && (
<div style={{ fontSize: 12, color: 'var(--text-faint)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.4 }}> <div style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.4 }}>
{item.description} {item.description}
</div> </div>
)} )}
@@ -59,7 +59,7 @@ export default function TodoRow({ item, members, categories, today, isSelected,
<div style={{ display: 'flex', gap: 5, marginTop: 5, flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: 5, marginTop: 5, flexWrap: 'wrap' }}>
{item.priority > 0 && PRIO_CONFIG[item.priority] && ( {item.priority > 0 && PRIO_CONFIG[item.priority] && (
<span style={{ <span style={{
fontSize: 10, display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 'calc(10px * var(--fs-scale-text, 1))', display: 'inline-flex', alignItems: 'center', gap: 3,
padding: '2px 7px', borderRadius: 5, fontWeight: 600, padding: '2px 7px', borderRadius: 5, fontWeight: 600,
color: PRIO_CONFIG[item.priority].color, color: PRIO_CONFIG[item.priority].color,
background: `${PRIO_CONFIG[item.priority].color}10`, background: `${PRIO_CONFIG[item.priority].color}10`,
@@ -70,7 +70,7 @@ export default function TodoRow({ item, members, categories, today, isSelected,
)} )}
{item.due_date && ( {item.due_date && (
<span style={{ <span style={{
fontSize: 10, display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 'calc(10px * var(--fs-scale-text, 1))', display: 'inline-flex', alignItems: 'center', gap: 3,
padding: '2px 7px', borderRadius: 5, fontWeight: 500, padding: '2px 7px', borderRadius: 5, fontWeight: 500,
color: isOverdue ? '#ef4444' : 'var(--text-secondary)', color: isOverdue ? '#ef4444' : 'var(--text-secondary)',
background: isOverdue ? 'rgba(239,68,68,0.08)' : 'var(--bg-hover)', background: isOverdue ? 'rgba(239,68,68,0.08)' : 'var(--bg-hover)',
@@ -81,7 +81,7 @@ export default function TodoRow({ item, members, categories, today, isSelected,
)} )}
{catColor && ( {catColor && (
<span style={{ <span style={{
fontSize: 10, display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 'calc(10px * var(--fs-scale-text, 1))', display: 'inline-flex', alignItems: 'center', gap: 4,
padding: '2px 7px', borderRadius: 5, fontWeight: 500, padding: '2px 7px', borderRadius: 5, fontWeight: 500,
color: 'var(--text-secondary)', background: 'var(--bg-hover)', color: 'var(--text-secondary)', background: 'var(--bg-hover)',
border: '1px solid var(--border-faint)', border: '1px solid var(--border-faint)',
@@ -92,7 +92,7 @@ export default function TodoRow({ item, members, categories, today, isSelected,
)} )}
{assignedUser && ( {assignedUser && (
<span style={{ <span style={{
fontSize: 10, display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 'calc(10px * var(--fs-scale-text, 1))', display: 'inline-flex', alignItems: 'center', gap: 4,
padding: '2px 7px', borderRadius: 5, fontWeight: 500, padding: '2px 7px', borderRadius: 5, fontWeight: 500,
color: 'var(--text-secondary)', background: 'var(--bg-hover)', color: 'var(--text-secondary)', background: 'var(--bg-hover)',
border: '1px solid var(--border-faint)', border: '1px solid var(--border-faint)',
@@ -100,7 +100,7 @@ export default function TodoRow({ item, members, categories, today, isSelected,
{assignedUser.avatar ? ( {assignedUser.avatar ? (
<img src={`/uploads/avatars/${assignedUser.avatar}`} style={{ width: 13, height: 13, borderRadius: '50%', objectFit: 'cover' }} alt="" /> <img src={`/uploads/avatars/${assignedUser.avatar}`} style={{ width: 13, height: 13, borderRadius: '50%', objectFit: 'cover' }} alt="" />
) : ( ) : (
<span style={{ width: 13, height: 13, borderRadius: '50%', background: 'var(--border-primary)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, color: 'var(--text-faint)', fontWeight: 700 }}> <span style={{ width: 13, height: 13, borderRadius: '50%', background: 'var(--border-primary)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 'calc(7px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', fontWeight: 700 }}>
{assignedUser.username.charAt(0).toUpperCase()} {assignedUser.username.charAt(0).toUpperCase()}
</span> </span>
)} )}
@@ -343,7 +343,7 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
<img src={coverPreview} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> <img src={coverPreview} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
<div style={{ position: 'absolute', bottom: 8, right: 8, display: 'flex', gap: 6 }}> <div style={{ position: 'absolute', bottom: 8, right: 8, display: 'flex', gap: 6 }}>
<button type="button" onClick={() => fileRef.current?.click()} disabled={uploadingCover} <button type="button" onClick={() => fileRef.current?.click()} disabled={uploadingCover}
style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '5px 10px', borderRadius: 8, background: 'rgba(0,0,0,0.55)', border: 'none', color: 'white', fontSize: 11.5, fontWeight: 600, cursor: 'pointer', backdropFilter: 'blur(4px)' }}> style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '5px 10px', borderRadius: 8, background: 'rgba(0,0,0,0.55)', border: 'none', color: 'white', fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', backdropFilter: 'blur(4px)' }}>
<Camera size={12} /> {uploadingCover ? t('common.uploading') : t('common.change')} <Camera size={12} /> {uploadingCover ? t('common.uploading') : t('common.change')}
</button> </button>
<button type="button" onClick={handleRemoveCover} <button type="button" onClick={handleRemoveCover}
@@ -357,7 +357,7 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
onDragOver={e => { e.preventDefault(); e.currentTarget.style.borderColor = '#6366f1'; e.currentTarget.style.background = 'rgba(99,102,241,0.04)' }} onDragOver={e => { e.preventDefault(); e.currentTarget.style.borderColor = '#6366f1'; e.currentTarget.style.background = 'rgba(99,102,241,0.04)' }}
onDragLeave={e => { e.currentTarget.style.borderColor = '#e5e7eb'; e.currentTarget.style.background = 'none' }} onDragLeave={e => { e.currentTarget.style.borderColor = '#e5e7eb'; e.currentTarget.style.background = 'none' }}
onDrop={e => { e.preventDefault(); e.currentTarget.style.borderColor = '#e5e7eb'; e.currentTarget.style.background = 'none'; const file = e.dataTransfer.files?.[0]; if (file?.type.startsWith('image/')) handleCoverSelect(file) }} onDrop={e => { e.preventDefault(); e.currentTarget.style.borderColor = '#e5e7eb'; e.currentTarget.style.background = 'none'; const file = e.dataTransfer.files?.[0]; if (file?.type.startsWith('image/')) handleCoverSelect(file) }}
style={{ width: '100%', padding: '18px', border: '2px dashed #e5e7eb', borderRadius: 10, background: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, fontSize: 13, color: '#9ca3af', fontFamily: 'inherit', transition: 'all 0.15s' }} style={{ width: '100%', padding: '18px', border: '2px dashed #e5e7eb', borderRadius: 10, background: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: '#9ca3af', fontFamily: 'inherit', transition: 'all 0.15s' }}
onMouseEnter={e => { e.currentTarget.style.borderColor = '#d1d5db'; e.currentTarget.style.color = '#6b7280' }} onMouseEnter={e => { e.currentTarget.style.borderColor = '#d1d5db'; e.currentTarget.style.color = '#6b7280' }}
onMouseLeave={e => { e.currentTarget.style.borderColor = '#e5e7eb'; e.currentTarget.style.color = '#9ca3af' }}> onMouseLeave={e => { e.currentTarget.style.borderColor = '#e5e7eb'; e.currentTarget.style.color = '#9ca3af' }}>
<Camera size={15} /> {uploadingCover ? t('common.uploading') : t('dashboard.addCoverImage')} <Camera size={15} /> {uploadingCover ? t('common.uploading') : t('dashboard.addCoverImage')}
@@ -523,7 +523,7 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
}} }}
style={{ style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '4px 10px', borderRadius: 99, display: 'flex', alignItems: 'center', gap: 5, padding: '4px 10px', borderRadius: 99,
fontSize: 12, fontWeight: 500, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: m.id === currentUser?.id ? 'default' : 'pointer', cursor: m.id === currentUser?.id ? 'default' : 'pointer',
}}> }}>
{m.username} {m.username}
@@ -543,7 +543,7 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
className="bg-surface-secondary text-content border border-edge cursor-pointer" className="bg-surface-secondary text-content border border-edge cursor-pointer"
style={{ style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '4px 10px', borderRadius: 99, display: 'flex', alignItems: 'center', gap: 5, padding: '4px 10px', borderRadius: 99,
fontSize: 12, fontWeight: 500, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500,
}}> }}>
{user.username} {user.username}
<X size={11} className="text-content-faint" /> <X size={11} className="text-content-faint" />
@@ -93,9 +93,9 @@ function ShareLinkSection({ tripId, t }: { tripId: number; t: (key: string, para
<div> <div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 10 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 10 }}>
<Link2 size={14} className="text-content-muted" /> <Link2 size={14} className="text-content-muted" />
<span className="text-content" style={{ fontSize: 13, fontWeight: 600 }}>{t('share.linkTitle')}</span> <span className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{t('share.linkTitle')}</span>
</div> </div>
<p className="text-content-faint" style={{ fontSize: 11, marginBottom: 10, lineHeight: 1.5 }}>{t('share.linkHint')}</p> <p className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', marginBottom: 10, lineHeight: 1.5 }}>{t('share.linkHint')}</p>
{/* Permission checkboxes */} {/* Permission checkboxes */}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 12 }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 12 }}>
@@ -109,7 +109,7 @@ function ShareLinkSection({ tripId, t }: { tripId: number; t: (key: string, para
<button key={opt.key} onClick={() => !opt.always && handleUpdatePerms(opt.key, !perms[opt.key])} <button key={opt.key} onClick={() => !opt.always && handleUpdatePerms(opt.key, !perms[opt.key])}
style={{ style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '4px 10px', borderRadius: 20, display: 'flex', alignItems: 'center', gap: 5, padding: '4px 10px', borderRadius: 20,
border: '1.5px solid', fontSize: 11, fontWeight: 500, cursor: opt.always ? 'default' : 'pointer', border: '1.5px solid', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500, cursor: opt.always ? 'default' : 'pointer',
fontFamily: 'inherit', transition: 'all 0.12s', fontFamily: 'inherit', transition: 'all 0.12s',
background: perms[opt.key] ? 'var(--text-primary)' : 'transparent', background: perms[opt.key] ? 'var(--text-primary)' : 'transparent',
borderColor: perms[opt.key] ? 'var(--text-primary)' : 'var(--border-primary)', borderColor: perms[opt.key] ? 'var(--text-primary)' : 'var(--border-primary)',
@@ -129,13 +129,13 @@ function ShareLinkSection({ tripId, t }: { tripId: number; t: (key: string, para
borderRadius: 8, borderRadius: 8,
}}> }}>
<input type="text" value={shareUrl} readOnly className="text-content" style={{ <input type="text" value={shareUrl} readOnly className="text-content" style={{
flex: 1, border: 'none', background: 'none', fontSize: 11, flex: 1, border: 'none', background: 'none', fontSize: 'calc(11px * var(--fs-scale-text, 1))',
outline: 'none', fontFamily: 'monospace', outline: 'none', fontFamily: 'monospace',
}} /> }} />
<button onClick={handleCopy} style={{ <button onClick={handleCopy} style={{
display: 'flex', alignItems: 'center', gap: 4, padding: '4px 8px', borderRadius: 6, display: 'flex', alignItems: 'center', gap: 4, padding: '4px 8px', borderRadius: 6,
border: 'none', background: copied ? '#16a34a' : 'var(--accent)', color: copied ? 'white' : 'var(--accent-text)', border: 'none', background: copied ? '#16a34a' : 'var(--accent)', color: copied ? 'white' : 'var(--accent-text)',
fontSize: 10, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', transition: 'background 0.2s', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', transition: 'background 0.2s',
}}> }}>
{copied ? <><Check size={10} /> {t('common.copied')}</> : <><Copy size={10} /> {t('common.copy')}</>} {copied ? <><Check size={10} /> {t('common.copied')}</> : <><Copy size={10} /> {t('common.copy')}</>}
</button> </button>
@@ -143,7 +143,7 @@ function ShareLinkSection({ tripId, t }: { tripId: number; t: (key: string, para
<button onClick={handleDelete} style={{ <button onClick={handleDelete} style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
padding: '6px 0', borderRadius: 8, border: '1px solid rgba(239,68,68,0.3)', padding: '6px 0', borderRadius: 8, border: '1px solid rgba(239,68,68,0.3)',
background: 'rgba(239,68,68,0.06)', color: '#ef4444', fontSize: 11, fontWeight: 500, background: 'rgba(239,68,68,0.06)', color: '#ef4444', fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
<Trash2 size={11} /> {t('share.deleteLink')} <Trash2 size={11} /> {t('share.deleteLink')}
@@ -153,7 +153,7 @@ function ShareLinkSection({ tripId, t }: { tripId: number; t: (key: string, para
<button onClick={handleCreate} className="border border-dashed border-edge text-content-muted" style={{ <button onClick={handleCreate} className="border border-dashed border-edge text-content-muted" style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
width: '100%', padding: '8px 0', borderRadius: 8, width: '100%', padding: '8px 0', borderRadius: 8,
background: 'none', fontSize: 12, fontWeight: 500, background: 'none', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit', cursor: 'pointer', fontFamily: 'inherit',
}}> }}>
<Link2 size={12} /> {t('share.createLink')} <Link2 size={12} /> {t('share.createLink')}
@@ -267,13 +267,13 @@ export default function TripMembersModal({ isOpen, onClose, tripId, tripTitle }:
{/* Trip name */} {/* Trip name */}
<div className="bg-surface-secondary border border-edge-secondary" style={{ padding: '10px 14px', borderRadius: 10 }}> <div className="bg-surface-secondary border border-edge-secondary" style={{ padding: '10px 14px', borderRadius: 10 }}>
<div className="text-content-faint" style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>{t('nav.trip')}</div> <div className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>{t('nav.trip')}</div>
<div className="text-content" style={{ fontSize: 14, fontWeight: 600 }}>{tripTitle}</div> <div className="text-content" style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{tripTitle}</div>
</div> </div>
{/* Add member dropdown */} {/* Add member dropdown */}
{canManageMembers && <div> {canManageMembers && <div>
<label className="text-content-secondary" style={{ display: 'block', fontSize: 12, fontWeight: 600, marginBottom: 8 }}> <label className="text-content-secondary" style={{ display: 'block', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, marginBottom: 8 }}>
{t('members.inviteUser')} {t('members.inviteUser')}
</label> </label>
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
@@ -298,7 +298,7 @@ export default function TripMembersModal({ isOpen, onClose, tripId, tripTitle }:
style={{ style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '8px 14px', display: 'flex', alignItems: 'center', gap: 5, padding: '8px 14px',
background: 'var(--accent)', color: 'var(--accent-text)', border: 'none', borderRadius: 10, background: 'var(--accent)', color: 'var(--accent-text)', border: 'none', borderRadius: 10,
fontSize: 13, fontWeight: 600, cursor: adding || !selectedUserId ? 'default' : 'pointer', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: adding || !selectedUserId ? 'default' : 'pointer',
fontFamily: 'inherit', opacity: adding || !selectedUserId ? 0.4 : 1, flexShrink: 0, fontFamily: 'inherit', opacity: adding || !selectedUserId ? 0.4 : 1, flexShrink: 0,
}} }}
> >
@@ -306,7 +306,7 @@ export default function TripMembersModal({ isOpen, onClose, tripId, tripTitle }:
</button> </button>
</div> </div>
{availableUsers.length === 0 && allUsers.length > 0 && canManageMembers && ( {availableUsers.length === 0 && allUsers.length > 0 && canManageMembers && (
<p className="text-content-faint" style={{ fontSize: 11.5, margin: '6px 0 0' }}>{t('members.allHaveAccess')}</p> <p className="text-content-faint" style={{ fontSize: 'calc(11.5px * var(--fs-scale-text, 1))', margin: '6px 0 0' }}>{t('members.allHaveAccess')}</p>
)} )}
</div>} </div>}
@@ -314,7 +314,7 @@ export default function TripMembersModal({ isOpen, onClose, tripId, tripTitle }:
<div> <div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 10 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 10 }}>
<Users size={13} className="text-content-faint" /> <Users size={13} className="text-content-faint" />
<span className="text-content-secondary" style={{ fontSize: 12, fontWeight: 600 }}> <span className="text-content-secondary" style={{ fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600 }}>
{t('members.access')} ({allMembers.length} {allMembers.length === 1 ? t('members.person') : t('members.persons')}) {t('members.access')} ({allMembers.length} {allMembers.length === 1 ? t('members.person') : t('members.persons')})
</span> </span>
</div> </div>
@@ -338,10 +338,10 @@ export default function TripMembersModal({ isOpen, onClose, tripId, tripTitle }:
<Avatar username={member.username} avatarUrl={member.avatar_url} /> <Avatar username={member.username} avatarUrl={member.avatar_url} />
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
<span className="text-content" style={{ fontSize: 13, fontWeight: 600 }}>{member.username}</span> <span className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600 }}>{member.username}</span>
{isSelf && <span className="text-content-faint" style={{ fontSize: 10 }}>({t('members.you')})</span>} {isSelf && <span className="text-content-faint" style={{ fontSize: 'calc(10px * var(--fs-scale-text, 1))' }}>({t('members.you')})</span>}
{member.role === 'owner' && ( {member.role === 'owner' && (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 10, fontWeight: 700, color: '#d97706', background: '#fef9c3', padding: '1px 6px', borderRadius: 99 }}> <span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, color: '#d97706', background: '#fef9c3', padding: '1px 6px', borderRadius: 99 }}>
<Crown size={9} /> {t('members.owner')} <Crown size={9} /> {t('members.owner')}
</span> </span>
)} )}
@@ -71,7 +71,7 @@ export default function VacaySettings({ onClose }: VacaySettingsProps) {
updatePlan({ weekend_days: next.join(',') }) updatePlan({ weekend_days: next.join(',') })
}} }}
style={{ style={{
padding: '4px 10px', borderRadius: 8, fontSize: 12, fontWeight: 600, cursor: 'pointer', padding: '4px 10px', borderRadius: 8, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer',
fontFamily: 'inherit', border: '1px solid', transition: 'all 0.12s', fontFamily: 'inherit', border: '1px solid', transition: 'all 0.12s',
background: active ? 'var(--text-primary)' : 'var(--bg-card)', background: active ? 'var(--text-primary)' : 'var(--bg-card)',
borderColor: active ? 'var(--text-primary)' : 'var(--border-primary)', borderColor: active ? 'var(--text-primary)' : 'var(--border-primary)',
@@ -103,7 +103,7 @@ export default function VacaySettings({ onClose }: VacaySettingsProps) {
return ( return (
<button key={value} onClick={() => updatePlan({ week_start: value })} <button key={value} onClick={() => updatePlan({ week_start: value })}
style={{ style={{
padding: '4px 10px', borderRadius: 8, fontSize: 12, fontWeight: 600, cursor: 'pointer', padding: '4px 10px', borderRadius: 8, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 600, cursor: 'pointer',
fontFamily: 'inherit', border: '1px solid', transition: 'all 0.12s', fontFamily: 'inherit', border: '1px solid', transition: 'all 0.12s',
background: active ? 'var(--text-primary)' : 'var(--bg-card)', background: active ? 'var(--text-primary)' : 'var(--bg-card)',
borderColor: active ? 'var(--text-primary)' : 'var(--border-primary)', borderColor: active ? 'var(--text-primary)' : 'var(--border-primary)',
@@ -331,7 +331,7 @@ function CalendarRow({ cal, countries, onUpdate, onDelete }: {
onBlur={() => { const v = localLabel.trim() || null; if (v !== cal.label) onUpdate({ label: v }) }} onBlur={() => { const v = localLabel.trim() || null; if (v !== cal.label) onUpdate({ label: v }) }}
onKeyDown={e => { if (e.key === 'Enter') (e.target as HTMLInputElement).blur() }} onKeyDown={e => { if (e.key === 'Enter') (e.target as HTMLInputElement).blur() }}
placeholder={t('vacay.calendarLabel')} placeholder={t('vacay.calendarLabel')}
style={{ width: '100%', fontSize: 12, padding: '6px 10px', borderRadius: 8, background: 'var(--bg-input)', border: '1px solid var(--border-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }} style={{ width: '100%', fontSize: 'calc(12px * var(--fs-scale-text, 1))', padding: '6px 10px', borderRadius: 8, background: 'var(--bg-input)', border: '1px solid var(--border-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }}
/> />
<CustomSelect <CustomSelect
value={selectedCountry} value={selectedCountry}
@@ -413,7 +413,7 @@ function AddCalendarForm({ countries, onAdd, onCancel }: {
value={label} value={label}
onChange={e => setLabel(e.target.value)} onChange={e => setLabel(e.target.value)}
placeholder={t('vacay.calendarLabel')} placeholder={t('vacay.calendarLabel')}
style={{ width: '100%', fontSize: 12, padding: '6px 10px', borderRadius: 8, background: 'var(--bg-input)', border: '1px solid var(--border-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }} style={{ width: '100%', fontSize: 'calc(12px * var(--fs-scale-text, 1))', padding: '6px 10px', borderRadius: 8, background: 'var(--bg-input)', border: '1px solid var(--border-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }}
/> />
<CustomSelect <CustomSelect
value={selectedCountry} value={selectedCountry}
@@ -98,13 +98,13 @@ export default function WeatherWidget({ lat, lng, date, compact = false, stacked
if (loading) { if (loading) {
return ( return (
<span style={{ fontSize: 11, color: '#d1d5db', ...fontStyle }}></span> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: '#d1d5db', ...fontStyle }}></span>
) )
} }
if (failed || !weather) { if (failed || !weather) {
return ( return (
<span style={{ fontSize: 11, color: '#9ca3af', ...fontStyle }}></span> <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: '#9ca3af', ...fontStyle }}></span>
) )
} }
@@ -115,7 +115,7 @@ export default function WeatherWidget({ lat, lng, date, compact = false, stacked
if (stacked) { if (stacked) {
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, fontSize: 9.5, fontWeight: 600, lineHeight: 1, color: 'inherit', ...fontStyle }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, fontSize: 'calc(9.5px * var(--fs-scale-text, 1))', fontWeight: 600, lineHeight: 1, color: 'inherit', ...fontStyle }}>
<WeatherIcon main={weather.main} size={13} /> <WeatherIcon main={weather.main} size={13} />
{temp !== null && <span>{isClimate ? 'Ø' : ''}{temp}°</span>} {temp !== null && <span>{isClimate ? 'Ø' : ''}{temp}°</span>}
</div> </div>
@@ -124,7 +124,7 @@ export default function WeatherWidget({ lat, lng, date, compact = false, stacked
if (compact) { if (compact) {
return ( return (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 11, color: isClimate ? '#a1a1aa' : '#6b7280', ...fontStyle }}> <span style={{ display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: isClimate ? '#a1a1aa' : '#6b7280', ...fontStyle }}>
<WeatherIcon main={weather.main} size={12} /> <WeatherIcon main={weather.main} size={12} />
{temp !== null && <span>{isClimate ? 'Ø ' : ''}{temp}{unit}</span>} {temp !== null && <span>{isClimate ? 'Ø ' : ''}{temp}{unit}</span>}
</span> </span>
@@ -132,10 +132,10 @@ export default function WeatherWidget({ lat, lng, date, compact = false, stacked
} }
return ( return (
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, color: isClimate ? '#71717a' : '#374151', background: 'rgba(0,0,0,0.04)', borderRadius: 8, padding: '5px 10px', ...fontStyle }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: isClimate ? '#71717a' : '#374151', background: 'rgba(0,0,0,0.04)', borderRadius: 8, padding: '5px 10px', ...fontStyle }}>
<WeatherIcon main={weather.main} size={15} /> <WeatherIcon main={weather.main} size={15} />
{temp !== null && <span style={{ fontWeight: 500 }}>{isClimate ? 'Ø ' : ''}{temp}{unit}</span>} {temp !== null && <span style={{ fontWeight: 500 }}>{isClimate ? 'Ø ' : ''}{temp}{unit}</span>}
{weather.description && <span style={{ fontSize: 11, color: '#9ca3af', textTransform: 'capitalize' }}>{weather.description}</span>} {weather.description && <span style={{ fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: '#9ca3af', textTransform: 'capitalize' }}>{weather.description}</span>}
</div> </div>
) )
} }
+1 -1
View File
@@ -83,7 +83,7 @@ export function ContextMenu({ menu, onClose }: ContextMenuProps) {
display: 'flex', alignItems: 'center', gap: 8, width: '100%', display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '7px 10px', borderRadius: 7, border: 'none', padding: '7px 10px', borderRadius: 7, border: 'none',
background: 'none', cursor: 'pointer', fontFamily: 'inherit', background: 'none', cursor: 'pointer', fontFamily: 'inherit',
fontSize: 12, fontWeight: 500, textAlign: 'left', fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: 500, textAlign: 'left',
color: item.danger ? '#ef4444' : 'var(--text-primary)', color: item.danger ? '#ef4444' : 'var(--text-primary)',
transition: 'background 0.1s', transition: 'background 0.1s',
}} }}
@@ -94,7 +94,7 @@ export function CustomDatePicker({ value, onChange, placeholder, style = {}, com
placeholder="DD.MM.YYYY" placeholder="DD.MM.YYYY"
style={{ style={{
width: '100%', padding: '8px 14px', borderRadius: 10, border: '1px solid var(--text-faint)', width: '100%', padding: '8px 14px', borderRadius: 10, border: '1px solid var(--text-faint)',
background: 'var(--bg-input)', color: 'var(--text-primary)', fontSize: 13, fontFamily: 'inherit', outline: 'none', background: 'var(--bg-input)', color: 'var(--text-primary)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'inherit', outline: 'none',
}} /> }} />
) : ( ) : (
<button type="button" onClick={() => setOpen(o => !o)} onDoubleClick={() => { setTextInput(value || ''); setIsTyping(true) }} <button type="button" onClick={() => setOpen(o => !o)} onDoubleClick={() => { setTextInput(value || ''); setIsTyping(true) }}
@@ -103,7 +103,7 @@ export function CustomDatePicker({ value, onChange, placeholder, style = {}, com
padding: compact ? '4px 6px' : '8px 14px', borderRadius: compact ? 4 : 10, padding: compact ? '4px 6px' : '8px 14px', borderRadius: compact ? 4 : 10,
border: borderless ? 'none' : '1px solid var(--border-primary)', border: borderless ? 'none' : '1px solid var(--border-primary)',
background: borderless ? 'transparent' : 'var(--bg-input)', color: displayValue ? 'var(--text-primary)' : 'var(--text-faint)', background: borderless ? 'transparent' : 'var(--bg-input)', color: displayValue ? 'var(--text-primary)' : 'var(--text-faint)',
fontSize: 13, fontFamily: 'inherit', cursor: 'pointer', outline: 'none', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'inherit', cursor: 'pointer', outline: 'none',
transition: 'border-color 0.15s', transition: 'border-color 0.15s',
}} }}
onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--text-faint)'} onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--text-faint)'}
@@ -143,7 +143,7 @@ export function CustomDatePicker({ value, onChange, placeholder, style = {}, com
onMouseEnter={e => e.currentTarget.style.color = 'var(--text-primary)'} onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}> onMouseEnter={e => e.currentTarget.style.color = 'var(--text-primary)'} onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}>
<ChevronLeft size={16} /> <ChevronLeft size={16} />
</button> </button>
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{monthLabel}</span> <span style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)' }}>{monthLabel}</span>
<button type="button" onClick={nextMonth} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 4, borderRadius: 6, display: 'flex', color: 'var(--text-faint)' }} <button type="button" onClick={nextMonth} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 4, borderRadius: 6, display: 'flex', color: 'var(--text-faint)' }}
onMouseEnter={e => e.currentTarget.style.color = 'var(--text-primary)'} onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}> onMouseEnter={e => e.currentTarget.style.color = 'var(--text-primary)'} onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}>
<ChevronRight size={16} /> <ChevronRight size={16} />
@@ -153,7 +153,7 @@ export function CustomDatePicker({ value, onChange, placeholder, style = {}, com
{/* Weekday headers */} {/* Weekday headers */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 2, marginBottom: 4 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 2, marginBottom: 4 }}>
{weekdays.map((d, i) => ( {weekdays.map((d, i) => (
<div key={i} style={{ textAlign: 'center', fontSize: 10, fontWeight: 600, color: 'var(--text-faint)', padding: '2px 0' }}>{d}</div> <div key={i} style={{ textAlign: 'center', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-faint)', padding: '2px 0' }}>{d}</div>
))} ))}
</div> </div>
@@ -170,7 +170,7 @@ export function CustomDatePicker({ value, onChange, placeholder, style = {}, com
width: 32, height: 32, borderRadius: 8, border: 'none', width: 32, height: 32, borderRadius: 8, border: 'none',
background: sel ? 'var(--accent)' : 'transparent', background: sel ? 'var(--accent)' : 'transparent',
color: sel ? 'var(--accent-text)' : 'var(--text-primary)', color: sel ? 'var(--accent-text)' : 'var(--text-primary)',
fontSize: 12, fontWeight: sel ? 700 : td ? 600 : 400, fontSize: 'calc(12px * var(--fs-scale-text, 1))', fontWeight: sel ? 700 : td ? 600 : 400,
cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
outline: td && !sel ? '2px solid var(--border-primary)' : 'none', outlineOffset: -2, outline: td && !sel ? '2px solid var(--border-primary)' : 'none', outlineOffset: -2,
transition: 'background 0.1s', transition: 'background 0.1s',
@@ -187,7 +187,7 @@ export function CustomDatePicker({ value, onChange, placeholder, style = {}, com
{value && ( {value && (
<div style={{ marginTop: 8, display: 'flex', justifyContent: 'center' }}> <div style={{ marginTop: 8, display: 'flex', justifyContent: 'center' }}>
<button type="button" onClick={() => { onChange(''); setOpen(false) }} <button type="button" onClick={() => { onChange(''); setOpen(false) }}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 11, color: 'var(--text-faint)', padding: '3px 8px', borderRadius: 6 }} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', padding: '3px 8px', borderRadius: 6 }}
onMouseEnter={e => e.currentTarget.style.color = '#ef4444'} onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}> onMouseEnter={e => e.currentTarget.style.color = '#ef4444'} onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}>
</button> </button>
@@ -95,7 +95,7 @@ export default function CustomSelect({
padding: sm ? '8px 12px' : '8px 14px', borderRadius: 10, padding: sm ? '8px 12px' : '8px 14px', borderRadius: 10,
border: '1px solid var(--border-primary)', border: '1px solid var(--border-primary)',
background: 'var(--bg-input)', color: 'var(--text-primary)', background: 'var(--bg-input)', color: 'var(--text-primary)',
fontSize: 13, fontWeight: 500, fontFamily: 'inherit', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, fontFamily: 'inherit',
cursor: disabled ? 'default' : 'pointer', outline: 'none', textAlign: 'left', cursor: disabled ? 'default' : 'pointer', outline: 'none', textAlign: 'left',
transition: 'border-color 0.15s', overflow: 'hidden', minWidth: 0, transition: 'border-color 0.15s', overflow: 'hidden', minWidth: 0,
opacity: disabled ? 0.5 : 1, opacity: disabled ? 0.5 : 1,
@@ -109,7 +109,7 @@ export default function CustomSelect({
</span> </span>
{selected?.badge && ( {selected?.badge && (
<span style={{ <span style={{
flexShrink: 0, fontSize: 10, fontWeight: 600, color: 'var(--text-muted)', flexShrink: 0, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)',
background: 'var(--bg-tertiary)', padding: '2px 7px', borderRadius: 999, background: 'var(--bg-tertiary)', padding: '2px 7px', borderRadius: 999,
letterSpacing: '0.01em', letterSpacing: '0.01em',
}}>{selected.badge}</span> }}>{selected.badge}</span>
@@ -153,7 +153,7 @@ export default function CustomSelect({
placeholder="..." placeholder="..."
style={{ style={{
width: '100%', border: '1px solid var(--border-secondary)', borderRadius: 6, width: '100%', border: '1px solid var(--border-secondary)', borderRadius: 6,
padding: '5px 8px', fontSize: 12, outline: 'none', fontFamily: 'inherit', padding: '5px 8px', fontSize: 'calc(12px * var(--fs-scale-text, 1))', outline: 'none', fontFamily: 'inherit',
background: 'var(--bg-secondary)', color: 'var(--text-primary)', background: 'var(--bg-secondary)', color: 'var(--text-primary)',
boxSizing: 'border-box', boxSizing: 'border-box',
}} }}
@@ -164,13 +164,13 @@ export default function CustomSelect({
{/* Options */} {/* Options */}
<div style={{ maxHeight: 220, overflowY: 'auto', padding: '4px' }}> <div style={{ maxHeight: 220, overflowY: 'auto', padding: '4px' }}>
{filtered.length === 0 ? ( {filtered.length === 0 ? (
<div style={{ padding: '10px 12px', fontSize: 12, color: 'var(--text-faint)', textAlign: 'center' }}></div> <div style={{ padding: '10px 12px', fontSize: 'calc(12px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', textAlign: 'center' }}></div>
) : ( ) : (
filtered.map(option => { filtered.map(option => {
if (option.isHeader) { if (option.isHeader) {
return ( return (
<div key={option.value} style={{ <div key={option.value} style={{
padding: '5px 10px', fontSize: 10, fontWeight: 700, color: 'var(--text-faint)', padding: '5px 10px', fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-faint)',
textTransform: 'uppercase', letterSpacing: '0.03em', textTransform: 'uppercase', letterSpacing: '0.03em',
background: 'var(--bg-tertiary)', borderRadius: 4, margin: '2px 0', background: 'var(--bg-tertiary)', borderRadius: 4, margin: '2px 0',
}}> }}>
@@ -188,7 +188,7 @@ export default function CustomSelect({
width: '100%', display: 'flex', alignItems: 'center', gap: 8, width: '100%', display: 'flex', alignItems: 'center', gap: 8,
padding: '7px 10px', borderRadius: 6, padding: '7px 10px', borderRadius: 6,
border: 'none', background: isSelected ? 'var(--bg-hover)' : 'transparent', border: 'none', background: isSelected ? 'var(--bg-hover)' : 'transparent',
color: 'var(--text-primary)', fontSize: 13, fontFamily: 'inherit', color: 'var(--text-primary)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'inherit',
cursor: 'pointer', textAlign: 'left', transition: 'background 0.1s', cursor: 'pointer', textAlign: 'left', transition: 'background 0.1s',
}} }}
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'} onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'}
@@ -198,7 +198,7 @@ export default function CustomSelect({
<span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{option.label}</span> <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{option.label}</span>
{option.badge && ( {option.badge && (
<span style={{ <span style={{
flexShrink: 0, fontSize: 10, fontWeight: 600, color: 'var(--text-muted)', flexShrink: 0, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-muted)',
background: 'var(--bg-tertiary)', padding: '2px 7px', borderRadius: 999, background: 'var(--bg-tertiary)', padding: '2px 7px', borderRadius: 999,
letterSpacing: '0.01em', letterSpacing: '0.01em',
}}>{option.badge}</span> }}>{option.badge}</span>
@@ -131,7 +131,7 @@ export default function CustomTimePicker({ value, onChange, placeholder = '00:00
placeholder={is12h ? '2:30 PM' : placeholder} placeholder={is12h ? '2:30 PM' : placeholder}
style={{ style={{
flex: 1, border: 'none', outline: 'none', background: 'transparent', flex: 1, border: 'none', outline: 'none', background: 'transparent',
padding: '8px 10px 8px 14px', fontSize: 13, fontFamily: 'inherit', padding: '8px 10px 8px 14px', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'inherit',
color: value ? 'var(--text-primary)' : 'var(--text-faint)', color: value ? 'var(--text-primary)' : 'var(--text-faint)',
minWidth: 0, minWidth: 0,
}} }}
@@ -171,7 +171,7 @@ export default function CustomTimePicker({ value, onChange, placeholder = '00:00
</button> </button>
<div style={{ <div style={{
width: 44, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center', width: 44, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 22, fontWeight: 700, color: 'var(--text-primary)', fontSize: 'calc(22px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)',
background: 'var(--bg-hover)', borderRadius: 8, background: 'var(--bg-hover)', borderRadius: 8,
fontVariantNumeric: 'tabular-nums', fontVariantNumeric: 'tabular-nums',
}}> }}>
@@ -184,7 +184,7 @@ export default function CustomTimePicker({ value, onChange, placeholder = '00:00
</button> </button>
</div> </div>
<span style={{ fontSize: 22, fontWeight: 700, color: 'var(--text-faint)', marginTop: -2 }}>:</span> <span style={{ fontSize: 'calc(22px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-faint)', marginTop: -2 }}>:</span>
{/* Minutes */} {/* Minutes */}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
@@ -195,7 +195,7 @@ export default function CustomTimePicker({ value, onChange, placeholder = '00:00
</button> </button>
<div style={{ <div style={{
width: 44, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center', width: 44, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 22, fontWeight: 700, color: 'var(--text-primary)', fontSize: 'calc(22px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)',
background: 'var(--bg-hover)', borderRadius: 8, background: 'var(--bg-hover)', borderRadius: 8,
fontVariantNumeric: 'tabular-nums', fontVariantNumeric: 'tabular-nums',
}}> }}>
@@ -218,7 +218,7 @@ export default function CustomTimePicker({ value, onChange, placeholder = '00:00
</button> </button>
<div style={{ <div style={{
width: 36, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center', width: 36, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 14, fontWeight: 700, color: 'var(--text-primary)', fontSize: 'calc(14px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)',
background: 'var(--bg-hover)', borderRadius: 8, background: 'var(--bg-hover)', borderRadius: 8,
}}> }}>
{hour >= 12 ? 'PM' : 'AM'} {hour >= 12 ? 'PM' : 'AM'}
@@ -234,7 +234,7 @@ export default function CustomTimePicker({ value, onChange, placeholder = '00:00
{/* Clear */} {/* Clear */}
{value && ( {value && (
<button type="button" onClick={() => { onChange(''); setOpen(false) }} <button type="button" onClick={() => { onChange(''); setOpen(false) }}
style={{ ...btnStyle, marginLeft: 4, fontSize: 11, color: 'var(--text-faint)', padding: '4px 6px' }} style={{ ...btnStyle, marginLeft: 4, fontSize: 'calc(11px * var(--fs-scale-text, 1))', color: 'var(--text-faint)', padding: '4px 6px' }}
onMouseEnter={e => e.currentTarget.style.color = '#ef4444'} onMouseEnter={e => e.currentTarget.style.color = '#ef4444'}
onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}> onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}>
+1 -1
View File
@@ -109,7 +109,7 @@ export function SlidingTabs<T extends string>({
{tab.label} {tab.label}
{tab.count != null && ( {tab.count != null && (
<span style={{ <span style={{
fontSize: 10, fontWeight: 600, fontSize: 'calc(10px * var(--fs-scale-text, 1))', fontWeight: 600,
padding: '1px 6px', borderRadius: 99, minWidth: 16, padding: '1px 6px', borderRadius: 99, minWidth: 16,
background: isActive ? 'rgba(255,255,255,0.22)' : 'var(--bg-tertiary)', background: isActive ? 'rgba(255,255,255,0.22)' : 'var(--bg-tertiary)',
color: isActive ? 'inherit' : 'var(--text-faint)', color: isActive ? 'inherit' : 'var(--text-faint)',
+1 -1
View File
@@ -123,7 +123,7 @@ export function ToastContainer() {
> >
{icons[toast.type] || icons.info} {icons[toast.type] || icons.info}
<span style={{ <span style={{
flex: 1, fontSize: 13, fontWeight: 500, color: 'rgba(255, 255, 255, 0.9)', flex: 1, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500, color: 'rgba(255, 255, 255, 0.9)',
lineHeight: 1.4, lineHeight: 1.4,
fontFamily: "var(--font-system)", fontFamily: "var(--font-system)",
}}> }}>
+1 -1
View File
@@ -86,7 +86,7 @@ export function Tooltip({ label, placement = 'bottom', delay = 250, disabled, ch
visibility: coords ? 'visible' : 'hidden', visibility: coords ? 'visible' : 'hidden',
pointerEvents: 'none', pointerEvents: 'none',
zIndex: 100000, zIndex: 100000,
fontSize: 11, fontSize: 'calc(11px * var(--fs-scale-text, 1))',
fontWeight: 500, fontWeight: 500,
padding: '5px 10px', padding: '5px 10px',
borderRadius: 8, borderRadius: 8,
+1 -1
View File
@@ -711,7 +711,7 @@ function UpcomingTool({ items, locale, onOpen }: {
<div className="tool-title"><Calendar size={14} /> {t('dashboard.upcoming.title')}</div> <div className="tool-title"><Calendar size={14} /> {t('dashboard.upcoming.title')}</div>
</div> </div>
{items.length === 0 ? ( {items.length === 0 ? (
<div style={{ fontSize: 13, color: 'var(--ink-3)' }}>{t('dashboard.upcoming.empty')}</div> <div style={{ fontSize: 'calc(13px * var(--fs-scale-text, 1))', color: 'var(--ink-3)' }}>{t('dashboard.upcoming.empty')}</div>
) : ( ) : (
<div className="upc-list"> <div className="upc-list">
{items.map(r => { {items.map(r => {

Some files were not shown because too many files have changed in this diff Show More