@mthanh/vespercli 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/assets/CATALOG.md +9 -2
- package/assets/claude/.claude-plugin/marketplace.json +9 -9
- package/assets/claude/.claude-plugin/plugin.json +1 -1
- package/assets/claude/agents/code-planner.md +1 -1
- package/assets/claude/agents/code-reviewer.md +1 -1
- package/assets/claude/agents/code-tester.md +1 -1
- package/assets/claude/agents/copywriter.md +1 -1
- package/assets/claude/agents/feature-wrap-document.md +1 -1
- package/assets/claude/agents/janitor.md +1 -1
- package/assets/claude/agents/modernization.md +1 -1
- package/assets/claude/agents/principal-software-engineer.md +1 -1
- package/assets/claude/agents/refine-issue.md +1 -1
- package/assets/claude/agents/se-system-architecture-reviewer.md +1 -1
- package/assets/claude/agents/se-technical-writer.md +1 -1
- package/assets/claude/agents/skill-creator.md +1 -1
- package/assets/claude/agents/task-researcher.md +1 -1
- package/assets/claude/agents/technical-product-manager.md +1 -1
- package/assets/claude/agents/vision-analyst.md +1 -1
- package/assets/claude/agents/vitest.md +1 -1
- package/assets/claude/agents/workspace-learner.md +2 -2
- package/assets/claude/packs/backend/.claude-plugin/plugin.json +1 -1
- package/assets/claude/packs/backend/agents/firebase-developer.md +1 -1
- package/assets/claude/packs/backend/agents/nodejs-express-developer.md +1 -1
- package/assets/claude/packs/docs/.claude-plugin/plugin.json +1 -1
- package/assets/claude/packs/frontend/.claude-plugin/plugin.json +1 -1
- package/assets/claude/packs/frontend/agents/expert-nextjs-developer.md +1 -1
- package/assets/claude/packs/frontend/agents/expert-react-frontend-engineer.md +1 -1
- package/assets/claude/packs/frontend/agents/jest-react-component-analyzer.md +1 -1
- package/assets/claude/packs/frontend/agents/jest-react-scenario-scanner.md +1 -1
- package/assets/claude/packs/frontend/agents/jest-react-test-generator.md +1 -1
- package/assets/claude/packs/frontend/agents/jest-react-test-planner.md +1 -1
- package/assets/claude/packs/frontend/skills/design-taste-frontend/LICENSE +21 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/SKILL.md +529 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/UPSTREAM.json +7 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/assets/app.css +216 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/assets/base.css +105 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/assets/base.js +37 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/assets/charts.js +617 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/assets/scenes/stage.css +28 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/assets/scenes/stage.js +133 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/evals/evals.json +76 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/library/app.json +1327 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/library/directions.json +1078 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/library/heroes.json +1086 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/library/motions.json +92 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/library/palettes.json +1485 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/library/photos.json +7240 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/library/sections.json +4429 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/library/sites.json +8350 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/library/type.json +2024 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/app-screens.md +1199 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/colour.md +141 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/copy.md +177 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/critique.md +182 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/imagery.md +791 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/layout.md +292 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/motion.md +980 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/research.md +356 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/scenes.md +174 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/tells.md +115 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/references/type.md +170 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/scripts/assets.mjs +865 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/scripts/draw.mjs +584 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/scripts/inspect.mjs +303 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/scripts/lib/app-draw.mjs +203 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/scripts/lib/cdp.mjs +248 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/scripts/lib/probe.mjs +909 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/scripts/lib/rules.mjs +243 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/scripts/lint.mjs +119 -0
- package/assets/claude/packs/frontend/skills/design-taste-frontend/scripts/scenes.mjs +124 -0
- package/assets/claude/packs/graphify/.claude-plugin/plugin.json +1 -1
- package/assets/claude/packs/integrations/.claude-plugin/plugin.json +1 -1
- package/assets/claude/packs/playwright/.claude-plugin/plugin.json +1 -1
- package/assets/claude/packs/playwright/agents/playwright-component-analyzer.md +1 -1
- package/assets/claude/packs/playwright/agents/playwright-scenario-scanner.md +1 -1
- package/assets/claude/packs/playwright/agents/playwright-test-generator.md +1 -1
- package/assets/claude/packs/playwright/agents/playwright-test-planner.md +1 -1
- package/assets/claude/packs/playwright/agents/playwright-tester.md +1 -1
- package/assets/claude/packs/polyglot/.claude-plugin/plugin.json +1 -1
- package/assets/claude/packs/styles/.claude-plugin/plugin.json +1 -1
- package/assets/copilot/agents/code-planner.agent.md +1 -1
- package/assets/copilot/agents/code-reviewer.agent.md +1 -1
- package/assets/copilot/agents/code-tester.agent.md +1 -1
- package/assets/copilot/agents/copywriter.agent.md +1 -1
- package/assets/copilot/agents/feature-wrap-document.agent.md +1 -1
- package/assets/copilot/agents/janitor.agent.md +1 -1
- package/assets/copilot/agents/modernization.agent.md +1 -1
- package/assets/copilot/agents/principal-software-engineer.agent.md +1 -1
- package/assets/copilot/agents/refine-issue.agent.md +1 -1
- package/assets/copilot/agents/se-system-architecture-reviewer.agent.md +1 -1
- package/assets/copilot/agents/se-technical-writer.agent.md +1 -1
- package/assets/copilot/agents/skill-creator.agent.md +1 -1
- package/assets/copilot/agents/task-researcher.agent.md +1 -1
- package/assets/copilot/agents/technical-product-manager.agent.md +1 -1
- package/assets/copilot/agents/vision-analyst.agent.md +1 -1
- package/assets/copilot/agents/vitest.agent.md +1 -1
- package/assets/copilot/agents/workspace-learner.agent.md +2 -2
- package/assets/copilot/packs/backend/agents/firebase-developer.agent.md +1 -1
- package/assets/copilot/packs/backend/agents/nodejs-express-developer.agent.md +1 -1
- package/assets/copilot/packs/frontend/agents/expert-nextjs-developer.agent.md +1 -1
- package/assets/copilot/packs/frontend/agents/expert-react-frontend-engineer.agent.md +1 -1
- package/assets/copilot/packs/frontend/agents/jest-react-component-analyzer.agent.md +1 -1
- package/assets/copilot/packs/frontend/agents/jest-react-scenario-scanner.agent.md +1 -1
- package/assets/copilot/packs/frontend/agents/jest-react-test-generator.agent.md +1 -1
- package/assets/copilot/packs/frontend/agents/jest-react-test-planner.agent.md +1 -1
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/LICENSE +21 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/SKILL.md +529 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/UPSTREAM.json +7 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/assets/app.css +216 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/assets/base.css +105 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/assets/base.js +37 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/assets/charts.js +617 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/assets/scenes/stage.css +28 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/assets/scenes/stage.js +133 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/evals/evals.json +76 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/library/app.json +1327 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/library/directions.json +1078 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/library/heroes.json +1086 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/library/motions.json +92 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/library/palettes.json +1485 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/library/photos.json +7240 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/library/sections.json +4429 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/library/sites.json +8350 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/library/type.json +2024 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/app-screens.md +1199 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/colour.md +141 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/copy.md +177 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/critique.md +182 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/imagery.md +791 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/layout.md +292 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/motion.md +980 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/research.md +356 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/scenes.md +174 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/tells.md +115 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/references/type.md +170 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/scripts/assets.mjs +865 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/scripts/draw.mjs +584 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/scripts/inspect.mjs +303 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/scripts/lib/app-draw.mjs +203 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/scripts/lib/cdp.mjs +248 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/scripts/lib/probe.mjs +909 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/scripts/lib/rules.mjs +243 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/scripts/lint.mjs +119 -0
- package/assets/copilot/packs/frontend/skills/design-taste-frontend/scripts/scenes.mjs +124 -0
- package/assets/copilot/packs/playwright/agents/playwright-component-analyzer.agent.md +1 -1
- package/assets/copilot/packs/playwright/agents/playwright-scenario-scanner.agent.md +1 -1
- package/assets/copilot/packs/playwright/agents/playwright-test-generator.agent.md +1 -1
- package/assets/copilot/packs/playwright/agents/playwright-test-planner.agent.md +1 -1
- package/assets/copilot/packs/playwright/agents/playwright-tester.agent.md +1 -1
- package/assets/manifest.json +2 -0
- package/dist/cli.min.js +102 -102
- package/package.json +1 -1
- package/scripts/__snapshots__/generate-claude-assets.test.js.snap +1 -1
- package/scripts/generate-claude-assets.strict.test.js +1 -1
- package/scripts/generate-claude-assets.test.js +6 -6
- package/scripts/packs-layout.test.js +2 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/* tasteskill / app.css
|
|
2
|
+
A neutral foundation for app screens (dashboards, admin, settings, inboxes), set to the defaults
|
|
3
|
+
measured in the study of real product screens (references/app-screens.md). Copy it into the project,
|
|
4
|
+
replace the token values with the ones on the app sheet (draw.mjs --app), and build on top.
|
|
5
|
+
Delete what you do not use. It has no brand of its own: neutral grounds, one quiet line, one accent. */
|
|
6
|
+
|
|
7
|
+
/* ---------- tokens (replace with the dealt style) ---------- */
|
|
8
|
+
:root {
|
|
9
|
+
/* names match the app sheet printed by draw.mjs --app */
|
|
10
|
+
--ground: #f9fafb; /* the app chrome and page */
|
|
11
|
+
--surface: #ffffff; /* panels and the work sheet */
|
|
12
|
+
--raised: #f4f5f6; /* a filled inner well, hover rows */
|
|
13
|
+
--active: #eeeeee; /* the active nav pill */
|
|
14
|
+
--border: #e8e8e8; /* panel edges: about 1.2:1 on white */
|
|
15
|
+
--rule: #f1f2f3; /* row rules inside tables */
|
|
16
|
+
--control: #d1d5db; /* inputs and outlined buttons: one step stronger */
|
|
17
|
+
--text: #111111;
|
|
18
|
+
--muted: #5f6368; /* secondary text, labels (4.5:1 or more on its ground) */
|
|
19
|
+
--faint: #6b6b6b; /* meta and placeholders: the lightest grey that holds 4.5:1 on white */
|
|
20
|
+
--accent: #1f6f5c; /* one hue: data, focus, the selected thing */
|
|
21
|
+
--primary: #1d1f20; /* the primary button is near-black unless the style says otherwise */
|
|
22
|
+
--primary-ink: #ffffff;
|
|
23
|
+
--positive: #247b50; --negative: #c4242a; --warning: #92400e; /* references/app-screens.md §9.5; a green accent is also --positive */
|
|
24
|
+
--pill-positive-bg: #dcfae5; --pill-positive-ink: #04250e;
|
|
25
|
+
--pill-negative-bg: #ffedea; --pill-negative-ink: #831c1e;
|
|
26
|
+
--pill-warning-bg: #fffbeb; --pill-warning-ink: #92400e;
|
|
27
|
+
--pill-neutral-bg: #eeeeee; --pill-neutral-ink: #3d3d3d;
|
|
28
|
+
--chart-1: var(--accent); --chart-2: #8fb8ad; --chart-3: #b08a2e; --chart-muted: #cfd3d6;
|
|
29
|
+
--chart-mark: var(--text); /* a marked period in Charts.diverging: the second tone */
|
|
30
|
+
--chart-span: 0.06; /* opacity of a shaded span (a selling window, an outage) */
|
|
31
|
+
--grid: var(--rule); /* chart gridlines (charts.js) */
|
|
32
|
+
--axis-ink: var(--muted); /* chart tick labels (charts.js) */
|
|
33
|
+
--ink: var(--text);
|
|
34
|
+
|
|
35
|
+
--font: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
36
|
+
--fs-title: 24px; --fs-section: 16px; --fs-kpi: 28px; --fs-body: 14px; --fs-small: 12px;
|
|
37
|
+
|
|
38
|
+
--sidebar-width: 232px;
|
|
39
|
+
--pad: 24px; /* canvas padding */
|
|
40
|
+
--gap: 16px; /* one gutter, horizontal and vertical */
|
|
41
|
+
--card-pad: 20px;
|
|
42
|
+
--radius-card: 8px; /* radius is bimodal: tight 4–8 (controls 6) or soft 16–24 (controls full); never 11–12 */
|
|
43
|
+
--radius-control: 6px;
|
|
44
|
+
--control-height: 32px; /* every control in the title and filter rows */
|
|
45
|
+
--row-height: 48px; /* single-line table row */
|
|
46
|
+
--nav-row: 32px; /* sidebar item height, on a 34px pitch */
|
|
47
|
+
|
|
48
|
+
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
|
|
49
|
+
--t-press: 120ms; --t-hover: 150ms; --t-ui: 200ms;
|
|
50
|
+
}
|
|
51
|
+
/* dark: one true-neutral ground, elevation by lightness; two greys that read apart (not #aaa beside #969696) */
|
|
52
|
+
:root[data-theme="dark"] {
|
|
53
|
+
--ground: #000000; --surface: #111111; --raised: #222222; --active: #222222;
|
|
54
|
+
--border: #1a1a1a; --rule: #161616; --control: #444444;
|
|
55
|
+
--text: #ffffff; --muted: #b4b4b4; --faint: #949494;
|
|
56
|
+
--primary: #ffffff; --primary-ink: #000000;
|
|
57
|
+
--positive: #3fc07a; --negative: #e5566f; --warning: #e5a93b; /* a green or yellow-green accent is also --positive */
|
|
58
|
+
--pill-positive-bg: #172a1d; --pill-positive-ink: #3fc07a;
|
|
59
|
+
--pill-negative-bg: #2c1a20; --pill-negative-ink: #e5566f;
|
|
60
|
+
--pill-warning-bg: #2b2414; --pill-warning-ink: #e5a93b;
|
|
61
|
+
--pill-neutral-bg: #222222; --pill-neutral-ink: #ffffff;
|
|
62
|
+
--chart-muted: #4a4a4a; --grid: #2b2b2b;
|
|
63
|
+
--chart-span: 0.12;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ---------- reset ---------- */
|
|
67
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
68
|
+
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
|
|
69
|
+
body { margin: 0; background: var(--ground); color: var(--text); font: var(--fs-body)/20px var(--font); -webkit-font-smoothing: antialiased; }
|
|
70
|
+
h1, h2, h3, h4, p, figure, ul, ol, dl, dd { margin: 0; }
|
|
71
|
+
ul[class], ol[class] { padding: 0; list-style: none; }
|
|
72
|
+
img, svg, video, canvas { display: block; max-width: 100%; }
|
|
73
|
+
button, input, select, textarea { font: inherit; color: inherit; }
|
|
74
|
+
a { color: inherit; text-decoration: none; }
|
|
75
|
+
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
|
|
76
|
+
.num, table, .kpi-value { font-variant-numeric: tabular-nums; }
|
|
77
|
+
|
|
78
|
+
/* ---------- shell: sidebar + canvas ---------- */
|
|
79
|
+
.app { display: grid; grid-template-columns: var(--sidebar-width) minmax(0, 1fr); min-height: 100svh; }
|
|
80
|
+
.sidebar { position: sticky; top: 0; height: 100svh; display: flex; flex-direction: column; gap: 2px; padding: 16px 12px; overflow: auto; }
|
|
81
|
+
.sidebar .group-label { margin: 20px 8px 6px; font-size: var(--fs-small); color: var(--faint); }
|
|
82
|
+
.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; }
|
|
83
|
+
.nav-item svg { width: 16px; height: 16px; flex: none; stroke-width: 1.5; }
|
|
84
|
+
.nav-item:hover { background: var(--raised); color: var(--text); }
|
|
85
|
+
.nav-item[aria-current="page"] { background: var(--active); color: var(--text); }
|
|
86
|
+
.sidebar .bottom { margin-top: auto; }
|
|
87
|
+
.canvas { min-width: 0; padding: var(--pad); }
|
|
88
|
+
|
|
89
|
+
/* ---------- title block and filters ---------- */
|
|
90
|
+
.title-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
|
|
91
|
+
.title-row h1 { font-size: var(--fs-title); line-height: 1.2; font-weight: 600; letter-spacing: -0.01em; }
|
|
92
|
+
.title-row .sub { margin-top: 4px; color: var(--muted); }
|
|
93
|
+
.filters { display: flex; align-items: center; gap: 8px; margin-bottom: var(--gap); flex-wrap: wrap; }
|
|
94
|
+
.filters .spacer { flex: 1; }
|
|
95
|
+
.stamp { color: var(--faint); font-size: var(--fs-small); }
|
|
96
|
+
|
|
97
|
+
/* ---------- panels and grid ---------- */
|
|
98
|
+
.grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--gap); }
|
|
99
|
+
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-card); padding: var(--card-pad); min-width: 0; }
|
|
100
|
+
.panel > header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
|
|
101
|
+
.panel h2 { font-size: var(--fs-section); line-height: 1.3; font-weight: 600; }
|
|
102
|
+
.panel .meta { color: var(--faint); font-size: var(--fs-small); }
|
|
103
|
+
/* paired cards in one row: a card stretches to its neighbour only when it has content to fill. Otherwise it keeps
|
|
104
|
+
its own height (add .top to the row), and only an action or a total may be pinned to a stretched card's bottom */
|
|
105
|
+
.grid.top { align-items: start; }
|
|
106
|
+
|
|
107
|
+
/* a chart's heading row: its title (and legend or period) with a fixed 8px to the svg */
|
|
108
|
+
.chart-head { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 4px 12px; margin-bottom: 8px; }
|
|
109
|
+
.chart-head h3 { font-size: var(--fs-body); line-height: 20px; font-weight: 500; }
|
|
110
|
+
.chart { position: relative; min-width: 0; }
|
|
111
|
+
|
|
112
|
+
/* a link at a card's foot or header ("Show all 7", "Sell-out history"): ink, a 12px chevron, an underline on hover,
|
|
113
|
+
and no inline padding, so its text sits on the card's content edge */
|
|
114
|
+
.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; }
|
|
115
|
+
.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; }
|
|
116
|
+
.card-link:hover { text-decoration: underline; }
|
|
117
|
+
: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"); }
|
|
118
|
+
|
|
119
|
+
/* 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
|
|
120
|
+
that carries a rule has square corners */
|
|
121
|
+
.rows { display: grid; }
|
|
122
|
+
.row { display: flex; align-items: center; gap: 12px; min-height: 44px; padding: 10px 0; }
|
|
123
|
+
.row + .row { border-top: 1px solid var(--rule); border-radius: 0; }
|
|
124
|
+
|
|
125
|
+
/* KPIs as one divided strip: a label above a number, no icons, no tiles */
|
|
126
|
+
.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); }
|
|
127
|
+
.kpi { padding: 16px var(--card-pad); }
|
|
128
|
+
.kpi + .kpi { border-left: 1px solid var(--border); }
|
|
129
|
+
.kpi-label { color: var(--muted); }
|
|
130
|
+
.kpi-value { margin-top: 6px; font-size: var(--fs-kpi); line-height: 1.15; font-weight: 500; letter-spacing: -0.01em; }
|
|
131
|
+
.kpi-value small { font-size: 0.55em; color: var(--muted); font-weight: 400; margin-left: 2px; }
|
|
132
|
+
.delta { margin-top: 4px; font-size: var(--fs-small); color: var(--muted); }
|
|
133
|
+
.delta .up { color: var(--positive); } .delta .down { color: var(--negative); }
|
|
134
|
+
/* KPIs as the chart's tabs: the selected one carries a 2px rule and an ink label; the others keep a grey label and
|
|
135
|
+
their value in ink at a lighter weight (a grey value reads as disabled). When the tabs are the per-entity
|
|
136
|
+
breakdown the brief asks for (takings by shop), add .entities: nothing changes but the rule */
|
|
137
|
+
.kpis[role="tablist"] .kpi { position: relative; cursor: pointer; }
|
|
138
|
+
.kpis[role="tablist"] .kpi[aria-selected="true"]::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--text); }
|
|
139
|
+
.kpis[role="tablist"]:not(.entities) .kpi[aria-selected="true"] .kpi-label { color: var(--text); }
|
|
140
|
+
.kpis[role="tablist"]:not(.entities) .kpi[aria-selected="false"] .kpi-value { color: var(--text); font-weight: 400; }
|
|
141
|
+
|
|
142
|
+
/* ---------- tables ---------- */
|
|
143
|
+
.table-wrap { overflow-x: auto; }
|
|
144
|
+
table { width: 100%; border-collapse: collapse; }
|
|
145
|
+
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; }
|
|
146
|
+
/* cells share one baseline, so a figure never sits 2px below the name beside it; the row height comes from padding */
|
|
147
|
+
td { padding: calc((var(--row-height) - 20px) / 2) 12px; border-bottom: 1px solid var(--rule); vertical-align: baseline; }
|
|
148
|
+
tbody tr:last-child td { border-bottom: 0; }
|
|
149
|
+
/* no hover that changes the fill; a row that drives a detail pane takes the nav pill's fill when selected */
|
|
150
|
+
tbody tr[aria-selected="true"] td { background: var(--active); }
|
|
151
|
+
th.n, td.n { text-align: right; }
|
|
152
|
+
td .sub { display: block; font-size: var(--fs-small); color: var(--muted); }
|
|
153
|
+
.truncate { max-width: 28ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
154
|
+
|
|
155
|
+
/* status pill: a pale tint with dark ink of the same hue, one per row */
|
|
156
|
+
.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; }
|
|
157
|
+
.pill.pos { background: var(--pill-positive-bg); color: var(--pill-positive-ink); }
|
|
158
|
+
.pill.neg { background: var(--pill-negative-bg); color: var(--pill-negative-ink); }
|
|
159
|
+
.pill.warn { background: var(--pill-warning-bg); color: var(--pill-warning-ink); }
|
|
160
|
+
|
|
161
|
+
/* ---------- controls ---------- */
|
|
162
|
+
.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); }
|
|
163
|
+
.btn:hover { background: var(--raised); }
|
|
164
|
+
.btn:active { transform: translateY(1px); }
|
|
165
|
+
.btn.primary { background: var(--primary); color: var(--primary-ink); border-color: transparent; }
|
|
166
|
+
.btn.ghost { border-color: transparent; background: transparent; }
|
|
167
|
+
.field { display: grid; gap: 8px; }
|
|
168
|
+
.field label { font-weight: 500; }
|
|
169
|
+
.field .hint { color: var(--muted); font-size: var(--fs-small); }
|
|
170
|
+
.input, select.input { height: 38px; padding: 0 12px; border: 1px solid var(--control); border-radius: var(--radius-control); background: var(--surface); }
|
|
171
|
+
.input::placeholder { color: var(--faint); }
|
|
172
|
+
.tabs { display: flex; gap: 24px; border-bottom: 1px solid var(--border); margin-bottom: var(--gap); }
|
|
173
|
+
.tabs a { position: relative; padding: 10px 0; color: var(--muted); transition: color var(--t-hover) ease; }
|
|
174
|
+
.tabs a[aria-selected="true"] { color: var(--text); }
|
|
175
|
+
.tabs a[aria-selected="true"]::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--text); }
|
|
176
|
+
/* as wide as its options; full width only on a narrow phone */
|
|
177
|
+
.segmented { display: inline-flex; width: max-content; max-width: 100%; padding: 2px; border-radius: 8px; background: var(--raised); }
|
|
178
|
+
.segmented button { height: 28px; padding: 0 10px; border: 0; border-radius: 6px; background: transparent; color: var(--muted); cursor: pointer; }
|
|
179
|
+
.segmented button[aria-pressed="true"] { background: var(--surface); color: var(--text); }
|
|
180
|
+
.switch { appearance: none; width: 32px; height: 20px; border-radius: 999px; background: var(--control); position: relative; cursor: pointer; transition: background-color var(--t-ui) ease; }
|
|
181
|
+
.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); }
|
|
182
|
+
.switch:checked { background: var(--text); } .switch:checked::after { transform: translateX(12px); }
|
|
183
|
+
|
|
184
|
+
/* empty and loading states keep the frame; a loading value is a flat grey bar at its real size, with no shimmer */
|
|
185
|
+
.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; }
|
|
186
|
+
.skeleton { background: var(--raised); border-radius: 4px; }
|
|
187
|
+
|
|
188
|
+
/* ---------- phone: the navigation folds away and the canvas takes the width ---------- */
|
|
189
|
+
.menu-toggle { display: none; }
|
|
190
|
+
@media (max-width: 860px) {
|
|
191
|
+
.app { grid-template-columns: minmax(0, 1fr); }
|
|
192
|
+
/* the menu opens as a full-screen sheet */
|
|
193
|
+
.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); }
|
|
194
|
+
.sidebar.open { transform: none; }
|
|
195
|
+
.menu-toggle { display: inline-flex; }
|
|
196
|
+
.canvas { padding: 16px; }
|
|
197
|
+
.grid > * { grid-column: 1 / -1 !important; }
|
|
198
|
+
.kpis { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
199
|
+
.kpi + .kpi { border-left: 0; }
|
|
200
|
+
.kpi:nth-child(n + 3) { border-top: 1px solid var(--border); }
|
|
201
|
+
.kpi:nth-child(2n) { border-left: 1px solid var(--border); }
|
|
202
|
+
/* an odd count leaves no half-empty row: the last cell takes the full width */
|
|
203
|
+
.kpi:last-child:nth-child(odd) { grid-column: 1 / -1; }
|
|
204
|
+
/* KPI tabs in a 2-up grid: the selected cell takes a fill, never an underline floating mid-grid */
|
|
205
|
+
.kpis[role="tablist"] .kpi[aria-selected="true"] { background: var(--active); }
|
|
206
|
+
.kpis[role="tablist"] .kpi[aria-selected="true"]::after { content: none; }
|
|
207
|
+
.title-row { flex-wrap: wrap; align-items: flex-start; }
|
|
208
|
+
}
|
|
209
|
+
@media (max-width: 480px) {
|
|
210
|
+
.segmented { display: flex; width: 100%; }
|
|
211
|
+
.segmented button { flex: 1; }
|
|
212
|
+
}
|
|
213
|
+
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition-duration: 0.01ms !important; animation: none !important; } }
|
|
214
|
+
|
|
215
|
+
/* an empty message spans the block it stands in, never one squeezed column */
|
|
216
|
+
.rows .empty, .grid > .empty, li > .empty { grid-column: 1 / -1; width: 100%; }
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* tasteskill / base.css
|
|
2
|
+
A neutral foundation: reset, token names, fluid type steps, a 12-column grid, states and the motion
|
|
3
|
+
helpers from references/motion.md. It has no look of its own. Copy it into the project, replace the
|
|
4
|
+
token values with the ones on the direction sheet, and build on top. Delete what you do not use. */
|
|
5
|
+
|
|
6
|
+
/* ---------- tokens (replace with the dealt palette and type system) ---------- */
|
|
7
|
+
:root {
|
|
8
|
+
--bg: #ffffff;
|
|
9
|
+
--surface: #f2f2f2;
|
|
10
|
+
--ink: #111111;
|
|
11
|
+
--muted: #5c5c5c;
|
|
12
|
+
--accent: #111111;
|
|
13
|
+
--accent-ink: #ffffff;
|
|
14
|
+
|
|
15
|
+
--font-display: system-ui, sans-serif;
|
|
16
|
+
--font-text: system-ui, sans-serif;
|
|
17
|
+
|
|
18
|
+
--step-small: clamp(0.8125rem, 0.79rem + 0.1vw, 0.875rem);
|
|
19
|
+
--step-body: clamp(1.0625rem, 1rem + 0.25vw, 1.1875rem);
|
|
20
|
+
--step-lead: clamp(1.25rem, 1.1rem + 0.7vw, 1.625rem);
|
|
21
|
+
--step-head: clamp(2rem, 1.3rem + 3.2vw, 4rem);
|
|
22
|
+
--step-display: clamp(3rem, 1rem + 9vw, 9rem); /* set from the hero blueprint */
|
|
23
|
+
|
|
24
|
+
--margin: clamp(1rem, 2.2vw, 2rem); /* page side margin: tight, so content reaches the edges */
|
|
25
|
+
--gutter: clamp(1rem, 2vw, 2rem); /* column gap */
|
|
26
|
+
--section: clamp(5rem, 12vw, 13rem); /* space between sections */
|
|
27
|
+
--radius: 0px; /* one radius for the whole page: 0, or large, decided once */
|
|
28
|
+
|
|
29
|
+
--ease-out: cubic-bezier(0.22, 1, 0.36, 1); /* entrances, moving hovers, UI under 600ms */
|
|
30
|
+
--ease-expo: cubic-bezier(0.16, 1, 0.3, 1); /* reveals of 800ms and longer, line rises, openings */
|
|
31
|
+
--ease-in-out: cubic-bezier(0.76, 0, 0.24, 1); /* wipes, panels, things that travel while visible */
|
|
32
|
+
--ease-soft: cubic-bezier(0.33, 1, 0.68, 1); /* slow scale and drift on photographs */
|
|
33
|
+
--ease-exit: cubic-bezier(0.55, 0.055, 0.675, 0.19); /* something leaving for good, short */
|
|
34
|
+
--t-press: 140ms;
|
|
35
|
+
--t-hover: 180ms;
|
|
36
|
+
--t-ui: 240ms;
|
|
37
|
+
--t-reveal: 800ms;
|
|
38
|
+
--t-open: 1200ms; /* picture openings and clip wipes */
|
|
39
|
+
--stagger-line: 90ms;
|
|
40
|
+
--stagger-item: 70ms;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ---------- reset ---------- */
|
|
44
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
45
|
+
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-behavior: auto; }
|
|
46
|
+
body { margin: 0; background: var(--bg); color: var(--ink); font-family: var(--font-text); font-size: var(--step-body); line-height: 1.5; font-kerning: normal; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
|
|
47
|
+
h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl, dd { margin: 0; }
|
|
48
|
+
ul[class], ol[class] { padding: 0; list-style: none; }
|
|
49
|
+
img, video, svg, canvas { display: block; max-width: 100%; }
|
|
50
|
+
img, video { height: auto; background: var(--surface); }
|
|
51
|
+
button, input, select, textarea { font: inherit; color: inherit; }
|
|
52
|
+
button { background: none; border: 0; padding: 0; cursor: pointer; }
|
|
53
|
+
a { color: inherit; }
|
|
54
|
+
:target { scroll-margin-top: 6rem; }
|
|
55
|
+
::selection { background: var(--accent); color: var(--accent-ink); }
|
|
56
|
+
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
57
|
+
|
|
58
|
+
/* ---------- type ---------- */
|
|
59
|
+
h1, h2, h3 { font-family: var(--font-display); font-weight: inherit; text-wrap: balance; hyphens: none; }
|
|
60
|
+
.display { font-size: var(--step-display); line-height: 0.95; letter-spacing: -0.035em; }
|
|
61
|
+
.head { font-size: var(--step-head); line-height: 1.02; letter-spacing: -0.025em; }
|
|
62
|
+
.lead { font-size: var(--step-lead); line-height: 1.35; max-width: 40ch; }
|
|
63
|
+
.small { font-size: var(--step-small); line-height: 1.45; }
|
|
64
|
+
.muted { color: var(--muted); }
|
|
65
|
+
p { max-width: 62ch; text-wrap: pretty; }
|
|
66
|
+
p + p { margin-top: 0.9em; }
|
|
67
|
+
.nums { font-variant-numeric: tabular-nums; }
|
|
68
|
+
.only-wide { display: none; }
|
|
69
|
+
@media (min-width: 48em) { .only-wide { display: inline; } }
|
|
70
|
+
|
|
71
|
+
/* ---------- layout ---------- */
|
|
72
|
+
.wrap { padding-inline: var(--margin); }
|
|
73
|
+
.grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gutter); }
|
|
74
|
+
.section { padding-block: calc(var(--section) / 2); }
|
|
75
|
+
.section + .section { margin-top: 0; }
|
|
76
|
+
.bleed { margin-inline: calc(var(--margin) * -1); }
|
|
77
|
+
.cover { width: 100%; height: 100%; object-fit: cover; }
|
|
78
|
+
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
79
|
+
|
|
80
|
+
/* ---------- controls ---------- */
|
|
81
|
+
.button { display: inline-flex; align-items: center; justify-content: center; min-height: 3rem; padding: 0 1.4rem; background: var(--accent); color: var(--accent-ink); border-radius: var(--radius); text-decoration: none; white-space: nowrap; transition: transform var(--t-press) var(--ease-out), background-color var(--t-hover) ease, color var(--t-hover) ease; }
|
|
82
|
+
.button:active { transform: scale(0.97); }
|
|
83
|
+
.link { text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 0.2em; text-decoration-thickness: 0.07em; transition: text-decoration-color var(--t-hover) ease; }
|
|
84
|
+
.link-plain { text-decoration: underline; text-underline-offset: 0.18em; text-decoration-thickness: 0.07em; }
|
|
85
|
+
@media (hover: hover) and (pointer: fine) {
|
|
86
|
+
.link:hover { text-decoration-color: currentColor; }
|
|
87
|
+
}
|
|
88
|
+
.field { width: 100%; min-height: 3rem; padding: 0.7rem 0.9rem; background: var(--surface); border: 0; border-radius: var(--radius); font-size: max(1rem, var(--step-body)); }
|
|
89
|
+
|
|
90
|
+
/* ---------- motion helpers (content stays visible without JavaScript) ---------- */
|
|
91
|
+
.lines .line { display: block; overflow: hidden; padding-bottom: 0.08em; margin-bottom: -0.08em; }
|
|
92
|
+
.lines [data-line] { display: block; }
|
|
93
|
+
.shutter { overflow: hidden; }
|
|
94
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
95
|
+
.js [data-rise] { opacity: 0; transform: translateY(18px); animation: ad-rise var(--t-reveal) var(--ease-out) forwards; animation-delay: calc(var(--i, 0) * 90ms + 120ms); }
|
|
96
|
+
.js .lines [data-line] { transform: translateY(105%); animation: ad-line 900ms var(--ease-out) forwards; animation-delay: calc(var(--i, 0) * 110ms + 150ms); }
|
|
97
|
+
.js [data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity 700ms var(--ease-out), transform 900ms var(--ease-out); }
|
|
98
|
+
.js [data-reveal].is-in { opacity: 1; transform: none; }
|
|
99
|
+
.js .shutter[data-reveal] { opacity: 1; transform: none; clip-path: inset(0 0 100% 0); transition: clip-path 1100ms var(--ease-in-out); }
|
|
100
|
+
.js .shutter[data-reveal] img { transform: scale(1.12); transition: transform 1500ms var(--ease-soft); }
|
|
101
|
+
.js .shutter[data-reveal].is-in { clip-path: inset(0); }
|
|
102
|
+
.js .shutter[data-reveal].is-in img { transform: none; }
|
|
103
|
+
}
|
|
104
|
+
@keyframes ad-rise { to { opacity: 1; transform: none; } }
|
|
105
|
+
@keyframes ad-line { to { transform: none; } }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// tasteskill / base.js — the small amount of script the motion helpers in base.css need.
|
|
2
|
+
// Put this line in <head> so the hidden states only apply when scripting works:
|
|
3
|
+
// <script>document.documentElement.classList.add('js')</script>
|
|
4
|
+
// Then load this file at the end of <body> (or with defer).
|
|
5
|
+
|
|
6
|
+
// Reveal once when an element enters the viewport. Never replays, never leaves content hidden.
|
|
7
|
+
(() => {
|
|
8
|
+
const targets = document.querySelectorAll('[data-reveal]');
|
|
9
|
+
if (!targets.length) return;
|
|
10
|
+
if (!('IntersectionObserver' in window)) { targets.forEach((el) => el.classList.add('is-in')); return; }
|
|
11
|
+
const io = new IntersectionObserver((entries) => {
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
if (!entry.isIntersecting) continue;
|
|
14
|
+
entry.target.classList.add('is-in');
|
|
15
|
+
io.unobserve(entry.target);
|
|
16
|
+
}
|
|
17
|
+
}, { rootMargin: '0px 0px -12% 0px', threshold: 0.1 });
|
|
18
|
+
targets.forEach((el) => io.observe(el));
|
|
19
|
+
// safety net: if anything is still hidden a few seconds after load (observer never fired), show it
|
|
20
|
+
window.addEventListener('load', () => setTimeout(() => {
|
|
21
|
+
targets.forEach((el) => { const r = el.getBoundingClientRect(); if (r.top < innerHeight && r.bottom > 0) el.classList.add('is-in'); });
|
|
22
|
+
}, 2500));
|
|
23
|
+
})();
|
|
24
|
+
|
|
25
|
+
// Optional, for "colour by chapter": each chapter paints its own full-bleed ground in CSS
|
|
26
|
+
// (style="background: var(--field-2); color: var(--field-2-ink)"), so the colour changes exactly at
|
|
27
|
+
// section edges. This only lets a fixed header take the ink of the chapter under it. Give each chapter
|
|
28
|
+
// data-ink="#hex" and style the header with color: var(--header-ink, var(--ink)).
|
|
29
|
+
// Never recolour the whole page from an observer: mid-change, a paragraph sits on two colours.
|
|
30
|
+
(() => {
|
|
31
|
+
const chapters = document.querySelectorAll('[data-ink]');
|
|
32
|
+
if (!chapters.length || !('IntersectionObserver' in window)) return;
|
|
33
|
+
const io = new IntersectionObserver((entries) => {
|
|
34
|
+
for (const entry of entries) if (entry.isIntersecting) document.documentElement.style.setProperty('--header-ink', entry.target.dataset.ink);
|
|
35
|
+
}, { rootMargin: '0px 0px -92% 0px', threshold: 0 });
|
|
36
|
+
chapters.forEach((el) => io.observe(el));
|
|
37
|
+
})();
|