design: adopt Open Props tokens, indigo accent + IBM Plex Sans, collision colour

- Add Open Props as the token foundation (@import "open-props/style"). Our
  semantic tokens (--bg/--surface/--border/--text/--shadow-*/--transition) are
  now backed by its gray ramp, shadows and easing; the rest of the CSS is
  untouched since it already references var(--…).
- Accent → indigo #4f46e5 (--accent + derived rings / month-hover), and the
  timeline bar default colours (appearance.ts) move to the same indigo so the
  content and UI share one palette.
- Font → IBM Plex Sans, self-hosted via @fontsource (latin + cyrillic, weights
  400/500/600/700) so there is no runtime Google Fonts dependency. Backed into
  --font-sans (overrides Open Props' :where() default). Tabular figures on the
  counters and map legend.
- Collision default colour → warm amber-orange #e8590c: distinct from the indigo
  bars and from the green/red map status, reads as a conflict warning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
aaverbitskiy 2026-08-06 21:15:50 +00:00
parent de0c369bb5
commit 17c54db501
6 changed files with 65 additions and 30 deletions

View File

@ -8,6 +8,8 @@
"name": "mapdash-frontend",
"version": "0.1.0",
"dependencies": {
"@fontsource/ibm-plex-sans": "^5.3.0",
"open-props": "^1.7.23",
"vis-data": "^7.1.9",
"vis-timeline": "^7.7.3"
},
@ -420,6 +422,15 @@
"node": ">=12"
}
},
"node_modules/@fontsource/ibm-plex-sans": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-sans/-/ibm-plex-sans-5.3.0.tgz",
"integrity": "sha512-CbE4CbbEEZJX860XyUiRpsksXIQR8Rp2XDva2VO53NJox9tVNtusrysd2x5YkUEY3ErQ66W1IiiQL8/wihhw5w==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.62.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
@ -898,6 +909,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/open-props": {
"version": "1.7.23",
"resolved": "https://registry.npmjs.org/open-props/-/open-props-1.7.23.tgz",
"integrity": "sha512-+xyrJmxV9QUBFbSwPROYhOg/FLXry+uuPr4R+B5EaE4556Oc18/8ZC/fL5A+/lbRSwNvA0Alh+C0KpyFWLmLZg==",
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",

View File

@ -9,6 +9,8 @@
"build": "tsc --noEmit && vite build"
},
"dependencies": {
"@fontsource/ibm-plex-sans": "^5.3.0",
"open-props": "^1.7.23",
"vis-data": "^7.1.9",
"vis-timeline": "^7.7.3"
},

View File

