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:
2026-09-19 12:27:29 +02:00
parent e4719cb9ae
commit ae6db7e16f
105 changed files with 5683 additions and 3690 deletions
+111 -301
View File
@@ -8,9 +8,21 @@
margin: 0 0 var(--sp-4) 0;
}
/* `auto-fit` only ever governs the desktop case (>1024px) — the tablet/
* mobile breakpoints in responsive.css force an explicit 2-column/1-column
* layout regardless of this basis. At desktop widths the container is
* capped by `--maxw` (960px) minus the fixed chrome around it (`layout-main`
* + `.dashboard__more-details` padding), landing at ~864px regardless of
* how much wider the viewport itself gets — so the basis only needs to let
* FOUR cards (the two-parent "expiring at 4" case) fit that one width: four
* columns at 13rem plus three 16px gaps (880px) doesn't quite fit 864px and
* `auto-fit` drops to 3, stranding the 4th card alone on its own dangling
* row. 12.5rem keeps that same one-row-of-four for the two-parent overview
* metrics/expiring-at-4 lists while still comfortably wrapping to 2 columns
* for anything narrower than that. */
.overview-metrics__list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr));
gap: var(--sp-4);
}
@@ -25,6 +37,22 @@
box-shadow: var(--shadow-sm);
}
/* The "all parents" combined total — a footer row spanning every column
* instead of just another grid cell, so it never leaves a dangling gap
* when the per-parent metrics above it don't fill a whole row. Tinted so
* it reads as "the sum of the above", not one more figure among equals. */
.overview-metrics__item--summary {
grid-column: 1 / -1;
flex-direction: row;
justify-content: center;
gap: var(--sp-3);
background-color: var(--c-accent-weak);
}
.overview-metrics__item--summary .overview-metrics__value {
margin-bottom: 0;
}
.overview-metrics__value {
display: block;
font-size: var(--fs-2xl);
@@ -76,7 +104,7 @@
}
.days-breakdown__table tbody tr:hover {
background-color: rgba(47, 111, 79, 0.02);
background-color: var(--c-row-hover);
}
.days-breakdown__table tbody tr:last-child td {
@@ -131,7 +159,7 @@
}
.parental-leave-breakdown__table tbody tr:hover {
background-color: rgba(47, 111, 79, 0.02);
background-color: var(--c-row-hover);
}
.parental-leave-breakdown__table tbody tr:last-child td {
@@ -174,6 +202,11 @@
background-color: var(--c-danger-weak);
}
.dashboard-hero--reassure {
border-left-color: var(--c-success);
background-color: var(--c-success-weak);
}
.dashboard-hero__message {
margin: 0;
font-size: var(--fs-2xl);
@@ -191,15 +224,29 @@
flex-wrap: wrap;
}
.dashboard__estimate-badge {
display: inline-block;
margin: var(--sp-3) 0;
padding: var(--sp-1) var(--sp-3);
border-radius: var(--radius-full);
/* "Ange manuellt" for a two-parent workspace asks which parent first —
* see `Dashboard.tsx`'s `handleOpenManualFetch`. `.card` gives it the same
* chrome as the rest of the dashboard's boxed content instead of a bare,
* floating row of buttons. */
.dashboard__balance-picker {
margin-top: var(--sp-2);
}
.dashboard__balance-picker-label {
margin: 0 0 var(--sp-3) 0;
font-size: var(--fs-sm);
font-weight: 600;
background-color: var(--c-warning-weak);
color: var(--c-warning);
font-weight: 500;
color: var(--c-text-muted);
}
.dashboard__balance-picker-actions {
display: flex;
gap: var(--sp-3);
flex-wrap: wrap;
}
.dashboard__estimate-badge {
margin: var(--sp-3) 0;
cursor: help;
}
@@ -275,6 +322,37 @@
margin-bottom: var(--sp-1);
}
.year-timeline__caption {
font-size: var(--fs-sm);
color: var(--c-text-muted);
margin: 0;
}
.year-timeline__legend {
display: flex;
flex-wrap: wrap;
gap: var(--sp-4);
list-style: none;
margin: 0;
padding: 0;
font-size: var(--fs-sm);
color: var(--c-text-muted);
}
.year-timeline__legend-item {
display: flex;
align-items: center;
gap: var(--sp-1);
}
.year-timeline__legend-swatch {
display: inline-block;
width: 10px;
height: 10px;
border-radius: var(--radius-full);
flex-shrink: 0;
}
.year-timeline__row {
display: flex;
align-items: center;
@@ -345,52 +423,6 @@
margin-top: var(--sp-4);
}
.plan-fold__error {
color: var(--c-danger);
}
/* Button Styles */
button {
background-color: var(--c-accent);
color: var(--c-surface);
font-weight: 500;
}
button:hover:not(:disabled) {
background-color: var(--c-accent-hover);
}
button:active:not(:disabled) {
background-color: var(--c-accent-active);
}
/* Secondary button */
.button--secondary,
button.secondary {
background-color: transparent;
color: var(--c-accent);
border: 1px solid var(--c-accent);
}
.button--secondary:hover:not(:disabled),
button.secondary:hover:not(:disabled) {
background-color: var(--c-accent-weak);
}
/* Ghost button */
.button--ghost,
button.ghost {
background-color: transparent;
color: var(--c-text);
border: 1px solid var(--c-border);
}
.button--ghost:hover:not(:disabled),
button.ghost:hover:not(:disabled) {
background-color: var(--c-bg);
border-color: var(--c-text);
}
/* Form styles */
.form-group {
margin-bottom: var(--sp-5);
@@ -432,24 +464,8 @@ button.ghost:hover:not(:disabled) {
max-width: none;
}
/* Settings Page Messages */
.settings-page__message {
padding: var(--sp-4);
border-radius: var(--radius);
.settings-page > .alert {
margin-bottom: var(--sp-4);
font-weight: 500;
}
.settings-page__message--success {
background-color: var(--c-success-weak);
color: var(--c-success);
border-left: 4px solid var(--c-success);
}
.settings-page__message--error {
background-color: var(--c-danger-weak);
color: var(--c-danger);
border-left: 4px solid var(--c-danger);
}
.settings-page__help {
@@ -483,11 +499,11 @@ button.ghost:hover:not(:disabled) {
gap: var(--sp-3);
}
/* Box/border/focus chrome comes from `.field__control` in primitives.css —
* this only adds the flex sizing `.field__control`'s own rule doesn't know
* about (it sits in an inline row next to a button, not a stacked `.field`). */
.settings-page__partner-input {
flex: 1;
padding: var(--sp-3) var(--sp-3);
border: 1px solid var(--c-border);
border-radius: var(--radius);
}
.settings-page__member-list {
@@ -507,11 +523,6 @@ button.ghost:hover:not(:disabled) {
padding: var(--sp-2) 0;
}
.settings-page__you-badge {
color: var(--c-text-muted);
font-size: var(--fs-sm);
}
.settings-page__remove-confirm {
display: flex;
align-items: center;
@@ -520,46 +531,6 @@ button.ghost:hover:not(:disabled) {
font-size: var(--fs-sm);
}
.settings-page__button:not(:disabled):hover {
background-color: var(--c-accent-hover);
}
.settings-page__button:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
}
.settings-page__button--secondary {
background-color: transparent;
color: var(--c-text);
border: 1px solid var(--c-border);
}
.settings-page__button--secondary:not(:disabled):hover {
background-color: var(--c-bg);
}
.settings-page__button--secondary:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
}
.settings-page__button--danger {
background-color: var(--c-danger-weak);
color: var(--c-danger);
border: 1px solid var(--c-danger);
}
.settings-page__button--danger:not(:disabled):hover {
background-color: var(--c-danger);
color: white;
}
.settings-page__button--danger:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
}
/* Vacation Days Table (multi-year) */
.vacation-days-table {
width: 100%;
@@ -592,14 +563,7 @@ button.ghost:hover:not(:disabled) {
}
.vacation-days-table__assumed-badge {
display: inline-block;
margin-left: var(--sp-2);
padding: var(--sp-1) var(--sp-2);
border-radius: 999px;
font-size: var(--fs-sm);
font-weight: 400;
background-color: var(--c-warning-weak);
color: var(--c-warning);
cursor: help;
}
@@ -607,36 +571,11 @@ button.ghost:hover:not(:disabled) {
width: 90px;
}
.vacation-days-table__save {
white-space: nowrap;
}
.vacation-days-table__save:not(:disabled):hover {
background-color: var(--c-accent-hover);
}
.vacation-days-table__save:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
}
.vacation-days-table__empty {
color: var(--c-text-muted);
font-size: var(--fs-sm);
}
/* Planning Mode Selector */
.planning-mode-selector__option {
display: flex;
align-items: center;
gap: var(--sp-2);
margin-bottom: var(--sp-2);
}
.planning-mode-selector__option:last-child {
margin-bottom: 0;
}
/* Fieldsets */
fieldset {
border: 1px solid var(--c-border);
@@ -652,18 +591,20 @@ legend {
margin-left: calc(var(--sp-2) * -1);
}
/* Input Components */
/* Input Components
*
* `ChildBirthdayInput` now builds its fields from `.field`/`.field__label`/
* `.field__control` (primitives.css) directly rather than its own
* `.child-birthday-input__*` set — that set is gone from here accordingly. */
.vacation-days-input,
.planning-goal-selector,
.parent-profile-input,
.child-birthday-input {
.parent-profile-input {
margin-bottom: var(--sp-5);
}
.vacation-days-input__label,
.planning-goal-selector__label,
.parent-profile-input__label,
.child-birthday-input__label {
.parent-profile-input__label {
display: block;
margin-bottom: var(--sp-2);
font-weight: 500;
@@ -673,15 +614,13 @@ legend {
.vacation-days-input__field,
.planning-goal-selector__select,
.parent-profile-input__field,
.child-birthday-input__field {
.parent-profile-input__field {
width: 100%;
max-width: 400px;
}
.vacation-days-input__range,
.parent-profile-input__help,
.child-birthday-input__help {
.parent-profile-input__help {
font-size: var(--fs-sm);
color: var(--c-text-muted);
margin-top: var(--sp-2);
@@ -772,33 +711,13 @@ legend {
margin-bottom: var(--sp-3);
}
.day-overrides-input__remove-button {
width: 100%;
padding: var(--sp-2) var(--sp-3);
font-size: var(--fs-sm);
background-color: var(--c-danger-weak);
color: var(--c-danger);
border: none;
border-radius: var(--radius);
cursor: pointer;
transition: background-color 0.2s;
}
.day-overrides-input__remove-button:hover:not(:disabled) {
background-color: var(--c-danger);
color: white;
}
.day-overrides-input__remove-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Plain section, not a bordered card — matches the rest of Settings (see
* layout.css's "sections separate by whitespace and a hairline rule, not a
* stack of bordered/shadowed cards" comment). Only a top hairline separates
* it from the list of existing exceptions above. */
.day-overrides-input__form {
padding: var(--sp-4);
background-color: var(--c-bg);
border: 1px solid var(--c-border);
border-radius: var(--radius);
padding-top: var(--sp-4);
border-top: 1px solid var(--c-border);
}
.day-overrides-input__form h3 {
@@ -808,24 +727,8 @@ legend {
}
.day-overrides-input__form-group {
margin-bottom: var(--sp-4);
}
.day-overrides-input__form-group label {
display: block;
margin-bottom: var(--sp-2);
font-weight: 500;
color: var(--c-text);
font-size: var(--fs-base);
}
.day-overrides-input__form-input {
width: 100%;
max-width: 400px;
padding: var(--sp-2) var(--sp-3);
border: 1px solid var(--c-border);
border-radius: var(--radius);
font-size: var(--fs-base);
margin-bottom: var(--sp-4);
}
.day-overrides-input__radio-group {
@@ -863,20 +766,6 @@ legend {
color: var(--c-text-muted);
}
.day-overrides-input__submit-button {
width: 100%;
padding: var(--sp-3) var(--sp-4);
font-weight: 500;
border: none;
border-radius: var(--radius);
cursor: pointer;
transition: background-color 0.2s;
}
.day-overrides-input__submit-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Red Days List (§3.8 — statutory red days + workspace overrides, per year) */
.red-days-list__pagination {
@@ -886,20 +775,6 @@ legend {
margin-bottom: var(--sp-4);
}
.red-days-list__nav-button {
padding: var(--sp-2) var(--sp-3);
border: 1px solid var(--c-border);
border-radius: var(--radius);
background-color: var(--c-surface);
color: var(--c-text);
cursor: pointer;
}
.red-days-list__nav-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.red-days-list__range {
font-weight: 600;
}
@@ -943,22 +818,6 @@ legend {
color: var(--c-text);
}
.red-days-list__override-badge {
padding: var(--sp-1) var(--sp-2);
border-radius: 999px;
font-size: var(--fs-sm);
}
.red-days-list__override-badge--working {
background-color: var(--c-danger-weak);
color: var(--c-danger);
}
.red-days-list__override-badge--free {
background-color: var(--c-success-weak);
color: var(--c-success);
}
.red-days-list__effective {
margin-left: auto;
font-size: var(--fs-sm);
@@ -1016,32 +875,13 @@ legend {
color: var(--c-text);
}
/* Sized down from the standard --control-h .btn--icon square — this dismiss
* glyph sits inline in a card header, not in a toolbar. */
.tip-card__dismiss {
flex-shrink: 0;
width: 24px;
height: 24px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
color: var(--c-text-muted);
border: none;
border-radius: var(--radius-sm);
font-size: var(--fs-base);
line-height: 1;
cursor: pointer;
transition: background-color 0.15s, color 0.15s;
}
.tip-card__dismiss:hover {
background-color: rgba(0, 0, 0, 0.06);
color: var(--c-text);
}
.tip-card__dismiss:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 1px;
}
.tip-card__body {
@@ -1049,33 +889,3 @@ legend {
font-size: var(--fs-sm);
color: var(--c-text-muted);
}
/* Responsive */
@media (max-width: 640px) {
[data-testid='metrics-list'] {
grid-template-columns: 1fr;
}
.form-group input,
.form-group select,
.form-group textarea,
.vacation-days-input__field,
.planning-goal-selector__select,
.parent-profile-input__field,
.child-birthday-input__field {
max-width: none;
}
.form-grid {
grid-template-columns: 1fr;
}
.days-breakdown__table {
font-size: var(--fs-sm);
}
.days-breakdown__table th,
.days-breakdown__table td {
padding: var(--sp-2) var(--sp-3);
}
}