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;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
background-color: var(--overlay);
display: flex;
align-items: center;
justify-content: center;
@@ -12,9 +12,9 @@
}
.period-popover__content {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
background-color: var(--c-surface);
border-radius: var(--radius);
box-shadow: var(--shadow-md);
max-width: 480px;
width: 90%;
max-height: 80vh;
@@ -25,166 +25,186 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #e0e0e0;
padding: var(--sp-5);
border-bottom: 1px solid var(--c-border);
}
.period-popover__header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
font-size: var(--fs-xl);
font-weight: 700;
}
.period-popover__close {
background: none;
border: none;
font-size: 28px;
font-size: var(--fs-xl);
cursor: pointer;
padding: 0;
color: #666;
color: var(--c-text-muted);
transition: color 0.2s;
}
.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 {
padding: 20px;
padding: var(--sp-5);
}
.period-popover__error {
background-color: #ffebee;
color: #c62828;
padding: 12px;
border-radius: 4px;
margin-bottom: 16px;
font-size: 14px;
background-color: var(--c-danger-weak);
color: var(--c-danger);
padding: var(--sp-3);
border-radius: var(--radius-sm);
margin-bottom: var(--sp-4);
font-size: var(--fs-sm);
}
.period-popover__adhoc-notice {
font-size: 13px;
color: #666;
margin: 0 0 12px 0;
font-size: var(--fs-sm);
color: var(--c-text-muted);
margin: 0 0 var(--sp-3) 0;
}
.period-popover__date-range {
font-size: 15px;
font-size: var(--fs-base);
font-weight: 500;
color: #333;
color: var(--c-text);
}
.period-popover__field {
margin-bottom: 16px;
margin-bottom: var(--sp-4);
}
.period-popover__label {
display: block;
font-size: 14px;
font-size: var(--fs-sm);
font-weight: 500;
margin-bottom: 6px;
color: #333;
margin-bottom: var(--sp-2);
color: var(--c-text);
}
.period-popover__input,
.period-popover__select {
width: 100%;
padding: 8px 10px;
border: 1px solid #d0d0d0;
border-radius: 4px;
font-size: 14px;
background-color: #fff;
padding: var(--sp-2) var(--sp-3);
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
font-size: var(--fs-sm);
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__select:disabled {
background-color: #f5f5f5;
background-color: var(--c-bg);
cursor: not-allowed;
}
.period-popover__fieldset {
border: none;
padding: 0;
margin: 0 0 16px 0;
margin: 0 0 var(--sp-4) 0;
}
.period-popover__radio-label,
.period-popover__checkbox-label {
display: flex;
align-items: center;
font-size: 14px;
font-size: var(--fs-sm);
cursor: pointer;
user-select: none;
margin-bottom: 8px;
margin-bottom: var(--sp-2);
}
.period-popover__radio-label input,
.period-popover__checkbox-label input {
margin-right: 8px;
margin-right: var(--sp-2);
cursor: pointer;
}
.period-popover__footer {
display: flex;
gap: 8px;
gap: var(--sp-2);
justify-content: flex-end;
padding: 16px 20px;
border-top: 1px solid #e0e0e0;
background-color: #fafafa;
padding: var(--sp-4) var(--sp-5);
border-top: 1px solid var(--c-border);
background-color: var(--c-bg);
}
.period-popover__button {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
padding: var(--sp-2) var(--sp-4);
border-radius: var(--radius-sm);
font-size: var(--fs-sm);
font-weight: 500;
cursor: pointer;
border: none;
transition: background-color 0.2s;
}
.period-popover__button:focus-visible {
outline: 2px solid var(--c-accent);
outline-offset: 2px;
}
.period-popover__button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.period-popover__button--primary {
background-color: #0066cc;
color: #fff;
background-color: var(--c-accent);
color: var(--c-surface);
}
.period-popover__button--primary:hover:not(:disabled) {
background-color: #0052a3;
background-color: var(--c-accent-hover);
}
.period-popover__button--secondary {
background-color: #e0e0e0;
color: #333;
background-color: var(--c-bg);
color: var(--c-text);
border: 1px solid var(--c-border);
}
.period-popover__button--secondary:hover:not(:disabled) {
background-color: #d0d0d0;
background-color: var(--c-border);
}
.period-popover__button--danger {
background-color: #d32f2f;
color: #fff;
background-color: var(--c-danger-weak);
color: var(--c-danger);
border: 1px solid var(--c-danger);
margin-right: auto;
}
.period-popover__button--danger:hover:not(:disabled) {
background-color: #b71c1c;
background-color: var(--c-danger);
color: var(--c-surface);
}
.period-popover__delete-confirm {
display: flex;
align-items: center;
gap: 12px;
gap: var(--sp-3);
width: 100%;
}
.period-popover__delete-confirm-text {
font-size: 14px;
color: #333;
font-size: var(--fs-sm);
color: var(--c-text);
margin-right: auto;
}
@@ -1,30 +1,30 @@
.preset-rail {
width: 200px;
flex-shrink: 0;
padding: 16px;
background-color: #f9f9f9;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: var(--sp-4);
background-color: var(--c-surface);
border-radius: var(--radius);
box-shadow: var(--shadow-sm);
align-self: flex-start;
}
.preset-rail__heading {
margin: 0 0 4px 0;
font-size: 14px;
margin: 0 0 var(--sp-1) 0;
font-size: var(--fs-sm);
font-weight: 600;
color: #333;
color: var(--c-text);
}
.preset-rail__hint {
margin: 0 0 12px 0;
font-size: 12px;
color: #666;
margin: 0 0 var(--sp-3) 0;
font-size: var(--fs-xs);
color: var(--c-text-muted);
}
.preset-rail__buttons {
display: flex;
flex-direction: column;
gap: 8px;
gap: var(--sp-2);
}
.preset-rail__button {
@@ -32,18 +32,18 @@
flex-direction: column;
align-items: flex-start;
gap: 2px;
padding: 10px 12px;
border: 1px solid #d0d0d0;
border-radius: 4px;
background-color: #fff;
padding: var(--sp-2) var(--sp-3);
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
background-color: var(--c-surface);
cursor: pointer;
text-align: left;
transition: background-color 0.2s, border-color 0.2s;
}
.preset-rail__button:hover:not(:disabled) {
background-color: #f0f8ff;
border-color: #0066cc;
background-color: var(--c-accent-weak);
border-color: var(--c-accent);
}
.preset-rail__button:disabled {
@@ -52,12 +52,12 @@
}
.preset-rail__button-name {
font-size: 14px;
font-size: var(--fs-sm);
font-weight: 600;
color: #333;
color: var(--c-text);
}
.preset-rail__button-detail {
font-size: 11px;
color: #666;
font-size: var(--fs-2xs);
color: var(--c-text-muted);
}
+22 -7
View File
@@ -11,7 +11,7 @@
max-width: var(--maxw);
width: 100%;
margin: 0 auto;
padding: var(--sp-5);
padding: var(--sp-6) var(--sp-5);
}
/* Dashboard & Pages */
@@ -23,16 +23,31 @@
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,
.settings-page__section,
.calendar__section,
.tips-page__section {
background-color: var(--c-surface);
border: 1px solid var(--c-border);
border-radius: var(--radius);
padding: var(--sp-5);
margin-bottom: var(--sp-5);
box-shadow: var(--shadow-sm);
background-color: transparent;
border: none;
border-top: 1px solid var(--c-border);
border-radius: 0;
padding: var(--sp-6) 0 0;
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,
@@ -4,7 +4,7 @@
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
background-color: var(--overlay);
display: flex;
align-items: center;
justify-content: center;
@@ -12,9 +12,9 @@
}
.manual-fetch-modal__content {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
background-color: var(--c-surface);
border-radius: var(--radius);
box-shadow: var(--shadow-md);
max-width: 600px;
width: 90%;
max-height: 85vh;
@@ -25,53 +25,58 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #e0e0e0;
padding: var(--sp-5);
border-bottom: 1px solid var(--c-border);
}
.manual-fetch-modal__header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
font-size: var(--fs-xl);
font-weight: 700;
}
.manual-fetch-modal__close {
background: none;
border: none;
font-size: 28px;
font-size: var(--fs-xl);
cursor: pointer;
padding: 0;
color: #666;
color: var(--c-text-muted);
transition: color 0.2s;
}
.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 {
padding: 20px;
padding: var(--sp-5);
}
.manual-fetch-modal__description {
font-size: 14px;
color: #666;
margin: 0 0 16px 0;
font-size: var(--fs-sm);
color: var(--c-text-muted);
margin: 0 0 var(--sp-4) 0;
}
.manual-fetch-modal__error {
background-color: #ffebee;
color: #c62828;
padding: 12px;
border-radius: 4px;
margin-bottom: 16px;
font-size: 14px;
background-color: var(--c-danger-weak);
color: var(--c-danger);
padding: var(--sp-3);
border-radius: var(--radius-sm);
margin-bottom: var(--sp-4);
font-size: var(--fs-sm);
}
.manual-fetch-modal__section {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #f0f0f0;
margin-bottom: var(--sp-5);
padding-bottom: var(--sp-5);
border-bottom: 1px solid var(--c-border);
}
.manual-fetch-modal__section:last-child {
@@ -81,85 +86,97 @@
}
.manual-fetch-modal__section h3 {
margin: 0 0 12px 0;
font-size: 14px;
margin: 0 0 var(--sp-3) 0;
font-size: var(--fs-sm);
font-weight: 600;
color: #333;
color: var(--c-text);
}
.manual-fetch-modal__field {
margin-bottom: 12px;
margin-bottom: var(--sp-3);
}
.manual-fetch-modal__field label {
display: block;
font-size: 13px;
font-size: var(--fs-xs);
font-weight: 500;
margin-bottom: 4px;
color: #555;
margin-bottom: var(--sp-1);
color: var(--c-text-muted);
}
.manual-fetch-modal__field input {
width: 100%;
padding: 8px 10px;
border: 1px solid #d0d0d0;
border-radius: 4px;
font-size: 14px;
padding: var(--sp-2) var(--sp-3);
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
font-size: var(--fs-sm);
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 {
background-color: #f5f5f5;
background-color: var(--c-bg);
cursor: not-allowed;
}
.manual-fetch-modal__info {
font-size: 13px;
color: #666;
margin-top: 8px;
padding: 8px;
background-color: #f9f9f9;
border-radius: 4px;
font-size: var(--fs-xs);
color: var(--c-text-muted);
margin-top: var(--sp-2);
padding: var(--sp-2);
background-color: var(--c-bg);
border-radius: var(--radius-sm);
}
.manual-fetch-modal__footer {
display: flex;
gap: 8px;
gap: var(--sp-2);
justify-content: flex-end;
padding: 16px 20px;
border-top: 1px solid #e0e0e0;
background-color: #fafafa;
padding: var(--sp-4) var(--sp-5);
border-top: 1px solid var(--c-border);
background-color: var(--c-bg);
}
.manual-fetch-modal__button {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
padding: var(--sp-2) var(--sp-4);
border-radius: var(--radius-sm);
font-size: var(--fs-sm);
font-weight: 500;
cursor: pointer;
border: none;
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 {
opacity: 0.6;
cursor: not-allowed;
}
.manual-fetch-modal__button--primary {
background-color: #0066cc;
color: #fff;
background-color: var(--c-accent);
color: var(--c-surface);
}
.manual-fetch-modal__button--primary:hover:not(:disabled) {
background-color: #0052a3;
background-color: var(--c-accent-hover);
}
.manual-fetch-modal__button--secondary {
background-color: #e0e0e0;
color: #333;
background-color: var(--c-bg);
color: var(--c-text);
border: 1px solid var(--c-border);
}
.manual-fetch-modal__button--secondary:hover:not(:disabled) {
background-color: #d0d0d0;
background-color: var(--c-border);
}
@@ -4,7 +4,7 @@
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
background-color: var(--overlay);
display: flex;
align-items: center;
justify-content: center;
@@ -12,9 +12,9 @@
}
.planned-day-modal__content {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
background-color: var(--c-surface);
border-radius: var(--radius);
box-shadow: var(--shadow-md);
max-width: 500px;
width: 90%;
max-height: 80vh;
@@ -25,87 +25,98 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #e0e0e0;
padding: var(--sp-5);
border-bottom: 1px solid var(--c-border);
}
.planned-day-modal__header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
font-size: var(--fs-xl);
font-weight: 700;
}
.planned-day-modal__close {
background: none;
border: none;
font-size: 28px;
font-size: var(--fs-xl);
cursor: pointer;
padding: 0;
color: #666;
color: var(--c-text-muted);
transition: color 0.2s;
}
.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 {
padding: 20px;
padding: var(--sp-5);
}
.planned-day-modal__date {
font-size: 14px;
color: #666;
margin: 0 0 16px 0;
font-size: var(--fs-sm);
color: var(--c-text-muted);
margin: 0 0 var(--sp-4) 0;
font-weight: 500;
}
.planned-day-modal__error {
background-color: #ffebee;
color: #c62828;
padding: 12px;
border-radius: 4px;
margin-bottom: 16px;
font-size: 14px;
background-color: var(--c-danger-weak);
color: var(--c-danger);
padding: var(--sp-3);
border-radius: var(--radius-sm);
margin-bottom: var(--sp-4);
font-size: var(--fs-sm);
}
.planned-day-modal__field {
margin-bottom: 16px;
margin-bottom: var(--sp-4);
}
.planned-day-modal__label {
display: block;
font-size: 14px;
font-size: var(--fs-sm);
font-weight: 500;
margin-bottom: 6px;
color: #333;
margin-bottom: var(--sp-2);
color: var(--c-text);
}
.planned-day-modal__select {
width: 100%;
padding: 8px 10px;
border: 1px solid #d0d0d0;
border-radius: 4px;
font-size: 14px;
background-color: #fff;
padding: var(--sp-2) var(--sp-3);
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
font-size: var(--fs-sm);
background-color: var(--c-surface);
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 {
background-color: #f5f5f5;
background-color: var(--c-bg);
cursor: not-allowed;
}
.planned-day-modal__checkbox-label {
display: flex;
align-items: center;
font-size: 14px;
font-size: var(--fs-sm);
cursor: pointer;
user-select: none;
}
.planned-day-modal__checkbox-label input[type='checkbox'] {
margin-right: 8px;
margin-right: var(--sp-2);
cursor: pointer;
}
@@ -114,23 +125,23 @@
}
.planned-day-modal__hint {
font-size: 12px;
color: #999;
margin-top: 6px;
margin-left: 24px;
font-size: var(--fs-xs);
color: var(--c-text-faint);
margin-top: var(--sp-2);
margin-left: var(--sp-6);
font-style: italic;
}
.planned-day-modal__pinned-notice {
font-size: 12px;
color: #666;
margin: -8px 0 16px 0;
font-size: var(--fs-xs);
color: var(--c-text-muted);
margin: calc(var(--sp-2) * -1) 0 var(--sp-4) 0;
}
.planned-day-modal__override-switch {
display: flex;
gap: 16px;
margin-top: 6px;
gap: var(--sp-4);
margin-top: var(--sp-2);
}
.planned-day-modal__override-switch .planned-day-modal__checkbox-label {
@@ -139,64 +150,72 @@
.planned-day-modal__footer {
display: flex;
gap: 8px;
gap: var(--sp-2);
justify-content: flex-end;
padding: 16px 20px;
border-top: 1px solid #e0e0e0;
background-color: #fafafa;
padding: var(--sp-4) var(--sp-5);
border-top: 1px solid var(--c-border);
background-color: var(--c-bg);
}
.planned-day-modal__button {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
padding: var(--sp-2) var(--sp-4);
border-radius: var(--radius-sm);
font-size: var(--fs-sm);
font-weight: 500;
cursor: pointer;
border: none;
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 {
opacity: 0.6;
cursor: not-allowed;
}
.planned-day-modal__button--primary {
background-color: #0066cc;
color: #fff;
background-color: var(--c-accent);
color: var(--c-surface);
}
.planned-day-modal__button--primary:hover:not(:disabled) {
background-color: #0052a3;
background-color: var(--c-accent-hover);
}
.planned-day-modal__button--secondary {
background-color: #e0e0e0;
color: #333;
background-color: var(--c-bg);
color: var(--c-text);
border: 1px solid var(--c-border);
}
.planned-day-modal__button--secondary:hover:not(:disabled) {
background-color: #d0d0d0;
background-color: var(--c-border);
}
.planned-day-modal__button--danger {
background-color: #d32f2f;
color: #fff;
background-color: var(--c-danger-weak);
color: var(--c-danger);
border: 1px solid var(--c-danger);
}
.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 {
display: flex;
align-items: center;
gap: 12px;
gap: var(--sp-3);
width: 100%;
}
.planned-day-modal__delete-confirm-text {
font-size: 14px;
color: #333;
font-size: var(--fs-sm);
color: var(--c-text);
margin-right: auto;
}
@@ -183,7 +183,7 @@
}
.qrcode-modal__button--success:hover {
background-color: #0d6a37;
background-color: var(--c-success-hover);
}
.qrcode-modal__button--danger {
@@ -192,7 +192,7 @@
}
.qrcode-modal__button--danger:hover {
background-color: #8a1a15;
background-color: var(--c-danger-hover);
}
.qrcode-modal__button:disabled {
+37 -29
View File
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'
import { useAuth } from '@/context/AuthContext'
import { useWorkspace } from '@/context/WorkspaceContext'
import { useTranslation } from '@/i18n'
import './auth.css'
export function Auth() {
const { login, register, isAuthenticated } = useAuth()
@@ -38,38 +39,45 @@ export function Auth() {
}
return (
<main>
<h1>{isRegister ? t('auth.createAccountTitle') : t('auth.loginTitle')}</h1>
<form onSubmit={(e) => void handleSubmit(e)}>
{isRegister && (
<main className="auth-page">
<div className="auth-page__card">
<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 && (
<input
placeholder={t('auth.namePlaceholder')}
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
/>
)}
<input
placeholder={t('auth.namePlaceholder')}
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
type="email"
data-testid="auth-email"
placeholder={t('auth.emailPlaceholder')}
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
)}
<input
type="email"
data-testid="auth-email"
placeholder={t('auth.emailPlaceholder')}
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
data-testid="auth-password"
placeholder={t('auth.passwordPlaceholder')}
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{error && <p role="alert">{error}</p>}
<button type="submit" data-testid="auth-submit">
{isRegister ? t('auth.registerButton') : t('auth.loginButton')}
<input
type="password"
data-testid="auth-password"
placeholder={t('auth.passwordPlaceholder')}
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{error && (
<p role="alert" className="auth-page__error">
{error}
</p>
)}
<button type="submit" data-testid="auth-submit">
{isRegister ? t('auth.registerButton') : t('auth.loginButton')}
</button>
</form>
<button type="button" className="auth-page__switch button--ghost" onClick={() => setIsRegister((v) => !v)}>
{isRegister ? t('auth.haveAccountButton') : t('auth.createAccountButton')}
</button>
</form>
<button type="button" onClick={() => setIsRegister((v) => !v)}>
{isRegister ? t('auth.haveAccountButton') : t('auth.createAccountButton')}
</button>
</div>
</main>
)
}
+11 -17
View File
@@ -229,23 +229,17 @@ export function Dashboard(): ReactNode {
{age4Bars.length > 0 && <Age4Bars bars={age4Bars} />}
<section className="dashboard__section">
<div style={{ display: 'flex', gap: '10px' }}>
<button
onClick={handleFetchFK}
disabled={!selectedChildId}
style={{ padding: '0.5rem 1rem', cursor: selectedChildId ? 'pointer' : 'not-allowed' }}
>
{t('dashboard.fetchFromFk')}
</button>
<button
onClick={() => setShowManualFetchModal(true)}
disabled={!selectedChildId}
style={{ padding: '0.5rem 1rem', cursor: selectedChildId ? 'pointer' : 'not-allowed' }}
>
{t('dashboard.enterManually')}
</button>
</div>
<section className="dashboard__section dashboard__fetch-actions">
<button onClick={handleFetchFK} disabled={!selectedChildId}>
{t('dashboard.fetchFromFk')}
</button>
<button
onClick={() => setShowManualFetchModal(true)}
disabled={!selectedChildId}
className="button--secondary"
>
{t('dashboard.enterManually')}
</button>
</section>
{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 {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
margin-bottom: var(--sp-5);
}
.calendar__header button {
min-width: 36px;
min-height: 36px;
padding: 8px 12px;
padding: var(--sp-2) var(--sp-3);
background: none;
color: var(--c-text);
border: 1px solid #d0d0d0;
border-radius: 4px;
font-size: 18px;
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
font-size: var(--fs-lg);
cursor: pointer;
transition: background-color 0.2s;
transition: background-color 0.2s, border-color 0.2s;
}
.calendar__header button:hover {
background-color: #f0f0f0;
background-color: var(--c-bg);
border-color: var(--c-text-muted);
}
.calendar__title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: #333;
font-size: var(--fs-xl);
font-weight: 700;
color: var(--c-text);
display: flex;
gap: 8px;
gap: var(--sp-2);
align-items: center;
justify-content: center;
}
.calendar__month-select,
.calendar__year-select {
background: #fff;
border: 1px solid #d0d0d0;
color: #333;
font-size: 20px;
font-weight: 600;
background: var(--c-surface);
border: 1px solid var(--c-border);
color: var(--c-text);
font-size: var(--fs-xl);
font-weight: 700;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
padding: var(--sp-1) var(--sp-2);
border-radius: var(--radius-sm);
transition: background-color 0.2s, border-color 0.2s, color 0.2s;
appearance: none;
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-position: right 6px center;
background-size: 12px;
@@ -60,28 +55,28 @@
.calendar__month-select:hover,
.calendar__year-select:hover {
background-color: #f0f0f0;
border-color: #0066cc;
color: #0066cc;
background-color: var(--c-bg);
border-color: var(--c-accent);
color: var(--c-accent);
}
.calendar__month-select:focus-visible,
.calendar__year-select:focus-visible {
outline: 2px solid #0066cc;
outline: 2px solid var(--c-accent);
outline-offset: 2px;
border-color: #0066cc;
border-color: var(--c-accent);
}
.calendar__month-select:active,
.calendar__year-select:active {
background-color: #e0e0e0;
background-color: var(--c-border);
}
.calendar__grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
margin-bottom: 8px;
gap: var(--sp-2);
margin-bottom: var(--sp-2);
}
/* The weeks themselves now live in a separate flex column below the header
@@ -93,114 +88,114 @@
.calendar__weeks {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 24px;
gap: var(--sp-2);
margin-bottom: var(--sp-5);
}
.calendar__weekday {
padding: 12px 8px;
padding: var(--sp-3) var(--sp-2);
text-align: center;
font-weight: 600;
font-size: 13px;
color: #666;
font-size: var(--fs-xs);
color: var(--c-text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
letter-spacing: 0.04em;
}
.calendar-day {
min-height: 84px;
padding: 8px;
border: 1px solid #e0e0e0;
border-radius: 4px;
background-color: #fff;
padding: var(--sp-2);
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
background-color: var(--c-surface);
cursor: pointer;
display: flex;
flex-direction: column;
transition: all 0.2s;
transition: border-color 0.15s, background-color 0.15s, box-shadow 0.15s;
position: relative;
user-select: none;
-webkit-user-select: none; /* Safari */
}
.calendar-day:hover {
border-color: #0066cc;
box-shadow: 0 2px 8px rgba(0, 102, 204, 0.1);
background-color: #f9fbff;
border-color: var(--c-accent);
box-shadow: 0 2px 8px rgba(47, 111, 79, 0.12);
background-color: var(--c-accent-weak);
}
.calendar-day:focus-visible {
outline: 2px solid #0066cc;
outline: 2px solid var(--c-accent);
outline-offset: 2px;
}
.calendar-day--disabled {
background-color: #fafafa;
color: #999;
background-color: var(--c-bg);
color: var(--c-text-faint);
cursor: default;
}
.calendar-day--disabled:hover {
border-color: #e0e0e0;
border-color: var(--c-border);
box-shadow: none;
background-color: #fafafa;
background-color: var(--c-bg);
}
.calendar-day--has-events {
background-color: #fafbff;
background-color: rgba(47, 111, 79, 0.04);
}
.calendar-day__number {
font-weight: 600;
font-size: 14px;
color: #333;
margin-bottom: 4px;
font-size: var(--fs-sm);
color: var(--c-text);
margin-bottom: var(--sp-1);
}
.calendar-day--disabled .calendar-day__number {
color: #bbb;
color: var(--c-text-faint);
}
.calendar-day--today .calendar-day__number {
background-color: #0066cc;
color: #fff;
background-color: var(--c-accent);
color: var(--c-surface);
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-size: var(--fs-xs);
}
.calendar-day--selected {
background-color: #f9fbff;
border-color: #0066cc;
box-shadow: 0 2px 8px rgba(0, 102, 204, 0.1);
background-color: var(--c-accent-weak);
border-color: var(--c-accent);
box-shadow: 0 2px 8px rgba(47, 111, 79, 0.18);
}
.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
signal: each also gets a text badge (see .calendar-day__type-badge) and a
title/aria-label carrying the holiday name. */
.calendar-day--red-day {
background-color: #fdeceb;
border-color: #d9776e;
background-color: var(--c-danger-weak);
border-color: var(--c-redday-border);
}
.calendar-day--red-day:hover {
background-color: #fbdedb;
background-color: var(--c-redday-hover);
}
.calendar-day--klamdag {
background-color: #fef8e7;
border-color: #d9b23c;
background-color: var(--c-klamdag-bg);
border-color: var(--c-klamdag-border);
}
.calendar-day--klamdag:hover {
background-color: #fcf0c8;
background-color: var(--c-klamdag-hover);
}
/* 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
(the backend collapses both into a single "weekend" day_type). */
.calendar-day--weekend {
background-color: #f0f0f0;
border-color: #cfcfcf;
background-color: var(--c-weekend-bg);
border-color: var(--c-weekend-border);
}
.calendar-day--weekend:hover {
background-color: #e8e8e8;
background-color: var(--c-weekend-hover);
}
.calendar-day__holiday-name {
font-size: 10px;
color: #a8453d;
font-size: var(--fs-2xs);
color: var(--c-danger);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -240,7 +235,7 @@
font-size: 9px;
font-weight: 700;
line-height: 1;
color: #fff;
color: var(--c-surface);
background-color: rgba(0, 0, 0, 0.4);
border-radius: 3px;
padding: 1px 3px;
@@ -277,7 +272,7 @@
.calendar-week {
display: grid;
grid-template-columns: repeat(7, 1fr);
column-gap: 8px;
column-gap: var(--sp-2);
row-gap: 3px;
}
@@ -292,7 +287,10 @@
/* One continuous band segment for this week row. Square by default —
.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
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 {
position: relative;
display: flex;
@@ -305,17 +303,17 @@
}
.calendar-band--start {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
border-top-left-radius: var(--radius-band);
border-bottom-left-radius: var(--radius-band);
}
.calendar-band--end {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
border-top-right-radius: var(--radius-band);
border-bottom-right-radius: var(--radius-band);
}
.calendar-band:focus-visible {
outline: 2px solid #0066cc;
outline: 2px solid var(--c-accent);
outline-offset: 1px;
}
@@ -330,7 +328,7 @@
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
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;
align-items: center;
padding: 0 6px;
font-size: 11px;
color: #fff;
font-size: var(--fs-2xs);
color: var(--c-surface);
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.35);
pointer-events: none;
z-index: 1;
@@ -393,7 +391,7 @@
}
.calendar-event__label {
font-size: 12px;
font-size: var(--fs-2xs);
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
@@ -416,31 +414,30 @@
}
.calendar__legend {
padding: 16px;
background-color: #f9f9f9;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: var(--sp-4);
background-color: var(--c-bg);
border-radius: var(--radius-sm);
}
.calendar__legend h3 {
margin: 0 0 12px 0;
font-size: 14px;
margin: 0 0 var(--sp-3) 0;
font-size: var(--fs-sm);
font-weight: 600;
color: #333;
color: var(--c-text);
}
.calendar__legend-items {
display: flex;
gap: 24px;
gap: var(--sp-5);
flex-wrap: wrap;
}
.calendar__legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #666;
gap: var(--sp-2);
font-size: var(--fs-xs);
color: var(--c-text-muted);
}
.calendar__legend-swatch {
@@ -449,32 +446,32 @@
justify-content: center;
width: 16px;
height: 16px;
border-radius: 2px;
border-radius: 3px;
flex-shrink: 0;
font-size: 11px;
font-weight: bold;
color: #fff;
color: var(--c-surface);
}
.calendar__legend-swatch--dubbeldag {
background-color: rgba(0, 0, 0, 0.2);
border: 1px solid #999;
color: #333;
border: 1px solid var(--c-text-faint);
color: var(--c-text);
}
.calendar__legend-swatch--red-day {
background-color: #fdeceb;
border: 1px solid #d9776e;
background-color: var(--c-danger-weak);
border: 1px solid var(--c-redday-border);
}
.calendar__legend-swatch--klamdag {
background-color: #fef8e7;
border: 1px solid #d9b23c;
background-color: var(--c-klamdag-bg);
border: 1px solid var(--c-klamdag-border);
}
.calendar__legend-swatch--override {
background-color: #fff;
border: 2px dashed #666;
background-color: var(--c-surface);
border: 2px dashed var(--c-text-muted);
}
/* §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). */
.calendar__layout {
display: flex;
gap: 20px;
gap: var(--sp-5);
align-items: flex-start;
}
@@ -494,22 +491,22 @@
}
.calendar__sync-indicator {
font-size: 12px;
color: #666;
margin: 0 0 12px 0;
font-size: var(--fs-xs);
color: var(--c-text-muted);
margin: 0 0 var(--sp-3) 0;
}
.calendar__toast {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
margin-bottom: 16px;
background-color: #eef6ff;
border: 1px solid #b8d9ff;
border-radius: 4px;
font-size: 13px;
color: #003a70;
gap: var(--sp-3);
padding: var(--sp-3) var(--sp-4);
margin-bottom: var(--sp-4);
background-color: var(--c-accent-weak);
border: 1px solid var(--c-accent);
border-radius: var(--radius-sm);
font-size: var(--fs-sm);
color: var(--c-text);
}
.calendar__toast-message {
@@ -517,19 +514,19 @@
}
.calendar__toast-undo {
padding: 4px 10px;
border: 1px solid #0066cc;
border-radius: 4px;
background: #fff;
color: #0066cc;
padding: var(--sp-1) var(--sp-3);
border: 1px solid var(--c-accent);
border-radius: var(--radius-sm);
background: var(--c-surface);
color: var(--c-accent);
cursor: pointer;
font-size: 12px;
font-size: var(--fs-xs);
font-weight: 600;
}
.calendar__toast-undo:hover:not(:disabled) {
background-color: #0066cc;
color: #fff;
background-color: var(--c-accent);
color: var(--c-surface);
}
.calendar__toast-undo:disabled {
@@ -540,10 +537,10 @@
.calendar__toast-dismiss {
background: none;
border: none;
font-size: 18px;
font-size: var(--fs-lg);
line-height: 1;
cursor: pointer;
color: #003a70;
color: var(--c-text-muted);
padding: 0 2px;
}
@@ -555,12 +552,12 @@
display: flex;
align-items: center;
justify-content: center;
padding: 12px 8px;
padding: var(--sp-3) var(--sp-2);
font-weight: 600;
font-size: 12px;
color: #999;
background-color: #fafafa;
border-right: 1px solid #d0d0d0;
font-size: var(--fs-xs);
color: var(--c-text-faint);
background-color: var(--c-bg);
border-right: 1px solid var(--c-border);
pointer-events: none;
user-select: none;
}
@@ -569,12 +566,12 @@
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
padding: var(--sp-2);
font-weight: 600;
font-size: 11px;
color: #999;
border-right: 1px solid #d0d0d0;
background-color: #fafafa;
font-size: var(--fs-2xs);
color: var(--c-text-faint);
border-right: 1px solid var(--c-border);
background-color: var(--c-bg);
pointer-events: none;
user-select: none;
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 {
max-width: 60ch;
margin: 0 auto;
padding-top: var(--sp-6);
}
.onboarding__indicator {
color: var(--c-text-muted);
font-size: var(--fs-sm);
margin-bottom: var(--sp-4);
font-size: var(--fs-xs);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: var(--sp-5);
}
.onboarding__step h1 {
margin-bottom: var(--sp-2);
margin-bottom: var(--sp-3);
}
.onboarding__step > p {
color: var(--c-text-muted);
margin-bottom: var(--sp-5);
margin-bottom: var(--sp-6);
}
.onboarding__partner {
@@ -41,7 +49,7 @@
border: 1px solid var(--c-border);
border-radius: var(--radius);
background-color: var(--c-surface);
padding: var(--sp-4);
padding: var(--sp-5);
}
.onboarding__ladder-item h2 {
@@ -51,7 +59,7 @@
.onboarding__ladder-item p {
color: var(--c-text-muted);
margin-bottom: var(--sp-3);
margin-bottom: var(--sp-4);
}
.onboarding__hint {
+163 -31
View File
@@ -20,9 +20,9 @@
align-items: center;
justify-content: center;
padding: var(--sp-5);
background-color: var(--c-bg);
background-color: var(--c-surface);
border-radius: var(--radius);
border: 1px solid var(--c-border);
box-shadow: var(--shadow-sm);
}
.overview-metrics__value {
@@ -157,36 +157,48 @@
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 {
padding: var(--sp-5);
padding: var(--sp-6) var(--sp-6);
margin-bottom: var(--sp-6);
border-radius: var(--radius);
border-left: 4px solid var(--c-accent);
border-left: 3px solid var(--c-accent);
background-color: var(--c-accent-weak);
}
.dashboard-hero--warning {
border-left-color: var(--c-warning);
border-left-color: var(--c-danger);
background-color: var(--c-danger-weak);
}
.dashboard-hero__message {
margin: 0;
font-size: var(--fs-xl);
font-weight: 600;
font-size: var(--fs-2xl);
font-weight: 700;
line-height: 1.25;
letter-spacing: -0.01em;
color: var(--c-text);
}
/* Shown while headline figures are still the statutory assumption (no FK
* 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 {
display: inline-block;
margin: var(--sp-3) 0;
padding: var(--sp-1) var(--sp-3);
border-radius: 999px;
border-radius: var(--radius-full);
font-size: var(--fs-sm);
font-weight: 600;
background-color: #fef8e7;
background-color: var(--c-warning-weak);
color: var(--c-warning);
cursor: help;
}
@@ -196,6 +208,16 @@
display: flex;
flex-direction: column;
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 {
@@ -210,16 +232,17 @@
.age4-bars__track {
width: 100%;
height: 20px;
background-color: var(--c-bg);
border-radius: var(--radius);
height: 10px;
background-color: var(--c-surface);
border-radius: var(--radius-full);
overflow: hidden;
border: 1px solid var(--c-border);
box-shadow: inset 0 0 0 1px var(--c-border);
}
.age4-bars__fill {
height: 100%;
background-color: var(--c-accent);
border-radius: var(--radius-full);
transition: width 0.3s ease;
}
@@ -240,6 +263,16 @@
display: flex;
flex-direction: column;
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 {
@@ -258,10 +291,10 @@
.year-timeline__track {
position: relative;
flex: 1;
height: 28px;
background-color: var(--c-bg);
border-radius: var(--radius);
border: 1px solid var(--c-border);
height: 24px;
background-color: var(--c-surface);
border-radius: var(--radius-sm);
box-shadow: inset 0 0 0 1px var(--c-border);
overflow: hidden;
}
@@ -269,27 +302,39 @@
position: absolute;
top: 0;
height: 100%;
min-width: 2px;
min-width: 3px;
border: none;
cursor: pointer;
padding: 0;
transition: opacity 0.15s;
}
.year-timeline__band:hover {
opacity: 0.85;
}
.year-timeline__band:hover,
.year-timeline__band:focus-visible {
outline: 2px solid var(--c-text);
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,
.dashboard__more-details {
border: 1px solid var(--c-border);
border-radius: var(--radius);
padding: var(--sp-4);
padding: var(--sp-4) var(--sp-5);
background-color: var(--c-surface);
}
.plan-fold + .plan-fold,
.dashboard__more-details + .dashboard__more-details {
margin-top: var(--sp-4);
}
.plan-fold__summary,
.dashboard__more-details summary {
cursor: pointer;
@@ -312,11 +357,11 @@ button {
}
button:hover:not(:disabled) {
background-color: #1f5a3b;
background-color: var(--c-accent-hover);
}
button:active:not(:disabled) {
background-color: #0f4a2f;
background-color: var(--c-accent-active);
}
/* Secondary button */
@@ -418,6 +463,14 @@ button.ghost:hover:not(:disabled) {
margin-bottom: var(--sp-4);
}
.settings-page__label {
font-size: var(--fs-base);
}
.settings-page__select {
max-width: 320px;
}
.settings-page__checkbox-label {
display: flex;
align-items: center;
@@ -468,7 +521,7 @@ button.ghost:hover:not(:disabled) {
}
.settings-page__button:not(:disabled):hover {
background-color: #1f5a3b;
background-color: var(--c-accent-hover);
}
.settings-page__button:focus-visible {
@@ -545,7 +598,7 @@ button.ghost:hover:not(:disabled) {
border-radius: 999px;
font-size: var(--fs-sm);
font-weight: 400;
background-color: #fef8e7;
background-color: var(--c-warning-weak);
color: var(--c-warning);
cursor: help;
}
@@ -559,7 +612,7 @@ button.ghost:hover:not(:disabled) {
}
.vacation-days-table__save:not(:disabled):hover {
background-color: #1f5a3b;
background-color: var(--c-accent-hover);
}
.vacation-days-table__save:focus-visible {
@@ -704,13 +757,13 @@ legend {
}
.day-overrides-input__status--working {
background-color: #fef3c7;
color: #92400e;
background-color: var(--c-warning-weak);
color: var(--c-warning);
}
.day-overrides-input__status--off {
background-color: #dbeafe;
color: #1e40af;
background-color: var(--c-success-weak);
color: var(--c-success);
}
.day-overrides-input__item-note {
@@ -918,6 +971,85 @@ legend {
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 */
@media (max-width: 640px) {
[data-testid='metrics-list'] {
+52 -6
View File
@@ -3,7 +3,16 @@
@import './modals.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 {
/* Color - calm, trustworthy palette */
--c-bg: #f7f8fa;
@@ -11,13 +20,37 @@
--c-border: #e3e6eb;
--c-text: #1c2733;
--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-hover: #1f5a3b;
--c-accent-active: #0f4a2f;
--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-weak: #e7f4ec;
--c-success-hover: #0d6a37;
--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) */
--sp-1: 4px;
@@ -29,6 +62,8 @@
--sp-8: 48px;
/* Type scale */
--fs-2xs: 0.6875rem;
--fs-xs: 0.75rem;
--fs-sm: 0.875rem;
--fs-base: 1rem;
--fs-lg: 1.25rem;
@@ -37,7 +72,13 @@
--lh: 1.5;
/* Shape & depth */
--radius-sm: 6px;
--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-md: 0 4px 12px rgba(16, 24, 40, 0.08);
@@ -148,22 +189,27 @@ body {
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 {
font-size: var(--fs-2xl);
font-weight: 700;
line-height: 1.2;
letter-spacing: -0.01em;
margin-bottom: var(--sp-4);
}
h2 {
font-size: var(--fs-xl);
font-weight: 700;
line-height: 1.25;
margin-bottom: var(--sp-3);
}
h3 {
font-size: var(--fs-lg);
font-weight: 600;
line-height: 1.3;
margin-bottom: var(--sp-3);
}
@@ -250,7 +296,7 @@ textarea:focus {
input:disabled,
select:disabled,
textarea:disabled {
background-color: #f0f0f0;
background-color: var(--c-bg);
cursor: not-allowed;
}
@@ -323,7 +369,7 @@ main {
.warning-message {
padding: var(--sp-3) var(--sp-4);
background-color: #fef8e7;
background-color: var(--c-warning-weak);
color: var(--c-warning);
border-left: 4px solid var(--c-warning);
border-radius: var(--radius);
+1 -1
View File
@@ -119,7 +119,7 @@
}
.qrcode-modal__status--pending {
background-color: #fef8e7;
background-color: var(--c-warning-weak);
color: var(--c-warning);
}
+12
View File
@@ -155,6 +155,18 @@
.calendar-day__number {
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) */