dark light mode generalized

This commit is contained in:
Filipriec
2026-08-21 10:03:36 +02:00
parent 4e13535729
commit f04bbd78c2
2 changed files with 214 additions and 169 deletions

View File

@@ -5,18 +5,18 @@
the @layer line in ui/base.html: above Tailwind's preflight, below Tailwind's the @layer line in ui/base.html: above Tailwind's preflight, below Tailwind's
utilities. So this file is still the site's look, and a Tailwind class on one utilities. So this file is still the site's look, and a Tailwind class on one
element still overrides it. Do not unwrap it — unlayered CSS beats every element still overrides it. Do not unwrap it — unlayered CSS beats every
layer, which would put this file above the utilities again. */ layer, which would put this file above the utilities again.
Every color in this file must be a var(--token); add a new token pair to
:root/:root.dark rather than writing a literal hex. */
@layer app { @layer app {
/* ---------- Theme tokens ---------- /* ---------- Theme tokens ----------
Only the colours reused across several rules are variables here; the The inline script in ui/base.html puts `dark` on <html> before first paint
rest of the file is still one-off hex per rule (see the file header for unless localStorage says "light", so :root is the light palette and
why). Dark is the default — the inline script in ui/base.html puts :root.dark is the dark palette. Penguin UI aliases these same properties
`dark` on <html> before first paint unless localStorage says "light" — in ui/base.html's @theme block. */
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 { :root {
--bg: #f3f5f7; --bg: #f3f5f7;
@@ -43,6 +43,34 @@
--input-bg: #ffffff; --input-bg: #ffffff;
--input-border: #cbd3dd; --input-border: #cbd3dd;
--input-text: #1e2938; --input-text: #1e2938;
--on-primary: #ffffff;
--secondary: #152238;
--on-secondary: #ffffff;
--outline-strong: #8f9bad;
--info: #1d4e89;
--on-info: #ffffff;
--warning: #8a5a08;
--on-warning: #ffffff;
--on-danger: #ffffff;
--on-success: #ffffff;
--success-strong: #067647;
--error: #b42318;
--error-border: #f1c4c0;
--info-bg: #eaf2ff;
--info-border: #cfdcef;
--info-hover-bg: #d8e8ff;
--danger-hover-bg: #fbe6e6;
--success-bg: #f2f9f3;
--success-border: #cfe0c4;
--warning-bg: #fdf3e9;
--warning-border: #f0cfae;
--code: #315078;
--link: #315f9d;
--disabled-bg: #f7f8fa;
--disabled-text: #adb6c3;
--shadow-color: 31 43 58;
--focus-color: 37 99 235;
--toolbar-bg: rgb(255 255 255 / 96%);
} }
/* Dark is the default — see the inline script in ui/base.html, which puts /* Dark is the default — see the inline script in ui/base.html, which puts
@@ -74,6 +102,34 @@
--input-bg: #101a29; --input-bg: #101a29;
--input-border: #33415a; --input-border: #33415a;
--input-text: #e5ebf3; --input-text: #e5ebf3;
--on-primary: #0f1722;
--secondary: #c7d3e6;
--on-secondary: #152238;
--outline-strong: #cbd7e8;
--info: #8fbfff;
--on-info: #0f1722;
--warning: #e4b45d;
--on-warning: #17202a;
--on-danger: #241113;
--on-success: #10251a;
--success-strong: #8fe0aa;
--error: #f08b82;
--error-border: #6f3a3c;
--info-bg: #1d3553;
--info-border: #3f5f8e;
--info-hover-bg: #2b456b;
--danger-hover-bg: #512b31;
--success-bg: #203a2b;
--success-border: #456848;
--warning-bg: #4a3420;
--warning-border: #765634;
--code: #9abdf0;
--link: #8fbfff;
--disabled-bg: #202c3e;
--disabled-text: #64748b;
--shadow-color: 0 0 0;
--focus-color: 143 191 255;
--toolbar-bg: rgb(21 34 56 / 96%);
} }
/* ---------- Foundations ---------- */ /* ---------- Foundations ---------- */
@@ -101,51 +157,51 @@
.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: var(--text-subtle); }
.eyebrow { color: var(--primary) !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: var(--surface); text-decoration: none; } .actions a, .actions span { padding: 8px 12px; border: 1px solid var(--border); border-radius: 7px; color: var(--text-secondary); 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 var(--border); border-radius: 11px; overflow: hidden; background: var(--surface); 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(var(--shadow-color) / 7%); }
.pane + .pane { border-left: 1px solid #e1e5eb; } .pane + .pane { border-left: 1px solid var(--border-soft); }
.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 { height: 54px; padding: 0 17px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--border-soft); background: var(--surface-alt); }
.pane-title h2 { margin: 0; font-size: 14px; } .pane-title h2 { margin: 0; font-size: 14px; }
.pane-title span { color: var(--text-muted); 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: var(--text-secondary); background: transparent; cursor: pointer; }
.browser-item:hover { background: #f2f6fc; } .browser-item:hover { background: var(--hover-bg); }
.browser-item.selected { border-color: var(--select-border); background: #eaf2ff; } .browser-item.selected { border-color: var(--select-border); background: var(--info-bg); }
.browser-item small, .column small { color: #788495; } .browser-item small, .column small { color: var(--text-muted); }
.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 var(--border-soft); }
.column code { color: #315078; font-size: 12px; } .column code { color: var(--code); font-size: 12px; }
.column small { grid-column: 1 / -1; } .column small { grid-column: 1 / -1; }
.column.system { color: #6b7686; background: var(--surface-alt); } .column.system { color: var(--text-muted); background: var(--surface-alt); }
.column.system code { color: #67788e; } .column.system code { color: var(--text-muted); }
.group-label { margin: 14px 0 4px; padding: 0 11px; color: var(--text-muted); 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 var(--select-border); 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: var(--primary-strong); background: var(--info-bg); font-size: 13px; font-weight: 600; text-decoration: none; }
.browser-actions a:hover { background: #d8e8ff; } .browser-actions a:hover { background: var(--info-hover-bg); }
.browser-actions a.danger-action { border-color: var(--danger-border); color: var(--danger); background: var(--danger-bg); } .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: var(--danger-hover-bg); }
.pane-footer { margin: 0 10px 10px; border-top: 1px solid #e5e8ed; padding-top: 12px; } .pane-footer { margin: 0 10px 10px; border-top: 1px solid var(--border-soft); padding-top: 12px; }
.pane-footer a { border-color: var(--border); color: var(--text); background: var(--surface); 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: var(--hover-bg); }
.empty { margin: 8px; color: var(--text-muted); } .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: var(--link); text-decoration: none; }
.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 { padding: 25px; border: 1px solid var(--border); border-radius: 11px; background: var(--surface); box-shadow: 0 10px 26px rgb(var(--shadow-color) / 7%); }
.form-card h1 { margin: 0; } .form-card h1 { margin: 0; }
.form-card > p { color: #687384; } .form-card > p { color: var(--text-subtle); }
.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: var(--label); 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; }
@@ -156,8 +212,8 @@
.form-grid .field-note { display: grid; align-content: start; gap: 6px; color: var(--label); 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: var(--text-secondary); }
.form-actions button { padding: 9px 16px; border: 0; border-radius: 6px; color: white; background: #2563eb; cursor: pointer; } .form-actions button { padding: 9px 16px; border: 0; border-radius: 6px; color: var(--on-primary); background: var(--primary); cursor: pointer; }
.check-group { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 12px 20px; } .check-group { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 12px 20px; }
.check { display: flex !important; grid-template-columns: auto 1fr; align-items: center; gap: 6px; } .check { display: flex !important; grid-template-columns: auto 1fr; align-items: center; gap: 6px; }
.check input { width: auto; } .check input { width: auto; }
@@ -167,27 +223,27 @@
/* The alert cards themselves are Penguin UI now — see ui/alert.html. What is /* The alert cards themselves are Penguin UI now — see ui/alert.html. What is
left here is the plain coloured text that ui/notice.html and the error page left here is the plain coloured text that ui/notice.html and the error page
use. */ use. */
.error { color: #b42318; } .error { color: var(--error); }
.success { color: #067647; } .success { color: var(--success-strong); }
.hint, .result-meta { color: var(--text-subtle); } .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: var(--surface); } .error-page { width: min(600px, calc(100% - 32px)); margin: 15vh auto; padding: 24px; border: 1px solid var(--error-border); 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: var(--danger); }
/* ---------- 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 var(--border); border-radius: 11px; background: var(--surface); 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(var(--shadow-color) / 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: var(--label); 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: var(--on-primary); background: var(--primary); 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: var(--text-subtle); 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: var(--surface); } .panel { margin-bottom: 16px; padding: 18px; border: 1px solid var(--border); 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; }
@@ -197,7 +253,7 @@
.catalog-table { border-top: 1px solid var(--border-soft); 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: var(--text-subtle); 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: var(--text-secondary); background: var(--secondary-bg); 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: var(--text-subtle); font-size: 11px; text-align: right; } .columns li span { color: var(--text-subtle); font-size: 11px; text-align: right; }
@@ -211,42 +267,42 @@
.query-form label { display: grid; gap: 5px; color: var(--label); 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: var(--on-primary); background: var(--primary); 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: var(--surface); } table { width: 100%; border-collapse: collapse; background: var(--surface); }
th, td { padding: 9px 11px; border: 1px solid var(--border-soft); 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: var(--surface-alt); }
/* ---------- 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 var(--border-soft); }
.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: var(--text); } .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: var(--text-muted); background: var(--secondary-bg); }
.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 { 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: var(--hover-bg); } .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: var(--text-muted); }
.builder-table td { font-size: 13px; } .builder-table td { font-size: 13px; }
.builder-table .mark { width: 34px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: #4a5568; } .builder-table .mark { width: 34px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text-muted); }
.builder-table.preview .source-system td { color: #7a8496; background: #fbfcfd; } .builder-table.preview .source-system td { color: var(--text-muted); background: var(--surface-alt); }
.builder-table.preview .source-relation td { color: #3c5a86; } .builder-table.preview .source-relation td { color: var(--code); }
/* A compound column's companions, in both tables: the server's columns, so /* A compound column's companions, in both tables: the server's columns, so
they read as a block hanging off the definition row above them. */ they read as a block hanging off the definition row above them. */
.builder-table .generated-row td, .builder-table.preview .source-generated td { color: #5b6678; background: #fbfcfd; } .builder-table .generated-row td, .builder-table.preview .source-generated td { color: var(--text-muted); background: var(--surface-alt); }
.builder-table .order { width: 78px; white-space: nowrap; } .builder-table .order { width: 78px; white-space: nowrap; }
.builder-table .order .hint { padding-left: 12px; } .builder-table .order .hint { padding-left: 12px; }
button.toggle.move { min-width: 26px; padding: 3px 5px; } button.toggle.move { min-width: 26px; padding: 3px 5px; }
button.toggle.move + button.toggle.move { margin-left: 4px; } button.toggle.move + button.toggle.move { margin-left: 4px; }
button.toggle:disabled { color: #b3bcc9; background: #f7f8fa; cursor: default; } button.toggle:disabled { color: var(--disabled-text); background: var(--disabled-bg); cursor: default; }
/* 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 var(--info-border); border-radius: 8px; background: var(--info-bg); }
.compound-note p { margin: 0; font-size: 13px; color: var(--text); } .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; }
@@ -255,25 +311,25 @@
/* 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: var(--text); cursor: pointer; } .rename-generated > summary { font-size: 15px; color: var(--text); cursor: pointer; }
.rename-generated > summary::marker { color: #8a94a6; } .rename-generated > summary::marker { color: var(--text-muted); }
.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: var(--surface); } .rename-list li { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border: 1px solid var(--border); 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: var(--surface); } .builder-list li { display: flex; align-items: center; gap: 8px; padding: 5px 10px; border: 1px solid var(--border); 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: var(--text-secondary); background: #f6f8fb; cursor: pointer; } button.toggle { min-width: 34px; padding: 3px 7px; border: 1px solid var(--border); border-radius: 5px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--text-secondary); background: var(--secondary-bg); cursor: pointer; }
button.toggle:hover { background: var(--hover-bg); } button.toggle:hover { background: var(--hover-bg); }
button.toggle.mode-optional { color: #1d4ed8; border-color: #bfd0f5; background: #eef3fe; } button.toggle.mode-optional { color: var(--primary-strong); border-color: var(--select-border); background: var(--info-bg); }
button.toggle.mode-required { color: #a1410a; border-color: #f0cfae; background: #fdf3e9; } button.toggle.mode-required { color: var(--warning); border-color: var(--warning-border); background: var(--warning-bg); }
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 { 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: var(--danger-hover-bg); }
.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: var(--text-muted); background: var(--secondary-bg); }
.tag + .tag { margin-left: 4px; } .tag + .tag { margin-left: 4px; }
/* ---------- Table definition workspace ---------- */ /* ---------- Table definition workspace ---------- */
@@ -284,48 +340,48 @@
.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: var(--text-muted); background: var(--secondary-bg); }
.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 { 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: var(--hover-bg); } .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: var(--surface); } .alias-list { overflow: hidden; border: 1px solid var(--border); 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: var(--text-muted); background: var(--surface-alt); 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 var(--border-soft); }
.alias-current { min-width: 0; overflow: hidden; color: var(--text); 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: var(--text-muted); text-align: center; }
.alias-row > input { min-width: 0; color: var(--text-secondary); 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: var(--select-border); outline: 3px solid rgb(var(--focus-color) / 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 var(--select-border); border-radius: 6px; color: var(--primary-strong); background: var(--info-bg); font-weight: 600; cursor: pointer; }
.alias-row > button:hover { border-color: #8eb1eb; background: #deebff; } .alias-row > button:hover { border-color: var(--select-border); background: var(--info-hover-bg); }
.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(var(--focus-color) / 18%); outline-offset: 2px; }
.alias-row-locked { background: var(--surface-alt); } .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: var(--text-muted); 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: var(--surface-alt); 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 var(--border); 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: var(--select-border); box-shadow: 0 3px 10px rgb(var(--shadow-color) / 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: var(--select-border); box-shadow: 0 9px 22px rgb(var(--shadow-color) / 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: var(--text-muted); 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: var(--link); background: var(--info-bg); }
.drag-handle:focus-visible { color: var(--primary-strong); outline: 3px solid rgb(37 99 235 / 18%); } .drag-handle:focus-visible { color: var(--primary-strong); outline: 3px solid rgb(var(--focus-color) / 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: var(--text-muted); background: var(--secondary-bg); font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }
.order-name { min-width: 0; overflow: hidden; color: var(--text); 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 var(--border-soft); }
.order-status { color: #788495; font-size: 12px; } .order-status { color: var(--text-muted); font-size: 12px; }
.order-editor.is-dirty .order-status { color: #9a5a08; font-weight: 600; } .order-editor.is-dirty .order-status { color: var(--warning); 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: var(--on-primary); background: var(--primary); font-weight: 600; cursor: pointer; }
.order-actions button.secondary { border: 1px solid var(--secondary-border); color: #344054; background: var(--surface); } .order-actions button.secondary { border: 1px solid var(--secondary-border); color: var(--text-secondary); background: var(--surface); }
.order-actions button:hover:not(:disabled) { background: #1d4ed8; } .order-actions button:hover:not(:disabled) { background: var(--primary-strong); }
.order-actions button.secondary:hover:not(:disabled) { background: #eef2f6; } .order-actions button.secondary:hover:not(:disabled) { background: var(--secondary-bg); }
.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(var(--focus-color) / 18%); outline-offset: 2px; }
.order-actions button:disabled { border-color: #e1e6ed; color: #adb6c3; background: #f3f5f7; cursor: default; } .order-actions button:disabled { border-color: var(--border); color: var(--disabled-text); background: var(--disabled-bg); cursor: default; }
.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; } .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: var(--danger-border); } .danger-panel { border-color: var(--danger-border); }
.danger-panel h2 { color: var(--danger); } .danger-panel h2 { color: var(--danger); }
.form-actions button.danger-submit { background: #a12b2b; } .form-actions button.danger-submit { color: var(--on-danger); background: var(--danger); }
/* ---------- Permissions (pages/permissions) ---------- */ /* ---------- Permissions (pages/permissions) ---------- */
@@ -337,8 +393,8 @@
.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 var(--border); border-radius: 9px; color: var(--text); background: var(--surface); 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: var(--text-muted); font-size: 11px; } .tab small { color: var(--text-muted); font-size: 11px; }
.tab:hover { background: #f2f6fc; } .tab:hover { background: var(--hover-bg); }
.tab.selected { border-color: var(--select-border); background: #eaf2ff; } .tab.selected { border-color: var(--select-border); background: var(--info-bg); }
.tab.selected span { font-weight: 700; color: var(--primary-strong); } .tab.selected span { font-weight: 700; color: var(--primary-strong); }
/* ---------- Exchange rates (pages/admin/ecb) ---------- */ /* ---------- Exchange rates (pages/admin/ecb) ---------- */
@@ -353,7 +409,7 @@
/* 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: var(--surface); } .postable { margin-top: 16px; padding: 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); }
.postable-lead { margin: 0 0 10px; color: var(--text-muted); 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: var(--text); font-size: 13px; font-weight: 600; } .postable-grid dt { color: var(--text); font-size: 13px; font-weight: 600; }
@@ -363,7 +419,7 @@
/* Coverage reads left to right as one sentence -- reached, gap, owed -- so /* Coverage reads left to right as one sentence -- reached, gap, owed -- so
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: var(--surface-alt); }
.coverage-point { display: flex; flex-direction: column; gap: 2px; } .coverage-point { display: flex; flex-direction: column; gap: 2px; }
.coverage-label { color: var(--text-muted); 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: var(--text-strong); font-size: 20px; font-variant-numeric: tabular-nums; } .coverage-date { color: var(--text-strong); font-size: 20px; font-variant-numeric: tabular-nums; }
@@ -372,8 +428,8 @@
.status-grid dt { color: var(--text-muted); 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: var(--text-strong); 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 var(--border-soft); }
.tag-ok { color: var(--success); background: #f2f9f3; border-color: #cfe0c4; } .tag-ok { color: var(--success); background: var(--success-bg); border-color: var(--success-border); }
.tag-bad { color: var(--danger); background: var(--danger-bg); border-color: var(--danger-border); } .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. */
@@ -386,12 +442,12 @@
/* The endpoint is the same long URL on nearly every row: kept for evidence, /* The endpoint is the same long URL on nearly every row: kept for evidence,
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: var(--danger-bg); }
.attempt-error td { padding-top: 0; color: var(--danger); font-size: 12px; background: #fdf7f7; border-top: 0; } .attempt-error td { padding-top: 0; color: var(--danger); font-size: 12px; background: var(--danger-bg); border-top: 0; }
.table-scroll { overflow-x: auto; } .table-scroll { overflow-x: auto; }
.link-action { color: var(--primary); text-decoration: none; } .link-action { color: var(--primary); text-decoration: none; }
.notice { padding: 10px 12px; border: 1px solid #cfe0c4; border-radius: 8px; color: var(--success); background: #f2f9f3; } .notice { padding: 10px 12px; border: 1px solid var(--success-border); border-radius: 8px; color: var(--success); background: var(--success-bg); }
/* 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
@@ -405,16 +461,16 @@
.grant-matrix th { text-transform: capitalize; } .grant-matrix th { text-transform: capitalize; }
.grant-matrix .object-label { display: block; font-weight: 600; color: var(--text-secondary); } .grant-matrix .object-label { display: block; font-weight: 600; color: var(--text-secondary); }
.grant-matrix td small { display: block; margin: 2px 0; color: var(--text-muted); } .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: var(--text-muted); }
.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: var(--success); background: #eefaf1; } button.cell-on { border: 1px solid var(--success-border); color: var(--success); background: var(--success-bg); }
button.cell-on:hover { border-color: #e0b7b3; color: var(--danger); background: var(--danger-bg); } button.cell-on:hover { border-color: var(--danger-border); color: var(--danger); background: var(--danger-bg); }
button.cell-off { border: 1px dashed #cdd5e0; color: #8a94a4; background: var(--surface); } button.cell-off { border: 1px dashed var(--border); color: var(--text-muted); background: var(--surface); }
button.cell-off:hover { border-style: solid; border-color: var(--select-border); color: #1d4ed8; background: #eef3fe; } button.cell-off:hover { border-style: solid; border-color: var(--select-border); color: var(--primary-strong); background: var(--info-bg); }
.cell-closed { color: #c3cad4; } .cell-closed { color: var(--disabled-text); }
.cell-inherited { color: var(--primary-strong); } .cell-inherited { color: var(--primary-strong); }
/* ---------- A running import (import/progress.html) ---------- */ /* ---------- A running import (import/progress.html) ---------- */
@@ -423,17 +479,17 @@
sits where the alert would and carries the same weight as one. Every sits where the alert would and carries the same weight as one. Every
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 var(--info-border); border-radius: 9px; background: var(--info-bg); }
.import-progress h3 { margin: 0 0 10px; color: var(--text-secondary); 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: var(--border-soft); overflow: hidden; }
.import-progress progress::-webkit-progress-bar { border-radius: 999px; background: #dde6f2; } .import-progress progress::-webkit-progress-bar { border-radius: 999px; background: var(--border-soft); }
.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: var(--primary); 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: var(--primary); }
.progress-percent { min-width: 42px; color: var(--text-secondary); 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: var(--text); 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: var(--text-muted); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
.progress-stats dd { margin: 2px 0 0; color: var(--text-secondary); 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; }
@@ -442,64 +498,64 @@
/* CSV columns are a movable palette; table columns stay fixed as the import /* CSV columns are a movable palette; table columns stay fixed as the import
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: var(--surface-alt); }
.mapping-summary strong { color: var(--text-secondary); font-variant-numeric: tabular-nums; } .mapping-summary strong { color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.mapping-workbench h3 { margin: 0; color: var(--text-secondary); 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 var(--border); border-radius: 8px; background: var(--toolbar-bg); box-shadow: 0 3px 12px rgb(var(--shadow-color) / 7%); }
.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 { 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: var(--hover-bg); } .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: var(--surface-alt); } .source-palette, .destination-map { padding: 14px; border: 1px solid var(--border); 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: var(--surface); font-size: 11px; cursor: pointer; } .palette-actions button.secondary { margin: 0; padding: 4px 8px; border: 1px solid var(--border); border-radius: 6px; color: var(--text-secondary); 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: var(--text-muted); 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: var(--text); background: var(--surface); 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 var(--border); 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: var(--select-border); box-shadow: 0 3px 10px rgb(var(--shadow-color) / 9%); }
.source-chip.is-mapped { border-color: #a8cfb3; background: #f4fbf6; } .source-chip.is-mapped { border-color: var(--success-border); background: var(--success-bg); }
.source-chip.is-selected { border-color: #78a1df; background: #f3f7ff; outline: 3px solid rgb(37 99 235 / 12%); } .source-chip.is-selected { border-color: var(--select-border); background: var(--info-bg); outline: 3px solid rgb(var(--focus-color) / 12%); }
.source-chip.is-dragging { opacity: .55; cursor: grabbing; } .source-chip.is-dragging { opacity: .55; cursor: grabbing; }
.source-chip code { overflow: hidden; text-overflow: ellipsis; } .source-chip code { overflow: hidden; text-overflow: ellipsis; }
/* The rank a selected column will take when the batch is placed. Its track is /* The rank a selected column will take when the batch is placed. Its track is
reserved whether or not it is showing. */ reserved whether or not it is showing. */
.chip-order { display: grid; width: 19px; height: 19px; place-items: center; border-radius: 50%; visibility: hidden; color: white; background: #2563eb; font-size: 11px; font-variant-numeric: tabular-nums; } .chip-order { display: grid; width: 19px; height: 19px; place-items: center; border-radius: 50%; visibility: hidden; color: var(--on-primary); background: var(--primary); font-size: 11px; font-variant-numeric: tabular-nums; }
.source-chip.is-selected .chip-order { visibility: visible; } .source-chip.is-selected .chip-order { visibility: visible; }
/* One slot, two things to say: the first data value while the column is still /* One slot, two things to say: the first data value while the column is still
free, and where it went once it is mapped — by then the destination row is free, and where it went once it is mapped — by then the destination row is
showing that value anyway. */ showing that value anyway. */
.source-chip small, .chip-target { grid-column: 3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .source-chip small, .chip-target { grid-column: 3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.source-chip small { color: #778396; } .source-chip small { color: var(--text-muted); }
.chip-target { color: #287443; font-size: 11px; } .chip-target { color: var(--success); font-size: 11px; }
.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: var(--surface); 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 var(--border); 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: var(--select-border); }
/* 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: var(--select-border); cursor: copy; }
.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.drop-ready { border-style: solid; border-color: var(--primary); background: var(--info-bg); box-shadow: 0 0 0 3px rgb(var(--focus-color) / 12%); }
.destination-rows li.just-mapped { background: #eef8f1; } .destination-rows li.just-mapped { background: var(--success-bg); }
.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: var(--text-secondary); 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: var(--text-muted); 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: var(--text-muted); font-size: 10px; letter-spacing: .04em; text-transform: uppercase; cursor: auto; }
.destination-rows select { min-width: 0; color: var(--text-secondary); 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: var(--text-subtle); 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: var(--surface); font-size: 11px; cursor: pointer; } .clear-mapping { padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; color: var(--text-secondary); background: var(--surface); font-size: 11px; cursor: pointer; }
.clear-mapping:hover:not(:disabled) { color: var(--danger); background: var(--danger-bg); } .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: var(--disabled-text); background: var(--disabled-bg); cursor: default; }
@media (max-width: 850px) { @media (max-width: 850px) {
.mapping-panels { grid-template-columns: 1fr; } .mapping-panels { grid-template-columns: 1fr; }
@@ -520,7 +576,7 @@
@media (max-width: 850px) { @media (max-width: 850px) {
.heading { align-items: start; flex-direction: column; } .heading { align-items: start; flex-direction: column; }
.workspace { grid-template-columns: 1fr; } .workspace { grid-template-columns: 1fr; }
.pane + .pane { border-left: 0; border-top: 1px solid #e1e5eb; } .pane + .pane { border-left: 0; border-top: 1px solid var(--border-soft); }
.analytics { grid-template-columns: 1fr; } .analytics { grid-template-columns: 1fr; }
.sidebar { position: static; max-height: none; } .sidebar { position: static; max-height: none; }
.query-row { grid-template-columns: 1fr; } .query-row { grid-template-columns: 1fr; }

View File

@@ -40,52 +40,41 @@
#} #}
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss"> <style type="text/tailwindcss">
/* /* Penguin UI's names stay as aliases; app.css owns the actual palette. */
The theme penguinui-components/style.css declares, with this app's palette
in the light slots. The names are the library's and are not renamed: the
component markup here is the library's markup, so it asks for
`text-on-surface` and `rounded-radius` and has to find them.
*/
@custom-variant dark (&:where(.dark, .dark *)); @custom-variant dark (&:where(.dark, .dark *));
@theme { @theme {
/* Light theme — this app's palette. */ /* All aliases resolve through app.css, including the dark aliases. */
--color-surface: #ffffff; --color-surface: var(--surface);
--color-surface-alt: #f3f5f7; --color-surface-alt: var(--surface-alt);
--color-on-surface: #465267; --color-on-surface: var(--label);
--color-on-surface-strong: #17202a; --color-on-surface-strong: var(--text-strong);
--color-primary: #2563eb; --color-primary: var(--primary);
--color-on-primary: #ffffff; --color-on-primary: var(--on-primary);
--color-secondary: #152238; --color-secondary: var(--secondary);
--color-on-secondary: #ffffff; --color-on-secondary: var(--on-secondary);
--color-outline: #d9dfe7; --color-outline: var(--border);
--color-outline-strong: #8f9bad; --color-outline-strong: var(--outline-strong);
/* --color-surface-dark: var(--surface);
Dark theme. Nothing sets the `.dark` class the custom variant above --color-surface-dark-alt: var(--surface-alt);
scopes these to, so they are inert — they exist because the library's --color-on-surface-dark: var(--text);
markup carries `dark:` classes and is used as it ships. Scoping the --color-on-surface-dark-strong: var(--text-strong);
variant to a class rather than leaving Tailwind's prefers-color-scheme --color-primary-dark: var(--primary);
default is what keeps them from firing on an OS set to dark. --color-on-primary-dark: var(--on-primary);
*/ --color-secondary-dark: var(--secondary);
--color-surface-dark: #0f1722; --color-on-secondary-dark: var(--on-secondary);
--color-surface-dark-alt: #152238; --color-outline-dark: var(--border);
--color-on-surface-dark: #cbd7e8; --color-outline-dark-strong: var(--outline-strong);
--color-on-surface-dark-strong: #ffffff;
--color-primary-dark: #68a4ff;
--color-on-primary-dark: #0f1722;
--color-outline-dark: #33415a;
--color-outline-dark-strong: #cbd7e8;
/* Shared colours. */ --color-info: var(--info);
--color-info: #1d4e89; --color-on-info: var(--on-info);
--color-on-info: #ffffff; --color-success: var(--success);
--color-success: #21643a; --color-on-success: var(--on-success);
--color-on-success: #ffffff; --color-warning: var(--warning);
--color-warning: #8a5a08; --color-on-warning: var(--on-warning);
--color-on-warning: #ffffff; --color-danger: var(--danger);
--color-danger: #9d342d; --color-on-danger: var(--on-danger);
--color-on-danger: #ffffff;
/* Border radius. */ /* Border radius. */
--radius-radius: 6px; --radius-radius: 6px;