Files
araharah-executor 506ff68976 chore(01-design-tokens-rams-foundation-01): commit design baseline — Rams docs, mockups, agent skills
- Root Rams design docs (DESIGN.md, IDEAS.md) and GSD install-fix notes (GSD_FIX.md, gsd-fix.sh)
- Four Telemetry Rams mockup screens + telemetry_rams/DESIGN.md (canonical token frontmatter)
- .agents/ project skills (design-taste-frontend) + skills-lock.json
- .gitignore: node_modules/dist/env secrets/OS junk; .planning stays local-only; Zone.Identifier scrubbed (9 files deleted)
2026-10-22 07:33:04 +08:00

217 lines
16 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* tasteskill / app.css
A neutral foundation for app screens (dashboards, admin, settings, inboxes), set to the defaults
measured in the study of real product screens (references/app-screens.md). Copy it into the project,
replace the token values with the ones on the app sheet (draw.mjs --app), and build on top.
Delete what you do not use. It has no brand of its own: neutral grounds, one quiet line, one accent. */
/* ---------- tokens (replace with the dealt style) ---------- */
:root {
/* names match the app sheet printed by draw.mjs --app */
--ground: #f9fafb; /* the app chrome and page */
--surface: #ffffff; /* panels and the work sheet */
--raised: #f4f5f6; /* a filled inner well, hover rows */
--active: #eeeeee; /* the active nav pill */
--border: #e8e8e8; /* panel edges: about 1.2:1 on white */
--rule: #f1f2f3; /* row rules inside tables */
--control: #d1d5db; /* inputs and outlined buttons: one step stronger */
--text: #111111;
--muted: #5f6368; /* secondary text, labels (4.5:1 or more on its ground) */
--faint: #6b6b6b; /* meta and placeholders: the lightest grey that holds 4.5:1 on white */
--accent: #1f6f5c; /* one hue: data, focus, the selected thing */
--primary: #1d1f20; /* the primary button is near-black unless the style says otherwise */
--primary-ink: #ffffff;
--positive: #247b50; --negative: #c4242a; --warning: #92400e; /* references/app-screens.md §9.5; a green accent is also --positive */
--pill-positive-bg: #dcfae5; --pill-positive-ink: #04250e;
--pill-negative-bg: #ffedea; --pill-negative-ink: #831c1e;
--pill-warning-bg: #fffbeb; --pill-warning-ink: #92400e;
--pill-neutral-bg: #eeeeee; --pill-neutral-ink: #3d3d3d;
--chart-1: var(--accent); --chart-2: #8fb8ad; --chart-3: #b08a2e; --chart-muted: #cfd3d6;
--chart-mark: var(--text); /* a marked period in Charts.diverging: the second tone */
--chart-span: 0.06; /* opacity of a shaded span (a selling window, an outage) */
--grid: var(--rule); /* chart gridlines (charts.js) */
--axis-ink: var(--muted); /* chart tick labels (charts.js) */
--ink: var(--text);
--font: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
--fs-title: 24px; --fs-section: 16px; --fs-kpi: 28px; --fs-body: 14px; --fs-small: 12px;
--sidebar-width: 232px;
--pad: 24px; /* canvas padding */
--gap: 16px; /* one gutter, horizontal and vertical */
--card-pad: 20px;
--radius-card: 8px; /* radius is bimodal: tight 4–8 (controls 6) or soft 16–24 (controls full); never 11–12 */
--radius-control: 6px;
--control-height: 32px; /* every control in the title and filter rows */
--row-height: 48px; /* single-line table row */
--nav-row: 32px; /* sidebar item height, on a 34px pitch */
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
--t-press: 120ms; --t-hover: 150ms; --t-ui: 200ms;
}
/* dark: one true-neutral ground, elevation by lightness; two greys that read apart (not #aaa beside #969696) */
:root[data-theme="dark"] {
--ground: #000000; --surface: #111111; --raised: #222222; --active: #222222;
--border: #1a1a1a; --rule: #161616; --control: #444444;
--text: #ffffff; --muted: #b4b4b4; --faint: #949494;
--primary: #ffffff; --primary-ink: #000000;
--positive: #3fc07a; --negative: #e5566f; --warning: #e5a93b; /* a green or yellow-green accent is also --positive */
--pill-positive-bg: #172a1d; --pill-positive-ink: #3fc07a;
--pill-negative-bg: #2c1a20; --pill-negative-ink: #e5566f;
--pill-warning-bg: #2b2414; --pill-warning-ink: #e5a93b;
--pill-neutral-bg: #222222; --pill-neutral-ink: #ffffff;
--chart-muted: #4a4a4a; --grid: #2b2b2b;
--chart-span: 0.12;
}
/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body { margin: 0; background: var(--ground); color: var(--text); font: var(--fs-body)/20px var(--font); -webkit-font-smoothing: antialiased; }
h1, h2, h3, h4, p, figure, ul, ol, dl, dd { margin: 0; }
ul[class], ol[class] { padding: 0; list-style: none; }
img, svg, video, canvas { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.num, table, .kpi-value { font-variant-numeric: tabular-nums; }
/* ---------- shell: sidebar + canvas ---------- */
.app { display: grid; grid-template-columns: var(--sidebar-width) minmax(0, 1fr); min-height: 100svh; }
.sidebar { position: sticky; top: 0; height: 100svh; display: flex; flex-direction: column; gap: 2px; padding: 16px 12px; overflow: auto; }
.sidebar .group-label { margin: 20px 8px 6px; font-size: var(--fs-small); color: var(--faint); }
.nav-item { display: flex; align-items: center; gap: 10px; height: var(--nav-row); padding: 0 8px; border-radius: 8px; color: var(--muted); transition: background-color var(--t-hover) ease, color var(--t-hover) ease; }
.nav-item svg { width: 16px; height: 16px; flex: none; stroke-width: 1.5; }
.nav-item:hover { background: var(--raised); color: var(--text); }
.nav-item[aria-current="page"] { background: var(--active); color: var(--text); }
.sidebar .bottom { margin-top: auto; }
.canvas { min-width: 0; padding: var(--pad); }
/* ---------- title block and filters ---------- */
.title-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
.title-row h1 { font-size: var(--fs-title); line-height: 1.2; font-weight: 600; letter-spacing: -0.01em; }
.title-row .sub { margin-top: 4px; color: var(--muted); }
.filters { display: flex; align-items: center; gap: 8px; margin-bottom: var(--gap); flex-wrap: wrap; }
.filters .spacer { flex: 1; }
.stamp { color: var(--faint); font-size: var(--fs-small); }
/* ---------- panels and grid ---------- */
.grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--gap); }
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-card); padding: var(--card-pad); min-width: 0; }
.panel > header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.panel h2 { font-size: var(--fs-section); line-height: 1.3; font-weight: 600; }
.panel .meta { color: var(--faint); font-size: var(--fs-small); }
/* paired cards in one row: a card stretches to its neighbour only when it has content to fill. Otherwise it keeps
its own height (add .top to the row), and only an action or a total may be pinned to a stretched card's bottom */
.grid.top { align-items: start; }
/* a chart's heading row: its title (and legend or period) with a fixed 8px to the svg */
.chart-head { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 4px 12px; margin-bottom: 8px; }
.chart-head h3 { font-size: var(--fs-body); line-height: 20px; font-weight: 500; }
.chart { position: relative; min-width: 0; }
/* a link at a card's foot or header ("Show all 7", "Sell-out history"): ink, a 12px chevron, an underline on hover,
and no inline padding, so its text sits on the card's content edge */
.card-link { display: inline-flex; align-items: center; gap: 2px; padding: 0; border: 0; background: none; color: var(--text); font-weight: 500; cursor: pointer; text-underline-offset: 3px; }
.card-link::after { content: ""; width: 12px; height: 12px; flex: none; background: currentColor; -webkit-mask: var(--chevron) center / 12px no-repeat; mask: var(--chevron) center / 12px no-repeat; }
.card-link:hover { text-decoration: underline; }
:root { --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M4.5 2.5 8 6l-3.5 3.5' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
/* list rows: a rule between rows and none above the first. A rule on a rounded box curls up at both ends, so a row
that carries a rule has square corners */
.rows { display: grid; }
.row { display: flex; align-items: center; gap: 12px; min-height: 44px; padding: 10px 0; }
.row + .row { border-top: 1px solid var(--rule); border-radius: 0; }
/* KPIs as one divided strip: a label above a number, no icons, no tiles */
.kpis { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-card); }
.kpi { padding: 16px var(--card-pad); }
.kpi + .kpi { border-left: 1px solid var(--border); }
.kpi-label { color: var(--muted); }
.kpi-value { margin-top: 6px; font-size: var(--fs-kpi); line-height: 1.15; font-weight: 500; letter-spacing: -0.01em; }
.kpi-value small { font-size: 0.55em; color: var(--muted); font-weight: 400; margin-left: 2px; }
.delta { margin-top: 4px; font-size: var(--fs-small); color: var(--muted); }
.delta .up { color: var(--positive); } .delta .down { color: var(--negative); }
/* KPIs as the chart's tabs: the selected one carries a 2px rule and an ink label; the others keep a grey label and
their value in ink at a lighter weight (a grey value reads as disabled). When the tabs are the per-entity
breakdown the brief asks for (takings by shop), add .entities: nothing changes but the rule */
.kpis[role="tablist"] .kpi { position: relative; cursor: pointer; }
.kpis[role="tablist"] .kpi[aria-selected="true"]::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--text); }
.kpis[role="tablist"]:not(.entities) .kpi[aria-selected="true"] .kpi-label { color: var(--text); }
.kpis[role="tablist"]:not(.entities) .kpi[aria-selected="false"] .kpi-value { color: var(--text); font-weight: 400; }
/* ---------- tables ---------- */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th { height: 36px; padding: 0 12px; text-align: left; font-weight: 400; font-size: var(--fs-small); color: var(--muted); border-bottom: 1px solid var(--border); white-space: nowrap; }
/* cells share one baseline, so a figure never sits 2px below the name beside it; the row height comes from padding */
td { padding: calc((var(--row-height) - 20px) / 2) 12px; border-bottom: 1px solid var(--rule); vertical-align: baseline; }
tbody tr:last-child td { border-bottom: 0; }
/* no hover that changes the fill; a row that drives a detail pane takes the nav pill's fill when selected */
tbody tr[aria-selected="true"] td { background: var(--active); }
th.n, td.n { text-align: right; }
td .sub { display: block; font-size: var(--fs-small); color: var(--muted); }
.truncate { max-width: 28ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* status pill: a pale tint with dark ink of the same hue, one per row */
.pill { display: inline-flex; align-items: center; gap: 6px; height: 22px; padding: 0 8px; border-radius: 999px; font-size: var(--fs-small); font-weight: 500; background: var(--pill-neutral-bg); color: var(--pill-neutral-ink); white-space: nowrap; }
.pill.pos { background: var(--pill-positive-bg); color: var(--pill-positive-ink); }
.pill.neg { background: var(--pill-negative-bg); color: var(--pill-negative-ink); }
.pill.warn { background: var(--pill-warning-bg); color: var(--pill-warning-ink); }
/* ---------- controls ---------- */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: var(--control-height); padding: 0 12px; border-radius: var(--radius-control); border: 1px solid var(--control); background: var(--surface); font-weight: 500; cursor: pointer; transition: background-color var(--t-hover) ease, transform var(--t-press) var(--ease-out); }
.btn:hover { background: var(--raised); }
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--primary); color: var(--primary-ink); border-color: transparent; }
.btn.ghost { border-color: transparent; background: transparent; }
.field { display: grid; gap: 8px; }
.field label { font-weight: 500; }
.field .hint { color: var(--muted); font-size: var(--fs-small); }
.input, select.input { height: 38px; padding: 0 12px; border: 1px solid var(--control); border-radius: var(--radius-control); background: var(--surface); }
.input::placeholder { color: var(--faint); }
.tabs { display: flex; gap: 24px; border-bottom: 1px solid var(--border); margin-bottom: var(--gap); }
.tabs a { position: relative; padding: 10px 0; color: var(--muted); transition: color var(--t-hover) ease; }
.tabs a[aria-selected="true"] { color: var(--text); }
.tabs a[aria-selected="true"]::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--text); }
/* as wide as its options; full width only on a narrow phone */
.segmented { display: inline-flex; width: max-content; max-width: 100%; padding: 2px; border-radius: 8px; background: var(--raised); }
.segmented button { height: 28px; padding: 0 10px; border: 0; border-radius: 6px; background: transparent; color: var(--muted); cursor: pointer; }
.segmented button[aria-pressed="true"] { background: var(--surface); color: var(--text); }
.switch { appearance: none; width: 32px; height: 20px; border-radius: 999px; background: var(--control); position: relative; cursor: pointer; transition: background-color var(--t-ui) ease; }
.switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: transform var(--t-ui) var(--ease-out); }
.switch:checked { background: var(--text); } .switch:checked::after { transform: translateX(12px); }
/* empty and loading states keep the frame; a loading value is a flat grey bar at its real size, with no shimmer */
.empty { display: grid; place-items: center; min-height: 96px; padding: 16px; border: 1px dashed var(--control); border-radius: var(--radius-control); color: var(--muted); text-align: center; }
.skeleton { background: var(--raised); border-radius: 4px; }
/* ---------- phone: the navigation folds away and the canvas takes the width ---------- */
.menu-toggle { display: none; }
@media (max-width: 860px) {
.app { grid-template-columns: minmax(0, 1fr); }
/* the menu opens as a full-screen sheet */
.sidebar { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 20; background: var(--surface); transform: translateX(-100%); transition: transform var(--t-ui) var(--ease-out); }
.sidebar.open { transform: none; }
.menu-toggle { display: inline-flex; }
.canvas { padding: 16px; }
.grid > * { grid-column: 1 / -1 !important; }
.kpis { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.kpi + .kpi { border-left: 0; }
.kpi:nth-child(n + 3) { border-top: 1px solid var(--border); }
.kpi:nth-child(2n) { border-left: 1px solid var(--border); }
/* an odd count leaves no half-empty row: the last cell takes the full width */
.kpi:last-child:nth-child(odd) { grid-column: 1 / -1; }
/* KPI tabs in a 2-up grid: the selected cell takes a fill, never an underline floating mid-grid */
.kpis[role="tablist"] .kpi[aria-selected="true"] { background: var(--active); }
.kpis[role="tablist"] .kpi[aria-selected="true"]::after { content: none; }
.title-row { flex-wrap: wrap; align-items: flex-start; }
}
@media (max-width: 480px) {
.segmented { display: flex; width: 100%; }
.segmented button { flex: 1; }
}
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition-duration: 0.01ms !important; animation: none !important; } }
/* an empty message spans the block it stands in, never one squeezed column */
.rows .empty, .grid > .empty, li > .empty { grid-column: 1 / -1; width: 100%; }