Settle on one visual language

The interface had drifted into two of everything. The calendar and its
popovers were built around a blue accent the rest of the app had long since
replaced with green, five different sizes served the same class of small
label, and page sections were a stack of identical bordered cards that gave
no sense of what mattered most. Sections now separate by space and a hairline,
the overview leads with its headline figure at a size that says so, and the
palette, type scale and spacing live in one place as variables rather than
scattered literals.

Two things were not styled at all. The sign-in screen was an unstyled form in
the top-left corner, and the tip cards had no rules whatsoever despite their
markup already carrying a severity, so every tip rendered identically with a
plain green square for its dismiss control.

The calendar was also unusable on a narrow screen: the preset rail never
collapsed and covered the last three columns of the grid.
This commit is contained in:
2026-09-18 01:39:34 +02:00
parent f2b3920cbc
commit 296b596778
15 changed files with 757 additions and 433 deletions
@@ -4,7 +4,7 @@
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.5); background-color: var(--overlay);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -12,9 +12,9 @@
} }
.period-popover__content { .period-popover__content {
background-color: #fff; background-color: var(--c-surface);
border-radius: 8px; border-radius: var(--radius);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); box-shadow: var(--shadow-md);
max-width: 480px; max-width: 480px;
width: 90%; width: 90%;
max-height: 80vh; max-height: 80vh;
@@ -25,166 +25,186 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 20px; padding: var(--sp-5);
border-bottom: 1px solid #e0e0e0; border-bottom: 1px solid var(--c-border);
} }
.period-popover__header h2 { .period-popover__header h2 {
margin: 0; margin: 0;
font-size: 18px; font-size: var(--fs-xl);
font-weight: 600; font-weight: 700;
} }
.period-popover__close { .period-popover__close {
background: none; background: none;
border: none; border: none;
font-size: 28px; font-size: var(--fs-xl);
cursor: pointer; cursor: pointer;
padding: 0; padding: 0;
color: #666; color: var(--c-text-muted);
transition: color 0.2s; transition: color 0.2s;
} }
.period-popover__close:hover { .period-popover__close:hover {
color: #000; color: var(--c-text);
}
.period-popover__close:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
} }
.period-popover__body { .period-popover__body {
padding: 20px; padding: var(--sp-5);
} }
.period-popover__error { .period-popover__error {
background-color: #ffebee; background-color: var(--c-danger-weak);
color: #c62828; color: var(--c-danger);
padding: 12px; padding: var(--sp-3);
border-radius: 4px; border-radius: var(--radius-sm);
margin-bottom: 16px; margin-bottom: var(--sp-4);
font-size: 14px; font-size: var(--fs-sm);
} }
.period-popover__adhoc-notice { .period-popover__adhoc-notice {
font-size: 13px; font-size: var(--fs-sm);
color: #666; color: var(--c-text-muted);
margin: 0 0 12px 0; margin: 0 0 var(--sp-3) 0;
} }
.period-popover__date-range { .period-popover__date-range {
font-size: 15px; font-size: var(--fs-base);
font-weight: 500; font-weight: 500;
color: #333; color: var(--c-text);
} }
.period-popover__field { .period-popover__field {
margin-bottom: 16px; margin-bottom: var(--sp-4);
} }
.period-popover__label { .period-popover__label {
display: block; display: block;
font-size: 14px; font-size: var(--fs-sm);
font-weight: 500; font-weight: 500;
margin-bottom: 6px; margin-bottom: var(--sp-2);
color: #333; color: var(--c-text);
} }
.period-popover__input, .period-popover__input,
.period-popover__select { .period-popover__select {
width: 100%; width: 100%;
padding: 8px 10px; padding: var(--sp-2) var(--sp-3);
border: 1px solid #d0d0d0; border: 1px solid var(--c-border);
border-radius: 4px; border-radius: var(--radius-sm);
font-size: 14px; font-size: var(--fs-sm);
background-color: #fff; background-color: var(--c-surface);
}
.period-popover__input:focus,
.period-popover__select:focus {
outline: none;
border-color: var(--c-accent);
box-shadow: 0 0 0 3px var(--c-accent-weak);
} }
.period-popover__input:disabled, .period-popover__input:disabled,
.period-popover__select:disabled { .period-popover__select:disabled {
background-color: #f5f5f5; background-color: var(--c-bg);
cursor: not-allowed; cursor: not-allowed;
} }
.period-popover__fieldset { .period-popover__fieldset {
border: none; border: none;
padding: 0; padding: 0;
margin: 0 0 16px 0; margin: 0 0 var(--sp-4) 0;
} }
.period-popover__radio-label, .period-popover__radio-label,
.period-popover__checkbox-label { .period-popover__checkbox-label {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 14px; font-size: var(--fs-sm);
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
margin-bottom: 8px; margin-bottom: var(--sp-2);
} }
.period-popover__radio-label input, .period-popover__radio-label input,
.period-popover__checkbox-label input { .period-popover__checkbox-label input {
margin-right: 8px; margin-right: var(--sp-2);
cursor: pointer; cursor: pointer;
} }
.period-popover__footer { .period-popover__footer {
display: flex; display: flex;
gap: 8px; gap: var(--sp-2);
justify-content: flex-end; justify-content: flex-end;
padding: 16px 20px; padding: var(--sp-4) var(--sp-5);
border-top: 1px solid #e0e0e0; border-top: 1px solid var(--c-border);
background-color: #fafafa; background-color: var(--c-bg);
} }
.period-popover__button { .period-popover__button {
padding: 8px 16px; padding: var(--sp-2) var(--sp-4);
border-radius: 4px; border-radius: var(--radius-sm);
font-size: 14px; font-size: var(--fs-sm);
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
border: none; border: none;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
.period-popover__button:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
}
.period-popover__button:disabled { .period-popover__button:disabled {
opacity: 0.6; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
} }
.period-popover__button--primary { .period-popover__button--primary {
background-color: #0066cc; background-color: var(--c-accent);
color: #fff; color: var(--c-surface);
} }
.period-popover__button--primary:hover:not(:disabled) { .period-popover__button--primary:hover:not(:disabled) {
background-color: #0052a3; background-color: var(--c-accent-hover);
} }
.period-popover__button--secondary { .period-popover__button--secondary {
background-color: #e0e0e0; background-color: var(--c-bg);
color: #333; color: var(--c-text);
border: 1px solid var(--c-border);
} }
.period-popover__button--secondary:hover:not(:disabled) { .period-popover__button--secondary:hover:not(:disabled) {
background-color: #d0d0d0; background-color: var(--c-border);
} }
.period-popover__button--danger { .period-popover__button--danger {
background-color: #d32f2f; background-color: var(--c-danger-weak);
color: #fff; color: var(--c-danger);
border: 1px solid var(--c-danger);
margin-right: auto; margin-right: auto;
} }
.period-popover__button--danger:hover:not(:disabled) { .period-popover__button--danger:hover:not(:disabled) {
background-color: #b71c1c; background-color: var(--c-danger);
color: var(--c-surface);
} }
.period-popover__delete-confirm { .period-popover__delete-confirm {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: var(--sp-3);
width: 100%; width: 100%;
} }
.period-popover__delete-confirm-text { .period-popover__delete-confirm-text {
font-size: 14px; font-size: var(--fs-sm);
color: #333; color: var(--c-text);
margin-right: auto; margin-right: auto;
} }
@@ -1,30 +1,30 @@
.preset-rail { .preset-rail {
width: 200px; width: 200px;
flex-shrink: 0; flex-shrink: 0;
padding: 16px; padding: var(--sp-4);
background-color: #f9f9f9; background-color: var(--c-surface);
border: 1px solid #e0e0e0; border-radius: var(--radius);
border-radius: 4px; box-shadow: var(--shadow-sm);
align-self: flex-start; align-self: flex-start;
} }
.preset-rail__heading { .preset-rail__heading {
margin: 0 0 4px 0; margin: 0 0 var(--sp-1) 0;
font-size: 14px; font-size: var(--fs-sm);
font-weight: 600; font-weight: 600;
color: #333; color: var(--c-text);
} }
.preset-rail__hint { .preset-rail__hint {
margin: 0 0 12px 0; margin: 0 0 var(--sp-3) 0;
font-size: 12px; font-size: var(--fs-xs);
color: #666; color: var(--c-text-muted);
} }
.preset-rail__buttons { .preset-rail__buttons {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: var(--sp-2);
} }
.preset-rail__button { .preset-rail__button {
@@ -32,18 +32,18 @@
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 2px; gap: 2px;
padding: 10px 12px; padding: var(--sp-2) var(--sp-3);
border: 1px solid #d0d0d0; border: 1px solid var(--c-border);
border-radius: 4px; border-radius: var(--radius-sm);
background-color: #fff; background-color: var(--c-surface);
cursor: pointer; cursor: pointer;
text-align: left; text-align: left;
transition: background-color 0.2s, border-color 0.2s; transition: background-color 0.2s, border-color 0.2s;
} }
.preset-rail__button:hover:not(:disabled) { .preset-rail__button:hover:not(:disabled) {
background-color: #f0f8ff; background-color: var(--c-accent-weak);
border-color: #0066cc; border-color: var(--c-accent);
} }
.preset-rail__button:disabled { .preset-rail__button:disabled {
@@ -52,12 +52,12 @@
} }
.preset-rail__button-name { .preset-rail__button-name {
font-size: 14px; font-size: var(--fs-sm);
font-weight: 600; font-weight: 600;
color: #333; color: var(--c-text);
} }
.preset-rail__button-detail { .preset-rail__button-detail {
font-size: 11px; font-size: var(--fs-2xs);
color: #666; color: var(--c-text-muted);
} }
+22 -7
View File
@@ -11,7 +11,7 @@
max-width: var(--maxw); max-width: var(--maxw);
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
padding: var(--sp-5); padding: var(--sp-6) var(--sp-5);
} }
/* Dashboard & Pages */ /* Dashboard & Pages */
@@ -23,16 +23,31 @@
width: 100%; width: 100%;
} }
/* Sections separate by whitespace and a hairline rule, not a stack of
* bordered/shadowed cards — every section on a page looking equally "boxed"
* flattens the hierarchy the headline figures above them are meant to have.
* The `<details>` accordions (plan-fold, dashboard__more-details) keep their
* own bordered treatment in components.css since they're interactive
* disclosure controls, not passive content groups. */
.dashboard__section, .dashboard__section,
.settings-page__section, .settings-page__section,
.calendar__section, .calendar__section,
.tips-page__section { .tips-page__section {
background-color: var(--c-surface); background-color: transparent;
border: 1px solid var(--c-border); border: none;
border-radius: var(--radius); border-top: 1px solid var(--c-border);
padding: var(--sp-5); border-radius: 0;
margin-bottom: var(--sp-5); padding: var(--sp-6) 0 0;
box-shadow: var(--shadow-sm); margin-bottom: var(--sp-6);
box-shadow: none;
}
.dashboard__section:first-child,
.settings-page__section:first-child,
.calendar__section:first-child,
.tips-page__section:first-child {
border-top: none;
padding-top: 0;
} }
.dashboard__section:last-child, .dashboard__section:last-child,
@@ -4,7 +4,7 @@
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.5); background-color: var(--overlay);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -12,9 +12,9 @@
} }
.manual-fetch-modal__content { .manual-fetch-modal__content {
background-color: #fff; background-color: var(--c-surface);
border-radius: 8px; border-radius: var(--radius);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); box-shadow: var(--shadow-md);
max-width: 600px; max-width: 600px;
width: 90%; width: 90%;
max-height: 85vh; max-height: 85vh;
@@ -25,53 +25,58 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 20px; padding: var(--sp-5);
border-bottom: 1px solid #e0e0e0; border-bottom: 1px solid var(--c-border);
} }
.manual-fetch-modal__header h2 { .manual-fetch-modal__header h2 {
margin: 0; margin: 0;
font-size: 18px; font-size: var(--fs-xl);
font-weight: 600; font-weight: 700;
} }
.manual-fetch-modal__close { .manual-fetch-modal__close {
background: none; background: none;
border: none; border: none;
font-size: 28px; font-size: var(--fs-xl);
cursor: pointer; cursor: pointer;
padding: 0; padding: 0;
color: #666; color: var(--c-text-muted);
transition: color 0.2s; transition: color 0.2s;
} }
.manual-fetch-modal__close:hover { .manual-fetch-modal__close:hover {
color: #000; color: var(--c-text);
}
.manual-fetch-modal__close:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
} }
.manual-fetch-modal__body { .manual-fetch-modal__body {
padding: 20px; padding: var(--sp-5);
} }
.manual-fetch-modal__description { .manual-fetch-modal__description {
font-size: 14px; font-size: var(--fs-sm);
color: #666; color: var(--c-text-muted);
margin: 0 0 16px 0; margin: 0 0 var(--sp-4) 0;
} }
.manual-fetch-modal__error { .manual-fetch-modal__error {
background-color: #ffebee; background-color: var(--c-danger-weak);
color: #c62828; color: var(--c-danger);
padding: 12px; padding: var(--sp-3);
border-radius: 4px; border-radius: var(--radius-sm);
margin-bottom: 16px; margin-bottom: var(--sp-4);
font-size: 14px; font-size: var(--fs-sm);
} }
.manual-fetch-modal__section { .manual-fetch-modal__section {
margin-bottom: 20px; margin-bottom: var(--sp-5);
padding-bottom: 20px; padding-bottom: var(--sp-5);
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid var(--c-border);
} }
.manual-fetch-modal__section:last-child { .manual-fetch-modal__section:last-child {
@@ -81,85 +86,97 @@
} }
.manual-fetch-modal__section h3 { .manual-fetch-modal__section h3 {
margin: 0 0 12px 0; margin: 0 0 var(--sp-3) 0;
font-size: 14px; font-size: var(--fs-sm);
font-weight: 600; font-weight: 600;
color: #333; color: var(--c-text);
} }
.manual-fetch-modal__field { .manual-fetch-modal__field {
margin-bottom: 12px; margin-bottom: var(--sp-3);
} }
.manual-fetch-modal__field label { .manual-fetch-modal__field label {
display: block; display: block;
font-size: 13px; font-size: var(--fs-xs);
font-weight: 500; font-weight: 500;
margin-bottom: 4px; margin-bottom: var(--sp-1);
color: #555; color: var(--c-text-muted);
} }
.manual-fetch-modal__field input { .manual-fetch-modal__field input {
width: 100%; width: 100%;
padding: 8px 10px; padding: var(--sp-2) var(--sp-3);
border: 1px solid #d0d0d0; border: 1px solid var(--c-border);
border-radius: 4px; border-radius: var(--radius-sm);
font-size: 14px; font-size: var(--fs-sm);
box-sizing: border-box; box-sizing: border-box;
} }
.manual-fetch-modal__field input:focus {
outline: none;
border-color: var(--c-accent);
box-shadow: 0 0 0 3px var(--c-accent-weak);
}
.manual-fetch-modal__field input:disabled { .manual-fetch-modal__field input:disabled {
background-color: #f5f5f5; background-color: var(--c-bg);
cursor: not-allowed; cursor: not-allowed;
} }
.manual-fetch-modal__info { .manual-fetch-modal__info {
font-size: 13px; font-size: var(--fs-xs);
color: #666; color: var(--c-text-muted);
margin-top: 8px; margin-top: var(--sp-2);
padding: 8px; padding: var(--sp-2);
background-color: #f9f9f9; background-color: var(--c-bg);
border-radius: 4px; border-radius: var(--radius-sm);
} }
.manual-fetch-modal__footer { .manual-fetch-modal__footer {
display: flex; display: flex;
gap: 8px; gap: var(--sp-2);
justify-content: flex-end; justify-content: flex-end;
padding: 16px 20px; padding: var(--sp-4) var(--sp-5);
border-top: 1px solid #e0e0e0; border-top: 1px solid var(--c-border);
background-color: #fafafa; background-color: var(--c-bg);
} }
.manual-fetch-modal__button { .manual-fetch-modal__button {
padding: 8px 16px; padding: var(--sp-2) var(--sp-4);
border-radius: 4px; border-radius: var(--radius-sm);
font-size: 14px; font-size: var(--fs-sm);
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
border: none; border: none;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
.manual-fetch-modal__button:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
}
.manual-fetch-modal__button:disabled { .manual-fetch-modal__button:disabled {
opacity: 0.6; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
} }
.manual-fetch-modal__button--primary { .manual-fetch-modal__button--primary {
background-color: #0066cc; background-color: var(--c-accent);
color: #fff; color: var(--c-surface);
} }
.manual-fetch-modal__button--primary:hover:not(:disabled) { .manual-fetch-modal__button--primary:hover:not(:disabled) {
background-color: #0052a3; background-color: var(--c-accent-hover);
} }
.manual-fetch-modal__button--secondary { .manual-fetch-modal__button--secondary {
background-color: #e0e0e0; background-color: var(--c-bg);
color: #333; color: var(--c-text);
border: 1px solid var(--c-border);
} }
.manual-fetch-modal__button--secondary:hover:not(:disabled) { .manual-fetch-modal__button--secondary:hover:not(:disabled) {
background-color: #d0d0d0; background-color: var(--c-border);
} }
@@ -4,7 +4,7 @@
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.5); background-color: var(--overlay);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -12,9 +12,9 @@
} }
.planned-day-modal__content { .planned-day-modal__content {
background-color: #fff; background-color: var(--c-surface);
border-radius: 8px; border-radius: var(--radius);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); box-shadow: var(--shadow-md);
max-width: 500px; max-width: 500px;
width: 90%; width: 90%;
max-height: 80vh; max-height: 80vh;
@@ -25,87 +25,98 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 20px; padding: var(--sp-5);
border-bottom: 1px solid #e0e0e0; border-bottom: 1px solid var(--c-border);
} }
.planned-day-modal__header h2 { .planned-day-modal__header h2 {
margin: 0; margin: 0;
font-size: 18px; font-size: var(--fs-xl);
font-weight: 600; font-weight: 700;
} }
.planned-day-modal__close { .planned-day-modal__close {
background: none; background: none;
border: none; border: none;
font-size: 28px; font-size: var(--fs-xl);
cursor: pointer; cursor: pointer;
padding: 0; padding: 0;
color: #666; color: var(--c-text-muted);
transition: color 0.2s; transition: color 0.2s;
} }
.planned-day-modal__close:hover { .planned-day-modal__close:hover {
color: #000; color: var(--c-text);
}
.planned-day-modal__close:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
} }
.planned-day-modal__body { .planned-day-modal__body {
padding: 20px; padding: var(--sp-5);
} }
.planned-day-modal__date { .planned-day-modal__date {
font-size: 14px; font-size: var(--fs-sm);
color: #666; color: var(--c-text-muted);
margin: 0 0 16px 0; margin: 0 0 var(--sp-4) 0;
font-weight: 500; font-weight: 500;
} }
.planned-day-modal__error { .planned-day-modal__error {
background-color: #ffebee; background-color: var(--c-danger-weak);
color: #c62828; color: var(--c-danger);
padding: 12px; padding: var(--sp-3);
border-radius: 4px; border-radius: var(--radius-sm);
margin-bottom: 16px; margin-bottom: var(--sp-4);
font-size: 14px; font-size: var(--fs-sm);
} }
.planned-day-modal__field { .planned-day-modal__field {
margin-bottom: 16px; margin-bottom: var(--sp-4);
} }
.planned-day-modal__label { .planned-day-modal__label {
display: block; display: block;
font-size: 14px; font-size: var(--fs-sm);
font-weight: 500; font-weight: 500;
margin-bottom: 6px; margin-bottom: var(--sp-2);
color: #333; color: var(--c-text);
} }
.planned-day-modal__select { .planned-day-modal__select {
width: 100%; width: 100%;
padding: 8px 10px; padding: var(--sp-2) var(--sp-3);
border: 1px solid #d0d0d0; border: 1px solid var(--c-border);
border-radius: 4px; border-radius: var(--radius-sm);
font-size: 14px; font-size: var(--fs-sm);
background-color: #fff; background-color: var(--c-surface);
cursor: pointer; cursor: pointer;
} }
.planned-day-modal__select:focus {
outline: none;
border-color: var(--c-accent);
box-shadow: 0 0 0 3px var(--c-accent-weak);
}
.planned-day-modal__select:disabled { .planned-day-modal__select:disabled {
background-color: #f5f5f5; background-color: var(--c-bg);
cursor: not-allowed; cursor: not-allowed;
} }
.planned-day-modal__checkbox-label { .planned-day-modal__checkbox-label {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 14px; font-size: var(--fs-sm);
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
} }
.planned-day-modal__checkbox-label input[type='checkbox'] { .planned-day-modal__checkbox-label input[type='checkbox'] {
margin-right: 8px; margin-right: var(--sp-2);
cursor: pointer; cursor: pointer;
} }
@@ -114,23 +125,23 @@
} }
.planned-day-modal__hint { .planned-day-modal__hint {
font-size: 12px; font-size: var(--fs-xs);
color: #999; color: var(--c-text-faint);
margin-top: 6px; margin-top: var(--sp-2);
margin-left: 24px; margin-left: var(--sp-6);
font-style: italic; font-style: italic;
} }
.planned-day-modal__pinned-notice { .planned-day-modal__pinned-notice {
font-size: 12px; font-size: var(--fs-xs);
color: #666; color: var(--c-text-muted);
margin: -8px 0 16px 0; margin: calc(var(--sp-2) * -1) 0 var(--sp-4) 0;
} }
.planned-day-modal__override-switch { .planned-day-modal__override-switch {
display: flex; display: flex;
gap: 16px; gap: var(--sp-4);
margin-top: 6px; margin-top: var(--sp-2);
} }
.planned-day-modal__override-switch .planned-day-modal__checkbox-label { .planned-day-modal__override-switch .planned-day-modal__checkbox-label {
@@ -139,64 +150,72 @@
.planned-day-modal__footer { .planned-day-modal__footer {
display: flex; display: flex;
gap: 8px; gap: var(--sp-2);
justify-content: flex-end; justify-content: flex-end;
padding: 16px 20px; padding: var(--sp-4) var(--sp-5);
border-top: 1px solid #e0e0e0; border-top: 1px solid var(--c-border);
background-color: #fafafa; background-color: var(--c-bg);
} }
.planned-day-modal__button { .planned-day-modal__button {
padding: 8px 16px; padding: var(--sp-2) var(--sp-4);
border-radius: 4px; border-radius: var(--radius-sm);
font-size: 14px; font-size: var(--fs-sm);
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
border: none; border: none;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
.planned-day-modal__button:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
}
.planned-day-modal__button:disabled { .planned-day-modal__button:disabled {
opacity: 0.6; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
} }
.planned-day-modal__button--primary { .planned-day-modal__button--primary {
background-color: #0066cc; background-color: var(--c-accent);
color: #fff; color: var(--c-surface);
} }
.planned-day-modal__button--primary:hover:not(:disabled) { .planned-day-modal__button--primary:hover:not(:disabled) {
background-color: #0052a3; background-color: var(--c-accent-hover);
} }
.planned-day-modal__button--secondary { .planned-day-modal__button--secondary {
background-color: #e0e0e0; background-color: var(--c-bg);
color: #333; color: var(--c-text);
border: 1px solid var(--c-border);
} }
.planned-day-modal__button--secondary:hover:not(:disabled) { .planned-day-modal__button--secondary:hover:not(:disabled) {
background-color: #d0d0d0; background-color: var(--c-border);
} }
.planned-day-modal__button--danger { .planned-day-modal__button--danger {
background-color: #d32f2f; background-color: var(--c-danger-weak);
color: #fff; color: var(--c-danger);
border: 1px solid var(--c-danger);
} }
.planned-day-modal__button--danger:hover:not(:disabled) { .planned-day-modal__button--danger:hover:not(:disabled) {
background-color: #b71c1c; background-color: var(--c-danger);
color: var(--c-surface);
} }
.planned-day-modal__delete-confirm { .planned-day-modal__delete-confirm {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: var(--sp-3);
width: 100%; width: 100%;
} }
.planned-day-modal__delete-confirm-text { .planned-day-modal__delete-confirm-text {
font-size: 14px; font-size: var(--fs-sm);
color: #333; color: var(--c-text);
margin-right: auto; margin-right: auto;
} }
@@ -183,7 +183,7 @@
} }
.qrcode-modal__button--success:hover { .qrcode-modal__button--success:hover {
background-color: #0d6a37; background-color: var(--c-success-hover);
} }
.qrcode-modal__button--danger { .qrcode-modal__button--danger {
@@ -192,7 +192,7 @@
} }
.qrcode-modal__button--danger:hover { .qrcode-modal__button--danger:hover {
background-color: #8a1a15; background-color: var(--c-danger-hover);
} }
.qrcode-modal__button:disabled { .qrcode-modal__button:disabled {
+13 -5
View File
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'
import { useAuth } from '@/context/AuthContext' import { useAuth } from '@/context/AuthContext'
import { useWorkspace } from '@/context/WorkspaceContext' import { useWorkspace } from '@/context/WorkspaceContext'
import { useTranslation } from '@/i18n' import { useTranslation } from '@/i18n'
import './auth.css'
export function Auth() { export function Auth() {
const { login, register, isAuthenticated } = useAuth() const { login, register, isAuthenticated } = useAuth()
@@ -38,9 +39,11 @@ export function Auth() {
} }
return ( return (
<main> <main className="auth-page">
<h1>{isRegister ? t('auth.createAccountTitle') : t('auth.loginTitle')}</h1> <div className="auth-page__card">
<form onSubmit={(e) => void handleSubmit(e)}> <p className="auth-page__brand">{t('app.name')}</p>
<h1 className="auth-page__title">{isRegister ? t('auth.createAccountTitle') : t('auth.loginTitle')}</h1>
<form className="auth-page__form" onSubmit={(e) => void handleSubmit(e)}>
{isRegister && ( {isRegister && (
<input <input
placeholder={t('auth.namePlaceholder')} placeholder={t('auth.namePlaceholder')}
@@ -62,14 +65,19 @@ export function Auth() {
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
/> />
{error && <p role="alert">{error}</p>} {error && (
<p role="alert" className="auth-page__error">
{error}
</p>
)}
<button type="submit" data-testid="auth-submit"> <button type="submit" data-testid="auth-submit">
{isRegister ? t('auth.registerButton') : t('auth.loginButton')} {isRegister ? t('auth.registerButton') : t('auth.loginButton')}
</button> </button>
</form> </form>
<button type="button" onClick={() => setIsRegister((v) => !v)}> <button type="button" className="auth-page__switch button--ghost" onClick={() => setIsRegister((v) => !v)}>
{isRegister ? t('auth.haveAccountButton') : t('auth.createAccountButton')} {isRegister ? t('auth.haveAccountButton') : t('auth.createAccountButton')}
</button> </button>
</div>
</main> </main>
) )
} }
+3 -9
View File
@@ -229,23 +229,17 @@ export function Dashboard(): ReactNode {
{age4Bars.length > 0 && <Age4Bars bars={age4Bars} />} {age4Bars.length > 0 && <Age4Bars bars={age4Bars} />}
<section className="dashboard__section"> <section className="dashboard__section dashboard__fetch-actions">
<div style={{ display: 'flex', gap: '10px' }}> <button onClick={handleFetchFK} disabled={!selectedChildId}>
<button
onClick={handleFetchFK}
disabled={!selectedChildId}
style={{ padding: '0.5rem 1rem', cursor: selectedChildId ? 'pointer' : 'not-allowed' }}
>
{t('dashboard.fetchFromFk')} {t('dashboard.fetchFromFk')}
</button> </button>
<button <button
onClick={() => setShowManualFetchModal(true)} onClick={() => setShowManualFetchModal(true)}
disabled={!selectedChildId} disabled={!selectedChildId}
style={{ padding: '0.5rem 1rem', cursor: selectedChildId ? 'pointer' : 'not-allowed' }} className="button--secondary"
> >
{t('dashboard.enterManually')} {t('dashboard.enterManually')}
</button> </button>
</div>
</section> </section>
{selectedChild && ( {selectedChild && (
+56
View File
@@ -0,0 +1,56 @@
/* Login/register — the app's front door, rendered outside <Layout> (no
* header yet, there's no session). Previously a bare, unstyled <main> that
* relied entirely on the global element resets; this gives it the same
* centered-card treatment as the onboarding wizard it leads into. */
.auth-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: var(--sp-5);
}
.auth-page__card {
width: 100%;
max-width: 380px;
}
.auth-page__brand {
margin: 0 0 var(--sp-5) 0;
font-size: var(--fs-sm);
font-weight: 600;
color: var(--c-text-muted);
text-align: center;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.auth-page__title {
text-align: center;
margin-bottom: var(--sp-5);
}
.auth-page__form {
display: flex;
flex-direction: column;
gap: var(--sp-3);
margin-bottom: var(--sp-4);
}
.auth-page__form button[type='submit'] {
margin-top: var(--sp-2);
}
.auth-page__error {
margin: 0;
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-sm);
font-size: var(--fs-sm);
}
.auth-page__switch {
width: 100%;
}
+137 -140
View File
@@ -1,58 +1,53 @@
.calendar {
max-width: 920px;
margin: 0 auto;
padding: 16px;
}
.calendar__header { .calendar__header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 24px; margin-bottom: var(--sp-5);
} }
.calendar__header button { .calendar__header button {
min-width: 36px; min-width: 36px;
min-height: 36px; min-height: 36px;
padding: 8px 12px; padding: var(--sp-2) var(--sp-3);
background: none; background: none;
color: var(--c-text); color: var(--c-text);
border: 1px solid #d0d0d0; border: 1px solid var(--c-border);
border-radius: 4px; border-radius: var(--radius-sm);
font-size: 18px; font-size: var(--fs-lg);
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; transition: background-color 0.2s, border-color 0.2s;
} }
.calendar__header button:hover { .calendar__header button:hover {
background-color: #f0f0f0; background-color: var(--c-bg);
border-color: var(--c-text-muted);
} }
.calendar__title { .calendar__title {
margin: 0; margin: 0;
font-size: 20px; font-size: var(--fs-xl);
font-weight: 600; font-weight: 700;
color: #333; color: var(--c-text);
display: flex; display: flex;
gap: 8px; gap: var(--sp-2);
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.calendar__month-select, .calendar__month-select,
.calendar__year-select { .calendar__year-select {
background: #fff; background: var(--c-surface);
border: 1px solid #d0d0d0; border: 1px solid var(--c-border);
color: #333; color: var(--c-text);
font-size: 20px; font-size: var(--fs-xl);
font-weight: 600; font-weight: 700;
cursor: pointer; cursor: pointer;
padding: 4px 8px; padding: var(--sp-1) var(--sp-2);
border-radius: 4px; border-radius: var(--radius-sm);
transition: background-color 0.2s, border-color 0.2s, color 0.2s; transition: background-color 0.2s, border-color 0.2s, color 0.2s;
appearance: none; appearance: none;
padding-right: 28px; padding-right: 28px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 8"><path d="M1 1l5 5 5-5" stroke="%23333" stroke-width="1.5" fill="none" stroke-linecap="round"/></svg>'); background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 8"><path d="M1 1l5 5 5-5" stroke="%231c2733" stroke-width="1.5" fill="none" stroke-linecap="round"/></svg>');
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 6px center; background-position: right 6px center;
background-size: 12px; background-size: 12px;
@@ -60,28 +55,28 @@
.calendar__month-select:hover, .calendar__month-select:hover,
.calendar__year-select:hover { .calendar__year-select:hover {
background-color: #f0f0f0; background-color: var(--c-bg);
border-color: #0066cc; border-color: var(--c-accent);
color: #0066cc; color: var(--c-accent);
} }
.calendar__month-select:focus-visible, .calendar__month-select:focus-visible,
.calendar__year-select:focus-visible { .calendar__year-select:focus-visible {
outline: 2px solid #0066cc; outline: 2px solid var(--c-accent);
outline-offset: 2px; outline-offset: 2px;
border-color: #0066cc; border-color: var(--c-accent);
} }
.calendar__month-select:active, .calendar__month-select:active,
.calendar__year-select:active { .calendar__year-select:active {
background-color: #e0e0e0; background-color: var(--c-border);
} }
.calendar__grid { .calendar__grid {
display: grid; display: grid;
grid-template-columns: repeat(7, 1fr); grid-template-columns: repeat(7, 1fr);
gap: 8px; gap: var(--sp-2);
margin-bottom: 8px; margin-bottom: var(--sp-2);
} }
/* The weeks themselves now live in a separate flex column below the header /* The weeks themselves now live in a separate flex column below the header
@@ -93,114 +88,114 @@
.calendar__weeks { .calendar__weeks {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: var(--sp-2);
margin-bottom: 24px; margin-bottom: var(--sp-5);
} }
.calendar__weekday { .calendar__weekday {
padding: 12px 8px; padding: var(--sp-3) var(--sp-2);
text-align: center; text-align: center;
font-weight: 600; font-weight: 600;
font-size: 13px; font-size: var(--fs-xs);
color: #666; color: var(--c-text-muted);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.04em;
} }
.calendar-day { .calendar-day {
min-height: 84px; min-height: 84px;
padding: 8px; padding: var(--sp-2);
border: 1px solid #e0e0e0; border: 1px solid var(--c-border);
border-radius: 4px; border-radius: var(--radius-sm);
background-color: #fff; background-color: var(--c-surface);
cursor: pointer; cursor: pointer;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition: all 0.2s; transition: border-color 0.15s, background-color 0.15s, box-shadow 0.15s;
position: relative; position: relative;
user-select: none; user-select: none;
-webkit-user-select: none; /* Safari */ -webkit-user-select: none; /* Safari */
} }
.calendar-day:hover { .calendar-day:hover {
border-color: #0066cc; border-color: var(--c-accent);
box-shadow: 0 2px 8px rgba(0, 102, 204, 0.1); box-shadow: 0 2px 8px rgba(47, 111, 79, 0.12);
background-color: #f9fbff; background-color: var(--c-accent-weak);
} }
.calendar-day:focus-visible { .calendar-day:focus-visible {
outline: 2px solid #0066cc; outline: 2px solid var(--c-accent);
outline-offset: 2px; outline-offset: 2px;
} }
.calendar-day--disabled { .calendar-day--disabled {
background-color: #fafafa; background-color: var(--c-bg);
color: #999; color: var(--c-text-faint);
cursor: default; cursor: default;
} }
.calendar-day--disabled:hover { .calendar-day--disabled:hover {
border-color: #e0e0e0; border-color: var(--c-border);
box-shadow: none; box-shadow: none;
background-color: #fafafa; background-color: var(--c-bg);
} }
.calendar-day--has-events { .calendar-day--has-events {
background-color: #fafbff; background-color: rgba(47, 111, 79, 0.04);
} }
.calendar-day__number { .calendar-day__number {
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: var(--fs-sm);
color: #333; color: var(--c-text);
margin-bottom: 4px; margin-bottom: var(--sp-1);
} }
.calendar-day--disabled .calendar-day__number { .calendar-day--disabled .calendar-day__number {
color: #bbb; color: var(--c-text-faint);
} }
.calendar-day--today .calendar-day__number { .calendar-day--today .calendar-day__number {
background-color: #0066cc; background-color: var(--c-accent);
color: #fff; color: var(--c-surface);
width: 24px; width: 24px;
height: 24px; height: 24px;
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 12px; font-size: var(--fs-xs);
} }
.calendar-day--selected { .calendar-day--selected {
background-color: #f9fbff; background-color: var(--c-accent-weak);
border-color: #0066cc; border-color: var(--c-accent);
box-shadow: 0 2px 8px rgba(0, 102, 204, 0.1); box-shadow: 0 2px 8px rgba(47, 111, 79, 0.18);
} }
.calendar-day--selected:hover { .calendar-day--selected:hover {
background-color: #f0f8ff; background-color: var(--c-accent-weak);
} }
/* §3.4 — statutory red days / klämdagar shading. Color is never the only /* §3.4 — statutory red days / klämdagar shading. Color is never the only
signal: each also gets a text badge (see .calendar-day__type-badge) and a signal: each also gets a text badge (see .calendar-day__type-badge) and a
title/aria-label carrying the holiday name. */ title/aria-label carrying the holiday name. */
.calendar-day--red-day { .calendar-day--red-day {
background-color: #fdeceb; background-color: var(--c-danger-weak);
border-color: #d9776e; border-color: var(--c-redday-border);
} }
.calendar-day--red-day:hover { .calendar-day--red-day:hover {
background-color: #fbdedb; background-color: var(--c-redday-hover);
} }
.calendar-day--klamdag { .calendar-day--klamdag {
background-color: #fef8e7; background-color: var(--c-klamdag-bg);
border-color: #d9b23c; border-color: var(--c-klamdag-border);
} }
.calendar-day--klamdag:hover { .calendar-day--klamdag:hover {
background-color: #fcf0c8; background-color: var(--c-klamdag-hover);
} }
/* Plain Saturday — neutral grey, deliberately NOT red. Matches the Swedish /* Plain Saturday — neutral grey, deliberately NOT red. Matches the Swedish
@@ -208,17 +203,17 @@
an ordinary rest day. See CalendarDay.tsx for the weekday-based split an ordinary rest day. See CalendarDay.tsx for the weekday-based split
(the backend collapses both into a single "weekend" day_type). */ (the backend collapses both into a single "weekend" day_type). */
.calendar-day--weekend { .calendar-day--weekend {
background-color: #f0f0f0; background-color: var(--c-weekend-bg);
border-color: #cfcfcf; border-color: var(--c-weekend-border);
} }
.calendar-day--weekend:hover { .calendar-day--weekend:hover {
background-color: #e8e8e8; background-color: var(--c-weekend-hover);
} }
.calendar-day__holiday-name { .calendar-day__holiday-name {
font-size: 10px; font-size: var(--fs-2xs);
color: #a8453d; color: var(--c-danger);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@@ -240,7 +235,7 @@
font-size: 9px; font-size: 9px;
font-weight: 700; font-weight: 700;
line-height: 1; line-height: 1;
color: #fff; color: var(--c-surface);
background-color: rgba(0, 0, 0, 0.4); background-color: rgba(0, 0, 0, 0.4);
border-radius: 3px; border-radius: 3px;
padding: 1px 3px; padding: 1px 3px;
@@ -277,7 +272,7 @@
.calendar-week { .calendar-week {
display: grid; display: grid;
grid-template-columns: repeat(7, 1fr); grid-template-columns: repeat(7, 1fr);
column-gap: 8px; column-gap: var(--sp-2);
row-gap: 3px; row-gap: 3px;
} }
@@ -292,7 +287,10 @@
/* One continuous band segment for this week row. Square by default — /* One continuous band segment for this week row. Square by default —
.calendar-band--start/--end round only the true first/last row of the .calendar-band--start/--end round only the true first/last row of the
whole (possibly multi-week) band, so a period crossing a week boundary whole (possibly multi-week) band, so a period crossing a week boundary
reads as one connected object, not a chain of separate chips. */ reads as one connected object, not a chain of separate chips.
Border radius here is intentionally NOT --radius-sm: it's asserted
literally (4px/0px) by frontend/e2e/tests/calendar-band.spec.ts, so it
has its own --radius-band token instead of following the shared scale. */
.calendar-band { .calendar-band {
position: relative; position: relative;
display: flex; display: flex;
@@ -305,17 +303,17 @@
} }
.calendar-band--start { .calendar-band--start {
border-top-left-radius: 4px; border-top-left-radius: var(--radius-band);
border-bottom-left-radius: 4px; border-bottom-left-radius: var(--radius-band);
} }
.calendar-band--end { .calendar-band--end {
border-top-right-radius: 4px; border-top-right-radius: var(--radius-band);
border-bottom-right-radius: 4px; border-bottom-right-radius: var(--radius-band);
} }
.calendar-band:focus-visible { .calendar-band:focus-visible {
outline: 2px solid #0066cc; outline: 2px solid var(--c-accent);
outline-offset: 1px; outline-offset: 1px;
} }
@@ -330,7 +328,7 @@
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
} }
30%, 70% { 30%, 70% {
box-shadow: 0 0 0 3px #ffd54f, 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: 0 0 0 3px var(--c-flash), 0 1px 2px rgba(0, 0, 0, 0.1);
} }
} }
@@ -340,8 +338,8 @@
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 6px; padding: 0 6px;
font-size: 11px; font-size: var(--fs-2xs);
color: #fff; color: var(--c-surface);
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.35); text-shadow: 0 1px 1px rgba(0, 0, 0, 0.35);
pointer-events: none; pointer-events: none;
z-index: 1; z-index: 1;
@@ -393,7 +391,7 @@
} }
.calendar-event__label { .calendar-event__label {
font-size: 12px; font-size: var(--fs-2xs);
font-weight: 500; font-weight: 500;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -416,31 +414,30 @@
} }
.calendar__legend { .calendar__legend {
padding: 16px; padding: var(--sp-4);
background-color: #f9f9f9; background-color: var(--c-bg);
border: 1px solid #e0e0e0; border-radius: var(--radius-sm);
border-radius: 4px;
} }
.calendar__legend h3 { .calendar__legend h3 {
margin: 0 0 12px 0; margin: 0 0 var(--sp-3) 0;
font-size: 14px; font-size: var(--fs-sm);
font-weight: 600; font-weight: 600;
color: #333; color: var(--c-text);
} }
.calendar__legend-items { .calendar__legend-items {
display: flex; display: flex;
gap: 24px; gap: var(--sp-5);
flex-wrap: wrap; flex-wrap: wrap;
} }
.calendar__legend-item { .calendar__legend-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: var(--sp-2);
font-size: 13px; font-size: var(--fs-xs);
color: #666; color: var(--c-text-muted);
} }
.calendar__legend-swatch { .calendar__legend-swatch {
@@ -449,32 +446,32 @@
justify-content: center; justify-content: center;
width: 16px; width: 16px;
height: 16px; height: 16px;
border-radius: 2px; border-radius: 3px;
flex-shrink: 0; flex-shrink: 0;
font-size: 11px; font-size: 11px;
font-weight: bold; font-weight: bold;
color: #fff; color: var(--c-surface);
} }
.calendar__legend-swatch--dubbeldag { .calendar__legend-swatch--dubbeldag {
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);
border: 1px solid #999; border: 1px solid var(--c-text-faint);
color: #333; color: var(--c-text);
} }
.calendar__legend-swatch--red-day { .calendar__legend-swatch--red-day {
background-color: #fdeceb; background-color: var(--c-danger-weak);
border: 1px solid #d9776e; border: 1px solid var(--c-redday-border);
} }
.calendar__legend-swatch--klamdag { .calendar__legend-swatch--klamdag {
background-color: #fef8e7; background-color: var(--c-klamdag-bg);
border: 1px solid #d9b23c; border: 1px solid var(--c-klamdag-border);
} }
.calendar__legend-swatch--override { .calendar__legend-swatch--override {
background-color: #fff; background-color: var(--c-surface);
border: 2px dashed #666; border: 2px dashed var(--c-text-muted);
} }
/* §Task7 — the calendar becomes the plan: a side rail of presets next to /* §Task7 — the calendar becomes the plan: a side rail of presets next to
@@ -484,7 +481,7 @@
popover (see PlannedDayModal's "Den här dagen är" switch). */ popover (see PlannedDayModal's "Den här dagen är" switch). */
.calendar__layout { .calendar__layout {
display: flex; display: flex;
gap: 20px; gap: var(--sp-5);
align-items: flex-start; align-items: flex-start;
} }
@@ -494,22 +491,22 @@
} }
.calendar__sync-indicator { .calendar__sync-indicator {
font-size: 12px; font-size: var(--fs-xs);
color: #666; color: var(--c-text-muted);
margin: 0 0 12px 0; margin: 0 0 var(--sp-3) 0;
} }
.calendar__toast { .calendar__toast {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: var(--sp-3);
padding: 10px 14px; padding: var(--sp-3) var(--sp-4);
margin-bottom: 16px; margin-bottom: var(--sp-4);
background-color: #eef6ff; background-color: var(--c-accent-weak);
border: 1px solid #b8d9ff; border: 1px solid var(--c-accent);
border-radius: 4px; border-radius: var(--radius-sm);
font-size: 13px; font-size: var(--fs-sm);
color: #003a70; color: var(--c-text);
} }
.calendar__toast-message { .calendar__toast-message {
@@ -517,19 +514,19 @@
} }
.calendar__toast-undo { .calendar__toast-undo {
padding: 4px 10px; padding: var(--sp-1) var(--sp-3);
border: 1px solid #0066cc; border: 1px solid var(--c-accent);
border-radius: 4px; border-radius: var(--radius-sm);
background: #fff; background: var(--c-surface);
color: #0066cc; color: var(--c-accent);
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: var(--fs-xs);
font-weight: 600; font-weight: 600;
} }
.calendar__toast-undo:hover:not(:disabled) { .calendar__toast-undo:hover:not(:disabled) {
background-color: #0066cc; background-color: var(--c-accent);
color: #fff; color: var(--c-surface);
} }
.calendar__toast-undo:disabled { .calendar__toast-undo:disabled {
@@ -540,10 +537,10 @@
.calendar__toast-dismiss { .calendar__toast-dismiss {
background: none; background: none;
border: none; border: none;
font-size: 18px; font-size: var(--fs-lg);
line-height: 1; line-height: 1;
cursor: pointer; cursor: pointer;
color: #003a70; color: var(--c-text-muted);
padding: 0 2px; padding: 0 2px;
} }
@@ -555,12 +552,12 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 12px 8px; padding: var(--sp-3) var(--sp-2);
font-weight: 600; font-weight: 600;
font-size: 12px; font-size: var(--fs-xs);
color: #999; color: var(--c-text-faint);
background-color: #fafafa; background-color: var(--c-bg);
border-right: 1px solid #d0d0d0; border-right: 1px solid var(--c-border);
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
} }
@@ -569,12 +566,12 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 8px; padding: var(--sp-2);
font-weight: 600; font-weight: 600;
font-size: 11px; font-size: var(--fs-2xs);
color: #999; color: var(--c-text-faint);
border-right: 1px solid #d0d0d0; border-right: 1px solid var(--c-border);
background-color: #fafafa; background-color: var(--c-bg);
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
min-height: 84px; min-height: 84px;
+15 -7
View File
@@ -1,21 +1,29 @@
/* Onboarding wizard (first-run: add child, solo/couple, balances ladder) */ /* Onboarding wizard (first-run: add child, solo/couple, balances ladder) —
* a step here should breathe: it's a handful of fields at a time, not a
* dense page, so it gets more top/bottom air than the dashboard/settings
* content sections do. */
.onboarding { .onboarding {
max-width: 60ch; max-width: 60ch;
margin: 0 auto;
padding-top: var(--sp-6);
} }
.onboarding__indicator { .onboarding__indicator {
color: var(--c-text-muted); color: var(--c-text-muted);
font-size: var(--fs-sm); font-size: var(--fs-xs);
margin-bottom: var(--sp-4); font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: var(--sp-5);
} }
.onboarding__step h1 { .onboarding__step h1 {
margin-bottom: var(--sp-2); margin-bottom: var(--sp-3);
} }
.onboarding__step > p { .onboarding__step > p {
color: var(--c-text-muted); color: var(--c-text-muted);
margin-bottom: var(--sp-5); margin-bottom: var(--sp-6);
} }
.onboarding__partner { .onboarding__partner {
@@ -41,7 +49,7 @@
border: 1px solid var(--c-border); border: 1px solid var(--c-border);
border-radius: var(--radius); border-radius: var(--radius);
background-color: var(--c-surface); background-color: var(--c-surface);
padding: var(--sp-4); padding: var(--sp-5);
} }
.onboarding__ladder-item h2 { .onboarding__ladder-item h2 {
@@ -51,7 +59,7 @@
.onboarding__ladder-item p { .onboarding__ladder-item p {
color: var(--c-text-muted); color: var(--c-text-muted);
margin-bottom: var(--sp-3); margin-bottom: var(--sp-4);
} }
.onboarding__hint { .onboarding__hint {
+163 -31
View File
@@ -20,9 +20,9 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: var(--sp-5); padding: var(--sp-5);
background-color: var(--c-bg); background-color: var(--c-surface);
border-radius: var(--radius); border-radius: var(--radius);
border: 1px solid var(--c-border); box-shadow: var(--shadow-sm);
} }
.overview-metrics__value { .overview-metrics__value {
@@ -157,36 +157,48 @@
color: var(--c-text-muted); color: var(--c-text-muted);
} }
/* Hero */ /* Hero — the headline figure. This is the one element on Översikt allowed
* a tinted background; everything below it gives up cards/borders in favor
* of whitespace precisely so this keeps reading as the most important thing
* on the page (see AGENTS.md's "clear hierarchy per screen"). */
.dashboard-hero { .dashboard-hero {
padding: var(--sp-5); padding: var(--sp-6) var(--sp-6);
margin-bottom: var(--sp-6);
border-radius: var(--radius); border-radius: var(--radius);
border-left: 4px solid var(--c-accent); border-left: 3px solid var(--c-accent);
background-color: var(--c-accent-weak); background-color: var(--c-accent-weak);
} }
.dashboard-hero--warning { .dashboard-hero--warning {
border-left-color: var(--c-warning); border-left-color: var(--c-danger);
background-color: var(--c-danger-weak); background-color: var(--c-danger-weak);
} }
.dashboard-hero__message { .dashboard-hero__message {
margin: 0; margin: 0;
font-size: var(--fs-xl); font-size: var(--fs-2xl);
font-weight: 600; font-weight: 700;
line-height: 1.25;
letter-spacing: -0.01em;
color: var(--c-text); color: var(--c-text);
} }
/* Shown while headline figures are still the statutory assumption (no FK /* Shown while headline figures are still the statutory assumption (no FK
* fetch or manual entry has happened yet) — see `Dashboard.tsx`. */ * fetch or manual entry has happened yet) — see `Dashboard.tsx`. */
.dashboard__fetch-actions {
display: flex;
gap: var(--sp-3);
flex-wrap: wrap;
}
.dashboard__estimate-badge { .dashboard__estimate-badge {
display: inline-block; display: inline-block;
margin: var(--sp-3) 0; margin: var(--sp-3) 0;
padding: var(--sp-1) var(--sp-3); padding: var(--sp-1) var(--sp-3);
border-radius: 999px; border-radius: var(--radius-full);
font-size: var(--fs-sm); font-size: var(--fs-sm);
font-weight: 600; font-weight: 600;
background-color: #fef8e7; background-color: var(--c-warning-weak);
color: var(--c-warning); color: var(--c-warning);
cursor: help; cursor: help;
} }
@@ -196,6 +208,16 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--sp-4); gap: var(--sp-4);
margin-bottom: var(--sp-6);
}
.age4-bars__title {
font-size: var(--fs-base);
font-weight: 600;
color: var(--c-text-muted);
text-transform: uppercase;
letter-spacing: 0.02em;
margin-bottom: 0;
} }
.age4-bars__row { .age4-bars__row {
@@ -210,16 +232,17 @@
.age4-bars__track { .age4-bars__track {
width: 100%; width: 100%;
height: 20px; height: 10px;
background-color: var(--c-bg); background-color: var(--c-surface);
border-radius: var(--radius); border-radius: var(--radius-full);
overflow: hidden; overflow: hidden;
border: 1px solid var(--c-border); box-shadow: inset 0 0 0 1px var(--c-border);
} }
.age4-bars__fill { .age4-bars__fill {
height: 100%; height: 100%;
background-color: var(--c-accent); background-color: var(--c-accent);
border-radius: var(--radius-full);
transition: width 0.3s ease; transition: width 0.3s ease;
} }
@@ -240,6 +263,16 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--sp-3); gap: var(--sp-3);
margin-bottom: var(--sp-6);
}
.year-timeline__title {
font-size: var(--fs-base);
font-weight: 600;
color: var(--c-text-muted);
text-transform: uppercase;
letter-spacing: 0.02em;
margin-bottom: var(--sp-1);
} }
.year-timeline__row { .year-timeline__row {
@@ -258,10 +291,10 @@
.year-timeline__track { .year-timeline__track {
position: relative; position: relative;
flex: 1; flex: 1;
height: 28px; height: 24px;
background-color: var(--c-bg); background-color: var(--c-surface);
border-radius: var(--radius); border-radius: var(--radius-sm);
border: 1px solid var(--c-border); box-shadow: inset 0 0 0 1px var(--c-border);
overflow: hidden; overflow: hidden;
} }
@@ -269,27 +302,39 @@
position: absolute; position: absolute;
top: 0; top: 0;
height: 100%; height: 100%;
min-width: 2px; min-width: 3px;
border: none; border: none;
cursor: pointer; cursor: pointer;
padding: 0; padding: 0;
transition: opacity 0.15s;
}
.year-timeline__band:hover {
opacity: 0.85;
} }
.year-timeline__band:hover,
.year-timeline__band:focus-visible { .year-timeline__band:focus-visible {
outline: 2px solid var(--c-text); outline: 2px solid var(--c-text);
outline-offset: -2px; outline-offset: -2px;
} }
/* Plan Fold (collapsed plan-proposal summary) */ /* Plan Fold (collapsed plan-proposal summary) — these stay bordered even
* though the plain content sections around them dropped their cards: a
* <details> disclosure control needs a visible boundary so it reads as
* something to click, not another paragraph of content. */
.plan-fold, .plan-fold,
.dashboard__more-details { .dashboard__more-details {
border: 1px solid var(--c-border); border: 1px solid var(--c-border);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--sp-4); padding: var(--sp-4) var(--sp-5);
background-color: var(--c-surface); background-color: var(--c-surface);
} }
.plan-fold + .plan-fold,
.dashboard__more-details + .dashboard__more-details {
margin-top: var(--sp-4);
}
.plan-fold__summary, .plan-fold__summary,
.dashboard__more-details summary { .dashboard__more-details summary {
cursor: pointer; cursor: pointer;
@@ -312,11 +357,11 @@ button {
} }
button:hover:not(:disabled) { button:hover:not(:disabled) {
background-color: #1f5a3b; background-color: var(--c-accent-hover);
} }
button:active:not(:disabled) { button:active:not(:disabled) {
background-color: #0f4a2f; background-color: var(--c-accent-active);
} }
/* Secondary button */ /* Secondary button */
@@ -418,6 +463,14 @@ button.ghost:hover:not(:disabled) {
margin-bottom: var(--sp-4); margin-bottom: var(--sp-4);
} }
.settings-page__label {
font-size: var(--fs-base);
}
.settings-page__select {
max-width: 320px;
}
.settings-page__checkbox-label { .settings-page__checkbox-label {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -468,7 +521,7 @@ button.ghost:hover:not(:disabled) {
} }
.settings-page__button:not(:disabled):hover { .settings-page__button:not(:disabled):hover {
background-color: #1f5a3b; background-color: var(--c-accent-hover);
} }
.settings-page__button:focus-visible { .settings-page__button:focus-visible {
@@ -545,7 +598,7 @@ button.ghost:hover:not(:disabled) {
border-radius: 999px; border-radius: 999px;
font-size: var(--fs-sm); font-size: var(--fs-sm);
font-weight: 400; font-weight: 400;
background-color: #fef8e7; background-color: var(--c-warning-weak);
color: var(--c-warning); color: var(--c-warning);
cursor: help; cursor: help;
} }
@@ -559,7 +612,7 @@ button.ghost:hover:not(:disabled) {
} }
.vacation-days-table__save:not(:disabled):hover { .vacation-days-table__save:not(:disabled):hover {
background-color: #1f5a3b; background-color: var(--c-accent-hover);
} }
.vacation-days-table__save:focus-visible { .vacation-days-table__save:focus-visible {
@@ -704,13 +757,13 @@ legend {
} }
.day-overrides-input__status--working { .day-overrides-input__status--working {
background-color: #fef3c7; background-color: var(--c-warning-weak);
color: #92400e; color: var(--c-warning);
} }
.day-overrides-input__status--off { .day-overrides-input__status--off {
background-color: #dbeafe; background-color: var(--c-success-weak);
color: #1e40af; color: var(--c-success);
} }
.day-overrides-input__item-note { .day-overrides-input__item-note {
@@ -918,6 +971,85 @@ legend {
font-size: var(--fs-sm); font-size: var(--fs-sm);
} }
/* Tip Card (Optimization Tips, Översikt) — previously had no CSS at all,
* so every tip fell back to a plain `<button>` for its dismiss control
* (a solid accent-green square) and no visual grouping. The severity
* modifier classes already existed on the markup; only the color-coding
* they imply was missing. */
.tip-card {
padding: var(--sp-4);
border-left: 3px solid var(--c-border);
background-color: var(--c-surface);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.tip-card + .tip-card {
margin-top: var(--sp-3);
}
.tip-card--info {
border-left-color: var(--c-text-faint);
}
.tip-card--warning {
border-left-color: var(--c-warning);
background-color: var(--c-warning-weak);
}
.tip-card--action {
border-left-color: var(--c-accent);
background-color: var(--c-accent-weak);
}
.tip-card__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--sp-3);
margin-bottom: var(--sp-1);
}
.tip-card__title {
margin: 0;
font-size: var(--fs-base);
font-weight: 600;
color: var(--c-text);
}
.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 {
margin: 0;
font-size: var(--fs-sm);
color: var(--c-text-muted);
}
/* Responsive */ /* Responsive */
@media (max-width: 640px) { @media (max-width: 640px) {
[data-testid='metrics-list'] { [data-testid='metrics-list'] {
+52 -6
View File
@@ -3,7 +3,16 @@
@import './modals.css'; @import './modals.css';
@import './responsive.css'; @import './responsive.css';
/* Design Tokens */ /* Design Tokens
*
* One neutral ground + one accent; every other colour is semantic (danger/
* warning/success) and exists as a solid + "-weak" tint pair. Everything
* below is used app-wide, including the calendar and modal families that
* used to hardcode their own hex values — see calendar.css/period-popover.css/
* etc. Two values are load-bearing for e2e: --c-danger-weak (red day
* background) and --c-weekend-bg (plain-Saturday background) — read
* directly by frontend/e2e/tests/red-days.spec.ts. Do not repurpose either.
*/
:root { :root {
/* Color - calm, trustworthy palette */ /* Color - calm, trustworthy palette */
--c-bg: #f7f8fa; --c-bg: #f7f8fa;
@@ -11,13 +20,37 @@
--c-border: #e3e6eb; --c-border: #e3e6eb;
--c-text: #1c2733; --c-text: #1c2733;
--c-text-muted: #5b6675; --c-text-muted: #5b6675;
--c-accent: #2f6f4f; /* calm green */ --c-text-faint: #94a0ac;
--c-accent: #2f6f4f; /* calm green — the app's one accent */
--c-accent-weak: #e8f1ec; --c-accent-weak: #e8f1ec;
--c-accent-hover: #1f5a3b;
--c-accent-active: #0f4a2f;
--c-danger: #b3261e; --c-danger: #b3261e;
--c-danger-weak: #fdeceb; --c-danger-weak: #fdeceb; /* also the red-day cell background — see note above */
--c-danger-hover: #8a1a15;
--c-success: #1e7d4f; --c-success: #1e7d4f;
--c-success-weak: #e7f4ec; --c-success-weak: #e7f4ec;
--c-success-hover: #0d6a37;
--c-warning: #8a6d1f; --c-warning: #8a6d1f;
--c-warning-weak: #fef8e7;
/* Calendar-specific semantics (klämdag/weekend/red-day borders + hover) —
* kept distinct from the generic danger/warning pair above because a red
* day and a klämdag are both visible on screen at once and need to read
* as different hues, not different intensities of the same one. */
--c-weekend-bg: #f0f0f0; /* plain Saturday — read directly by red-days.spec.ts */
--c-weekend-border: #cfcfcf;
--c-weekend-hover: #e8e8e8;
--c-redday-border: #d9776e;
--c-redday-hover: #fbdedb;
--c-klamdag-bg: #fef8e7;
--c-klamdag-border: #d9b23c;
--c-klamdag-hover: #fcf0c8;
/* The one deliberately loud color in the app — a "this just changed"
* flash ring on a calendar band, never a static/resting state. */
--c-flash: #ffd54f;
--overlay: rgba(16, 24, 40, 0.5);
/* Spacing scale (8px base) */ /* Spacing scale (8px base) */
--sp-1: 4px; --sp-1: 4px;
@@ -29,6 +62,8 @@
--sp-8: 48px; --sp-8: 48px;
/* Type scale */ /* Type scale */
--fs-2xs: 0.6875rem;
--fs-xs: 0.75rem;
--fs-sm: 0.875rem; --fs-sm: 0.875rem;
--fs-base: 1rem; --fs-base: 1rem;
--fs-lg: 1.25rem; --fs-lg: 1.25rem;
@@ -37,7 +72,13 @@
--lh: 1.5; --lh: 1.5;
/* Shape & depth */ /* Shape & depth */
--radius-sm: 6px;
--radius: 8px; --radius: 8px;
--radius-full: 999px;
/* Matches frontend/e2e/tests/calendar-band.spec.ts's literal 4px/0px
* assertions on band corners — kept as its own token (not tied to
* --radius-sm) so a future radius change there can't silently break it. */
--radius-band: 4px;
--shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06); --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
--shadow-md: 0 4px 12px rgba(16, 24, 40, 0.08); --shadow-md: 0 4px 12px rgba(16, 24, 40, 0.08);
@@ -148,22 +189,27 @@ body {
font-size: var(--fs-base); font-size: var(--fs-base);
} }
/* Headings */ /* Headings — tighter leading than body text, the usual editorial cue that a
* line is a heading rather than another sentence of the paragraph below it. */
h1 { h1 {
font-size: var(--fs-2xl); font-size: var(--fs-2xl);
font-weight: 700; font-weight: 700;
line-height: 1.2;
letter-spacing: -0.01em;
margin-bottom: var(--sp-4); margin-bottom: var(--sp-4);
} }
h2 { h2 {
font-size: var(--fs-xl); font-size: var(--fs-xl);
font-weight: 700; font-weight: 700;
line-height: 1.25;
margin-bottom: var(--sp-3); margin-bottom: var(--sp-3);
} }
h3 { h3 {
font-size: var(--fs-lg); font-size: var(--fs-lg);
font-weight: 600; font-weight: 600;
line-height: 1.3;
margin-bottom: var(--sp-3); margin-bottom: var(--sp-3);
} }
@@ -250,7 +296,7 @@ textarea:focus {
input:disabled, input:disabled,
select:disabled, select:disabled,
textarea:disabled { textarea:disabled {
background-color: #f0f0f0; background-color: var(--c-bg);
cursor: not-allowed; cursor: not-allowed;
} }
@@ -323,7 +369,7 @@ main {
.warning-message { .warning-message {
padding: var(--sp-3) var(--sp-4); padding: var(--sp-3) var(--sp-4);
background-color: #fef8e7; background-color: var(--c-warning-weak);
color: var(--c-warning); color: var(--c-warning);
border-left: 4px solid var(--c-warning); border-left: 4px solid var(--c-warning);
border-radius: var(--radius); border-radius: var(--radius);
+1 -1
View File
@@ -119,7 +119,7 @@
} }
.qrcode-modal__status--pending { .qrcode-modal__status--pending {
background-color: #fef8e7; background-color: var(--c-warning-weak);
color: var(--c-warning); color: var(--c-warning);
} }
+12
View File
@@ -155,6 +155,18 @@
.calendar-day__number { .calendar-day__number {
font-size: var(--fs-sm); font-size: var(--fs-sm);
} }
/* Pre-existing bug this pass also fixes: `.calendar__layout` never
* stacked, so the fixed-width preset rail sat on top of Friday/Saturday/
* Sunday's columns instead of beside them once the month grid had no room
* left. The rail moves below the grid and goes full-width. */
.calendar__layout {
flex-direction: column;
}
.preset-rail {
width: 100%;
}
} }
/* Tablet - Medium screens (641px to 1024px) */ /* Tablet - Medium screens (641px to 1024px) */