diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5fbc04c..071a456 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index b0a7ab8..dce464e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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" }, diff --git a/frontend/src/appearance.ts b/frontend/src/appearance.ts index 79423c4..e863448 100644 --- a/frontend/src/appearance.ts +++ b/frontend/src/appearance.ts @@ -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 = { - 'РК. Новая': '#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, diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 2df400e..5a8eca3 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -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'; diff --git a/frontend/src/map.ts b/frontend/src/map.ts index f713892..7671844 100644 --- a/frontend/src/map.ts +++ b/frontend/src/map.ts @@ -28,7 +28,7 @@ function donutSvg(total: number, busy: number): string { `` + `${total}` + + `font-family="'IBM Plex Sans',sans-serif" font-size="14" font-weight="700" fill="#1f2430">${total}` + `` ); } diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 392d0ed..c34abcd 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -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 */