@ -22,20 +22,20 @@ const LS_KEY = 'mapdash.appearance.v1';
/** Sensible out-of-the-box colours for the known statuses. */
export const DEFAULT_STATUS_COLORS: Record<string, string> = {
'РК. Новая': '#2e66d6',
'РК. Размещено': '#2e66d6',
'РК. Макет РИМ согласован': '#2e66d6',
'РК. Ожидает монтаж': '#2e66d6',
'РК. Ожидает демонтаж': '#2e66d6',
'РК. Ожидает перемонтаж': '#2e66d6',
'РК. Архив': '#2e66d6',
'РК. Новая': '#4f46e5',
'РК. Размещено': '#4f46e5',
'РК. Макет РИМ согласован': '#4f46e5',
'РК. Ожидает монтаж': '#4f46e5',
'РК. Ожидает демонтаж': '#4f46e5',
'РК. Ожидает перемонтаж': '#4f46e5',
'РК. Архив': '#4f46e5',
};
export function defaultAppearance(): Appearance {
return {
colors: { ...DEFAULT_STATUS_COLORS },
collisionColor: '#BA55D3',
defaultColor: '#2e66d6',
collisionColor: '#e8590c',
defaultColor: '#4f46e5',
rowHeightPx: 30,
barHeightPct: 62,
barFontPx: 11,

View File

@ -1,3 +1,13 @@
// IBM Plex Sans — self-hosted (no runtime Google Fonts dependency), latin + cyrillic.
import '@fontsource/ibm-plex-sans/400.css';
import '@fontsource/ibm-plex-sans/500.css';
import '@fontsource/ibm-plex-sans/600.css';
import '@fontsource/ibm-plex-sans/700.css';
import '@fontsource/ibm-plex-sans/cyrillic-400.css';
import '@fontsource/ibm-plex-sans/cyrillic-500.css';
import '@fontsource/ibm-plex-sans/cyrillic-600.css';
import '@fontsource/ibm-plex-sans/cyrillic-700.css';
import './styles.css';
import 'vis-timeline/styles/vis-timeline-graph2d.min.css';

View File

@ -28,7 +28,7 @@ function donutSvg(total: number, busy: number): string {
`<circle cx="${m}" cy="${m}" r="${r}" fill="none" stroke="${CLUSTER_BUSY}" stroke-width="${sw}" ` +
`stroke-dasharray="${busyLen.toFixed(1)} ${c.toFixed(1)}" transform="rotate(-90 ${m} ${m})"/>` +
`<text x="${m}" y="${m}" text-anchor="middle" dominant-baseline="central" ` +
`font-family="-apple-system,Segoe UI,Roboto,Arial,sans-serif" font-size="14" font-weight="700" fill="#1f2430">${total}</text>` +
`font-family="'IBM Plex Sans',sans-serif" font-size="14" font-weight="700" fill="#1f2430">${total}</text>` +
`</svg>`
);
}

View File

@ -1,3 +1,8 @@
/* Open Props design-token foundation (colours, shadows, easing, scales).
Our semantic tokens below are backed by its ramps, so the rest of the CSS
(which references var(--accent) etc.) is untouched. */
@import "open-props/style";
:root {
/* appearance (overridden at runtime by appearance.ts) */
--row-h: 30px;
@ -6,32 +11,33 @@
--label-font: 12px;
--label-w: 340px;
/* ---- design tokens (light) ---- */
/* ---- design tokens (light), backed by Open Props ---- */
--bg: #ffffff;
--surface: #f7f8fa;
--surface-2: #eef1f5;
--border: #e5e7eb;
--border-strong: #d1d6de;
--text: #1f2430;
--text-muted: #6b7280;
--accent: #2e66d6;
--accent-weak: rgba(46, 102, 214, .12);
--accent-ring: rgba(46, 102, 214, .20);
--surface: var(--gray-0);
--surface-2: var(--gray-1);
--border: var(--gray-3);
--border-strong: var(--gray-4);
--text: var(--gray-9);
--text-muted: var(--gray-6);
--font-sans: "IBM Plex Sans", -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
--accent: #4f46e5;
--accent-weak: rgba(79, 70, 229, .12);
--accent-ring: rgba(79, 70, 229, .22);
--radius: 8px;
--radius-sm: 6px;
--shadow-sm: 0 1px 2px rgba(16, 24, 40, .06);
--shadow-md: 0 6px 18px rgba(16, 24, 40, .12);
--transition: .13s ease;
--shadow-sm: var(--shadow-1);
--shadow-md: var(--shadow-3);
--transition: .13s var(--ease-3);
/* timeline / map surfaces (built on the tokens above) */
--grid-line: var(--border);
--row-even: #f5f6f8;
--row-even: var(--gray-1);
--row-odd: #ffffff;
--row-hover: #fff3cd;
--month-hover: rgba(46, 102, 214, .08);
--tip-bg: #1f2430;
--month-hover: rgba(79, 70, 229, .08);
--tip-bg: var(--gray-9);
--tip-text: #ffffff;
--tip-sub: #cfd3d6;
--tip-sub: var(--gray-4);
--today: #ef5350;
}
@ -62,7 +68,7 @@ html, body { height: 100%; margin: 0; overflow: hidden; }
body {
display: flex;
flex-direction: column;
font-family: -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
font-family: var(--font-sans);
background: var(--bg);
color: var(--text);
}
@ -176,7 +182,7 @@ input::placeholder { color: var(--text-muted); }
margin-top: auto; padding: 8px 12px;
background: var(--surface-2); border: none;
border-radius: var(--radius-sm); font-size: 12px; color: var(--text-muted);
line-height: 1.5; white-space: nowrap;
line-height: 1.5; white-space: nowrap; font-variant-numeric: tabular-nums;
}
/* Scale group: stack the "Оформление" button under the zoom slider. */
@ -345,7 +351,7 @@ input::placeholder { color: var(--text-muted); }
background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
padding: 8px 12px; font-size: 12px; color: var(--text);
display: flex; flex-direction: column; align-items: flex-start; gap: 5px;
box-shadow: var(--shadow-md);
box-shadow: var(--shadow-md); font-variant-numeric: tabular-nums;
}
#map-legend .lg-item { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; font-size: 12px; }
#map-legend #map-counter { padding-left: 18px; } /* align "всего" with the dotted rows */