Redesign the setup flow and consolidate the CSS into one primitives layer
Onboarding asks explicitly for one or two parents and shows one card per parent for balances; the balance form is per parent with inline validation; the partner can be filled from the FK deduction or added from it. Settings and Dashboard reuse the same form. CSS: bare buttons carry no colour any more; buttons, cards, choice cards, fields, alerts, badges, eyebrow and modal chrome are defined once in primitives.css. Media queries live only in responsive.css. A style guard test blocks hex, !important and stray media queries. Bilingual fixes: calendar selects size by content, header no longer wraps. Vitest is capped at four workers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* Component styles - must come first */
|
||||
@import './primitives.css';
|
||||
@import './components.css';
|
||||
@import './modals.css';
|
||||
@import './responsive.css';
|
||||
|
||||
/* Design Tokens
|
||||
@@ -52,6 +52,19 @@
|
||||
|
||||
--overlay: rgba(16, 24, 40, 0.5);
|
||||
|
||||
/* Translucent tints used only for subtle hover/shadow/scrim effects — kept
|
||||
* as tokens (rather than inlined rgba() elsewhere) because the style
|
||||
* guard test (styles.test.ts) forbids rgba() outside this file. */
|
||||
--c-row-hover: rgba(47, 111, 79, 0.02);
|
||||
--c-accent-tint: rgba(47, 111, 79, 0.04);
|
||||
--c-accent-shadow: rgba(47, 111, 79, 0.12);
|
||||
--c-scrim-strong: rgba(0, 0, 0, 0.4);
|
||||
--c-band-shadow: rgba(0, 0, 0, 0.1);
|
||||
--c-label-text-shadow: rgba(0, 0, 0, 0.35);
|
||||
--c-fill-light: rgba(255, 255, 255, 0.35);
|
||||
--c-badge-fill: rgba(255, 255, 255, 0.3);
|
||||
--c-scrim-soft: rgba(0, 0, 0, 0.2);
|
||||
|
||||
/* Spacing scale (8px base) */
|
||||
--sp-1: 4px;
|
||||
--sp-2: 8px;
|
||||
@@ -62,6 +75,8 @@
|
||||
--sp-8: 48px;
|
||||
|
||||
/* Type scale */
|
||||
--fs-5xs: 0.5rem;
|
||||
--fs-4xs: 0.5625rem;
|
||||
--fs-2xs: 0.6875rem;
|
||||
--fs-xs: 0.75rem;
|
||||
--fs-sm: 0.875rem;
|
||||
@@ -85,6 +100,16 @@
|
||||
/* Content width */
|
||||
--maxw: 960px;
|
||||
|
||||
/* Interaction chrome shared by every focusable control */
|
||||
--c-focus-ring: rgba(47, 111, 79, 0.32);
|
||||
--fs-3xs: 0.625rem;
|
||||
--control-h: 40px;
|
||||
|
||||
/* Icon glyph sizes (spinner, etc.) — a separate scale from --fs-* since
|
||||
* these size a single glyph, not running text. */
|
||||
--icon-lg: 48px;
|
||||
--icon-md: 36px;
|
||||
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@@ -250,36 +275,42 @@ a:focus-visible {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Buttons - base */
|
||||
/* Buttons - base
|
||||
*
|
||||
* A plain reset only — no color, no padding beyond zero. Every visible
|
||||
* button opts into a look via a `.btn` variant (see primitives.css) so a
|
||||
* semantic element that happens to be a `<button>` (a card, a chip, a day
|
||||
* cell) never inherits a color scheme it didn't ask for. */
|
||||
button {
|
||||
font-family: inherit;
|
||||
font-size: var(--fs-base);
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
background: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
padding: var(--sp-3) var(--sp-4);
|
||||
transition: background-color 0.2s, color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
button:focus-visible {
|
||||
outline: 2px solid var(--c-accent);
|
||||
outline-offset: 2px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Inputs & Selects */
|
||||
input,
|
||||
/* Inputs & Selects
|
||||
*
|
||||
* Matches `.field__control` in primitives.css (same border/radius/padding/
|
||||
* focus formula) so an unclassed input still looks right. Checkboxes and
|
||||
* radios are excluded — they keep their native intrinsic size and just pick
|
||||
* up the accent color, not the text-field chrome. */
|
||||
input:not([type='checkbox']):not([type='radio']),
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: var(--control-h);
|
||||
font-family: inherit;
|
||||
font-size: var(--fs-base);
|
||||
border: 1px solid var(--c-border);
|
||||
border-radius: var(--radius);
|
||||
padding: var(--sp-3) var(--sp-4);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
background: var(--c-surface);
|
||||
color: var(--c-text);
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
@@ -290,7 +321,17 @@ select:focus,
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--c-accent);
|
||||
box-shadow: 0 0 0 3px rgba(47, 111, 79, 0.1);
|
||||
box-shadow: 0 0 0 3px var(--c-focus-ring);
|
||||
}
|
||||
|
||||
/* Same crisp keyboard-focus outline as `.field__control:focus-visible`
|
||||
* (primitives.css) for an unclassed input/select/textarea — see that rule's
|
||||
* comment. */
|
||||
input:focus-visible,
|
||||
select:focus-visible,
|
||||
textarea:focus-visible {
|
||||
outline: 2px solid var(--c-accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
input:disabled,
|
||||
@@ -300,6 +341,36 @@ textarea:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input[type='checkbox'],
|
||||
input[type='radio'] {
|
||||
width: auto;
|
||||
accent-color: var(--c-accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Number inputs — hide the native up/down spinner everywhere. It doubles
|
||||
* the field's effective click target with no matching `.field__control`
|
||||
* affordance and its width isn't accounted for by any of this app's `ch`-
|
||||
* based sizing. Callers still get numeric-only mobile keyboards via
|
||||
* `inputmode="numeric"` on the element itself. */
|
||||
input[type='number'] {
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
input[type='number']::-webkit-outer-spin-button,
|
||||
input[type='number']::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Date inputs — same height/padding/font as `.field__control` by default
|
||||
* (the shared `input:not([type=checkbox]):not([type=radio])` rule above
|
||||
* already covers it), just with a visibly clickable calendar affordance. */
|
||||
input[type='date']::-webkit-calendar-picker-indicator {
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Label */
|
||||
label {
|
||||
display: block;
|
||||
@@ -329,14 +400,7 @@ td {
|
||||
}
|
||||
|
||||
tr:hover td {
|
||||
background-color: rgba(47, 111, 79, 0.02);
|
||||
}
|
||||
|
||||
/* Section container */
|
||||
main {
|
||||
max-width: var(--maxw);
|
||||
margin: 0 auto;
|
||||
padding: var(--sp-5);
|
||||
background-color: var(--c-row-hover);
|
||||
}
|
||||
|
||||
/* Utility classes for form */
|
||||
@@ -347,43 +411,3 @@ main {
|
||||
.form-group label {
|
||||
margin-bottom: var(--sp-2);
|
||||
}
|
||||
|
||||
/* Message boxes */
|
||||
.error-message {
|
||||
padding: var(--sp-3) var(--sp-4);
|
||||
background-color: var(--c-danger-weak);
|
||||
color: var(--c-danger);
|
||||
border-left: 4px solid var(--c-danger);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: var(--sp-4);
|
||||
}
|
||||
|
||||
.success-message {
|
||||
padding: var(--sp-3) var(--sp-4);
|
||||
background-color: var(--c-success-weak);
|
||||
color: var(--c-success);
|
||||
border-left: 4px solid var(--c-success);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: var(--sp-4);
|
||||
}
|
||||
|
||||
.warning-message {
|
||||
padding: var(--sp-3) var(--sp-4);
|
||||
background-color: var(--c-warning-weak);
|
||||
color: var(--c-warning);
|
||||
border-left: 4px solid var(--c-warning);
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: var(--sp-4);
|
||||
}
|
||||
|
||||
/* Reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user