import { Check, Info } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Combobox } from '@/components/ui/combobox'; import { Slider } from '@/components/ui/slider'; import { SegmentedControl } from '@/components/ui/segmented-control'; import { TogglePill } from '@/components/ui/toggle-pill'; import { useDensity } from '@/hooks/use-density'; import type { Density } from '@/hooks/use-density'; import { useLogChipColorMode, type LogChipColorMode } from '@/hooks/use-log-chip-color-mode'; import { useTopNavLabels } from '@/hooks/use-top-nav-labels'; import { useTopNavAlign, type TopNavAlign } from '@/hooks/use-top-nav-align'; import { useTheme, activeVisualStyle, THEME_MODE_OPTIONS, ACCENTS, CONTRAST, BORDER_BOOST, GLOW, TYPE_SCALE, type VisualStyle, type HeadingStyle, type ChartStyle, } from '@/hooks/use-theme'; import { AccentPicker } from '@/components/theme/AccentPicker'; import { ThemePreview } from '@/components/theme/ThemePreview'; import { TypeChips } from '@/components/theme/TypeChips'; import { UI_FONT_OPTIONS, MONO_FONT_OPTIONS } from '@/components/theme/typeOptions'; import { SettingsSection } from './SettingsSection'; import { SettingsField } from './SettingsField'; import { SettingsActions, SettingsSecondaryButton } from './SettingsActions'; import { SettingsCallout } from './SettingsCallout'; const DENSITY_OPTIONS: { value: Density; label: string }[] = [ { value: 'comfortable', label: 'Comfortable' }, { value: 'compact', label: 'Compact' }, ]; const DENSITY_DESCRIPTIONS: Record = { comfortable: 'Default spacing. Roomy rows for review and orientation.', compact: 'Tighter rows and tiles. Fits more on screen for dense dashboards.', }; const TOP_NAV_ALIGN_OPTIONS: { value: TopNavAlign; label: string }[] = [ { value: 'left', label: 'Left' }, { value: 'center', label: 'Center' }, ]; const CHART_STYLE_OPTIONS: { value: ChartStyle; label: string }[] = [ { value: 'muted', label: 'Muted' }, { value: 'heat', label: 'Heat' }, { value: 'signature', label: 'Signature' }, ]; const HEADING_STYLE_OPTIONS: { value: HeadingStyle; label: string }[] = [ { value: 'clean', label: 'Clean' }, { value: 'signature', label: 'Signature' }, ]; const CHIP_COLOR_OPTIONS: { value: LogChipColorMode; label: string }[] = [ { value: 'unified', label: 'Unified' }, { value: 'per-service', label: 'Per service' }, ]; const fmtSigned = (v: number) => `${v > 0 ? '+' : ''}${v.toFixed(2)}`; // Preview swatches for the Visual style cards. Calm uses the muted ramp; Signature // deliberately puts the brand bar next to destructive to show the clash it fixes. interface VisualCardData { kind: VisualStyle; name: string; blurb: string; bars: string[]; } const VISUAL_CARDS: VisualCardData[] = [ { kind: 'calm', name: 'Calm', blurb: 'Upright headings, muted flat charts. The readable default.', bars: ['oklch(0.605 0.105 28)', 'oklch(0.715 0.085 70)', 'oklch(0.675 0.045 88)', 'oklch(0.565 0.018 250)'], }, { kind: 'signature', name: 'Signature', blurb: "Italic serif headings, saturated charts. Today's look.", bars: ['var(--destructive)', 'var(--warning)', 'var(--brand)', 'color-mix(in oklch, var(--warning) 50%, var(--stat-icon))'], }, ]; const VISUAL_BAR_HEIGHTS = [16, 11, 13, 8]; function VisualCard({ card, selected, disabled, onSelect, }: { card: VisualCardData; selected: boolean; disabled?: boolean; onSelect: () => void; }) { const { kind, name, blurb, bars } = card; const calm = kind === 'calm'; return ( ); } export function AppearanceSection() { const [density, setDensity] = useDensity(); const [chipColorMode, setChipColorMode] = useLogChipColorMode(); const [topNavLabels, setTopNavLabels] = useTopNavLabels(); const [topNavAlign, setTopNavAlign] = useTopNavAlign(); const { theme, accent, borderBoost, glow, contrast, uiFont, monoFont, typeScale, headingStyle, chartStyle, reducedEffects, reducedMotion, readability, setTheme, setAccent, setBorderBoost, setGlow, setContrast, setUiFont, setMonoFont, setTypeScale, setVisualStyle, setHeadingStyle, setChartStyle, setReducedEffects, setReducedMotion, setReadability, } = useTheme(); const accentLabel = ACCENTS.find((a) => a.id === accent)?.label ?? 'Cyan'; // Readability is a sticky master: it forces the calm resolution at apply time // without mutating the stored sub-axes, so the controls reflect the forced // value and lock while it is on. Effects are reduced under readability too. const effectiveReduced = readability || reducedEffects; const effectiveContrast = contrast + (readability ? 0.18 : 0); // A card is selected only while the stored sub-axes still match its preset, so // a custom combination de-selects both (and readability resolves to null). // Shared with the topbar quick-switch via activeVisualStyle so both surfaces // agree on which style is active. const activeVisual = activeVisualStyle({ headingStyle, chartStyle, reducedEffects, readability }); return (
{VISUAL_CARDS.map((c) => ( setVisualStyle(c.kind)} /> ))}
{readability ? (

Readability mode is on. It forces the calmest settings and a small contrast lift. Turn it off below to choose a style by hand.

) : null}
setContrast(v)} aria-label="Contrast" /> {fmtSigned(effectiveContrast)}
{!reducedMotion ? ( } title="Constrained graphics" subtitle="If the active Sencho tab feels heavy on integrated graphics or similar devices, turn on Reduced motion. Reduced effects and Calm alone may not be enough for that idle cost." /> ) : null}
setGlow(v)} disabled={effectiveReduced} aria-label="Ambient glow" /> {glow.toFixed(2)}
setVisualStyle('calm')} disabled={readability} > Reset to default
setBorderBoost(v)} disabled={readability} aria-label="Border brightness" /> {fmtSigned(readability ? 0.03 : borderBoost)}
{ setContrast(CONTRAST.default); setBorderBoost(BORDER_BOOST.default); setGlow(GLOW.default); }} > Reset fine-tune
setTypeScale(v)} aria-label="Text size" /> {typeScale.toFixed(2)}×
{ if (v === 'comfortable' || v === 'compact') setDensity(v); }} placeholder="Select density" /> {!topNavLabels && ( )}

ⓘ saved to this browser only · every device remembers its own choice

); }