mirror of
https://github.com/kbenestad/invoice.git
synced 2026-06-18 16:14:33 +00:00
356 lines
19 KiB
CSS
356 lines
19 KiB
CSS
/* ============================================================================
|
|
kBenestad — unified forms design language
|
|
Shared foundation for invoice · timesheet · reimburse
|
|
----------------------------------------------------------------------------
|
|
Customer-facing white-label apps: the CUSTOMER's identity leads (logo + org
|
|
name in the header); kBenestad is the quiet craft signature.
|
|
|
|
Configurable in each app's config.yml (sensible defaults shown):
|
|
accent-colour: "#2F6FED" → --accent (recolour to the customer brand)
|
|
font-size: 1.0 → --font-scale (screen text multiplier)
|
|
code colours (timesheet) → per-chip --chip-border / --chip-bg
|
|
----------------------------------------------------------------------------
|
|
Type: Schibsted Grotesk (text) + JetBrains Mono (figures), system fallback
|
|
so the forms render fully offline if the webfonts are unavailable.
|
|
========================================================================== */
|
|
|
|
/* ── Fonts: load if present, but the stacks below fall back to system ─────── */
|
|
@font-face {
|
|
font-family: "Schibsted Grotesk"; font-style: normal; font-weight: 400 800;
|
|
font-display: swap;
|
|
src: local("Schibsted Grotesk"),
|
|
url("https://fonts.bunny.net/schibsted-grotesk/files/schibsted-grotesk-latin-400-normal.woff2") format("woff2");
|
|
}
|
|
@font-face {
|
|
font-family: "JetBrains Mono"; font-style: normal; font-weight: 400 600;
|
|
font-display: swap;
|
|
src: local("JetBrains Mono"),
|
|
url("https://fonts.bunny.net/jetbrains-mono/files/jetbrains-mono-latin-500-normal.woff2") format("woff2");
|
|
}
|
|
|
|
/* ── Tokens ───────────────────────────────────────────────────────────────── */
|
|
:root {
|
|
--font-sans: "Schibsted Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
Roboto, system-ui, sans-serif;
|
|
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
|
|
/* screen type multiplier (config: font-size) — base unit is 16px */
|
|
--font-scale: 1;
|
|
--fs-base: calc(15px * var(--font-scale));
|
|
--fs-input: calc(15px * var(--font-scale));
|
|
--fs-label: calc(12px * var(--font-scale));
|
|
--fs-title: calc(13px * var(--font-scale));
|
|
--fs-small: calc(12.5px * var(--font-scale));
|
|
--fs-h1: calc(22px * var(--font-scale));
|
|
|
|
/* accent — single recolourable token (config: accent-colour) */
|
|
--accent: #2F6FED;
|
|
--accent-hover: #1F57CF;
|
|
--accent-soft: #EEF3FE;
|
|
--accent-border: #C7D9FB;
|
|
--on-accent: #FFFFFF;
|
|
|
|
/* surfaces & ink (light) */
|
|
--bg: #F4F6F9;
|
|
--surface: #FFFFFF;
|
|
--surface-2: #F8F9FB;
|
|
--surface-3: #F1F3F6;
|
|
--border: #E3E7EE;
|
|
--border-strong:#D3D9E2;
|
|
--text: #14181E;
|
|
--text-soft: #3A434F;
|
|
--text-muted: #5F6975;
|
|
--placeholder: #9AA3AF;
|
|
|
|
/* semantic */
|
|
--danger: #D64545; --danger-soft: #FBEAEA; --danger-border: #F0C9C9;
|
|
--warning: #C9851F; --warning-soft: #FBF1DD; --warning-border: #EED9AD;
|
|
--success: #1F9D5F; --success-soft: #E2F3EA; --success-border: #BFE3CF;
|
|
--info: #2F6FED; --info-soft: #EEF3FE; --info-border: #C7D9FB;
|
|
|
|
/* shape & depth */
|
|
--radius: 8px;
|
|
--radius-sm: 6px;
|
|
--radius-pill: 999px;
|
|
--shadow-sm: 0 1px 2px rgba(20,24,30,.05);
|
|
--shadow: 0 6px 22px rgba(20,24,30,.08);
|
|
--ring: 0 0 0 3px rgba(47,111,237,.20);
|
|
|
|
color-scheme: light;
|
|
}
|
|
|
|
/* ── Dark — auto by system, or forced via [data-theme="dark"] ─────────────── */
|
|
@media (prefers-color-scheme: dark) {
|
|
:root:not([data-theme="light"]) {
|
|
--accent: #5685E9; --accent-hover: #6C98EF; --accent-soft: #16233F; --accent-border: #21386A; --on-accent: #FFFFFF;
|
|
--bg: #0D1117; --surface: #161B22; --surface-2: #1C232C; --surface-3: #1C232C;
|
|
--border: #232A33; --border-strong: #2D3641;
|
|
--text: #EEF1F5; --text-soft: #C2CAD3; --text-muted: #8B95A1; --placeholder: #6F7986;
|
|
--danger:#E06464; --danger-soft:#341819; --danger-border:#5A2A2A;
|
|
--warning:#D99A3A; --warning-soft:#33270F; --warning-border:#574017;
|
|
--success:#3BB97A; --success-soft:#13301F; --success-border:#1F4D33;
|
|
--info:#88ABF2; --info-soft:#16233F; --info-border:#21386A;
|
|
--shadow-sm: 0 1px 2px rgba(0,0,0,.4); --shadow: 0 8px 28px rgba(0,0,0,.5);
|
|
--ring: 0 0 0 3px rgba(86,133,233,.32);
|
|
color-scheme: dark;
|
|
}
|
|
}
|
|
:root[data-theme="dark"] {
|
|
--accent: #5685E9; --accent-hover: #6C98EF; --accent-soft: #16233F; --accent-border: #21386A; --on-accent: #FFFFFF;
|
|
--bg: #0D1117; --surface: #161B22; --surface-2: #1C232C; --surface-3: #1C232C;
|
|
--border: #232A33; --border-strong: #2D3641;
|
|
--text: #EEF1F5; --text-soft: #C2CAD3; --text-muted: #8B95A1; --placeholder: #6F7986;
|
|
--danger:#E06464; --danger-soft:#341819; --danger-border:#5A2A2A;
|
|
--warning:#D99A3A; --warning-soft:#33270F; --warning-border:#574017;
|
|
--success:#3BB97A; --success-soft:#13301F; --success-border:#1F4D33;
|
|
--info:#88ABF2; --info-soft:#16233F; --info-border:#21386A;
|
|
--shadow-sm: 0 1px 2px rgba(0,0,0,.4); --shadow: 0 8px 28px rgba(0,0,0,.5);
|
|
--ring: 0 0 0 3px rgba(86,133,233,.32);
|
|
color-scheme: dark;
|
|
}
|
|
|
|
/* ── Base ─────────────────────────────────────────────────────────────────── */
|
|
* { box-sizing: border-box; }
|
|
.kb {
|
|
font-family: var(--font-sans);
|
|
font-size: var(--fs-base);
|
|
line-height: 1.55;
|
|
color: var(--text);
|
|
background: var(--bg);
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
letter-spacing: -0.006em;
|
|
}
|
|
.kb-mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
|
|
|
|
/* ── Page shell ───────────────────────────────────────────────────────────── */
|
|
.kb-wrap { max-width: 960px; margin: 0 auto; padding: 28px 20px 56px; }
|
|
|
|
/* ── Top utility bar (language / text-size / about) ───────────────────────── */
|
|
.kb-toolbar {
|
|
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
|
margin-bottom: 16px;
|
|
}
|
|
.kb-toolbar .spacer { flex: 1; }
|
|
.kb-seg {
|
|
display: inline-flex; align-items: center; gap: 2px;
|
|
background: var(--surface); border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm); padding: 3px;
|
|
}
|
|
.kb-seg button {
|
|
font: 600 var(--fs-small)/1 var(--font-sans);
|
|
color: var(--text-muted); background: transparent; border: 0;
|
|
white-space: nowrap;
|
|
padding: 6px 11px; border-radius: 4px; cursor: pointer;
|
|
}
|
|
.kb-seg button.is-active { background: var(--accent-soft); color: var(--accent); }
|
|
.kb-seg button:hover:not(.is-active) { color: var(--text); }
|
|
.kb-iconbtn {
|
|
display: inline-grid; place-items: center; width: 34px; height: 34px;
|
|
background: var(--surface); border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm); color: var(--text-muted); cursor: pointer;
|
|
}
|
|
.kb-iconbtn:hover { color: var(--accent); border-color: var(--accent-border); }
|
|
|
|
/* ── Document header: customer leads ──────────────────────────────────────── */
|
|
.kb-header {
|
|
display: flex; justify-content: space-between; align-items: flex-start;
|
|
gap: 24px; padding-bottom: 20px; margin-bottom: 22px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.kb-brand { display: flex; align-items: center; gap: 14px; min-width: 0; }
|
|
.kb-brand .logo {
|
|
height: 46px; width: 46px; flex: 0 0 46px; border-radius: 10px;
|
|
display: grid; place-items: center; background: var(--accent-soft);
|
|
color: var(--accent); font-weight: 800; font-size: 18px; overflow: hidden;
|
|
}
|
|
.kb-brand .logo img { width: 100%; height: 100%; object-fit: contain; }
|
|
.kb-brand .org { font-size: 17px; font-weight: 700; color: var(--text); letter-spacing: -0.01em; }
|
|
.kb-brand .org small { display: block; font-size: var(--fs-small); font-weight: 500; color: var(--text-muted); letter-spacing: 0; }
|
|
.kb-doctitle { text-align: right; }
|
|
.kb-doctitle h1 {
|
|
margin: 0; font-size: var(--fs-h1); font-weight: 700; letter-spacing: -0.01em;
|
|
color: var(--text);
|
|
}
|
|
.kb-doctitle .meta { margin-top: 4px; font-size: var(--fs-small); color: var(--text-muted); font-family: var(--font-mono); }
|
|
|
|
/* ── Cards / sections ─────────────────────────────────────────────────────── */
|
|
.kb-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow-sm);
|
|
padding: 20px 22px;
|
|
margin-bottom: 16px;
|
|
}
|
|
.kb-card__title {
|
|
display: flex; align-items: center; gap: 9px;
|
|
font-size: var(--fs-title); font-weight: 700; letter-spacing: -0.005em;
|
|
color: var(--text-soft);
|
|
margin: 0 0 16px;
|
|
}
|
|
.kb-card__title::before {
|
|
content: ""; width: 3px; height: 14px; border-radius: 2px; background: var(--accent);
|
|
}
|
|
.kb-card__title .count { margin-left: auto; font-weight: 500; color: var(--text-muted); font-size: var(--fs-small); }
|
|
|
|
/* ── Fields ───────────────────────────────────────────────────────────────── */
|
|
.kb-grid { display: grid; gap: 14px 16px; }
|
|
.kb-grid.cols-2 { grid-template-columns: 1fr 1fr; }
|
|
.kb-grid.cols-3 { grid-template-columns: 1fr 1fr 1fr; }
|
|
.kb-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
|
|
.kb-field.grow { flex: 1; }
|
|
.kb-label {
|
|
font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.03em;
|
|
text-transform: uppercase; color: var(--text-muted);
|
|
}
|
|
.kb-input, .kb-select, .kb-textarea {
|
|
width: 100%; font: 400 var(--fs-input)/1.4 var(--font-sans);
|
|
color: var(--text); background: var(--surface);
|
|
border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
|
|
padding: 9px 11px; outline: none; transition: border-color .14s, box-shadow .14s;
|
|
}
|
|
.kb-input::placeholder, .kb-textarea::placeholder { color: var(--placeholder); }
|
|
.kb-input:focus, .kb-select:focus, .kb-textarea:focus {
|
|
border-color: var(--accent); box-shadow: var(--ring);
|
|
}
|
|
.kb-input:disabled, .kb-select:disabled, .kb-input[readonly] {
|
|
background: var(--surface-3); color: var(--text-muted); cursor: not-allowed;
|
|
}
|
|
.kb-input.num { font-family: var(--font-mono); text-align: right; font-variant-numeric: tabular-nums; }
|
|
.kb-textarea { resize: vertical; min-height: 46px; }
|
|
.kb-select {
|
|
appearance: none;
|
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='%235F6975' stroke-width='1.6'><path d='M4 6l4 4 4-4'/></svg>");
|
|
background-repeat: no-repeat; background-position: right 10px center; padding-right: 32px;
|
|
}
|
|
.kb-input.is-error, .kb-select.is-error { border-color: var(--danger); }
|
|
.kb-input.is-warn { border-color: var(--warning); background: var(--warning-soft); }
|
|
|
|
/* ── Buttons ──────────────────────────────────────────────────────────────── */
|
|
.kb-btn {
|
|
display: inline-flex; align-items: center; justify-content: center; gap: 7px;
|
|
white-space: nowrap;
|
|
font: 600 var(--fs-input)/1 var(--font-sans);
|
|
padding: 10px 16px; border-radius: var(--radius-sm);
|
|
border: 1px solid transparent; cursor: pointer; transition: background .14s, border-color .14s, color .14s;
|
|
}
|
|
.kb-btn svg { width: 16px; height: 16px; }
|
|
.kb-btn--primary { background: var(--accent); color: var(--on-accent); }
|
|
.kb-btn--primary:hover { background: var(--accent-hover); }
|
|
.kb-btn--primary:disabled { opacity: .5; cursor: not-allowed; }
|
|
.kb-btn--ghost { background: var(--surface); color: var(--text-soft); border-color: var(--border-strong); }
|
|
.kb-btn--ghost:hover { border-color: var(--accent); color: var(--accent); }
|
|
.kb-btn--soft { background: var(--accent-soft); color: var(--accent); }
|
|
.kb-btn--soft:hover { background: var(--accent); color: var(--on-accent); }
|
|
.kb-btn--dashed { background: transparent; color: var(--accent); border: 1px dashed var(--accent-border); }
|
|
.kb-btn--dashed:hover { background: var(--accent-soft); border-color: var(--accent); }
|
|
.kb-btn--danger-ghost { background: transparent; color: var(--danger); padding: 6px 10px; }
|
|
.kb-btn--danger-ghost:hover { background: var(--danger-soft); }
|
|
.kb-btn--lg { padding: 13px 26px; font-size: calc(15px * var(--font-scale)); }
|
|
.kb-btn--block { width: 100%; }
|
|
|
|
/* round add/remove */
|
|
.kb-circbtn {
|
|
width: 24px; height: 24px; border-radius: 50%; display: inline-grid; place-items: center;
|
|
font-size: 15px; line-height: 1; font-weight: 700; cursor: pointer; padding: 0;
|
|
background: var(--surface); border: 1px solid var(--accent); color: var(--accent);
|
|
}
|
|
.kb-circbtn:hover { background: var(--accent); color: var(--on-accent); }
|
|
.kb-circbtn--rm { border-color: var(--danger); color: var(--danger); }
|
|
.kb-circbtn--rm:hover { background: var(--danger); color: #fff; }
|
|
|
|
/* ── Dividers — deliberately simple (no overlap, no doubled rules) ────────── */
|
|
.kb-divider { height: 1px; background: var(--border); border: 0; margin: 18px 0; }
|
|
.kb-divider--strong { background: var(--border-strong); }
|
|
|
|
/* ── Item / line blocks ───────────────────────────────────────────────────── */
|
|
.kb-block {
|
|
border: 1px solid var(--border); border-radius: var(--radius);
|
|
background: var(--surface-2); padding: 16px 18px; margin-bottom: 14px;
|
|
}
|
|
.kb-block__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
|
.kb-block__head .tag { font-size: var(--fs-label); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); }
|
|
.kb-subtotal { font-family: var(--font-mono); font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
|
|
|
|
/* ── Tables / row grids ───────────────────────────────────────────────────── */
|
|
.kb-rowhead, .kb-row { display: grid; align-items: center; gap: 8px; }
|
|
.kb-rowhead {
|
|
padding: 0 10px 8px; font-size: var(--fs-label); font-weight: 700;
|
|
text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted);
|
|
}
|
|
.kb-rowhead .r { text-align: right; }
|
|
.kb-row {
|
|
padding: 7px 10px; border-radius: var(--radius-sm);
|
|
border-left: 3px solid transparent;
|
|
}
|
|
.kb-row:hover { background: var(--surface-2); }
|
|
.kb-row .r { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
|
|
|
|
/* ── Code chips (timesheet) — colours come from config per code ───────────── */
|
|
.kb-chip {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
padding: 3px 10px; border-radius: var(--radius-pill);
|
|
font-size: var(--fs-small); font-weight: 600; line-height: 1.3;
|
|
white-space: nowrap;
|
|
/* per-code overrides set --chip-bg / --chip-border / --chip-text inline */
|
|
background: var(--chip-bg, var(--surface-3));
|
|
border: 1px solid var(--chip-border, var(--border-strong));
|
|
color: var(--chip-text, var(--text-soft));
|
|
}
|
|
.kb-chip .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--chip-border, var(--text-muted)); }
|
|
|
|
/* a row tinted by its code colour */
|
|
.kb-row.coded { border-left-color: var(--chip-border, transparent); }
|
|
.kb-row.coded.tint { background: color-mix(in srgb, var(--chip-bg, transparent) 45%, var(--surface)); }
|
|
|
|
/* ── Totals panel ─────────────────────────────────────────────────────────── */
|
|
.kb-totals { margin-left: auto; width: min(380px, 100%); }
|
|
.kb-totals--fill { margin-left: 0; width: 100%; }
|
|
.kb-card--flex { display: flex; flex-direction: column; }
|
|
.kb-card--flex .kb-totals { margin-top: auto; }
|
|
.kb-totals .row { display: flex; justify-content: space-between; gap: 16px; padding: 6px 0; font-size: var(--fs-base); }
|
|
.kb-totals .row .lab { color: var(--text-muted); }
|
|
.kb-totals .row .val { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--text); }
|
|
.kb-totals .grand {
|
|
margin-top: 8px; padding-top: 12px; border-top: 1px solid var(--border-strong);
|
|
display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
|
|
}
|
|
.kb-totals .grand .lab { font-weight: 700; color: var(--text); }
|
|
.kb-totals .grand .val { font-family: var(--font-mono); font-weight: 700; font-size: calc(20px * var(--font-scale)); color: var(--accent); font-variant-numeric: tabular-nums; }
|
|
|
|
/* ── Validation summary ───────────────────────────────────────────────────── */
|
|
.kb-note {
|
|
border-radius: var(--radius-sm); padding: 12px 16px; margin-bottom: 16px;
|
|
font-size: var(--fs-small); line-height: 1.7;
|
|
display: flex; gap: 10px; align-items: flex-start;
|
|
}
|
|
.kb-note svg { width: 17px; height: 17px; flex: 0 0 17px; margin-top: 1px; }
|
|
.kb-note--error { background: var(--danger-soft); border: 1px solid var(--danger-border); color: var(--danger); }
|
|
.kb-note--warning { background: var(--warning-soft); border: 1px solid var(--warning-border); color: var(--warning); }
|
|
.kb-note--success { background: var(--success-soft); border: 1px solid var(--success-border); color: var(--success); }
|
|
.kb-note--info { background: var(--info-soft); border: 1px solid var(--info-border); color: var(--info); }
|
|
.kb-note b { font-weight: 700; }
|
|
|
|
/* ── Signature ────────────────────────────────────────────────────────────── */
|
|
.kb-sig { border: 1px dashed var(--border-strong); border-radius: var(--radius-sm); background: var(--surface); height: 96px; }
|
|
|
|
/* ── Footer (software credit — stays kBenestad) ───────────────────────────── */
|
|
.kb-footer {
|
|
max-width: 960px; margin: 0 auto; padding: 18px 20px 8px;
|
|
font-size: var(--fs-small); color: var(--text-muted);
|
|
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
|
|
}
|
|
.kb-footer a { color: var(--text-muted); text-decoration: none; }
|
|
.kb-footer a:hover { color: var(--accent); text-decoration: underline; }
|
|
.kb-footer .sep { opacity: .45; }
|
|
/* kBenestad mark — two offset rounded squares, upper-right outlined + lower-left solid.
|
|
Usage: <span class="kb-mark"><svg …>…</svg>kBenestad</span> */
|
|
.kb-mark { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; color: var(--text-soft); }
|
|
.kb-mark svg { width: 18px; height: 18px; flex: 0 0 18px; overflow: visible; }
|
|
|
|
@media (max-width: 680px) {
|
|
.kb-grid.cols-2, .kb-grid.cols-3 { grid-template-columns: 1fr; }
|
|
.kb-header { flex-direction: column; gap: 14px; }
|
|
.kb-doctitle { text-align: left; }
|
|
}
|