Files
BountyBoard/web/static/css/app.css
T
etalon df08682bed fix: audit log pagination + full-width table, board card button alignment, light sidebar dither
- Audit log: backend only returns a next cursor when the page is full, so the
  "Load more" button no longer needs two clicks (and hides at the last page).
  Table now spans the full screen width and long detail JSON wraps instead of
  overflowing.
- Bounty board: pin each card's action row to the bottom so "Request
  assignment" lines up across equal-height cards.
- Light theme: restore the Y2K dither texture on the sidebar nav panel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 11:36:32 +02:00

888 lines
32 KiB
CSS

/* Bounty Board — "The Ledger".
A vintage bounty-poster / financial-ledger aesthetic on warm paper:
engraved display serif, characterful grotesk UI, tabular mono numerals,
hairline double-rules, stamped badges, paper grain. Hand-written CSS, no
framework (§2.2). Design tokens below are the exact §10 values. */
/* ---- self-hosted variable fonts (no build step, CSP: font-src 'self') ---- */
@font-face {
font-family: "Fraunces";
src: url("/static/fonts/Fraunces-var.woff2") format("woff2");
font-weight: 100 900;
font-display: swap;
}
@font-face {
font-family: "Schibsted Grotesk";
src: url("/static/fonts/SchibstedGrotesk-var.woff2") format("woff2");
font-weight: 400 900;
font-display: swap;
}
@font-face {
font-family: "Spline Sans Mono";
src: url("/static/fonts/SplineSansMono-var.woff2") format("woff2");
font-weight: 300 700;
font-display: swap;
}
/* ---- design tokens — "Y2K dither" rework (user-requested deviation from
the §10 beige): paper-white ground, brown ink everywhere, ordered-dither
halftone textures, hard offset shadows. Structure and radius unchanged. */
:root[data-theme=light] {
--bg:#ffffff; --surface:#fdfbf6; --surface2:#f2e9d8; --border:#a88452;
--text:#33230e; --muted:#7c6647; --accent:#7a4a14; --accent-contrast:#fff;
--ok:#2e6b3c; --warn:#955d0e; --err:#9c3a2e; --radius:2px;
}
:root[data-theme=dark] {
--bg:#171209; --surface:#221a0e; --surface2:#2e2414; --border:#7c5f33;
--text:#f3e8d2; --muted:#b59c74; --accent:#d9a548; --accent-contrast:#241606;
--ok:#7fb78a; --warn:#d9a44a; --err:#d97b6c; --radius:2px;
}
/* derived, theme-aware atmosphere */
:root {
--font-display: "Fraunces", Georgia, serif;
--font-body: "Schibsted Grotesk", system-ui, sans-serif;
--font-mono: "Spline Sans Mono", ui-monospace, monospace;
--hairline: 1px solid var(--border);
}
:root[data-theme=light] {
/* Y2K hard offset shadow, no blur */
--ink-shadow: 3px 3px 0 color-mix(in srgb, var(--border) 45%, transparent);
--ink-shadow-lift: 5px 5px 0 color-mix(in srgb, var(--border) 55%, transparent);
/* ordered-dither tile: sparse diagonal brown pixels on transparent */
--dither: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Crect width='1' height='1' x='0' y='0' fill='%237a4a14'/%3E%3Crect width='1' height='1' x='2' y='2' fill='%237a4a14'/%3E%3C/svg%3E");
--dither-dense: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2' height='2'%3E%3Crect width='1' height='1' x='0' y='0' fill='%237a4a14'/%3E%3C/svg%3E");
}
:root[data-theme=dark] {
--ink-shadow: 3px 3px 0 rgba(0, 0, 0, 0.55);
--ink-shadow-lift: 5px 5px 0 rgba(0, 0, 0, 0.65);
--dither: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Crect width='1' height='1' x='0' y='0' fill='%233a2c12'/%3E%3C/svg%3E");
--dither-dense: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Crect width='1' height='1' x='0' y='0' fill='%233a2c12'/%3E%3C/svg%3E");
}
/* ============================================================= */
/* Additional dramatic themes (user-requested). Each redefines the
full token set + an atmospheric override for a distinct look. */
/* ============================================================= */
/* ---- NEON — cyber, magenta/cyan gradients, glowing accents ---- */
:root[data-theme=neon] {
--bg:#080014; --surface:#160a32; --surface2:#221248; --border:#7a3bf0;
--text:#ece6ff; --muted:#a48fe0; --accent:#00e5ff; --accent-contrast:#06000f;
--ok:#39ff9e; --warn:#ffd23f; --err:#ff3b8b; --radius:8px;
--ink-shadow: 0 0 14px color-mix(in srgb, var(--accent) 55%, transparent);
--ink-shadow-lift: 0 0 26px color-mix(in srgb, var(--accent) 70%, transparent);
--dither: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Crect width='1' height='1' x='0' y='0' fill='%23221046'/%3E%3C/svg%3E");
--dither-dense: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Crect width='1' height='1' x='0' y='0' fill='%23221046'/%3E%3C/svg%3E");
}
:root[data-theme=neon] body {
background:
radial-gradient(900px 500px at 12% -8%, rgba(255,59,214,0.28), transparent 60%),
radial-gradient(900px 500px at 92% 4%, rgba(0,229,255,0.24), transparent 60%),
linear-gradient(170deg, #0c0020 0%, #080014 55%, #04000c 100%);
background-attachment: fixed;
}
:root[data-theme=neon] .card,
:root[data-theme=neon] .kanban-col {
border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent),
0 0 22px rgba(122,59,240,0.18);
}
:root[data-theme=neon] .btn.primary {
background: linear-gradient(120deg, #00e5ff, #b34dff);
border: none; color: #06000f;
box-shadow: 0 0 16px rgba(0,229,255,0.5);
}
:root[data-theme=neon] h1, :root[data-theme=neon] .brand {
text-shadow: 0 0 18px rgba(0,229,255,0.45);
}
/* ---- TERMINAL — green phosphor CRT, mono everything, scanlines ---- */
:root[data-theme=terminal] {
--bg:#000a02; --surface:#04140a; --surface2:#06200f; --border:#1f8f3f;
--text:#41ff7a; --muted:#1fb04f; --accent:#7dff9f; --accent-contrast:#021006;
--ok:#7dff9f; --warn:#d7ff3f; --err:#ff5d5d; --radius:0px;
--ink-shadow: 0 0 10px rgba(65,255,122,0.4);
--ink-shadow-lift: 0 0 18px rgba(65,255,122,0.55);
/* interlaced CRT scanlines (horizontal lines, not dots) */
--dither: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1' height='3'%3E%3Crect width='1' height='1' x='0' y='0' fill='%230a3315'/%3E%3C/svg%3E");
--dither-dense: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1' height='2'%3E%3Crect width='1' height='1' x='0' y='0' fill='%230a3315'/%3E%3C/svg%3E");
--font-display: "Spline Sans Mono", ui-monospace, monospace;
--font-body: "Spline Sans Mono", ui-monospace, monospace;
}
:root[data-theme=terminal] body {
background:
repeating-linear-gradient(0deg, rgba(0,0,0,0) 0 2px, rgba(0,40,15,0.55) 2px 3px),
radial-gradient(120% 90% at 50% 0%, #03240f 0%, #000a02 70%);
background-attachment: fixed;
text-shadow: 0 0 6px rgba(65,255,122,0.35);
}
:root[data-theme=terminal] h1 { text-transform: uppercase; letter-spacing: 0.1em; }
/* ---- BLUEPRINT — technical drawing, graph paper, cyan ink ---- */
:root[data-theme=blueprint] {
--bg:#082a4d; --surface:#0c3460; --surface2:#114576; --border:#6fb8ff;
--text:#eaf4ff; --muted:#9cc6f0; --accent:#ffd24a; --accent-contrast:#082a4d;
--ok:#7fffd4; --warn:#ffd24a; --err:#ff9a8a; --radius:0px;
--ink-shadow: 4px 4px 0 rgba(0,0,0,0.3);
--ink-shadow-lift: 6px 6px 0 rgba(0,0,0,0.4);
--dither: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Crect width='1' height='1' x='0' y='0' fill='%230e3c68'/%3E%3C/svg%3E");
--dither-dense: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Crect width='1' height='1' x='0' y='0' fill='%230e3c68'/%3E%3C/svg%3E");
}
:root[data-theme=blueprint] body {
background:
repeating-linear-gradient(0deg, transparent 0 23px, rgba(111,184,255,0.16) 23px 24px),
repeating-linear-gradient(90deg, transparent 0 23px, rgba(111,184,255,0.16) 23px 24px),
#082a4d;
background-attachment: fixed;
}
:root[data-theme=blueprint] .card,
:root[data-theme=blueprint] .kanban-col {
background: rgba(12,52,96,0.82);
border: 1px solid var(--border);
}
/* ---- SUNSET — warm vaporwave gradient, soft pastel ink ---- */
:root[data-theme=sunset] {
--bg:#2a1138; --surface:#3a1a4e; --surface2:#4d2363; --border:#ff8ec7;
--text:#fff0fb; --muted:#e0a9d6; --accent:#ffb347; --accent-contrast:#2a1138;
--ok:#7ef0c4; --warn:#ffd76a; --err:#ff6b8e; --radius:12px;
--ink-shadow: 0 8px 24px rgba(255,107,142,0.25);
--ink-shadow-lift: 0 12px 34px rgba(255,142,199,0.35);
--dither: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Crect width='1' height='1' x='0' y='0' fill='%233a1a4e'/%3E%3C/svg%3E");
--dither-dense: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Crect width='1' height='1' x='0' y='0' fill='%233a1a4e'/%3E%3C/svg%3E");
}
:root[data-theme=sunset] body {
background:
linear-gradient(180deg, #ff6b8e 0%, #b14d9c 26%, #5e2a72 55%, #2a1138 100%);
background-attachment: fixed;
}
:root[data-theme=sunset] .card,
:root[data-theme=sunset] .kanban-col {
background: rgba(58,26,78,0.7);
backdrop-filter: blur(6px);
border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
:root[data-theme=sunset] .btn.primary {
background: linear-gradient(120deg, #ffb347, #ff6b8e);
border: none; color: #2a1138;
}
/* ---- base ---- */
* { box-sizing: border-box; }
/* author display rules (flex etc.) must never defeat the hidden attribute */
[hidden] { display: none !important; }
html { font-size: 16px; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font-body);
line-height: 1.55;
letter-spacing: 0.005em;
}
/* dithered poster band fading from the masthead into the white page */
body::before {
content: "";
position: absolute; top: 0; left: 0; right: 0; height: 220px;
z-index: -1;
pointer-events: none;
background-image: var(--dither);
opacity: 0.5;
-webkit-mask-image: linear-gradient(#000, transparent);
mask-image: linear-gradient(#000, transparent);
}
::selection { background: var(--accent); color: var(--accent-contrast); }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }
h1, h2, h3 {
font-family: var(--font-display);
font-weight: 600;
line-height: 1.15;
letter-spacing: 0.005em;
margin: 0 0 16px;
}
h1 { font-size: 2.1rem; }
h1::after {
content: "";
display: block;
width: 100%; height: 8px;
margin-top: 10px;
background-image: var(--dither-dense);
border-bottom: 2px solid var(--accent);
}
h2 { font-size: 1.25rem; }
h3 { font-size: 1.02rem; }
code, pre { font-family: var(--font-mono); }
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* numerals everywhere read like a ledger */
table, .badge, input[type=number] { font-variant-numeric: tabular-nums; }
/* ---- layout ---- */
.container { max-width: 1100px; margin: 0 auto; padding: 32px 16px 64px; }
.narrow { max-width: 460px; }
/* staggered page reveal (one orchestrated moment, then calm) */
@media (prefers-reduced-motion: no-preference) {
main.container > * {
animation: rise 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
}
main.container > *:nth-child(1) { animation-delay: 0.03s; }
main.container > *:nth-child(2) { animation-delay: 0.09s; }
main.container > *:nth-child(3) { animation-delay: 0.15s; }
main.container > *:nth-child(4) { animation-delay: 0.21s; }
main.container > *:nth-child(5) { animation-delay: 0.27s; }
main.container > *:nth-child(n+6) { animation-delay: 0.33s; }
}
@keyframes rise {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: none; }
}
/* ---- top navigation: dithered masthead bar ---- */
.topnav {
display: flex; align-items: center; gap: 18px;
background-color: var(--surface2);
background-image: var(--dither);
background-blend-mode: normal;
border-bottom: 2px solid var(--border);
padding: 10px 20px;
position: relative;
}
:root[data-theme=light] .topnav { background-image: none; background-color: var(--surface2); }
.topnav::after {
content: "";
position: absolute; bottom: -10px; left: 0; right: 0; height: 8px;
background-image: var(--dither);
-webkit-mask-image: linear-gradient(#000, transparent);
mask-image: linear-gradient(#000, transparent);
pointer-events: none;
}
.topnav::before {
content: "";
position: absolute; top: 0; left: 0; right: 0; height: 3px;
background: var(--accent);
}
.topnav .brand {
font-family: var(--font-display);
font-weight: 700;
font-size: 1.18rem;
color: var(--text);
letter-spacing: 0.01em;
white-space: nowrap;
}
.topnav .brand::before {
content: "◈ ";
color: var(--accent);
}
.topnav .brand:hover { text-decoration: none; }
.topnav .links { display: flex; gap: 2px; flex: 1; flex-wrap: wrap; }
.topnav .links a {
/* brighter than --muted so menu items stay readable on dark themes */
color: color-mix(in srgb, var(--text) 72%, var(--muted));
padding: 7px 11px;
border-radius: var(--radius);
font-weight: 500;
font-size: 0.92rem;
letter-spacing: 0.02em;
border-bottom: 2px solid transparent;
}
.topnav .links a:hover {
color: var(--text);
background: var(--surface2);
text-decoration: none;
}
.topnav .links a[aria-current=page] {
color: var(--text);
border-bottom-color: var(--accent);
}
.topnav .right { display: flex; align-items: center; gap: 8px; }
/* ---- sidebar navigation (default; user can switch to top bar) ---- */
body[data-nav=side] .topnav {
justify-content: flex-end; /* only the right controls remain in the flow */
padding-left: 232px;
}
body[data-nav=side] .topnav .brand {
position: fixed; top: 14px; left: 18px; z-index: 41;
font-size: 1.05rem;
}
body[data-nav=side] .topnav .links {
position: fixed; top: 0; left: 0; bottom: 0;
width: 214px;
flex: none; flex-direction: column; flex-wrap: nowrap; align-items: stretch;
gap: 4px;
background-color: var(--surface2);
background-image: var(--dither);
border-right: 2px solid var(--border);
padding: 58px 12px 18px;
overflow-y: auto;
z-index: 40;
}
body[data-nav=side] .topnav .links a {
border-bottom: none;
border-left: 3px solid transparent;
border-radius: 0;
padding: 9px 12px;
}
body[data-nav=side] .topnav .links a[aria-current=page] {
border-left-color: var(--accent);
background: var(--surface);
}
body[data-nav=side] main.container { margin-left: 214px; }
@media (max-width: 760px) {
/* collapse the sidebar back into a normal top bar on small screens */
body[data-nav=side] .topnav { padding-left: 20px; justify-content: flex-start; flex-wrap: wrap; }
body[data-nav=side] .topnav .brand { position: static; }
body[data-nav=side] .topnav .links {
position: static; width: auto; flex: 1; flex-direction: row; flex-wrap: wrap;
background: none; border-right: none; padding: 0; overflow: visible;
}
body[data-nav=side] .topnav .links a { border-left: none; border-bottom: 2px solid transparent; }
body[data-nav=side] .topnav .links a[aria-current=page] { border-bottom-color: var(--accent); background: none; }
body[data-nav=side] main.container { margin-left: auto; }
}
/* ---- cards: printed stock with hard Y2K shadows ---- */
.card {
background: var(--surface);
border: var(--hairline);
border-radius: var(--radius);
box-shadow: var(--ink-shadow);
padding: 24px;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.card + .card { margin-top: 16px; }
/* the stacking margin must never leak into grid/flex layouts — it shifted
every card except the first one ("always the first item" bug) */
.grid > .card, .kanban .card, .row > .card { margin-top: 0; }
.grid > .card { position: relative; overflow: hidden; }
.grid > .card::before {
content: "";
position: absolute; top: 0; left: 0; right: 0; height: 6px;
background-image: var(--dither-dense);
border-bottom: 1px solid var(--accent);
opacity: 0.8;
}
.grid > .card:hover {
transform: translate(-2px, -2px);
box-shadow: var(--ink-shadow-lift);
}
/* ---- buttons: Y2K hard-shadow chips ---- */
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
font-family: var(--font-body);
font-size: 0.86rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
cursor: pointer;
white-space: nowrap;
margin: 0; /* label-as-button must not inherit label margins */
padding: 8px 16px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface2);
color: var(--text);
box-shadow: 2px 2px 0 color-mix(in srgb, var(--border) 60%, transparent);
transition: transform 0.07s ease, box-shadow 0.07s ease, filter 0.12s ease;
}
.btn:hover { filter: brightness(0.97); text-decoration: none; }
.btn:active { transform: translate(2px, 2px); box-shadow: none; }
.btn.primary {
background: var(--accent);
color: var(--accent-contrast);
border-color: color-mix(in srgb, var(--accent) 65%, black);
}
.btn.danger { background: var(--err); color: #fff; border-color: color-mix(in srgb, var(--err) 65%, black); }
.btn.ghost { background: transparent; border-color: transparent; box-shadow: none; }
.btn.ghost:hover { border-color: var(--border); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
.btn.small { padding: 4px 10px; font-size: 0.74rem; box-shadow: 1px 1px 0 color-mix(in srgb, var(--border) 60%, transparent); }
/* ---- forms ---- */
label { display: block; font-weight: 700; font-size: 0.88rem; letter-spacing: 0.02em; margin-bottom: 5px; }
.hint { color: var(--muted); font-size: 0.86rem; margin: 5px 0 0; }
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=search], input[type=date], input[type=file], select, textarea {
width: 100%;
font: inherit; color: var(--text);
background: var(--bg);
border: var(--hairline);
border-radius: var(--radius);
padding: 9px 10px;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
input:focus, select:focus, textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
outline: none;
}
textarea { resize: vertical; min-height: 84px; }
.field { margin-bottom: 18px; }
.row { display: flex; gap: 16px; }
.row > * { flex: 1; }
.error-box, .ok-box {
border: 1px solid var(--err);
border-left-width: 4px;
color: var(--err);
background: color-mix(in srgb, var(--err) 7%, var(--surface));
border-radius: var(--radius);
padding: 10px 14px;
margin-bottom: 16px;
font-weight: 500;
}
.ok-box {
border-color: var(--ok); color: var(--ok);
background: color-mix(in srgb, var(--ok) 8%, var(--surface));
}
.error-box:empty, .ok-box:empty { display: none; }
/* ---- badges: rubber stamps & banknote chips ---- */
.badge {
display: inline-block;
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 500;
letter-spacing: 0.09em;
text-transform: uppercase;
padding: 2px 8px;
border-radius: var(--radius);
background: transparent;
color: var(--muted);
border: 1px solid var(--border);
}
.badge.accent {
font-weight: 700;
background: var(--surface);
color: var(--accent);
border: 1px solid var(--accent);
box-shadow: inset 0 0 0 2px var(--surface), inset 0 0 0 3px color-mix(in srgb, var(--accent) 45%, transparent);
padding: 3px 10px;
}
.avatar {
width: 32px; height: 32px; border-radius: var(--radius);
background: var(--accent); color: var(--accent-contrast);
display: inline-flex; align-items: center; justify-content: center;
font-family: var(--font-display);
font-weight: 700; font-size: 0.9rem;
object-fit: cover; overflow: hidden;
border: 1px solid color-mix(in srgb, var(--accent) 70%, black);
}
.avatar.large { width: 96px; height: 96px; font-size: 2.1rem; }
img.avatar { background: var(--surface2); }
/* ---- tables: the ledger itself ---- */
table.list { width: 100%; border-collapse: collapse; }
table.list th, table.list td {
text-align: left; padding: 9px 10px;
border-bottom: var(--hairline);
}
table.list th {
font-family: var(--font-mono);
color: var(--muted);
font-size: 0.72rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.1em;
border-bottom: 2px solid var(--border);
}
table.list tbody tr:hover { background: color-mix(in srgb, var(--surface2) 60%, transparent); }
table.list tbody tr:nth-child(even) { background: color-mix(in srgb, var(--surface2) 28%, transparent); }
.grid { display: grid; gap: 18px; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.grid > .card { display: flex; flex-direction: column; } /* equal-height rows */
/* pin the action row to the bottom so buttons line up across cards */
.grid > .card > .spread:last-child { margin-top: auto; }
/* Audit log: span the full screen width; long detail values wrap, never overflow */
#tab-audit {
width: calc(100vw - 32px);
margin-left: calc(50% - 50vw + 16px);
margin-right: calc(50% - 50vw + 16px);
}
body[data-nav=side] #tab-audit {
width: calc(100vw - 214px - 32px);
margin-left: 0;
margin-right: 0;
}
#audit-table td, #audit-table th { white-space: normal; overflow-wrap: anywhere; vertical-align: top; }
#audit-table td:last-child { font-family: var(--font-mono); font-size: 0.82rem; }
/* ---- toolbar: filter rows boxed and baseline-aligned ---- */
.toolbar {
display: flex; gap: 14px; align-items: flex-end; flex-wrap: wrap;
background: var(--surface);
border: var(--hairline);
border-radius: var(--radius);
box-shadow: var(--ink-shadow);
padding: 14px 16px;
position: relative;
overflow: hidden;
}
.toolbar::before {
content: "";
position: absolute; top: 0; left: 0; right: 0; height: 5px;
background-image: var(--dither-dense);
opacity: 0.7;
}
.toolbar .field { margin-bottom: 0; flex: 1 1 150px; }
.toolbar .field.tight { flex: 0 1 auto; }
.toolbar > .btn { flex: 0 0 auto; height: 41px; } /* matches input height */
.toolbar label { white-space: nowrap; }
/* ---- stat cards (metrics) ---- */
.stat { display: flex; flex-direction: column; gap: 4px; }
.stat .stat-label {
font-family: var(--font-mono);
font-size: 0.7rem; font-weight: 500;
letter-spacing: 0.12em; text-transform: uppercase;
color: var(--muted);
margin: 0;
}
.stat .stat-value {
font-family: var(--font-display);
font-size: 1.9rem; font-weight: 700;
font-variant-numeric: tabular-nums;
line-height: 1.1;
margin: 0;
}
.stat .hint { margin-top: auto; }
.muted { color: var(--muted); }
.spread { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
.stack > * + * { margin-top: 16px; }
.mt { margin-top: 16px; }
.kv-row { display: flex; gap: 8px; margin-bottom: 8px; }
.kv-row input { flex: 1; }
/* ---- toasts: telegram slips (above the chat bubble) ---- */
#toasts {
position: fixed; bottom: 84px; right: 16px; z-index: 2147483647;
display: flex; flex-direction: column; gap: 8px; max-width: 360px;
}
.toast {
background: var(--surface);
border: var(--hairline);
border-left: 4px solid var(--accent);
border-radius: var(--radius);
padding: 12px 16px;
font-size: 0.92rem;
box-shadow: 0 10px 24px -10px rgba(0,0,0,0.4);
animation: slipIn 0.25s cubic-bezier(0.2, 0.7, 0.2, 1);
}
@keyframes slipIn {
from { opacity: 0; transform: translateX(12px); }
to { opacity: 1; transform: none; }
}
.toast.err { border-left-color: var(--err); }
.toast.ok { border-left-color: var(--ok); }
/* ---- masthead (login / register) ---- */
.masthead { text-align: center; margin-bottom: 26px; }
.masthead-kicker {
font-family: var(--font-mono);
font-size: 0.68rem;
letter-spacing: 0.32em;
text-transform: uppercase;
color: var(--muted);
margin: 0 0 10px;
}
.masthead-title {
font-family: var(--font-display);
font-weight: 700;
font-size: 2.6rem;
letter-spacing: 0.01em;
margin: 0;
}
.masthead-title::after { content: none; }
.masthead-rule {
display: flex; align-items: center; gap: 10px;
margin: 14px auto 0; max-width: 280px;
color: var(--accent);
}
.masthead-rule::before, .masthead-rule::after {
content: "";
flex: 1;
border-top: var(--hairline);
border-bottom: var(--hairline);
height: 3px;
}
/* ---- hover cards ---- */
.hovercard {
position: absolute; z-index: 90; width: 300px;
box-shadow: 0 14px 30px -12px rgba(0,0,0,0.45);
animation: rise 0.18s ease;
}
/* ---- chat ---- */
.chat-layout { display: grid; grid-template-columns: 280px 1fr; gap: 18px; min-height: 70vh; }
.chat-sidebar { overflow: auto; }
.conv-list { list-style: none; margin: 8px 0 0; padding: 0; }
.conv-list li { border-bottom: var(--hairline); }
.conv-list li.active .conv-item {
background: var(--surface2);
box-shadow: inset 3px 0 0 var(--accent);
}
.conv-item {
display: flex; justify-content: space-between; align-items: center; gap: 8px;
width: 100%; padding: 11px 10px; font: inherit; text-align: left;
background: none; border: none; color: var(--text); cursor: pointer;
border-radius: var(--radius);
}
.conv-item:hover { background: var(--surface2); }
.chat-main { display: flex; flex-direction: column; }
.chat-messages { flex: 1; overflow: auto; padding: 8px 0; min-height: 320px; }
.chat-msg {
max-width: 75%; margin: 10px 0; padding: 9px 13px;
background: var(--surface2);
border: var(--hairline);
border-radius: var(--radius);
font-size: 0.95rem;
}
.chat-msg.own {
margin-left: auto;
background: color-mix(in srgb, var(--accent) 11%, var(--surface2));
border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
}
.chat-msg p, .chat-msg ul, .chat-msg ol { margin: 4px 0; }
.chat-img { max-width: 240px; max-height: 180px; cursor: zoom-in; border-radius: var(--radius); border: var(--hairline); }
.chat-toolbar { display: flex; gap: 4px; margin-bottom: 8px; }
.chat-composer {
min-height: 60px; max-height: 200px; overflow: auto;
background: var(--bg);
border: var(--hairline);
border-radius: var(--radius);
padding: 10px;
}
.chat-composer:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
outline: none;
}
.chat-composer:empty::before { content: attr(data-placeholder); color: var(--muted); }
.chat-attachments { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.lightbox { border: none; background: transparent; max-width: 92vw; max-height: 92vh; }
.lightbox img { max-width: 90vw; max-height: 88vh; cursor: zoom-out; border: 4px solid var(--surface); }
.lightbox::backdrop { background: rgba(12, 10, 8, 0.85); }
@media (max-width: 800px) { .chat-layout { grid-template-columns: 1fr; } }
/* ---- kanban: ledger columns ---- */
.kanban { display: grid; gap: 18px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.kanban-col {
background: color-mix(in srgb, var(--surface2) 55%, transparent);
border: var(--hairline);
border-top: 3px double var(--border);
border-radius: var(--radius);
padding: 16px;
min-height: 220px;
}
.kanban-col h2 {
font-family: var(--font-mono);
font-size: 0.74rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.16em;
color: var(--muted);
}
/* Project (customer) label on bounty cards */
.card-project {
font-family: var(--font-mono);
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--accent);
margin: 6px 0 0;
}
/* My Tasks: stack status columns vertically so each task gets full width */
.kanban.mt { grid-template-columns: 1fr; }
.kanban.mt .kanban-col { min-height: 0; }
/* Card action rows: wrap and right-align so buttons never overflow the card */
.kanban .card .spread { flex-wrap: wrap; }
.kanban .card [data-actions] {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
/* ---- notification bell ---- */
.bell-badge {
position: absolute; top: -4px; right: -4px;
background: var(--err); color: #fff;
font-family: var(--font-mono);
font-size: 0.62rem; font-weight: 700;
padding: 1px 5px; border-radius: var(--radius);
}
.notif-panel {
position: absolute; right: 0; top: 38px; z-index: 50;
width: 330px; max-height: 420px; overflow: auto;
background: var(--surface);
border: var(--hairline);
border-top: 3px double var(--border);
border-radius: var(--radius);
padding: 12px;
box-shadow: 0 16px 32px -12px rgba(0,0,0,0.45);
animation: rise 0.18s ease;
}
.notif-list { list-style: none; margin: 0 0 8px; padding: 0; }
.notif-list li { padding: 9px 0; border-bottom: var(--hairline); }
.notif-list a { color: var(--text); }
.notif-list a:hover { text-decoration: none; opacity: 0.85; }
/* ---- atomizing shimmer ---- */
.shimmer { position: relative; overflow: hidden; }
.shimmer::after {
content: "";
position: absolute; inset: 0;
background: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%);
animation: shimmer 1.6s infinite;
}
@keyframes shimmer {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}
input[type=range] { width: 100%; accent-color: var(--accent); }
/* ---- tabs ---- */
.tabs { display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap; }
.tabs [aria-selected=true] {
background: var(--accent);
color: var(--accent-contrast);
border-color: color-mix(in srgb, var(--accent) 70%, black);
}
/* ---- dialogs: paper slips ---- */
dialog {
background: var(--surface); color: var(--text);
border: var(--hairline);
border-top: 4px double var(--accent);
border-radius: var(--radius);
padding: 26px;
max-width: 90vw; max-height: 90vh; overflow: auto;
box-shadow: 0 30px 70px -20px rgba(0,0,0,0.55);
}
dialog[open] { animation: rise 0.22s cubic-bezier(0.2, 0.7, 0.2, 1); }
dialog::backdrop { background: rgba(20, 16, 12, 0.55); }
fieldset {
border: var(--hairline);
border-radius: var(--radius);
margin: 0 0 16px; padding: 16px;
}
legend {
font-family: var(--font-mono);
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
padding: 0 8px;
}
/* ---- new-conversation dialog: fixed geometry, no jumping ---- */
#newconv-form { width: 440px; max-width: 86vw; }
#nc-results {
height: 180px; /* fixed: search results never resize the dialog */
overflow: auto;
border: var(--hairline);
border-radius: var(--radius);
background: var(--bg);
margin-top: 8px;
padding: 4px;
}
#nc-results .nc-row {
display: block; width: 100%;
text-align: left;
font: inherit; font-size: 0.92rem;
background: none; border: none; color: var(--text);
padding: 8px 10px; cursor: pointer;
border-bottom: var(--hairline);
border-radius: var(--radius);
}
#nc-results .nc-row:hover { background: var(--surface2); }
#nc-results .nc-empty { color: var(--muted); padding: 10px; font-size: 0.88rem; }
#nc-selected { min-height: 30px; display: flex; gap: 6px; flex-wrap: wrap; }
/* ---- floating messages bubble + mini panel ---- */
.chat-fab {
position: fixed; bottom: 16px; right: 16px; z-index: 2147483645;
width: 52px; height: 52px;
display: flex; align-items: center; justify-content: center;
font-size: 1.35rem;
background: var(--accent); color: var(--accent-contrast);
border: 1px solid color-mix(in srgb, var(--accent) 65%, black);
border-radius: var(--radius);
box-shadow: 3px 3px 0 color-mix(in srgb, var(--border) 70%, transparent);
cursor: pointer;
transition: transform 0.08s ease, box-shadow 0.08s ease;
}
.chat-fab:hover { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 color-mix(in srgb, var(--border) 70%, transparent); }
.chat-fab:active { transform: translate(2px, 2px); box-shadow: none; }
.chat-fab .bell-badge { top: -6px; right: -6px; }
.chat-panel {
position: fixed; bottom: 80px; right: 16px; z-index: 2147483645;
width: 350px; height: 480px; max-height: 75vh;
display: flex; flex-direction: column;
background: var(--surface);
border: var(--hairline);
border-top: 4px double var(--accent);
border-radius: var(--radius);
box-shadow: 6px 6px 0 color-mix(in srgb, var(--border) 50%, transparent);
animation: rise 0.18s ease;
}
.chat-panel header {
display: flex; align-items: center; justify-content: space-between; gap: 8px;
padding: 10px 12px;
border-bottom: 2px solid var(--border);
background-image: var(--dither);
background-color: var(--surface2);
}
.chat-panel header strong {
font-family: var(--font-display); font-size: 1rem;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-panel header .btn { background: var(--surface); }
.chat-panel .cw-body { flex: 1; overflow: auto; padding: 6px 10px; }
.chat-panel .cw-conv {
display: flex; justify-content: space-between; align-items: center; gap: 8px;
width: 100%; padding: 10px 8px; font: inherit; text-align: left;
background: none; border: none; border-bottom: var(--hairline);
color: var(--text); cursor: pointer; border-radius: var(--radius);
}
.chat-panel .cw-conv:hover { background: var(--surface2); }
.chat-panel .cw-msg {
max-width: 85%; margin: 8px 0; padding: 7px 10px;
font-size: 0.88rem;
background: var(--surface2);
border: var(--hairline);
border-radius: var(--radius);
}
.chat-panel .cw-msg.own {
margin-left: auto;
background: color-mix(in srgb, var(--accent) 11%, var(--surface2));
}
.chat-panel .cw-msg .cw-who { color: var(--muted); font-size: 0.72rem; margin: 0 0 2px; font-family: var(--font-mono); }
.chat-panel footer { padding: 10px; border-top: var(--hairline); display: flex; gap: 8px; }
.chat-panel footer input { flex: 1; }
/* ---- scrollbars ---- */
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
@media (max-width: 640px) {
.row { flex-direction: column; gap: 0; }
.topnav { flex-wrap: wrap; }
h1 { font-size: 1.6rem; }
}