mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] settings: snap type onto the shared scale, 17 ad-hoc sizes down to 6 steps
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
"""Settings type has to stay on the shared scale.
|
||||
|
||||
Measured 2026-07-27: the Settings area carried 17 distinct hardcoded rem sizes, including 0.72,
|
||||
0.74, 0.75 and 0.78 in the same screen. Nobody chose those four; they are drift, and drift is what
|
||||
makes a UI read as unfinished no matter how good any single page is. claudeTokens already defines
|
||||
the scale and its own comment says to use `c.font.size.*` instead of a raw rem string, so this is a
|
||||
standard the code had rather than a new opinion.
|
||||
|
||||
The guard is here rather than in eslint because this suite is what actually runs on every change.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
|
||||
P_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
P_SETTINGS = os.path.join(P_REPO_ROOT, "frontend", "src", "app", "pages", "Settings")
|
||||
P_TOKENS = os.path.join(P_REPO_ROOT, "frontend", "src", "shared", "styles", "claudeTokens.ts")
|
||||
|
||||
P_RAW_SIZE_RE = re.compile(r"fontSize: '[0-9.]+rem'")
|
||||
|
||||
|
||||
def p_settings_files():
|
||||
for dirpath, dirs, files in os.walk(P_SETTINGS):
|
||||
for fn in sorted(files):
|
||||
if fn.endswith((".tsx", ".ts")):
|
||||
yield os.path.join(dirpath, fn)
|
||||
|
||||
|
||||
def test_the_scale_exists_to_snap_to():
|
||||
"""If the scale is ever removed, the rule below becomes unfollowable and this says so first."""
|
||||
with open(P_TOKENS, encoding="utf-8") as fh:
|
||||
src = fh.read()
|
||||
assert "export const fontSize: FontSizeScale" in src
|
||||
assert "size: fontSize," in src, "the scale must be wired into the token objects, not just declared"
|
||||
|
||||
|
||||
def test_no_hardcoded_rem_font_sizes_in_settings():
|
||||
"""Every size goes through c.font.size.*, so a new page inherits the scale instead of guessing."""
|
||||
offenders = []
|
||||
for path in p_settings_files():
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
for i, line in enumerate(fh, 1):
|
||||
if P_RAW_SIZE_RE.search(line):
|
||||
offenders.append(f"{os.path.relpath(path, P_REPO_ROOT)}:{i}: {line.strip()}")
|
||||
assert not offenders, (
|
||||
"hardcoded rem font sizes are back in Settings; use c.font.size.* instead:\n "
|
||||
+ "\n ".join(offenders))
|
||||
|
||||
|
||||
def test_settings_uses_a_small_number_of_steps():
|
||||
"""A scale only buys anything if the page actually restrains itself to a few steps. Nine exist;
|
||||
one screen reaching for most of them is the same noise problem wearing a nicer name."""
|
||||
used = set()
|
||||
for path in p_settings_files():
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
used.update(re.findall(r"c\.font\.size\.([a-z]+)", fh.read()))
|
||||
assert used, "Settings should reference the scale at all"
|
||||
assert len(used) <= 7, f"Settings spreads across too many type steps: {sorted(used)}"
|
||||
@@ -22,7 +22,7 @@ const SettingsHeader: React.FC<{
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', pt: 1.5, pb: 0.5 }}>
|
||||
<Typography sx={{ color: c.text.primary, fontWeight: 600, fontSize: '1rem' }}>
|
||||
<Typography sx={{ color: c.text.primary, fontWeight: 600, fontSize: c.font.size.md }}>
|
||||
Settings
|
||||
</Typography>
|
||||
<IconButton onClick={onClose} size="small" data-onboarding="settings-close-button" sx={{ color: c.text.tertiary, '&:hover': { color: c.text.primary } }}>
|
||||
@@ -40,7 +40,7 @@ const SettingsHeader: React.FC<{
|
||||
'& .MuiTab-root': {
|
||||
minHeight: 30,
|
||||
textTransform: 'none',
|
||||
fontSize: '0.85rem',
|
||||
fontSize: c.font.size.base,
|
||||
fontWeight: 500,
|
||||
color: c.text.muted,
|
||||
px: 1.75,
|
||||
|
||||
@@ -93,9 +93,9 @@ const DataPrivacySection: React.FC<{
|
||||
borderRadius: 2.5,
|
||||
maxWidth: 360,
|
||||
};
|
||||
const titleSx = { color: c.text.primary, fontSize: '0.95rem', fontWeight: 600, mb: 1 };
|
||||
const bodySx = { color: c.text.secondary, fontSize: '0.8rem', lineHeight: 1.5, mb: 2 };
|
||||
const errSx = { color: c.status.error, fontSize: '0.75rem', mb: 1.5 };
|
||||
const titleSx = { color: c.text.primary, fontSize: c.font.size.md, fontWeight: 600, mb: 1 };
|
||||
const bodySx = { color: c.text.secondary, fontSize: c.font.size.sm, lineHeight: 1.5, mb: 2 };
|
||||
const errSx = { color: c.status.error, fontSize: c.font.size.xs, mb: 1.5 };
|
||||
const cancelSx = { color: c.text.secondary, textTransform: 'none', fontWeight: 500 };
|
||||
const actionRowSx = { display: 'flex', justifyContent: 'flex-end', gap: 1 };
|
||||
|
||||
@@ -104,7 +104,7 @@ const DataPrivacySection: React.FC<{
|
||||
color: c.text.secondary,
|
||||
borderColor: c.border.medium,
|
||||
textTransform: 'none' as const,
|
||||
fontSize: '0.8rem',
|
||||
fontSize: c.font.size.sm,
|
||||
whiteSpace: 'nowrap' as const,
|
||||
'&:hover': { color: c.accent.primary, borderColor: c.accent.primary },
|
||||
};
|
||||
@@ -201,7 +201,7 @@ const DataPrivacySection: React.FC<{
|
||||
size="small"
|
||||
autoFocus
|
||||
disabled={busy}
|
||||
sx={{ mb: 2, '& .MuiOutlinedInput-root': { fontSize: '0.8rem' } }}
|
||||
sx={{ mb: 2, '& .MuiOutlinedInput-root': { fontSize: c.font.size.sm } }}
|
||||
/>
|
||||
{err && <Typography sx={errSx}>{err}</Typography>}
|
||||
<Box sx={actionRowSx}>
|
||||
|
||||
@@ -123,7 +123,7 @@ const GeneralAdvanced: React.FC<{
|
||||
color: c.text.secondary,
|
||||
borderColor: c.border.medium,
|
||||
textTransform: 'none',
|
||||
fontSize: '0.8rem',
|
||||
fontSize: c.font.size.sm,
|
||||
whiteSpace: 'nowrap',
|
||||
'&:hover': { color: c.accent.primary, borderColor: c.accent.primary },
|
||||
}}
|
||||
|
||||
@@ -49,7 +49,7 @@ const GeneralAgentDefaults: React.FC<{
|
||||
sx={{
|
||||
color: c.accent.primary,
|
||||
textTransform: 'none',
|
||||
fontSize: '0.75rem',
|
||||
fontSize: c.font.size.xs,
|
||||
py: 0.25,
|
||||
'&:hover': { bgcolor: `${c.accent.primary}10` },
|
||||
}}
|
||||
@@ -72,7 +72,7 @@ const GeneralAgentDefaults: React.FC<{
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
fontFamily: c.font.mono,
|
||||
fontSize: '0.8rem',
|
||||
fontSize: c.font.size.sm,
|
||||
lineHeight: 1.6,
|
||||
color: c.text.secondary,
|
||||
},
|
||||
@@ -110,7 +110,7 @@ const GeneralAgentDefaults: React.FC<{
|
||||
textTransform: 'none',
|
||||
whiteSpace: 'nowrap',
|
||||
minWidth: 'auto',
|
||||
fontSize: '0.8rem',
|
||||
fontSize: c.font.size.sm,
|
||||
'&:hover': { color: c.accent.primary, borderColor: c.accent.primary },
|
||||
}}
|
||||
>
|
||||
@@ -128,7 +128,7 @@ const GeneralAgentDefaults: React.FC<{
|
||||
<Select
|
||||
value={form.default_model}
|
||||
onChange={(e) => setForm({ ...form, default_model: e.target.value })}
|
||||
sx={{ fontSize: '0.85rem' }}
|
||||
sx={{ fontSize: c.font.size.base }}
|
||||
MenuProps={{ PaperProps: { sx: { bgcolor: c.bg.surface, color: c.text.primary } } }}
|
||||
renderValue={(val) => {
|
||||
const m = modelOptions.flat.find((x) => x.value === val);
|
||||
@@ -136,7 +136,7 @@ const GeneralAgentDefaults: React.FC<{
|
||||
return (
|
||||
<Box component="span" sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.75 }}>
|
||||
<span>{m.label}</span>
|
||||
<Typography component="span" sx={{ fontSize: '0.65rem', color: c.text.ghost }}>
|
||||
<Typography component="span" sx={{ fontSize: c.font.size.micro, color: c.text.ghost }}>
|
||||
· {m.provider}
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -171,7 +171,7 @@ const GeneralAgentDefaults: React.FC<{
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '0.68rem',
|
||||
fontSize: c.font.size.xxs,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.08em',
|
||||
textTransform: 'uppercase',
|
||||
@@ -190,7 +190,7 @@ const GeneralAgentDefaults: React.FC<{
|
||||
</Box>
|
||||
</ListSubheader>,
|
||||
...models.map((m) => (
|
||||
<MenuItem key={m.value} value={m.value} sx={{ fontSize: '0.85rem', pl: 3 }}>
|
||||
<MenuItem key={m.value} value={m.value} sx={{ fontSize: c.font.size.base, pl: 3 }}>
|
||||
{m.label}
|
||||
</MenuItem>
|
||||
)),
|
||||
@@ -209,7 +209,7 @@ const GeneralAgentDefaults: React.FC<{
|
||||
<Select
|
||||
value={form.default_mode}
|
||||
onChange={(e) => setForm({ ...form, default_mode: e.target.value })}
|
||||
sx={{ fontSize: '0.85rem' }}
|
||||
sx={{ fontSize: c.font.size.base }}
|
||||
MenuProps={{ PaperProps: { sx: { bgcolor: c.bg.surface, color: c.text.primary } } }}
|
||||
>
|
||||
{modesList.map((m) => (
|
||||
@@ -228,7 +228,7 @@ const GeneralAgentDefaults: React.FC<{
|
||||
<Select
|
||||
value={form.default_thinking_level}
|
||||
onChange={(e) => setForm({ ...form, default_thinking_level: e.target.value as AppSettings['default_thinking_level'] })}
|
||||
sx={{ fontSize: '0.85rem' }}
|
||||
sx={{ fontSize: c.font.size.base }}
|
||||
MenuProps={{ PaperProps: { sx: { bgcolor: c.bg.surface, color: c.text.primary } } }}
|
||||
>
|
||||
<MenuItem value="auto">Auto</MenuItem>
|
||||
|
||||
@@ -46,7 +46,7 @@ const GeneralInterface: React.FC<{
|
||||
px: 2,
|
||||
py: 0.5,
|
||||
gap: 0.5,
|
||||
fontSize: '0.8rem',
|
||||
fontSize: c.font.size.sm,
|
||||
'&.Mui-selected': {
|
||||
bgcolor: `${c.accent.primary}15`,
|
||||
color: c.accent.primary,
|
||||
@@ -85,7 +85,7 @@ const GeneralInterface: React.FC<{
|
||||
]}
|
||||
sx={{
|
||||
color: c.accent.primary,
|
||||
'& .MuiSlider-markLabel': { color: c.text.tertiary, fontSize: '0.7rem' },
|
||||
'& .MuiSlider-markLabel': { color: c.text.tertiary, fontSize: c.font.size.xxs },
|
||||
'& .MuiSlider-valueLabel': { bgcolor: c.accent.primary },
|
||||
}}
|
||||
/>
|
||||
@@ -130,11 +130,11 @@ const GeneralInterface: React.FC<{
|
||||
>
|
||||
<KeyboardIcon sx={{ fontSize: 16, color: recordingShortcut ? c.accent.primary : c.text.tertiary }} />
|
||||
{recordingShortcut ? (
|
||||
<Typography sx={{ fontSize: '0.8rem', color: c.accent.primary, fontWeight: 500 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.sm, color: c.accent.primary, fontWeight: 500 }}>
|
||||
Press shortcut…
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography sx={{ fontSize: '0.8rem', color: c.text.primary, fontFamily: c.font.mono, fontWeight: 500 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.sm, color: c.text.primary, fontFamily: c.font.mono, fontWeight: 500 }}>
|
||||
{form.new_agent_shortcut
|
||||
.split('+')
|
||||
.map((p) => {
|
||||
|
||||
@@ -87,7 +87,7 @@ const SoftwareUpdateRow: React.FC<{ styles: SettingsStyles }> = ({ styles }) =>
|
||||
color: c.text.secondary,
|
||||
borderColor: c.border.medium,
|
||||
textTransform: 'none',
|
||||
fontSize: '0.8rem',
|
||||
fontSize: c.font.size.sm,
|
||||
whiteSpace: 'nowrap',
|
||||
'&:hover': { color: c.accent.primary, borderColor: c.accent.primary },
|
||||
}}
|
||||
@@ -105,7 +105,7 @@ const SoftwareUpdateRow: React.FC<{ styles: SettingsStyles }> = ({ styles }) =>
|
||||
color: c.accent.primary,
|
||||
borderColor: c.accent.primary,
|
||||
textTransform: 'none',
|
||||
fontSize: '0.8rem',
|
||||
fontSize: c.font.size.sm,
|
||||
whiteSpace: 'nowrap',
|
||||
'&:hover': { bgcolor: `${c.accent.primary}10` },
|
||||
}}
|
||||
@@ -127,7 +127,7 @@ const SoftwareUpdateRow: React.FC<{ styles: SettingsStyles }> = ({ styles }) =>
|
||||
'&:hover': { bgcolor: c.accent.pressed },
|
||||
'&.Mui-disabled': { bgcolor: c.accent.primary, color: '#fff', opacity: 0.7 },
|
||||
textTransform: 'none',
|
||||
fontSize: '0.8rem',
|
||||
fontSize: c.font.size.sm,
|
||||
whiteSpace: 'nowrap',
|
||||
borderRadius: 1.5,
|
||||
}}
|
||||
|
||||
@@ -44,7 +44,7 @@ const ApiKeyCard: React.FC<{
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography sx={labelSx}>{config.label}</Typography>
|
||||
{value ? (
|
||||
<Typography sx={{ fontSize: '0.6rem', fontWeight: 600, color: c.status.success, bgcolor: `${c.status.success}15`, px: 0.75, py: 0.15, borderRadius: '3px' }}>CONNECTED</Typography>
|
||||
<Typography sx={{ fontSize: c.font.size.micro, fontWeight: 600, color: c.status.success, bgcolor: `${c.status.success}15`, px: 0.75, py: 0.15, borderRadius: '3px' }}>CONNECTED</Typography>
|
||||
) : null}
|
||||
</Box>
|
||||
<Typography sx={{ ...descSx, mb: 1 }}>{config.desc}</Typography>
|
||||
@@ -72,7 +72,7 @@ const ApiKeyCard: React.FC<{
|
||||
href={config.href}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
sx={{ color: c.accent.primary, fontSize: '0.72rem', whiteSpace: 'nowrap', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 0.3, '&:hover': { textDecoration: 'underline' } }}
|
||||
sx={{ color: c.accent.primary, fontSize: c.font.size.xs, whiteSpace: 'nowrap', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 0.3, '&:hover': { textDecoration: 'underline' } }}
|
||||
>
|
||||
Get key <OpenInNewIcon sx={{ fontSize: 11 }} />
|
||||
</Typography>
|
||||
|
||||
@@ -35,7 +35,7 @@ const CustomProvidersEditor: React.FC<{
|
||||
const allReady = readyCount === list.length;
|
||||
return (
|
||||
<Typography sx={{
|
||||
fontSize: '0.6rem',
|
||||
fontSize: c.font.size.micro,
|
||||
fontWeight: 600,
|
||||
color: allReady ? c.status.success : c.status.warning,
|
||||
bgcolor: allReady ? `${c.status.success}15` : `${c.status.warning}1F`,
|
||||
@@ -105,7 +105,7 @@ const CustomProvidersEditor: React.FC<{
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: -0.25 }}>
|
||||
<Typography sx={{
|
||||
fontSize: '0.62rem',
|
||||
fontSize: c.font.size.micro,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.05em',
|
||||
textTransform: 'uppercase' as const,
|
||||
@@ -147,7 +147,7 @@ const CustomProvidersEditor: React.FC<{
|
||||
required
|
||||
error={dupeNameWithEarlier}
|
||||
helperText={dupeNameWithEarlier ? 'Name must be unique' : undefined}
|
||||
InputLabelProps={{ shrink: true, sx: { fontSize: '0.72rem', color: c.text.tertiary } }}
|
||||
InputLabelProps={{ shrink: true, sx: { fontSize: c.font.size.xs, color: c.text.tertiary } }}
|
||||
sx={fieldSx}
|
||||
/>
|
||||
<TextField
|
||||
@@ -158,7 +158,7 @@ const CustomProvidersEditor: React.FC<{
|
||||
placeholder="https://ollama.com/v1"
|
||||
label="Base URL"
|
||||
required
|
||||
InputLabelProps={{ shrink: true, sx: { fontSize: '0.72rem', color: c.text.tertiary } }}
|
||||
InputLabelProps={{ shrink: true, sx: { fontSize: c.font.size.xs, color: c.text.tertiary } }}
|
||||
sx={{ ...fieldSx, '& .MuiOutlinedInput-root': { ...fieldSx['& .MuiOutlinedInput-root'], fontFamily: c.font.mono } }}
|
||||
/>
|
||||
<TextField
|
||||
@@ -169,7 +169,7 @@ const CustomProvidersEditor: React.FC<{
|
||||
fullWidth
|
||||
placeholder="Leave blank for local servers (LM Studio, Ollama, ...)"
|
||||
label="API Key (optional)"
|
||||
InputLabelProps={{ shrink: true, sx: { fontSize: '0.72rem', color: c.text.tertiary } }}
|
||||
InputLabelProps={{ shrink: true, sx: { fontSize: c.font.size.xs, color: c.text.tertiary } }}
|
||||
sx={{ ...fieldSx, '& .MuiOutlinedInput-root': { ...fieldSx['& .MuiOutlinedInput-root'], fontFamily: c.font.mono } }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
@@ -184,11 +184,11 @@ const CustomProvidersEditor: React.FC<{
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, mt: 0.25 }}>
|
||||
<Typography sx={{ fontSize: '0.65rem', fontWeight: 600, color: c.text.tertiary, textTransform: 'uppercase' as const, letterSpacing: '0.05em' }}>
|
||||
<Typography sx={{ fontSize: c.font.size.micro, fontWeight: 600, color: c.text.tertiary, textTransform: 'uppercase' as const, letterSpacing: '0.05em' }}>
|
||||
Models
|
||||
</Typography>
|
||||
{((cp.models || []).length === 0) ? (
|
||||
<Typography sx={{ fontSize: '0.7rem', color: c.text.muted, fontStyle: 'italic', px: 0.5 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.muted, fontStyle: 'italic', px: 0.5 }}>
|
||||
No models yet, add the model IDs this endpoint serves.
|
||||
</Typography>
|
||||
) : (
|
||||
@@ -200,7 +200,7 @@ const CustomProvidersEditor: React.FC<{
|
||||
size="small"
|
||||
fullWidth
|
||||
placeholder="e.g. gpt-oss:120b"
|
||||
sx={{ ...fieldSx, '& .MuiOutlinedInput-root': { ...fieldSx['& .MuiOutlinedInput-root'], fontFamily: c.font.mono, fontSize: '0.78rem' } }}
|
||||
sx={{ ...fieldSx, '& .MuiOutlinedInput-root': { ...fieldSx['& .MuiOutlinedInput-root'], fontFamily: c.font.mono, fontSize: c.font.size.xs } }}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={() => removeModel(mIdx)}
|
||||
@@ -224,7 +224,7 @@ const CustomProvidersEditor: React.FC<{
|
||||
mt: 0.25,
|
||||
textTransform: 'none',
|
||||
color: c.accent.primary,
|
||||
fontSize: '0.72rem',
|
||||
fontSize: c.font.size.xs,
|
||||
minWidth: 'auto',
|
||||
px: 0.75,
|
||||
py: 0.25,
|
||||
@@ -254,7 +254,7 @@ const CustomProvidersEditor: React.FC<{
|
||||
color: c.text.primary,
|
||||
borderColor: c.border.medium,
|
||||
borderStyle: 'dashed',
|
||||
fontSize: '0.78rem',
|
||||
fontSize: c.font.size.xs,
|
||||
px: 1.5,
|
||||
py: 0.6,
|
||||
'&:hover': { borderColor: c.accent.primary, bgcolor: `${c.accent.primary}08` },
|
||||
|
||||
@@ -22,7 +22,7 @@ const ModelsTab: React.FC<{
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', pt: 2.5, pb: 1, gap: 2.5, animation: 'fadeIn 0.2s ease', '@keyframes fadeIn': { from: { opacity: 0 }, to: { opacity: 1 } } }}>
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2.5 }}>
|
||||
<Typography sx={{ fontSize: '0.7rem', color: c.text.ghost, textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.ghost, textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>
|
||||
Connect a Subscription
|
||||
</Typography>
|
||||
|
||||
@@ -41,7 +41,7 @@ const ModelsTab: React.FC<{
|
||||
</Box>
|
||||
|
||||
<Box data-onboarding="settings-api-keys" sx={{ display: 'flex', flexDirection: 'column', gap: 2.5 }}>
|
||||
<Typography sx={{ fontSize: '0.7rem', color: c.text.ghost, textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600, mt: 1 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.ghost, textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600, mt: 1 }}>
|
||||
Or Connect With API Keys
|
||||
</Typography>
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
export function makeSettingsStyles(c: any) {
|
||||
const fieldSx = {
|
||||
'& .MuiOutlinedInput-root': {
|
||||
fontSize: '0.85rem',
|
||||
fontSize: c.font.size.base,
|
||||
},
|
||||
};
|
||||
|
||||
const sectionSx = {
|
||||
fontSize: '0.7rem',
|
||||
fontSize: c.font.size.xxs,
|
||||
fontWeight: 600,
|
||||
letterSpacing: '0.06em',
|
||||
textTransform: 'uppercase' as const,
|
||||
@@ -42,13 +42,13 @@ export function makeSettingsStyles(c: any) {
|
||||
const labelSx = {
|
||||
color: c.text.primary,
|
||||
fontWeight: 500,
|
||||
fontSize: '0.875rem',
|
||||
fontSize: c.font.size.base,
|
||||
lineHeight: 1.4,
|
||||
};
|
||||
|
||||
const descSx = {
|
||||
color: c.text.tertiary,
|
||||
fontSize: '0.75rem',
|
||||
fontSize: c.font.size.xs,
|
||||
lineHeight: 1.4,
|
||||
};
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ const AccountCard: React.FC = () => {
|
||||
if (!userId && !hasBearer) {
|
||||
return (
|
||||
<Box sx={{ p: 2, mb: 2, borderRadius: `${c.radius.lg}px`, border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.surface }}>
|
||||
<Typography sx={{ fontSize: '0.85rem', color: c.text.primary, mb: 0.5 }}>Not signed in</Typography>
|
||||
<Typography sx={{ fontSize: '0.78rem', color: c.text.muted, mb: 1.25 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.base, color: c.text.primary, mb: 0.5 }}>Not signed in</Typography>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, mb: 1.25 }}>
|
||||
Sign in to sync settings across devices and back up your data.
|
||||
</Typography>
|
||||
<Button
|
||||
@@ -70,7 +70,7 @@ const AccountCard: React.FC = () => {
|
||||
onClick={() => setSignInOpen(true)}
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
fontSize: '0.8rem',
|
||||
fontSize: c.font.size.sm,
|
||||
borderColor: c.border.medium,
|
||||
color: c.text.primary,
|
||||
'&:hover': { borderColor: c.accent.primary, color: c.accent.primary, bgcolor: 'transparent' },
|
||||
@@ -88,14 +88,14 @@ const AccountCard: React.FC = () => {
|
||||
<Box sx={{ p: 2, mb: 2, borderRadius: `${c.radius.lg}px`, border: `1px solid ${c.border.subtle}`, bgcolor: c.bg.surface }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 2 }}>
|
||||
<Box sx={{ minWidth: 0, flex: 1 }}>
|
||||
<Typography sx={{ fontSize: '0.9rem', fontWeight: 600, color: c.text.primary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
<Typography sx={{ fontSize: c.font.size.base, fontWeight: 600, color: c.text.primary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{userEmail || 'Signed in'}
|
||||
</Typography>
|
||||
{methodLabel && (
|
||||
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted, mt: 0.25 }}>{methodLabel}</Typography>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, mt: 0.25 }}>{methodLabel}</Typography>
|
||||
)}
|
||||
{!userId && hasBearer && (
|
||||
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted, mt: 0.25 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, mt: 0.25 }}>
|
||||
Subscription connected. Sign in to also link this device to your account.
|
||||
</Typography>
|
||||
)}
|
||||
@@ -108,7 +108,7 @@ const AccountCard: React.FC = () => {
|
||||
onClick={onSignIn}
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
fontSize: '0.75rem',
|
||||
fontSize: c.font.size.xs,
|
||||
borderColor: c.border.medium,
|
||||
color: c.text.primary,
|
||||
'&:hover': { borderColor: c.accent.primary, color: c.accent.primary, bgcolor: 'transparent' },
|
||||
@@ -124,7 +124,7 @@ const AccountCard: React.FC = () => {
|
||||
disabled={signingOut}
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
fontSize: '0.75rem',
|
||||
fontSize: c.font.size.xs,
|
||||
color: c.text.muted,
|
||||
'&:hover': { color: c.status.error, bgcolor: 'transparent' },
|
||||
}}
|
||||
|
||||
@@ -139,16 +139,16 @@ const OpenSwarmProCard: React.FC = () => {
|
||||
<Box sx={{ width: 8, height: 8, borderRadius: '50%', flexShrink: 0, bgcolor: c.border.medium }} />
|
||||
<Box sx={{ minWidth: 0 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.7 }}>
|
||||
<Typography sx={{ fontSize: '0.78rem', fontWeight: 600, color: c.text.primary }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, fontWeight: 600, color: c.text.primary }}>
|
||||
OpenSwarm Pro
|
||||
</Typography>
|
||||
<Box sx={{ px: 0.7, py: 0.15, borderRadius: 999, bgcolor: `${c.accent.primary}12` }}>
|
||||
<Typography sx={{ fontSize: '0.58rem', color: c.accent.primary, fontWeight: 600, letterSpacing: '0.03em' }}>
|
||||
<Typography sx={{ fontSize: c.font.size.micro, color: c.accent.primary, fontWeight: 600, letterSpacing: '0.03em' }}>
|
||||
RECOMMENDED
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography noWrap sx={{ fontSize: '0.65rem', color: c.text.muted }}>
|
||||
<Typography noWrap sx={{ fontSize: c.font.size.micro, color: c.text.muted }}>
|
||||
{copy.desc}
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -158,7 +158,7 @@ const OpenSwarmProCard: React.FC = () => {
|
||||
variant="contained"
|
||||
size="small"
|
||||
sx={{
|
||||
textTransform: 'none', fontSize: '0.7rem', minWidth: 70,
|
||||
textTransform: 'none', fontSize: c.font.size.xxs, minWidth: 70,
|
||||
borderRadius: `${c.radius.md}px`,
|
||||
bgcolor: c.accent.primary, boxShadow: 'none',
|
||||
'&:hover': { bgcolor: c.accent.hover, boxShadow: 'none' },
|
||||
@@ -218,7 +218,7 @@ const OpenSwarmProCard: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5 }}>
|
||||
<Typography sx={{ fontSize: '0.95rem', fontWeight: 600, color: c.text.primary }}>
|
||||
<Typography sx={{ fontSize: c.font.size.md, fontWeight: 600, color: c.text.primary }}>
|
||||
OpenSwarm Pro
|
||||
</Typography>
|
||||
<Box
|
||||
@@ -236,7 +236,7 @@ const OpenSwarmProCard: React.FC = () => {
|
||||
px: 1.2, py: 0.6, mb: 1.2, borderRadius: `${c.radius.sm}px`,
|
||||
bgcolor: `${c.status.warning}15`, border: `1px solid ${c.status.warning}40`,
|
||||
}}>
|
||||
<Typography sx={{ fontSize: '0.72rem', color: c.status.warning, fontWeight: 500 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.status.warning, fontWeight: 500 }}>
|
||||
Subscription canceled. You still have access until {expiresLabel || 'the end of the period'}.
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -245,10 +245,10 @@ const OpenSwarmProCard: React.FC = () => {
|
||||
{/* Usage bar; percentage only, no raw counts. */}
|
||||
<Box sx={{ mb: 1.2 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', mb: 0.5 }}>
|
||||
<Typography sx={{ fontSize: '0.78rem', color: c.text.secondary, fontWeight: 500 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.secondary, fontWeight: 500 }}>
|
||||
Current usage
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted }}>
|
||||
{pct}% used
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -266,7 +266,7 @@ const OpenSwarmProCard: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
{windowEndsAt && (
|
||||
<Typography sx={{ fontSize: '0.68rem', color: c.text.muted, mt: 0.4 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.muted, mt: 0.4 }}>
|
||||
Resets {(() => {
|
||||
const diff = windowEndsAt - Date.now();
|
||||
if (diff <= 0) return 'soon';
|
||||
@@ -279,7 +279,7 @@ const OpenSwarmProCard: React.FC = () => {
|
||||
)}
|
||||
</Box>
|
||||
{expiresLabel && !isCanceled && (
|
||||
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted, mb: 1.5 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, mb: 1.5 }}>
|
||||
Renews on {expiresLabel}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -289,7 +289,7 @@ const OpenSwarmProCard: React.FC = () => {
|
||||
onClick={() => setPickerOpen(true)}
|
||||
size="small"
|
||||
variant="contained"
|
||||
sx={{ textTransform: 'none', fontSize: '0.78rem', borderRadius: `${c.radius.md}px` }}
|
||||
sx={{ textTransform: 'none', fontSize: c.font.size.xs, borderRadius: `${c.radius.md}px` }}
|
||||
>
|
||||
Resubscribe
|
||||
</Button>
|
||||
@@ -299,7 +299,7 @@ const OpenSwarmProCard: React.FC = () => {
|
||||
disabled={managing}
|
||||
size="small"
|
||||
variant={isCanceled ? 'outlined' : 'contained'}
|
||||
sx={{ textTransform: 'none', fontSize: '0.78rem', borderRadius: `${c.radius.md}px` }}
|
||||
sx={{ textTransform: 'none', fontSize: c.font.size.xs, borderRadius: `${c.radius.md}px` }}
|
||||
>
|
||||
{managing ? 'Opening…' : 'Manage in Stripe'}
|
||||
</Button>
|
||||
|
||||
@@ -37,15 +37,15 @@ const SubscriptionCard: React.FC<{ provider: SubscriptionProvider; connected: bo
|
||||
} : {}),
|
||||
}} />
|
||||
<Box sx={{ minWidth: 0 }}>
|
||||
<Typography sx={{ fontSize: '0.78rem', fontWeight: 600, color: c.text.primary }}>{provider.name}</Typography>
|
||||
<Typography noWrap sx={{ fontSize: '0.65rem', color: connecting ? c.accent.primary : c.text.muted, transition: 'color 0.2s ease' }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, fontWeight: 600, color: c.text.primary }}>{provider.name}</Typography>
|
||||
<Typography noWrap sx={{ fontSize: c.font.size.micro, color: connecting ? c.accent.primary : c.text.muted, transition: 'color 0.2s ease' }}>
|
||||
{connecting ? 'Waiting for authorization...' : provider.desc}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{isPreview ? (
|
||||
<Typography sx={{ fontSize: '0.65rem', color: c.text.ghost, fontStyle: 'italic', flexShrink: 0 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.micro, color: c.text.ghost, fontStyle: 'italic', flexShrink: 0 }}>
|
||||
Coming soon
|
||||
</Typography>
|
||||
) : connected ? (
|
||||
@@ -53,26 +53,26 @@ const SubscriptionCard: React.FC<{ provider: SubscriptionProvider; connected: bo
|
||||
<CircularProgress size={14} sx={{ color: c.text.ghost }} />
|
||||
) : (
|
||||
<Box sx={{ position: 'relative', flexShrink: 0, minWidth: 72, height: 16 }}>
|
||||
<Typography className="sub-rest" sx={{ position: 'absolute', right: 0, top: 0, fontSize: '0.68rem', fontWeight: 500, color: c.status.success, transition: 'opacity 0.18s ease' }}>
|
||||
<Typography className="sub-rest" sx={{ position: 'absolute', right: 0, top: 0, fontSize: c.font.size.xxs, fontWeight: 500, color: c.status.success, transition: 'opacity 0.18s ease' }}>
|
||||
Connected
|
||||
</Typography>
|
||||
<Typography className="sub-undo" onClick={onDisconnect} sx={{ position: 'absolute', right: 0, top: 0, fontSize: '0.68rem', color: c.text.tertiary, cursor: 'pointer', opacity: 0, pointerEvents: 'none', transition: 'opacity 0.18s ease', '&:hover': { color: c.status.error } }}>
|
||||
<Typography className="sub-undo" onClick={onDisconnect} sx={{ position: 'absolute', right: 0, top: 0, fontSize: c.font.size.xxs, color: c.text.tertiary, cursor: 'pointer', opacity: 0, pointerEvents: 'none', transition: 'opacity 0.18s ease', '&:hover': { color: c.status.error } }}>
|
||||
Disconnect
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
) : connecting && userCode ? (
|
||||
<Box sx={{ textAlign: 'right', flexShrink: 0 }}>
|
||||
<Typography sx={{ fontSize: '0.68rem', color: c.text.muted }}>Enter code:</Typography>
|
||||
<Typography sx={{ fontSize: '0.85rem', fontWeight: 700, color: c.accent.primary, fontFamily: c.font.mono, letterSpacing: '0.1em' }}>{userCode}</Typography>
|
||||
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.muted }}>Enter code:</Typography>
|
||||
<Typography sx={{ fontSize: c.font.size.base, fontWeight: 700, color: c.accent.primary, fontFamily: c.font.mono, letterSpacing: '0.1em' }}>{userCode}</Typography>
|
||||
</Box>
|
||||
) : connecting ? (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.8, flexShrink: 0 }}>
|
||||
<CircularProgress size={14} sx={{ color: c.accent.primary }} />
|
||||
<Typography sx={{ fontSize: '0.68rem', color: c.accent.primary }}>Connecting...</Typography>
|
||||
<Typography sx={{ fontSize: c.font.size.xxs, color: c.accent.primary }}>Connecting...</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Button onClick={onConnect} variant="outlined" size="small" sx={{ textTransform: 'none', fontSize: '0.7rem', fontWeight: 600, color: c.text.primary, borderColor: c.border.medium, borderRadius: `${c.radius.sm}px`, minWidth: 72, flexShrink: 0, '&:hover': { borderColor: c.accent.primary, bgcolor: `${c.accent.primary}0a` }, transition: 'all 0.2s ease' }}>
|
||||
<Button onClick={onConnect} variant="outlined" size="small" sx={{ textTransform: 'none', fontSize: c.font.size.xxs, fontWeight: 600, color: c.text.primary, borderColor: c.border.medium, borderRadius: `${c.radius.sm}px`, minWidth: 72, flexShrink: 0, '&:hover': { borderColor: c.accent.primary, bgcolor: `${c.accent.primary}0a` }, transition: 'all 0.2s ease' }}>
|
||||
Connect
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -169,10 +169,10 @@ const SubscriptionCards: React.FC = () => {
|
||||
return (
|
||||
<Box sx={{ p: 2, borderRadius: `${c.radius.md}px`, border: `1px solid ${c.border.subtle}`, textAlign: 'center' }}>
|
||||
<CircularProgress size={18} sx={{ color: c.text.ghost, mb: 1 }} />
|
||||
<Typography sx={{ fontSize: '0.78rem', color: c.text.muted, mb: 0.5 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, mb: 0.5 }}>
|
||||
Starting subscription service...
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '0.65rem', color: c.text.ghost }}>
|
||||
<Typography sx={{ fontSize: c.font.size.micro, color: c.text.ghost }}>
|
||||
This connects your existing AI subscriptions. If this doesn't load, make sure Node.js is installed.
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -187,14 +187,14 @@ const SubscriptionCards: React.FC = () => {
|
||||
borderRadius: `${c.radius.md}px`, border: `1px solid ${c.border.subtle}`,
|
||||
bgcolor: c.bg.surface,
|
||||
}}>
|
||||
<Typography sx={{ fontSize: '0.72rem', color: c.text.secondary, flex: 1, lineHeight: 1.4 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.secondary, flex: 1, lineHeight: 1.4 }}>
|
||||
{connectError}
|
||||
</Typography>
|
||||
<Box
|
||||
role="button"
|
||||
aria-label="Dismiss"
|
||||
onClick={() => setConnectError(null)}
|
||||
sx={{ color: c.text.muted, cursor: 'pointer', fontSize: '0.9rem', lineHeight: 1, px: 0.3, '&:hover': { color: c.text.secondary } }}
|
||||
sx={{ color: c.text.muted, cursor: 'pointer', fontSize: c.font.size.base, lineHeight: 1, px: 0.3, '&:hover': { color: c.text.secondary } }}
|
||||
>
|
||||
×
|
||||
</Box>
|
||||
|
||||
@@ -94,9 +94,9 @@ const UsageStats: React.FC = () => {
|
||||
bgcolor: c.bg.elevated,
|
||||
border: `1px solid ${c.border.subtle}`,
|
||||
};
|
||||
const labelSx = { fontSize: '0.58rem', fontWeight: 700, color: c.text.ghost, textTransform: 'uppercase' as const, letterSpacing: '0.06em', mb: 0.25 };
|
||||
const valueSx = { fontSize: '1.05rem', fontWeight: 700, color: c.text.primary, lineHeight: 1.2 };
|
||||
const subSx = { fontSize: '0.62rem', color: c.text.tertiary, mt: 0.25 };
|
||||
const labelSx = { fontSize: c.font.size.micro, fontWeight: 700, color: c.text.ghost, textTransform: 'uppercase' as const, letterSpacing: '0.06em', mb: 0.25 };
|
||||
const valueSx = { fontSize: c.font.size.md, fontWeight: 700, color: c.text.primary, lineHeight: 1.2 };
|
||||
const subSx = { fontSize: c.font.size.micro, color: c.text.tertiary, mt: 0.25 };
|
||||
|
||||
const modelEntries = Object.entries(stats.models_used || {}).sort((a: any, b: any) => b[1] - a[1]) as [string, number][];
|
||||
const providerEntries = Object.entries(stats.providers_used || {}).sort((a: any, b: any) => b[1] - a[1]) as [string, number][];
|
||||
@@ -140,8 +140,8 @@ const UsageStats: React.FC = () => {
|
||||
bgcolor: `${c.accent.primary}0F`, border: `1px solid ${c.accent.primary}26`,
|
||||
display: 'flex', alignItems: 'center', gap: 1,
|
||||
}}>
|
||||
<Typography sx={{ fontSize: '0.95rem', lineHeight: 1 }}>✨</Typography>
|
||||
<Typography sx={{ fontSize: '0.74rem', color: c.text.secondary, fontStyle: 'italic', lineHeight: 1.4 }}>
|
||||
<Typography sx={{ fontSize: c.font.size.md, lineHeight: 1 }}>✨</Typography>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.secondary, fontStyle: 'italic', lineHeight: 1.4 }}>
|
||||
{savingsQuip}
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -220,15 +220,15 @@ const UsageStats: React.FC = () => {
|
||||
return (
|
||||
<Box key={model} sx={{ mb: 1 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', mb: 0 }}>
|
||||
<Typography sx={{ fontSize: '0.78rem', color: c.text.muted, fontWeight: 500 }}>{model}</Typography>
|
||||
<Typography sx={{ fontSize: '0.68rem', color: c.text.tertiary, fontFamily: c.font.mono }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, fontWeight: 500 }}>{model}</Typography>
|
||||
<Typography sx={{ fontSize: c.font.size.xxs, color: c.text.tertiary, fontFamily: c.font.mono }}>
|
||||
{count} ({pct}%)
|
||||
</Typography>
|
||||
</Box>
|
||||
<PixelBar value={count} max={stats.total_sessions} palette={PIXEL_BLUE} />
|
||||
</Box>
|
||||
);
|
||||
}) : <Typography sx={{ fontSize: '0.75rem', color: c.text.ghost }}>No sessions yet</Typography>}
|
||||
}) : <Typography sx={{ fontSize: c.font.size.xs, color: c.text.ghost }}>No sessions yet</Typography>}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ ...cardSx, p: 2 }}>
|
||||
@@ -239,15 +239,15 @@ const UsageStats: React.FC = () => {
|
||||
return (
|
||||
<Box key={tool} sx={{ mb: 1 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', mb: 0 }}>
|
||||
<Typography sx={{ fontSize: '0.72rem', color: c.text.muted, fontWeight: 500 }}>{shortName}</Typography>
|
||||
<Typography sx={{ fontSize: '0.62rem', color: c.text.tertiary, fontFamily: c.font.mono }}>
|
||||
<Typography sx={{ fontSize: c.font.size.xs, color: c.text.muted, fontWeight: 500 }}>{shortName}</Typography>
|
||||
<Typography sx={{ fontSize: c.font.size.micro, color: c.text.tertiary, fontFamily: c.font.mono }}>
|
||||
{count} call{count !== 1 ? 's' : ''} ({pct}%)
|
||||
</Typography>
|
||||
</Box>
|
||||
<PixelBar value={count} max={maxToolCount} />
|
||||
</Box>
|
||||
);
|
||||
}) : <Typography sx={{ fontSize: '0.75rem', color: c.text.ghost }}>No tool calls yet</Typography>}
|
||||
}) : <Typography sx={{ fontSize: c.font.size.xs, color: c.text.ghost }}>No tool calls yet</Typography>}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
// The type scale. OpenSwarm had ~30 near-duplicate hardcoded rem sizes (0.78/0.8/0.82/0.85...), which
|
||||
// reads as noise; snap to these 7 steps so type feels intentional and consistent. px comments assume a
|
||||
// 16px root. Use c.font.size.* instead of a raw rem string in anything you touch.
|
||||
export interface FontSizeScale {
|
||||
micro: string; // 10px, densest chrome (badges, kbd hints)
|
||||
xxs: string; // 11px, tiny meta / timestamps
|
||||
xs: string; // 12px, captions, meta, micro labels
|
||||
sm: string; // 13px, secondary / small body
|
||||
base: string; // 14px, default body + most UI text
|
||||
md: string; // 16px, emphasized body, inputs
|
||||
lg: string; // 18px, subheads
|
||||
xl: string; // 22px, section headings
|
||||
display: string; // 28px, hero / empty-state headline
|
||||
}
|
||||
|
||||
export const fontSize: FontSizeScale = {
|
||||
micro: '0.625rem',
|
||||
xxs: '0.6875rem',
|
||||
xs: '0.75rem',
|
||||
sm: '0.8125rem',
|
||||
base: '0.875rem',
|
||||
md: '1rem',
|
||||
lg: '1.125rem',
|
||||
xl: '1.375rem',
|
||||
display: '1.75rem',
|
||||
};
|
||||
|
||||
export interface ClaudeTokens {
|
||||
bg: { page: string; surface: string; elevated: string; secondary: string; inverse: string };
|
||||
text: { primary: string; secondary: string; tertiary: string; muted: string; inverse: string; ghost: string };
|
||||
@@ -7,7 +34,7 @@ export interface ClaudeTokens {
|
||||
radius: { xs: number; sm: number; md: number; lg: number; xl: number; full: number };
|
||||
status: { success: string; successBg: string; warning: string; warningBg: string; error: string; errorBg: string; info: string; infoBg: string };
|
||||
user: { bubble: string };
|
||||
font: { sans: string; mono: string };
|
||||
font: { sans: string; mono: string; size: FontSizeScale };
|
||||
transition: string;
|
||||
}
|
||||
|
||||
@@ -58,6 +85,7 @@ export const lightTokens: ClaudeTokens = {
|
||||
font: {
|
||||
sans: '"Anthropic Sans", ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
||||
mono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
||||
size: fontSize,
|
||||
},
|
||||
transition: 'all 150ms cubic-bezier(0.165, 0.85, 0.45, 1)',
|
||||
};
|
||||
@@ -109,6 +137,7 @@ export const darkTokens: ClaudeTokens = {
|
||||
font: {
|
||||
sans: '"Anthropic Sans", ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
||||
mono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
||||
size: fontSize,
|
||||
},
|
||||
transition: 'all 150ms cubic-bezier(0.165, 0.85, 0.45, 1)',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user