dark light mode

This commit is contained in:
Filipriec
2026-08-21 09:53:37 +02:00
parent ab8f2e5f5f
commit 4e13535729
7 changed files with 203 additions and 104 deletions

View File

@@ -30,6 +30,7 @@ nav-log-out = Odhlásit se
nav-login = Přihlášení nav-login = Přihlášení
nav-register = Registrace nav-register = Registrace
nav-main-menu = hlavní menu nav-main-menu = hlavní menu
nav-toggle-theme = Přepnout tmavý režim
nav-mobile-menu = mobilní menu nav-mobile-menu = mobilní menu
# --- Názvy polí používané ve složených zprávách ------------------------------ # --- Názvy polí používané ve složených zprávách ------------------------------

View File

@@ -35,6 +35,7 @@ nav-log-out = Log out
nav-login = Login nav-login = Login
nav-register = Register nav-register = Register
nav-main-menu = main menu nav-main-menu = main menu
nav-toggle-theme = Toggle dark mode
nav-mobile-menu = mobile menu nav-mobile-menu = mobile menu
# --- Field labels used inside composed messages ---------------------------- # --- Field labels used inside composed messages ----------------------------

View File

@@ -30,6 +30,7 @@ nav-log-out = Odhlásiť sa
nav-login = Prihlásenie nav-login = Prihlásenie
nav-register = Registrácia nav-register = Registrácia
nav-main-menu = hlavné menu nav-main-menu = hlavné menu
nav-toggle-theme = Prepnúť tmavý režim
nav-mobile-menu = mobilné menu nav-mobile-menu = mobilné menu
# --- Názvy polí používané v poskladaných správach --------------------------- # --- Názvy polí používané v poskladaných správach ---------------------------

View File

@@ -9,10 +9,77 @@
@layer app { @layer app {
/* ---------- Theme tokens ----------
Only the colours reused across several rules are variables here; the
rest of the file is still one-off hex per rule (see the file header for
why). Dark is the default — the inline script in ui/base.html puts
`dark` on <html> before first paint unless localStorage says "light" —
so :root itself carries the dark values and :root.light carries the
original light ones. Values line up with the Penguin UI dark palette in
ui/base.html's @theme block where a token overlaps with one of these. */
:root {
--bg: #f3f5f7;
--surface: #ffffff;
--surface-alt: #fafbfc;
--border: #d9dfe7;
--border-soft: #e4e7ec;
--text-strong: #17202a;
--text: #33415c;
--text-secondary: #24324a;
--text-muted: #7c8796;
--text-subtle: #667385;
--label: #465267;
--primary: #2563eb;
--primary-strong: #1d4ed8;
--danger: #a12b2b;
--danger-bg: #fdf3f3;
--danger-border: #eccfcf;
--success: #21643a;
--hover-bg: #e9edf4;
--secondary-bg: #f4f6fa;
--secondary-border: #c9d2de;
--select-border: #a9c7f6;
--input-bg: #ffffff;
--input-border: #cbd3dd;
--input-text: #1e2938;
}
/* Dark is the default — see the inline script in ui/base.html, which puts
this class on <html> before first paint unless localStorage says
"light". Values line up with the Penguin UI dark palette in
ui/base.html's @theme block where a token overlaps with one of these. */
:root.dark {
--bg: #0f1722;
--surface: #152238;
--surface-alt: #1b2a42;
--border: #33415a;
--border-soft: #26334c;
--text-strong: #f3f6fb;
--text: #c7d3e6;
--text-secondary: #b7c4da;
--text-muted: #8b9ab3;
--text-subtle: #7d8ca6;
--label: #aebbd0;
--primary: #68a4ff;
--primary-strong: #8fbfff;
--danger: #e5837e;
--danger-bg: #3a2224;
--danger-border: #5c3236;
--success: #6cbf8c;
--hover-bg: #223049;
--secondary-bg: #1a2740;
--secondary-border: #3a4a68;
--select-border: #4a6da3;
--input-bg: #101a29;
--input-border: #33415a;
--input-text: #e5ebf3;
}
/* ---------- Foundations ---------- */ /* ---------- Foundations ---------- */
* { box-sizing: border-box; } * { box-sizing: border-box; }
:root { color: #17202a; background: #f3f5f7; font: 14px/1.45 Inter, ui-sans-serif, system-ui, sans-serif; } :root { color: var(--text-strong); background: var(--bg); font: 14px/1.45 Inter, ui-sans-serif, system-ui, sans-serif; }
body { margin: 0; } body { margin: 0; }
[x-cloak] { display: none !important; } [x-cloak] { display: none !important; }
/* Penguin UI's toast component file (included by ui/base.html) opens with the /* Penguin UI's toast component file (included by ui/base.html) opens with the
@@ -21,7 +88,7 @@
own trigger is the dispatch in ui/toast.html. */ own trigger is the dispatch in ui/toast.html. */
.penguin-toast > button { display: none; } .penguin-toast > button { display: none; }
button, input, select, textarea { font: inherit; } button, input, select, textarea { font: inherit; }
select, input, textarea { width: 100%; padding: 9px 10px; border: 1px solid #cbd3dd; border-radius: 6px; color: #1e2938; background: white; } select, input, textarea { width: 100%; padding: 9px 10px; border: 1px solid var(--input-border); border-radius: 6px; color: var(--input-text); background: var(--input-bg); }
textarea { resize: vertical; font: 13px/1.5 ui-monospace, monospace; } textarea { resize: vertical; font: 13px/1.5 ui-monospace, monospace; }
main { width: min(1500px, calc(100% - 40px)); margin: 34px auto; } main { width: min(1500px, calc(100% - 40px)); margin: 34px auto; }
@@ -35,58 +102,58 @@
.heading { display: flex; justify-content: space-between; align-items: end; gap: 24px; margin-bottom: 22px; } .heading { display: flex; justify-content: space-between; align-items: end; gap: 24px; margin-bottom: 22px; }
.heading h1 { margin: 0; font-size: 28px; } .heading h1 { margin: 0; font-size: 28px; }
.heading p { margin: 5px 0 0; color: #687384; } .heading p { margin: 5px 0 0; color: #687384; }
.eyebrow { color: #2563eb !important; font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; } .eyebrow { color: var(--primary) !important; font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
.actions { display: flex; flex-wrap: wrap; gap: 8px; } .actions { display: flex; flex-wrap: wrap; gap: 8px; }
.actions a, .actions span { padding: 8px 12px; border: 1px solid #cfd7e1; border-radius: 7px; color: #283548; background: white; text-decoration: none; } .actions a, .actions span { padding: 8px 12px; border: 1px solid #cfd7e1; border-radius: 7px; color: #283548; background: var(--surface); text-decoration: none; }
/* ---------- Admin three-pane browser ---------- */ /* ---------- Admin three-pane browser ---------- */
.workspace { display: grid; grid-template-columns: minmax(210px, .75fr) minmax(280px, 1.15fr) minmax(300px, 1.25fr); min-height: 560px; border: 1px solid #d9dfe7; border-radius: 11px; overflow: hidden; background: white; box-shadow: 0 10px 26px rgb(31 43 58 / 7%); } .workspace { display: grid; grid-template-columns: minmax(210px, .75fr) minmax(280px, 1.15fr) minmax(300px, 1.25fr); min-height: 560px; border: 1px solid var(--border); border-radius: 11px; overflow: hidden; background: var(--surface); box-shadow: 0 10px 26px rgb(31 43 58 / 7%); }
.pane + .pane { border-left: 1px solid #e1e5eb; } .pane + .pane { border-left: 1px solid #e1e5eb; }
.pane-title { height: 54px; padding: 0 17px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e5e8ed; background: #fafbfc; } .pane-title { height: 54px; padding: 0 17px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e5e8ed; background: var(--surface-alt); }
.pane-title h2 { margin: 0; font-size: 14px; } .pane-title h2 { margin: 0; font-size: 14px; }
.pane-title span { color: #7c8796; font-size: 12px; } .pane-title span { color: var(--text-muted); font-size: 12px; }
.pane-list { padding: 10px; } .pane-list { padding: 10px; }
.pane-list form { margin: 0; } .pane-list form { margin: 0; }
.browser-item { width: 100%; display: grid; gap: 3px; padding: 11px 12px; text-align: left; border: 1px solid transparent; border-radius: 7px; color: #253246; background: transparent; cursor: pointer; } .browser-item { width: 100%; display: grid; gap: 3px; padding: 11px 12px; text-align: left; border: 1px solid transparent; border-radius: 7px; color: #253246; background: transparent; cursor: pointer; }
.browser-item:hover { background: #f2f6fc; } .browser-item:hover { background: #f2f6fc; }
.browser-item.selected { border-color: #a9c7f6; background: #eaf2ff; } .browser-item.selected { border-color: var(--select-border); background: #eaf2ff; }
.browser-item small, .column small { color: #788495; } .browser-item small, .column small { color: #788495; }
.column { display: grid; grid-template-columns: minmax(100px, 1fr) auto; gap: 3px 12px; padding: 10px 11px; border-bottom: 1px solid #edf0f3; } .column { display: grid; grid-template-columns: minmax(100px, 1fr) auto; gap: 3px 12px; padding: 10px 11px; border-bottom: 1px solid #edf0f3; }
.column code { color: #315078; font-size: 12px; } .column code { color: #315078; font-size: 12px; }
.column small { grid-column: 1 / -1; } .column small { grid-column: 1 / -1; }
.column.system { color: #6b7686; background: #fafbfc; } .column.system { color: #6b7686; background: var(--surface-alt); }
.column.system code { color: #67788e; } .column.system code { color: #67788e; }
.group-label { margin: 14px 0 4px; padding: 0 11px; color: #7c8796; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; } .group-label { margin: 14px 0 4px; padding: 0 11px; color: var(--text-muted); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; }
/* What can be done to whatever the pane is pointed at, rendered directly /* What can be done to whatever the pane is pointed at, rendered directly
under it: under the selected table, and under the tables list for the under it: under the selected table, and under the tables list for the
profile-wide actions. Each link is a page of its own. */ profile-wide actions. Each link is a page of its own. */
.browser-actions { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 12px 12px; } .browser-actions { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 12px 12px; }
.browser-actions a { padding: 6px 12px; border: 1px solid #a9c7f6; border-radius: 7px; color: #1d4ed8; background: #eaf2ff; font-size: 13px; font-weight: 600; text-decoration: none; } .browser-actions a { padding: 6px 12px; border: 1px solid var(--select-border); border-radius: 7px; color: #1d4ed8; background: #eaf2ff; font-size: 13px; font-weight: 600; text-decoration: none; }
.browser-actions a:hover { background: #d8e8ff; } .browser-actions a:hover { background: #d8e8ff; }
.browser-actions a.danger-action { border-color: #eccfcf; color: #a12b2b; background: #fdf3f3; } .browser-actions a.danger-action { border-color: var(--danger-border); color: var(--danger); background: var(--danger-bg); }
.browser-actions a.danger-action:hover { background: #fbe6e6; } .browser-actions a.danger-action:hover { background: #fbe6e6; }
.pane-footer { margin: 0 10px 10px; border-top: 1px solid #e5e8ed; padding-top: 12px; } .pane-footer { margin: 0 10px 10px; border-top: 1px solid #e5e8ed; padding-top: 12px; }
.pane-footer a { border-color: #d9dfe7; color: #33415c; background: white; font-weight: 500; } .pane-footer a { border-color: var(--border); color: var(--text); background: var(--surface); font-weight: 500; }
.pane-footer a:hover { background: #f2f6fc; } .pane-footer a:hover { background: #f2f6fc; }
.empty { margin: 8px; color: #7c8796; } .empty { margin: 8px; color: var(--text-muted); }
/* ---------- Forms (components/form_card.html) ---------- */ /* ---------- Forms (components/form_card.html) ---------- */
.form-main { width: min(900px, calc(100% - 32px)); } .form-main { width: min(900px, calc(100% - 32px)); }
.back-link { display: inline-block; margin-bottom: 14px; color: #315f9d; text-decoration: none; } .back-link { display: inline-block; margin-bottom: 14px; color: #315f9d; text-decoration: none; }
.form-card { padding: 25px; border: 1px solid #d9dfe7; border-radius: 11px; background: white; box-shadow: 0 10px 26px rgb(31 43 58 / 7%); } .form-card { padding: 25px; border: 1px solid var(--border); border-radius: 11px; background: var(--surface); box-shadow: 0 10px 26px rgb(31 43 58 / 7%); }
.form-card h1 { margin: 0; } .form-card h1 { margin: 0; }
.form-card > p { color: #687384; } .form-card > p { color: #687384; }
.form-grid { margin-top: 22px; display: grid; grid-template-columns: 1fr 1fr; gap: 17px; } .form-grid { margin-top: 22px; display: grid; grid-template-columns: 1fr 1fr; gap: 17px; }
.form-grid label { display: grid; align-content: start; gap: 6px; color: #465267; font-size: 12px; } .form-grid label { display: grid; align-content: start; gap: 6px; color: var(--label); font-size: 12px; }
.form-grid label.wide { grid-column: 1 / -1; } .form-grid label.wide { grid-column: 1 / -1; }
.form-grid small { color: #7c8796; } .form-grid small { color: var(--text-muted); }
/* Where a field would be, when the answer is the server's rather than the /* Where a field would be, when the answer is the server's rather than the
user's — a link's index, which the server always makes. It sits in the user's — a link's index, which the server always makes. It sits in the
grid like the label it replaces. */ grid like the label it replaces. */
.form-grid .field-note { display: grid; align-content: start; gap: 6px; color: #465267; font-size: 12px; } .form-grid .field-note { display: grid; align-content: start; gap: 6px; color: var(--label); font-size: 12px; }
.form-grid .field-note p { margin: 0; } .form-grid .field-note p { margin: 0; }
.form-actions { margin-top: 20px; display: flex; justify-content: end; align-items: center; gap: 14px; } .form-actions { margin-top: 20px; display: flex; justify-content: end; align-items: center; gap: 14px; }
.form-actions a { color: #59677a; } .form-actions a { color: #59677a; }
@@ -102,65 +169,65 @@
use. */ use. */
.error { color: #b42318; } .error { color: #b42318; }
.success { color: #067647; } .success { color: #067647; }
.hint, .result-meta { color: #667385; } .hint, .result-meta { color: var(--text-subtle); }
.error-page { width: min(600px, calc(100% - 32px)); margin: 15vh auto; padding: 24px; border: 1px solid #f1c4c0; border-radius: 10px; background: white; } .error-page { width: min(600px, calc(100% - 32px)); margin: 15vh auto; padding: 24px; border: 1px solid #f1c4c0; border-radius: 10px; background: var(--surface); }
.error-page h1 { margin-top: 0; } .error-page h1 { margin-top: 0; }
.error-page p { color: #a33a31; } .error-page p { color: #a33a31; }
/* ---------- Login and register (pages/login, pages/register) ---------- */ /* ---------- Login and register (pages/login, pages/register) ---------- */
.login-main { width: min(420px, calc(100% - 32px)); margin: 12vh auto; } .login-main { width: min(420px, calc(100% - 32px)); margin: 12vh auto; }
.login-card { padding: 25px; border: 1px solid #d9dfe7; border-radius: 11px; background: white; box-shadow: 0 10px 26px rgb(31 43 58 / 7%); } .login-card { padding: 25px; border: 1px solid var(--border); border-radius: 11px; background: var(--surface); box-shadow: 0 10px 26px rgb(31 43 58 / 7%); }
.login-card h1 { margin: 0 0 18px; font-size: 24px; } .login-card h1 { margin: 0 0 18px; font-size: 24px; }
.login-card label { display: grid; gap: 5px; margin-bottom: 14px; color: #465267; font-size: 12px; } .login-card label { display: grid; gap: 5px; margin-bottom: 14px; color: var(--label); font-size: 12px; }
.login-card button { width: 100%; border: 0; border-radius: 6px; padding: 10px 18px; color: white; background: #2563eb; cursor: pointer; } .login-card button { width: 100%; border: 0; border-radius: 6px; padding: 10px 18px; color: white; background: #2563eb; cursor: pointer; }
.login-card button:disabled, .login-card button.htmx-request { opacity: .55; cursor: wait; } .login-card button:disabled, .login-card button.htmx-request { opacity: .55; cursor: wait; }
.login-alt { margin: 14px 0 0; color: #667385; font-size: 12px; text-align: center; } .login-alt { margin: 14px 0 0; color: var(--text-subtle); font-size: 12px; text-align: center; }
/* ---------- Analytics (pages/analytics.html) ---------- */ /* ---------- Analytics (pages/analytics.html) ---------- */
.analytics { display: grid; grid-template-columns: minmax(290px, 380px) minmax(0, 1fr); align-items: start; gap: 16px; } .analytics { display: grid; grid-template-columns: minmax(290px, 380px) minmax(0, 1fr); align-items: start; gap: 16px; }
.panel { margin-bottom: 16px; padding: 18px; border: 1px solid #dfe4ea; border-radius: 10px; background: white; } .panel { margin-bottom: 16px; padding: 18px; border: 1px solid #dfe4ea; border-radius: 10px; background: var(--surface); }
.panel h2 { margin: 0 0 14px; font-size: 17px; } .panel h2 { margin: 0 0 14px; font-size: 17px; }
.sidebar { position: sticky; top: 16px; max-height: calc(100vh - 32px); overflow: auto; } .sidebar { position: sticky; top: 16px; max-height: calc(100vh - 32px); overflow: auto; }
.sidebar h2 { margin-bottom: 6px; } .sidebar h2 { margin-bottom: 6px; }
.catalog-load { display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 8px; margin: 14px 0; } .catalog-load { display: grid; grid-template-columns: 1fr auto; align-items: end; gap: 8px; margin: 14px 0; }
.catalog-load label { margin: 0; } .catalog-load label { margin: 0; }
.schema-summary { margin: 8px 0; } .schema-summary { margin: 8px 0; }
.catalog-table { border-top: 1px solid #e4e7ec; padding: 9px 0; } .catalog-table { border-top: 1px solid var(--border-soft); padding: 9px 0; }
.catalog-table summary { display: flex; align-items: center; gap: 8px; cursor: pointer; } .catalog-table summary { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.currency { color: #667385; font-size: 11px; } .currency { color: var(--text-subtle); font-size: 11px; }
.starter-query, .secondary { margin-top: 9px; padding: 6px 9px; width: auto; border: 0; border-radius: 6px; color: #344054; background: #eef2f6; cursor: pointer; } .starter-query, .secondary { margin-top: 9px; padding: 6px 9px; width: auto; border: 0; border-radius: 6px; color: #344054; background: #eef2f6; cursor: pointer; }
.columns, .links ul { margin: 8px 0; padding: 0; list-style: none; } .columns, .links ul { margin: 8px 0; padding: 0; list-style: none; }
.columns li { display: flex; justify-content: space-between; gap: 8px; padding: 3px 0; } .columns li { display: flex; justify-content: space-between; gap: 8px; padding: 3px 0; }
.columns li span { color: #667385; font-size: 11px; text-align: right; } .columns li span { color: var(--text-subtle); font-size: 11px; text-align: right; }
.column-name { width: auto; padding: 0; border: 0; color: #2563eb; background: none; font-family: ui-monospace, monospace; text-align: left; cursor: pointer; } .column-name { width: auto; padding: 0; border: 0; color: var(--primary); background: none; font-family: ui-monospace, monospace; text-align: left; cursor: pointer; }
.links { color: #667385; font-size: 12px; } .links { color: var(--text-subtle); font-size: 12px; }
.links li { padding: 2px 0; } .links li { padding: 2px 0; }
.llm-context { border-top: 1px solid #e4e7ec; margin-top: 10px; padding-top: 10px; } .llm-context { border-top: 1px solid var(--border-soft); margin-top: 10px; padding-top: 10px; }
.llm-context summary { cursor: pointer; font-weight: 600; } .llm-context summary { cursor: pointer; font-weight: 600; }
.llm-context textarea { min-height: 320px; margin-top: 8px; font-size: 11px; } .llm-context textarea { min-height: 320px; margin-top: 8px; font-size: 11px; }
.query-row { display: grid; grid-template-columns: 180px 120px; gap: 12px; margin-bottom: 12px; } .query-row { display: grid; grid-template-columns: 180px 120px; gap: 12px; margin-bottom: 12px; }
.query-form label { display: grid; gap: 5px; color: #465267; font-size: 12px; } .query-form label { display: grid; gap: 5px; color: var(--label); font-size: 12px; }
.query-form textarea { min-height: 130px; } .query-form textarea { min-height: 130px; }
.query-form .actions { margin-top: 12px; align-items: center; gap: 12px; } .query-form .actions { margin-top: 12px; align-items: center; gap: 12px; }
.query-form button { border: 0; border-radius: 6px; padding: 10px 18px; width: auto; color: white; background: #2563eb; cursor: pointer; } .query-form button { border: 0; border-radius: 6px; padding: 10px 18px; width: auto; color: white; background: #2563eb; cursor: pointer; }
.query-form button:disabled, .htmx-request button, button.htmx-request { opacity: .55; cursor: wait; } .query-form button:disabled, .htmx-request button, button.htmx-request { opacity: .55; cursor: wait; }
.chart { height: 560px; } .chart { height: 560px; }
.table-wrap { max-height: 560px; overflow: auto; } .table-wrap { max-height: 560px; overflow: auto; }
table { width: 100%; border-collapse: collapse; background: white; } table { width: 100%; border-collapse: collapse; background: var(--surface); }
th, td { padding: 9px 11px; border: 1px solid #e4e7ec; text-align: left; white-space: nowrap; } th, td { padding: 9px 11px; border: 1px solid var(--border-soft); text-align: left; white-space: nowrap; }
th { position: sticky; top: 0; background: #f9fafb; } th { position: sticky; top: 0; background: #f9fafb; }
/* ---------- Table builder (Add table) ---------- */ /* ---------- Table builder (Add table) ---------- */
.builder-section { margin-top: 26px; padding-top: 20px; border-top: 1px solid #e8ebf0; } .builder-section { margin-top: 26px; padding-top: 20px; border-top: 1px solid #e8ebf0; }
.builder-section:first-of-type { margin-top: 0; padding-top: 0; border-top: 0; } .builder-section:first-of-type { margin-top: 0; padding-top: 0; border-top: 0; }
.builder-section h2 { margin: 0; font-size: 15px; color: #33415c; } .builder-section h2 { margin: 0; font-size: 15px; color: var(--text); }
.builder-section > .hint { margin: 8px 0 0; font-size: 13px; } .builder-section > .hint { margin: 8px 0 0; font-size: 13px; }
.builder-section .count { margin-left: 6px; padding: 1px 7px; border-radius: 9px; font-size: 12px; color: #4b5563; background: #eef1f6; } .builder-section .count { margin-left: 6px; padding: 1px 7px; border-radius: 9px; font-size: 12px; color: #4b5563; background: #eef1f6; }
.builder-section > button.secondary { margin-top: 16px; padding: 9px 16px; border: 1px solid #c9d2de; border-radius: 6px; color: #24324a; background: #f4f6fa; cursor: pointer; } .builder-section > button.secondary { margin-top: 16px; padding: 9px 16px; border: 1px solid var(--secondary-border); border-radius: 6px; color: var(--text-secondary); background: var(--secondary-bg); cursor: pointer; }
.builder-section > button.secondary:hover { background: #e9edf4; } .builder-section > button.secondary:hover { background: var(--hover-bg); }
.builder-table { margin-top: 14px; } .builder-table { margin-top: 14px; }
.builder-table th { position: static; font-size: 12px; color: #5b6678; } .builder-table th { position: static; font-size: 12px; color: #5b6678; }
@@ -180,30 +247,30 @@
/* What a compound choice will add, shown before it is added. */ /* What a compound choice will add, shown before it is added. */
.compound-note { margin-top: 14px; padding: 12px 14px; border: 1px solid #dfe6f3; border-radius: 8px; background: #f7f9fe; } .compound-note { margin-top: 14px; padding: 12px 14px; border: 1px solid #dfe6f3; border-radius: 8px; background: #f7f9fe; }
.compound-note p { margin: 0; font-size: 13px; color: #33415c; } .compound-note p { margin: 0; font-size: 13px; color: var(--text); }
.compound-note ul { margin: 8px 0; padding-left: 18px; font-size: 13px; } .compound-note ul { margin: 8px 0; padding-left: 18px; font-size: 13px; }
.compound-note li { padding: 1px 0; } .compound-note li { padding: 1px 0; }
.compound-note .hint { font-size: 12px; } .compound-note .hint { font-size: 12px; }
/* Renaming what a compound row generates: closed by default, because the /* Renaming what a compound row generates: closed by default, because the
backend's names are the answer most tables keep. */ backend's names are the answer most tables keep. */
.rename-generated > summary { font-size: 15px; color: #33415c; cursor: pointer; } .rename-generated > summary { font-size: 15px; color: var(--text); cursor: pointer; }
.rename-generated > summary::marker { color: #8a94a6; } .rename-generated > summary::marker { color: #8a94a6; }
.rename-generated > .hint { margin: 8px 0 0; font-size: 13px; } .rename-generated > .hint { margin: 8px 0 0; font-size: 13px; }
.rename-list { margin: 12px 0 0; padding: 0; list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 8px; } .rename-list { margin: 12px 0 0; padding: 0; list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 8px; }
.rename-list li { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border: 1px solid #e1e6ee; border-radius: 7px; background: white; } .rename-list li { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border: 1px solid #e1e6ee; border-radius: 7px; background: var(--surface); }
.rename-list li > code { flex: none; } .rename-list li > code { flex: none; }
.rename-list li > .hint { flex: none; font-size: 12px; } .rename-list li > .hint { flex: none; font-size: 12px; }
.rename-list input { flex: 1; min-width: 90px; } .rename-list input { flex: 1; min-width: 90px; }
.builder-list { margin: 12px 0 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: 8px; } .builder-list { margin: 12px 0 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: 8px; }
.builder-list li { display: flex; align-items: center; gap: 8px; padding: 5px 10px; border: 1px solid #e1e6ee; border-radius: 7px; background: white; } .builder-list li { display: flex; align-items: center; gap: 8px; padding: 5px 10px; border: 1px solid #e1e6ee; border-radius: 7px; background: var(--surface); }
button.toggle { min-width: 34px; padding: 3px 7px; border: 1px solid #cdd5e0; border-radius: 5px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: #24324a; background: #f6f8fb; cursor: pointer; } button.toggle { min-width: 34px; padding: 3px 7px; border: 1px solid #cdd5e0; border-radius: 5px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--text-secondary); background: #f6f8fb; cursor: pointer; }
button.toggle:hover { background: #e9edf4; } button.toggle:hover { background: var(--hover-bg); }
button.toggle.mode-optional { color: #1d4ed8; border-color: #bfd0f5; background: #eef3fe; } button.toggle.mode-optional { color: #1d4ed8; border-color: #bfd0f5; background: #eef3fe; }
button.toggle.mode-required { color: #a1410a; border-color: #f0cfae; background: #fdf3e9; } button.toggle.mode-required { color: #a1410a; border-color: #f0cfae; background: #fdf3e9; }
button.danger { padding: 4px 10px; border: 1px solid #eccfcf; border-radius: 5px; font-size: 12px; color: #a12b2b; background: #fdf3f3; cursor: pointer; } button.danger { padding: 4px 10px; border: 1px solid var(--danger-border); border-radius: 5px; font-size: 12px; color: var(--danger); background: var(--danger-bg); cursor: pointer; }
button.danger:hover { background: #f8e5e5; } button.danger:hover { background: #f8e5e5; }
.tag { display: inline-block; padding: 1px 7px; border-radius: 9px; font-size: 11px; color: #4b5563; background: #eef1f6; } .tag { display: inline-block; padding: 1px 7px; border-radius: 9px; font-size: 11px; color: #4b5563; background: #eef1f6; }
@@ -213,51 +280,51 @@
/* Each operation is a .panel of its own, stacked; the deleting one is the /* Each operation is a .panel of its own, stacked; the deleting one is the
only place on the site that drops data, so it is coloured like it. */ only place on the site that drops data, so it is coloured like it. */
.panel-subhead { margin: 22px 0 0; font-size: 13px; color: #33415c; } .panel-subhead { margin: 22px 0 0; font-size: 13px; color: var(--text); }
.panel-actions { margin-top: 16px; } .panel-actions { margin-top: 16px; }
.panel .form-grid { margin-top: 14px; } .panel .form-grid { margin-top: 14px; }
.panel .form-actions { margin-top: 16px; } .panel .form-actions { margin-top: 16px; }
.panel .count { margin-left: 6px; padding: 1px 7px; border-radius: 9px; font-size: 12px; color: #4b5563; background: #eef1f6; } .panel .count { margin-left: 6px; padding: 1px 7px; border-radius: 9px; font-size: 12px; color: #4b5563; background: #eef1f6; }
.panel > button.secondary { margin-top: 16px; padding: 9px 16px; border: 1px solid #c9d2de; border-radius: 6px; color: #24324a; background: #f4f6fa; cursor: pointer; } .panel > button.secondary { margin-top: 16px; padding: 9px 16px; border: 1px solid var(--secondary-border); border-radius: 6px; color: var(--text-secondary); background: var(--secondary-bg); cursor: pointer; }
.panel > button.secondary:hover { background: #e9edf4; } .panel > button.secondary:hover { background: var(--hover-bg); }
.presentation-layout { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(320px, .9fr); align-items: start; gap: 16px; } .presentation-layout { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(320px, .9fr); align-items: start; gap: 16px; }
.presentation-panel > .hint { min-height: 40px; margin: -7px 0 15px; } .presentation-panel > .hint { min-height: 40px; margin: -7px 0 15px; }
.alias-list { overflow: hidden; border: 1px solid #e2e7ee; border-radius: 8px; background: white; } .alias-list { overflow: hidden; border: 1px solid #e2e7ee; border-radius: 8px; background: var(--surface); }
.alias-list-head, .alias-row { display: grid; grid-template-columns: minmax(90px, .8fr) 20px minmax(120px, 1.2fr) auto; align-items: center; gap: 8px; } .alias-list-head, .alias-row { display: grid; grid-template-columns: minmax(90px, .8fr) 20px minmax(120px, 1.2fr) auto; align-items: center; gap: 8px; }
.alias-list-head { padding: 8px 10px 6px; color: #7b8797; background: #f7f9fb; font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; } .alias-list-head { padding: 8px 10px 6px; color: #7b8797; background: #f7f9fb; font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.alias-row { margin: 0; padding: 8px 10px; border-top: 1px solid #e8ecf1; } .alias-row { margin: 0; padding: 8px 10px; border-top: 1px solid #e8ecf1; }
.alias-current { min-width: 0; overflow: hidden; color: #33415c; font-size: 13px; text-overflow: ellipsis; } .alias-current { min-width: 0; overflow: hidden; color: var(--text); font-size: 13px; text-overflow: ellipsis; }
.alias-arrow { color: #9aa5b4; text-align: center; } .alias-arrow { color: #9aa5b4; text-align: center; }
.alias-row > input { min-width: 0; color: #24324a; font-size: 13px; font-weight: 500; } .alias-row > input { min-width: 0; color: var(--text-secondary); font-size: 13px; font-weight: 500; }
.alias-row > input:focus { border-color: #6ea1ed; outline: 3px solid rgb(37 99 235 / 12%); } .alias-row > input:focus { border-color: #6ea1ed; outline: 3px solid rgb(37 99 235 / 12%); }
.alias-row > button { padding: 9px 14px; border: 1px solid #b9cdf0; border-radius: 6px; color: #1d4ed8; background: #eef4ff; font-weight: 600; cursor: pointer; } .alias-row > button { padding: 9px 14px; border: 1px solid #b9cdf0; border-radius: 6px; color: #1d4ed8; background: #eef4ff; font-weight: 600; cursor: pointer; }
.alias-row > button:hover { border-color: #8eb1eb; background: #deebff; } .alias-row > button:hover { border-color: #8eb1eb; background: #deebff; }
.alias-row > button:focus-visible { outline: 3px solid rgb(37 99 235 / 18%); outline-offset: 2px; } .alias-row > button:focus-visible { outline: 3px solid rgb(37 99 235 / 18%); outline-offset: 2px; }
.alias-row-locked { background: #fafbfc; } .alias-row-locked { background: var(--surface-alt); }
.alias-row-locked .locked-label { grid-column: 3 / -1; } .alias-row-locked .locked-label { grid-column: 3 / -1; }
.locked-label { color: #8a94a4; font-size: 12px; } .locked-label { color: #8a94a4; font-size: 12px; }
.column-order { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; } .column-order { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
.column-order li { display: grid; grid-template-columns: 32px 30px minmax(0, 1fr); align-items: center; gap: 8px; min-height: 52px; padding: 8px 12px 8px 8px; border: 1px solid #e2e7ee; border-radius: 8px; background: #fafbfc; transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease, opacity 120ms ease; } .column-order li { display: grid; grid-template-columns: 32px 30px minmax(0, 1fr); align-items: center; gap: 8px; min-height: 52px; padding: 8px 12px 8px 8px; border: 1px solid #e2e7ee; border-radius: 8px; background: var(--surface-alt); transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease, opacity 120ms ease; }
.column-order li:hover { border-color: #b8cae3; box-shadow: 0 3px 10px rgb(31 43 58 / 7%); } .column-order li:hover { border-color: #b8cae3; box-shadow: 0 3px 10px rgb(31 43 58 / 7%); }
.column-order li.is-dragging { z-index: 1; border-color: #7fa5df; box-shadow: 0 9px 22px rgb(31 43 58 / 18%); opacity: .82; transform: scale(1.015); } .column-order li.is-dragging { z-index: 1; border-color: #7fa5df; box-shadow: 0 9px 22px rgb(31 43 58 / 18%); opacity: .82; transform: scale(1.015); }
.drag-handle { display: grid; place-items: center; width: 32px; height: 34px; padding: 0; border: 0; border-radius: 6px; color: #8b97a8; background: transparent; font-size: 22px; line-height: 1; cursor: grab; touch-action: none; user-select: none; } .drag-handle { display: grid; place-items: center; width: 32px; height: 34px; padding: 0; border: 0; border-radius: 6px; color: #8b97a8; background: transparent; font-size: 22px; line-height: 1; cursor: grab; touch-action: none; user-select: none; }
.drag-handle:hover { color: #315f9d; background: #e9f0fb; } .drag-handle:hover { color: #315f9d; background: #e9f0fb; }
.drag-handle:focus-visible { color: #1d4ed8; outline: 3px solid rgb(37 99 235 / 18%); } .drag-handle:focus-visible { color: var(--primary-strong); outline: 3px solid rgb(37 99 235 / 18%); }
.drag-handle:active { cursor: grabbing; } .drag-handle:active { cursor: grabbing; }
.order-position { display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; color: #64748b; background: #e9eef5; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; } .order-position { display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; color: #64748b; background: #e9eef5; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }
.order-name { min-width: 0; overflow: hidden; color: #33415c; font-size: 13px; text-overflow: ellipsis; } .order-name { min-width: 0; overflow: hidden; color: var(--text); font-size: 13px; text-overflow: ellipsis; }
.order-actions { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 8px; margin-top: 14px; padding-top: 14px; border-top: 1px solid #e5e9ef; } .order-actions { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 8px; margin-top: 14px; padding-top: 14px; border-top: 1px solid #e5e9ef; }
.order-status { color: #788495; font-size: 12px; } .order-status { color: #788495; font-size: 12px; }
.order-editor.is-dirty .order-status { color: #9a5a08; font-weight: 600; } .order-editor.is-dirty .order-status { color: #9a5a08; font-weight: 600; }
.order-actions button { margin: 0; padding: 8px 13px; border: 0; border-radius: 6px; color: white; background: #2563eb; font-weight: 600; cursor: pointer; } .order-actions button { margin: 0; padding: 8px 13px; border: 0; border-radius: 6px; color: white; background: #2563eb; font-weight: 600; cursor: pointer; }
.order-actions button.secondary { border: 1px solid #c9d2de; color: #344054; background: white; } .order-actions button.secondary { border: 1px solid var(--secondary-border); color: #344054; background: var(--surface); }
.order-actions button:hover:not(:disabled) { background: #1d4ed8; } .order-actions button:hover:not(:disabled) { background: #1d4ed8; }
.order-actions button.secondary:hover:not(:disabled) { background: #eef2f6; } .order-actions button.secondary:hover:not(:disabled) { background: #eef2f6; }
.order-actions button:focus-visible { outline: 3px solid rgb(37 99 235 / 18%); outline-offset: 2px; } .order-actions button:focus-visible { outline: 3px solid rgb(37 99 235 / 18%); outline-offset: 2px; }
.order-actions button:disabled { border-color: #e1e6ed; color: #adb6c3; background: #f3f5f7; cursor: default; } .order-actions button:disabled { border-color: #e1e6ed; color: #adb6c3; background: #f3f5f7; cursor: default; }
.sql-preview { margin: 12px 0 0; padding: 12px; max-height: 260px; overflow: auto; border: 1px solid #e4e7ec; border-radius: 8px; font: 12px/1.5 ui-monospace, monospace; color: #33415c; background: #fafbfc; white-space: pre-wrap; } .sql-preview { margin: 12px 0 0; padding: 12px; max-height: 260px; overflow: auto; border: 1px solid var(--border-soft); border-radius: 8px; font: 12px/1.5 ui-monospace, monospace; color: var(--text); background: var(--surface-alt); white-space: pre-wrap; }
.danger-panel { border-color: #eccfcf; } .danger-panel { border-color: var(--danger-border); }
.danger-panel h2 { color: #a12b2b; } .danger-panel h2 { color: var(--danger); }
.form-actions button.danger-submit { background: #a12b2b; } .form-actions button.danger-submit { background: #a12b2b; }
/* ---------- Permissions (pages/permissions) ---------- */ /* ---------- Permissions (pages/permissions) ---------- */
@@ -268,11 +335,11 @@
Both split one crowded screen into a page per decision, so both need the Both split one crowded screen into a page per decision, so both need the
same thing — a way back to the siblings. */ same thing — a way back to the siblings. */
.tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; } .tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.tab { display: grid; gap: 2px; padding: 10px 16px; border: 1px solid #d9dfe7; border-radius: 9px; color: #33415c; background: white; text-decoration: none; } .tab { display: grid; gap: 2px; padding: 10px 16px; border: 1px solid var(--border); border-radius: 9px; color: var(--text); background: var(--surface); text-decoration: none; }
.tab small { color: #7c8796; font-size: 11px; } .tab small { color: var(--text-muted); font-size: 11px; }
.tab:hover { background: #f2f6fc; } .tab:hover { background: #f2f6fc; }
.tab.selected { border-color: #a9c7f6; background: #eaf2ff; } .tab.selected { border-color: var(--select-border); background: #eaf2ff; }
.tab.selected span { font-weight: 700; color: #1d4ed8; } .tab.selected span { font-weight: 700; color: var(--primary-strong); }
/* ---------- Exchange rates (pages/admin/ecb) ---------- */ /* ---------- Exchange rates (pages/admin/ecb) ---------- */
@@ -280,17 +347,17 @@
amount be posted right now", so it is the one thing on the page that has amount be posted right now", so it is the one thing on the page that has
to be readable without reading. */ to be readable without reading. */
.status-card { border-left-width: 4px; } .status-card { border-left-width: 4px; }
.status-card.healthy { border-left-color: #21643a; } .status-card.healthy { border-left-color: var(--success); }
.status-card.unhealthy { border-left-color: #a12b2b; } .status-card.unhealthy { border-left-color: var(--danger); }
.status-card.unhealthy h2 { color: #a12b2b; } .status-card.unhealthy h2 { color: var(--danger); }
/* The postable limits. This is what the reader came for -- coverage dates /* The postable limits. This is what the reader came for -- coverage dates
are the mechanism behind it -- so it sits directly under the headline and are the mechanism behind it -- so it sits directly under the headline and
is the only thing on the card set at reading size. */ is the only thing on the card set at reading size. */
.postable { margin-top: 16px; padding: 14px; border: 1px solid #dfe4ea; border-radius: 8px; background: white; } .postable { margin-top: 16px; padding: 14px; border: 1px solid #dfe4ea; border-radius: 8px; background: var(--surface); }
.postable-lead { margin: 0 0 10px; color: #7c8796; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; } .postable-lead { margin: 0 0 10px; color: var(--text-muted); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; }
.postable-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; margin: 0 0 10px; } .postable-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; margin: 0 0 10px; }
.postable-grid dt { color: #33415c; font-size: 13px; font-weight: 600; } .postable-grid dt { color: var(--text); font-size: 13px; font-weight: 600; }
.postable-grid dd { margin: 3px 0 0; color: #17202a; font-size: 14px; } .postable-grid dd { margin: 3px 0 0; color: var(--text-strong); font-size: 14px; }
.postable-grid dd strong { font-size: 16px; font-variant-numeric: tabular-nums; } .postable-grid dd strong { font-size: 16px; font-variant-numeric: tabular-nums; }
.postable-grid dd.hint { font-size: 12px; } .postable-grid dd.hint { font-size: 12px; }
@@ -298,16 +365,16 @@
the two dates are compared rather than looked up one at a time. */ the two dates are compared rather than looked up one at a time. */
.coverage { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin-top: 16px; padding: 12px 14px; border-radius: 8px; background: #f7f9fc; } .coverage { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin-top: 16px; padding: 12px 14px; border-radius: 8px; background: #f7f9fc; }
.coverage-point { display: flex; flex-direction: column; gap: 2px; } .coverage-point { display: flex; flex-direction: column; gap: 2px; }
.coverage-label { color: #7c8796; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; } .coverage-label { color: var(--text-muted); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; }
.coverage-date { color: #17202a; font-size: 20px; font-variant-numeric: tabular-nums; } .coverage-date { color: var(--text-strong); font-size: 20px; font-variant-numeric: tabular-nums; }
.coverage-gap { flex: 1; text-align: center; } .coverage-gap { flex: 1; text-align: center; }
.status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; margin: 16px 0 0; } .status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; margin: 16px 0 0; }
.status-grid dt { color: #7c8796; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; } .status-grid dt { color: var(--text-muted); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; }
.status-grid dd { margin: 3px 0 0; color: #17202a; font-size: 15px; font-variant-numeric: tabular-nums; } .status-grid dd { margin: 3px 0 0; color: var(--text-strong); font-size: 15px; font-variant-numeric: tabular-nums; }
.currency-list { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 14px; } .currency-list { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 14px; }
.failure-note { margin-top: 14px; padding-top: 12px; border-top: 1px solid #edf0f3; } .failure-note { margin-top: 14px; padding-top: 12px; border-top: 1px solid #edf0f3; }
.tag-ok { color: #21643a; background: #f2f9f3; border-color: #cfe0c4; } .tag-ok { color: var(--success); background: #f2f9f3; border-color: #cfe0c4; }
.tag-bad { color: #a12b2b; background: #fdf3f3; border-color: #eccfcf; } .tag-bad { color: var(--danger); background: var(--danger-bg); border-color: var(--danger-border); }
/* Two facts per cell, so the second sits under the first rather than /* Two facts per cell, so the second sits under the first rather than
stretching the column. */ stretching the column. */
.status-grid dd { font-size: 14px; line-height: 1.45; } .status-grid dd { font-size: 14px; line-height: 1.45; }
@@ -320,11 +387,11 @@
truncated so it cannot set the column width, full text in the tooltip. */ truncated so it cannot set the column width, full text in the tooltip. */
.endpoint { display: block; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .endpoint { display: block; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attempt-failed td { background: #fdf7f7; } .attempt-failed td { background: #fdf7f7; }
.attempt-error td { padding-top: 0; color: #a12b2b; font-size: 12px; background: #fdf7f7; border-top: 0; } .attempt-error td { padding-top: 0; color: var(--danger); font-size: 12px; background: #fdf7f7; border-top: 0; }
.table-scroll { overflow-x: auto; } .table-scroll { overflow-x: auto; }
.link-action { color: #2563eb; text-decoration: none; } .link-action { color: var(--primary); text-decoration: none; }
.notice { padding: 10px 12px; border: 1px solid #cfe0c4; border-radius: 8px; color: #21643a; background: #f2f9f3; } .notice { padding: 10px 12px; border: 1px solid #cfe0c4; border-radius: 8px; color: var(--success); background: #f2f9f3; }
/* The shortcut buttons — read everything, full access, clear — sit above the /* The shortcut buttons — read everything, full access, clear — sit above the
matrix they fill in, so they are wider than the cell buttons and read as matrix they fill in, so they are wider than the cell buttons and read as
@@ -336,19 +403,19 @@
because a grant is one call to the server either way. */ because a grant is one call to the server either way. */
.grant-matrix td, .grant-matrix th { white-space: normal; } .grant-matrix td, .grant-matrix th { white-space: normal; }
.grant-matrix th { text-transform: capitalize; } .grant-matrix th { text-transform: capitalize; }
.grant-matrix .object-label { display: block; font-weight: 600; color: #24324a; } .grant-matrix .object-label { display: block; font-weight: 600; color: var(--text-secondary); }
.grant-matrix td small { display: block; margin: 2px 0; color: #7c8796; } .grant-matrix td small { display: block; margin: 2px 0; color: var(--text-muted); }
.grant-matrix td code { font-size: 11px; color: #5b6678; } .grant-matrix td code { font-size: 11px; color: #5b6678; }
.grant-cell { width: 82px; text-align: center; vertical-align: middle; } .grant-cell { width: 82px; text-align: center; vertical-align: middle; }
.grant-cell .actions { justify-content: center; gap: 4px; } .grant-cell .actions { justify-content: center; gap: 4px; }
.grant-cell form { margin: 0 auto; } .grant-cell form { margin: 0 auto; }
.grant-cell button { min-width: 34px; padding: 4px 9px; border-radius: 5px; cursor: pointer; } .grant-cell button { min-width: 34px; padding: 4px 9px; border-radius: 5px; cursor: pointer; }
button.cell-on { border: 1px solid #a9d3b4; color: #21643a; background: #eefaf1; } button.cell-on { border: 1px solid #a9d3b4; color: var(--success); background: #eefaf1; }
button.cell-on:hover { border-color: #e0b7b3; color: #a12b2b; background: #fdf3f3; } button.cell-on:hover { border-color: #e0b7b3; color: var(--danger); background: var(--danger-bg); }
button.cell-off { border: 1px dashed #cdd5e0; color: #8a94a4; background: white; } button.cell-off { border: 1px dashed #cdd5e0; color: #8a94a4; background: var(--surface); }
button.cell-off:hover { border-style: solid; border-color: #a9c7f6; color: #1d4ed8; background: #eef3fe; } button.cell-off:hover { border-style: solid; border-color: var(--select-border); color: #1d4ed8; background: #eef3fe; }
.cell-closed { color: #c3cad4; } .cell-closed { color: #c3cad4; }
.cell-inherited { color: #1d4ed8; } .cell-inherited { color: var(--primary-strong); }
/* ---------- A running import (import/progress.html) ---------- */ /* ---------- A running import (import/progress.html) ---------- */
@@ -357,17 +424,17 @@
number is tabular: the figures update every second, and digits that change number is tabular: the figures update every second, and digits that change
width make the whole line jitter. */ width make the whole line jitter. */
.import-progress { margin: 14px 0; padding: 14px 16px; border: 1px solid #cfdcef; border-radius: 9px; background: #f7faff; } .import-progress { margin: 14px 0; padding: 14px 16px; border: 1px solid #cfdcef; border-radius: 9px; background: #f7faff; }
.import-progress h3 { margin: 0 0 10px; color: #24324a; font-size: 14px; } .import-progress h3 { margin: 0 0 10px; color: var(--text-secondary); font-size: 14px; }
.progress-line { display: flex; align-items: center; gap: 10px; } .progress-line { display: flex; align-items: center; gap: 10px; }
.import-progress progress { flex: 1; width: 100%; height: 10px; border: 0; border-radius: 999px; background: #dde6f2; overflow: hidden; } .import-progress progress { flex: 1; width: 100%; height: 10px; border: 0; border-radius: 999px; background: #dde6f2; overflow: hidden; }
.import-progress progress::-webkit-progress-bar { border-radius: 999px; background: #dde6f2; } .import-progress progress::-webkit-progress-bar { border-radius: 999px; background: #dde6f2; }
.import-progress progress::-webkit-progress-value { border-radius: 999px; background: #3b6fc4; transition: width 400ms ease; } .import-progress progress::-webkit-progress-value { border-radius: 999px; background: #3b6fc4; transition: width 400ms ease; }
.import-progress progress::-moz-progress-bar { border-radius: 999px; background: #3b6fc4; } .import-progress progress::-moz-progress-bar { border-radius: 999px; background: #3b6fc4; }
.progress-percent { min-width: 42px; color: #24324a; font-size: 13px; font-variant-numeric: tabular-nums; text-align: right; } .progress-percent { min-width: 42px; color: var(--text-secondary); font-size: 13px; font-variant-numeric: tabular-nums; text-align: right; }
.progress-rows { margin: 8px 0 0; color: #33415c; font-size: 13px; font-variant-numeric: tabular-nums; } .progress-rows { margin: 8px 0 0; color: var(--text); font-size: 13px; font-variant-numeric: tabular-nums; }
.progress-stats { display: flex; flex-wrap: wrap; gap: 6px 24px; margin: 10px 0 0; } .progress-stats { display: flex; flex-wrap: wrap; gap: 6px 24px; margin: 10px 0 0; }
.progress-stats dt { color: #6d798a; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; } .progress-stats dt { color: #6d798a; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
.progress-stats dd { margin: 2px 0 0; color: #24324a; font-size: 14px; font-variant-numeric: tabular-nums; } .progress-stats dd { margin: 2px 0 0; color: var(--text-secondary); font-size: 14px; font-variant-numeric: tabular-nums; }
.import-progress .hint { margin: 10px 0 0; font-size: 12px; } .import-progress .hint { margin: 10px 0 0; font-size: 12px; }
/* ---------- Import preparation (pages/import_export/import) ---------- */ /* ---------- Import preparation (pages/import_export/import) ---------- */
@@ -376,30 +443,30 @@
destinations. A select in every row is the keyboard/touch equivalent of destinations. A select in every row is the keyboard/touch equivalent of
dragging a source chip onto it. */ dragging a source chip onto it. */
.mapping-summary { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; margin: 14px 0; padding: 10px 12px; border-radius: 8px; background: #f5f7fa; } .mapping-summary { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; margin: 14px 0; padding: 10px 12px; border-radius: 8px; background: #f5f7fa; }
.mapping-summary strong { color: #24324a; font-variant-numeric: tabular-nums; } .mapping-summary strong { color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.mapping-workbench h3 { margin: 0; color: #24324a; font-size: 14px; } .mapping-workbench h3 { margin: 0; color: var(--text-secondary); font-size: 14px; }
.mapping-workbench h3 + .hint { margin: 4px 0 12px; font-size: 12px; } .mapping-workbench h3 + .hint { margin: 4px 0 12px; font-size: 12px; }
/* The batch actions stay reachable while a long destination list scrolls. */ /* The batch actions stay reachable while a long destination list scrolls. */
.mapping-toolbar { position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-bottom: 12px; padding: 8px; border: 1px solid #d8e1ec; border-radius: 8px; background: rgb(255 255 255 / 96%); box-shadow: 0 3px 12px rgb(31 43 58 / 7%); } .mapping-toolbar { position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-bottom: 12px; padding: 8px; border: 1px solid #d8e1ec; border-radius: 8px; background: rgb(255 255 255 / 96%); box-shadow: 0 3px 12px rgb(31 43 58 / 7%); }
.mapping-toolbar button.secondary { margin: 0; padding: 6px 10px; border: 1px solid #c9d2de; border-radius: 6px; color: #24324a; background: #f4f6fa; font-size: 12px; cursor: pointer; } .mapping-toolbar button.secondary { margin: 0; padding: 6px 10px; border: 1px solid var(--secondary-border); border-radius: 6px; color: var(--text-secondary); background: var(--secondary-bg); font-size: 12px; cursor: pointer; }
.mapping-toolbar button.secondary:hover { background: #e9edf4; } .mapping-toolbar button.secondary:hover { background: var(--hover-bg); }
.mapping-panels { display: grid; grid-template-columns: minmax(240px, .8fr) minmax(460px, 1.5fr); align-items: start; gap: 14px; } .mapping-panels { display: grid; grid-template-columns: minmax(240px, .8fr) minmax(460px, 1.5fr); align-items: start; gap: 14px; }
.source-palette, .destination-map { padding: 14px; border: 1px solid #dfe5ed; border-radius: 9px; background: #fafbfc; } .source-palette, .destination-map { padding: 14px; border: 1px solid #dfe5ed; border-radius: 9px; background: var(--surface-alt); }
/* The palette follows the destination list down, so a column near the bottom /* The palette follows the destination list down, so a column near the bottom
of a wide table still has something to be dropped from. */ of a wide table still has something to be dropped from. */
.source-palette { position: sticky; top: 56px; max-height: calc(100vh - 76px); overflow: auto; } .source-palette { position: sticky; top: 56px; max-height: calc(100vh - 76px); overflow: auto; }
.palette-heading { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 6px; } .palette-heading { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 6px; }
.palette-actions { display: flex; align-items: center; gap: 6px; } .palette-actions { display: flex; align-items: center; gap: 6px; }
.palette-actions button.secondary { margin: 0; padding: 4px 8px; border: 1px solid #cfd8e5; border-radius: 6px; color: #46566d; background: white; font-size: 11px; cursor: pointer; } .palette-actions button.secondary { margin: 0; padding: 4px 8px; border: 1px solid #cfd8e5; border-radius: 6px; color: #46566d; background: var(--surface); font-size: 11px; cursor: pointer; }
.selection-count { min-width: 42px; color: #6d798a; font-size: 11px; font-variant-numeric: tabular-nums; text-align: right; } .selection-count { min-width: 42px; color: #6d798a; font-size: 11px; font-variant-numeric: tabular-nums; text-align: right; }
/* Every track is a fixed width and there is only ever one row, so selecting a /* Every track is a fixed width and there is only ever one row, so selecting a
chip or mapping it changes colour and nothing else — no part of the list chip or mapping it changes colour and nothing else — no part of the list
moves under the pointer that just clicked it. */ moves under the pointer that just clicked it. */
.source-chips { display: grid; gap: 7px; } .source-chips { display: grid; gap: 7px; }
.source-chip { display: grid; grid-template-columns: 19px minmax(0, 1fr) 108px; align-items: center; gap: 8px; width: 100%; padding: 9px 10px; border: 1px solid #cfd9e6; border-radius: 7px; color: #33415c; background: white; text-align: left; cursor: grab; transition: border-color 120ms ease, box-shadow 120ms ease, opacity 120ms ease; } .source-chip { display: grid; grid-template-columns: 19px minmax(0, 1fr) 108px; align-items: center; gap: 8px; width: 100%; padding: 9px 10px; border: 1px solid #cfd9e6; border-radius: 7px; color: var(--text); background: var(--surface); text-align: left; cursor: grab; transition: border-color 120ms ease, box-shadow 120ms ease, opacity 120ms ease; }
.source-chip:hover { border-color: #78a1df; box-shadow: 0 3px 10px rgb(31 43 58 / 9%); } .source-chip:hover { border-color: #78a1df; box-shadow: 0 3px 10px rgb(31 43 58 / 9%); }
.source-chip.is-mapped { border-color: #a8cfb3; background: #f4fbf6; } .source-chip.is-mapped { border-color: #a8cfb3; background: #f4fbf6; }
.source-chip.is-selected { border-color: #78a1df; background: #f3f7ff; outline: 3px solid rgb(37 99 235 / 12%); } .source-chip.is-selected { border-color: #78a1df; background: #f3f7ff; outline: 3px solid rgb(37 99 235 / 12%); }
@@ -418,20 +485,20 @@
.source-chip.is-mapped small, .chip-target:empty { display: none; } .source-chip.is-mapped small, .chip-target:empty { display: none; }
.destination-rows { display: grid; gap: 7px; margin: 0; padding: 0; list-style: none; } .destination-rows { display: grid; gap: 7px; margin: 0; padding: 0; list-style: none; }
.destination-rows li { display: grid; grid-template-columns: minmax(100px, .7fr) 22px minmax(210px, 1.3fr) minmax(70px, .65fr) auto; align-items: center; gap: 8px; min-height: 62px; padding: 8px 9px; border: 1px dashed #cdd6e2; border-radius: 7px; background: white; transition: border-color 120ms ease, background 120ms ease, box-shadow 120ms ease; } .destination-rows li { display: grid; grid-template-columns: minmax(100px, .7fr) 22px minmax(210px, 1.3fr) minmax(70px, .65fr) auto; align-items: center; gap: 8px; min-height: 62px; padding: 8px 9px; border: 1px dashed #cdd6e2; border-radius: 7px; background: var(--surface); transition: border-color 120ms ease, background 120ms ease, box-shadow 120ms ease; }
.destination-rows li.is-mapped { border-style: solid; border-color: #b9cce8; } .destination-rows li.is-mapped { border-style: solid; border-color: #b9cce8; }
/* With a selection live, every row is somewhere the batch can be dropped. */ /* With a selection live, every row is somewhere the batch can be dropped. */
.mapping-workbench.has-selection .destination-rows li { border-color: #9bbcf0; cursor: copy; } .mapping-workbench.has-selection .destination-rows li { border-color: #9bbcf0; cursor: copy; }
.destination-rows li.drop-ready { border-style: solid; border-color: #2563eb; background: #edf4ff; box-shadow: 0 0 0 3px rgb(37 99 235 / 12%); } .destination-rows li.drop-ready { border-style: solid; border-color: var(--primary); background: #edf4ff; box-shadow: 0 0 0 3px rgb(37 99 235 / 12%); }
.destination-rows li.just-mapped { background: #eef8f1; } .destination-rows li.just-mapped { background: #eef8f1; }
.destination-name { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; min-width: 0; } .destination-name { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; min-width: 0; }
.destination-name code { overflow: hidden; color: #24324a; text-overflow: ellipsis; } .destination-name code { overflow: hidden; color: var(--text-secondary); text-overflow: ellipsis; }
.mapping-arrow { color: #9aa5b4; text-align: center; } .mapping-arrow { color: #9aa5b4; text-align: center; }
.destination-rows label { display: grid; gap: 3px; color: #778396; font-size: 10px; letter-spacing: .04em; text-transform: uppercase; cursor: auto; } .destination-rows label { display: grid; gap: 3px; color: #778396; font-size: 10px; letter-spacing: .04em; text-transform: uppercase; cursor: auto; }
.destination-rows select { min-width: 0; color: #24324a; font-size: 13px; letter-spacing: normal; text-transform: none; } .destination-rows select { min-width: 0; color: var(--text-secondary); font-size: 13px; letter-spacing: normal; text-transform: none; }
.mapped-example { min-width: 0; overflow: hidden; color: #667385; font: 11px/1.4 ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; } .mapped-example { min-width: 0; overflow: hidden; color: var(--text-subtle); font: 11px/1.4 ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
.clear-mapping { padding: 6px 8px; border: 1px solid #d6dde7; border-radius: 6px; color: #59677a; background: white; font-size: 11px; cursor: pointer; } .clear-mapping { padding: 6px 8px; border: 1px solid #d6dde7; border-radius: 6px; color: #59677a; background: var(--surface); font-size: 11px; cursor: pointer; }
.clear-mapping:hover:not(:disabled) { color: #a12b2b; background: #fdf3f3; } .clear-mapping:hover:not(:disabled) { color: var(--danger); background: var(--danger-bg); }
.clear-mapping:disabled { color: #bdc4ce; background: #f7f8fa; cursor: default; } .clear-mapping:disabled { color: #bdc4ce; background: #f7f8fa; cursor: default; }
@media (max-width: 850px) { @media (max-width: 850px) {
@@ -445,8 +512,8 @@
/* A secondary action in a form's action row — "back a step". The row's own /* A secondary action in a form's action row — "back a step". The row's own
rule paints every button as the primary one, so this has to say otherwise rule paints every button as the primary one, so this has to say otherwise
at the same specificity. */ at the same specificity. */
.form-actions button.secondary { margin-top: 0; border: 1px solid #c9d2de; color: #24324a; background: #f4f6fa; } .form-actions button.secondary { margin-top: 0; border: 1px solid var(--secondary-border); color: var(--text-secondary); background: var(--secondary-bg); }
.form-actions button.secondary:hover { background: #e9edf4; } .form-actions button.secondary:hover { background: var(--hover-bg); }
/* ---------- Narrow screens ---------- */ /* ---------- Narrow screens ---------- */

View File

@@ -11,6 +11,13 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Komp Accounting{% endblock %} · Komp Accounting</title> <title>{% block title %}Komp Accounting{% endblock %} · Komp Accounting</title>
<script>
// Runs before any stylesheet so the `dark` class is already on <html> at
// first paint — otherwise the page would flash light before this fires.
// Dark is the default: only an explicit "light" choice in localStorage
// (set by the toggle in ui/navbar.html) turns it off.
document.documentElement.classList.toggle('dark', localStorage.getItem('theme') !== 'light');
</script>
{# {#
Cascade order, declared before anything ships CSS so that it is this line Cascade order, declared before anything ships CSS so that it is this line
that decides it. `app` is static/app.css, which is wrapped in that layer: that decides it. `app` is static/app.css, which is wrapped in that layer:

View File

@@ -35,6 +35,7 @@
<li><a href="/login" class="{% if nav.active == "login" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark dark:hover:text-primary-dark{% endif %} underline-offset-2 hover:text-primary focus:outline-hidden focus:underline" {% if nav.active == "login" %}aria-current="page"{% endif %}>{{ nav.tr("nav-login") }}</a></li> <li><a href="/login" class="{% if nav.active == "login" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark dark:hover:text-primary-dark{% endif %} underline-offset-2 hover:text-primary focus:outline-hidden focus:underline" {% if nav.active == "login" %}aria-current="page"{% endif %}>{{ nav.tr("nav-login") }}</a></li>
<li><a href="/register" class="{% if nav.active == "register" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark dark:hover:text-primary-dark{% endif %} underline-offset-2 hover:text-primary focus:outline-hidden focus:underline" {% if nav.active == "register" %}aria-current="page"{% endif %}>{{ nav.tr("nav-register") }}</a></li> <li><a href="/register" class="{% if nav.active == "register" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark dark:hover:text-primary-dark{% endif %} underline-offset-2 hover:text-primary focus:outline-hidden focus:underline" {% if nav.active == "register" %}aria-current="page"{% endif %}>{{ nav.tr("nav-register") }}</a></li>
{% endif %} {% endif %}
<li>{% include "ui/theme_toggle.html" %}</li>
</ul> </ul>
<!-- Mobile Menu Button --> <!-- Mobile Menu Button -->
<button x-on:click="mobileMenuIsOpen = !mobileMenuIsOpen" x-bind:aria-expanded="mobileMenuIsOpen" x-bind:class="mobileMenuIsOpen ? 'fixed top-6 right-6 z-20' : null" type="button" class="flex text-on-surface dark:text-on-surface-dark md:hidden" aria-label="{{ nav.tr("nav-mobile-menu") }}" aria-controls="mobileMenu"> <button x-on:click="mobileMenuIsOpen = !mobileMenuIsOpen" x-bind:aria-expanded="mobileMenuIsOpen" x-bind:class="mobileMenuIsOpen ? 'fixed top-6 right-6 z-20' : null" type="button" class="flex text-on-surface dark:text-on-surface-dark md:hidden" aria-label="{{ nav.tr("nav-mobile-menu") }}" aria-controls="mobileMenu">
@@ -60,5 +61,6 @@
<li class="py-4"><a href="/login" class="w-full text-lg {% if nav.active == "login" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark{% endif %} focus:underline" {% if nav.active == "login" %}aria-current="page"{% endif %}>{{ nav.tr("nav-login") }}</a></li> <li class="py-4"><a href="/login" class="w-full text-lg {% if nav.active == "login" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark{% endif %} focus:underline" {% if nav.active == "login" %}aria-current="page"{% endif %}>{{ nav.tr("nav-login") }}</a></li>
<li class="py-4"><a href="/register" class="w-full text-lg {% if nav.active == "register" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark{% endif %} focus:underline" {% if nav.active == "register" %}aria-current="page"{% endif %}>{{ nav.tr("nav-register") }}</a></li> <li class="py-4"><a href="/register" class="w-full text-lg {% if nav.active == "register" %}font-bold text-primary dark:text-primary-dark{% else %}font-medium text-on-surface dark:text-on-surface-dark{% endif %} focus:underline" {% if nav.active == "register" %}aria-current="page"{% endif %}>{{ nav.tr("nav-register") }}</a></li>
{% endif %} {% endif %}
<li class="py-4">{% include "ui/theme_toggle.html" %}</li>
</ul> </ul>
</nav> </nav>

View File

@@ -0,0 +1,20 @@
{#
Dark/light toggle, included twice by ui/navbar.html (desktop menu and
mobile menu). Initial state mirrors whatever base.html's inline script
already put on <html>, so this never fights that on first render; clicking
it flips both the class and the localStorage flag that script reads.
#}
<button
type="button"
x-data="{ dark: document.documentElement.classList.contains('dark') }"
x-on:click="dark = !dark; document.documentElement.classList.toggle('dark', dark); localStorage.setItem('theme', dark ? 'dark' : 'light')"
class="flex text-on-surface hover:text-primary focus:outline-hidden dark:text-on-surface-dark dark:hover:text-primary-dark"
aria-label="{{ nav.tr("nav-toggle-theme") }}"
>
<svg x-cloak x-show="!dark" xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
</svg>
<svg x-cloak x-show="dark" xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
</svg>
</button>