From 557d22517acff4cbb7b59196272f8406d98634d2 Mon Sep 17 00:00:00 2001 From: "azykov@mail.ru" Date: Thu, 3 Sep 2026 11:52:44 +0300 Subject: [PATCH] display refactoring --- package-lock.json | 18 ++- package.json | 3 +- src/components/BehaviourCard.tsx | 12 +- src/components/ConverterCardInfo.tsx | 2 +- src/engine/behaviours/converter.ts | 2 - src/index.css | 200 +++++++++++++++++++++------ src/prefabs/game.ts | 20 +-- src/types/resource.ts | 2 +- src/ui/graph.ts | 6 +- src/ui/resource.ts | 4 +- 10 files changed, 196 insertions(+), 73 deletions(-) diff --git a/package-lock.json b/package-lock.json index d42c056..6fe1ffb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "break_eternity.js": "^2.1.3", "elkjs": "^0.12.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "yaml": "^2.9.0" }, "devDependencies": { "@types/react": "^18.3.12", @@ -1759,6 +1760,21 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } } } } diff --git a/package.json b/package.json index c72b74f..3c3740d 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "break_eternity.js": "^2.1.3", "elkjs": "^0.12.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "yaml": "^2.9.0" }, "devDependencies": { "@types/react": "^18.3.12", diff --git a/src/components/BehaviourCard.tsx b/src/components/BehaviourCard.tsx index 93b837a..384d96a 100644 --- a/src/components/BehaviourCard.tsx +++ b/src/components/BehaviourCard.tsx @@ -6,6 +6,8 @@ import { Game } from '../engine/game'; import { ConverterCardInfo } from './ConverterCardInfo'; import { Converter } from '../engine/behaviours/converter'; import { Behaviour } from '../engine/behaviours/base'; +import * as yaml from 'yaml'; + interface Props { game: Game; @@ -71,15 +73,13 @@ export const BehaviourCard = forwardRef(function NodeCard )) } -
- { - info.links.map((link) =>
→{link.target}/{link.resource}
) - } -
+ {/*
+ {info.links.map((link) =>
→{link.target}/{link.resource}
)} +
*/} { behaviour.kind === 'converter' && } -
{JSON.stringify(info.raw, undefined, 4)}
+
{yaml.stringify(info.raw, undefined, 4)}
); }); diff --git a/src/components/ConverterCardInfo.tsx b/src/components/ConverterCardInfo.tsx index f319020..d098a07 100644 --- a/src/components/ConverterCardInfo.tsx +++ b/src/components/ConverterCardInfo.tsx @@ -7,7 +7,7 @@ type Props = { export const ConverterCardInfo = function ({ converter }: Props) { return <>
-
+
{converter.cycleTime.toFixed(4)} of {converter.cycleLength.toFixed(4)}
diff --git a/src/engine/behaviours/converter.ts b/src/engine/behaviours/converter.ts index c20b09b..40d06f8 100644 --- a/src/engine/behaviours/converter.ts +++ b/src/engine/behaviours/converter.ts @@ -95,8 +95,6 @@ export class Converter extends Behaviour { return { ...info, raw: { - cycleTime: this.cycleTime, - cycleLength: this.cycleLength, inputRates: this.inputRates, outputRates: this.outputRates, }, diff --git a/src/index.css b/src/index.css index d711b21..4d8d1e8 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,3 @@ -@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap'); - :root { --bg: #201f1c; --panel: #29271f; @@ -18,14 +16,19 @@ --display: 'Space Grotesk', ui-sans-serif, sans-serif; } -* { box-sizing: border-box; } +* { + box-sizing: border-box; +} -html, body, #root { +html, +body, +#root { margin: 0; height: 100%; background: var(--bg); color: var(--ink); font-family: var(--mono); + font-size: 10pt; } #app { @@ -49,7 +52,7 @@ html, body, #root { .topbar__title { font-family: var(--display); font-weight: 700; - font-size: 15px; + font-size: 150%; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink); @@ -62,7 +65,6 @@ html, body, #root { display: block; font-family: var(--mono); font-weight: 400; - font-size: 10px; letter-spacing: 0.04em; color: var(--ink-dim); text-transform: none; @@ -71,23 +73,23 @@ html, body, #root { .tick-counter { font-family: var(--mono); - font-size: 12px; color: var(--ink-dim); background: var(--panel-2); border: 1px solid var(--line); padding: 6px 12px; border-radius: 3px; } + .tick-counter b { color: var(--accent-converter); - font-size: 14px; } -.spacer { flex: 1; } +.spacer { + flex: 1; +} .btn { font-family: var(--mono); - font-size: 12px; font-weight: 500; letter-spacing: 0.03em; border: 1px solid var(--line); @@ -98,9 +100,19 @@ html, body, #root { cursor: pointer; transition: border-color 0.12s, transform 0.05s; } -.btn:hover { border-color: var(--ink-dim); } -.btn:active { transform: translateY(1px); } -.btn:focus-visible { outline: 2px solid var(--accent-converter); outline-offset: 2px; } + +.btn:hover { + border-color: var(--ink-dim); +} + +.btn:active { + transform: translateY(1px); +} + +.btn:focus-visible { + outline: 2px solid var(--accent-converter); + outline-offset: 2px; +} .btn--primary { background: var(--accent-converter); @@ -108,7 +120,10 @@ html, body, #root { color: #201f1c; font-weight: 600; } -.btn--primary:hover { border-color: #f0c169; } + +.btn--primary:hover { + border-color: #f0c169; +} .btn--danger { background: transparent; @@ -117,7 +132,10 @@ html, body, #root { margin-top: 14px; width: 100%; } -.btn--danger:hover { background: rgba(193, 90, 78, 0.12); } + +.btn--danger:hover { + background: rgba(193, 90, 78, 0.12); +} .btn--ghost { background: transparent; @@ -129,12 +147,10 @@ html, body, #root { .btn--icon { padding: 4px 8px; - font-size: 11px; } select { font-family: var(--mono); - font-size: 12px; background: var(--panel-2); color: var(--ink); border: 1px solid var(--line); @@ -169,14 +185,18 @@ select { height: 900px; } -#world > svg { +#world>svg { position: absolute; - top: 0; left: 0; + top: 0; + left: 0; width: 1600px; height: 900px; pointer-events: none; } -#world > svg line { pointer-events: stroke; } + +#world>svg line { + pointer-events: stroke; +} .node { position: absolute; @@ -184,28 +204,45 @@ select { border: 1px solid var(--line); border-left: 4px solid var(--accent-storage); border-radius: 4px; - padding: 8px 10px; + padding: 0.25em 0.5em; cursor: grab; user-select: none; - box-shadow: 0 2px 6px rgba(0,0,0,0.35); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35); touch-action: none; + display: flex; + flex-direction: column; + gap: 0.25em; } -.node:active { cursor: grabbing; } + +.node:active { + cursor: grabbing; +} + .node.is-selected { border-left-width: 4px; outline: 1.5px dashed var(--ink); outline-offset: 3px; } -.node--source { border-left-color: var(--accent-source); } -.node--storage { border-left-color: var(--accent-storage); } -.node--converter { border-left-color: var(--accent-converter); } -.node--consumer { border-left-color: var(--accent-consumer); } +.node--source { + border-left-color: var(--accent-source); +} + +.node--storage { + border-left-color: var(--accent-storage); +} + +.node--converter { + border-left-color: var(--accent-converter); +} + +.node--consumer { + border-left-color: var(--accent-consumer); +} .unit__id { font-family: var(--display); font-weight: 200; - font-size: 10px; color: var(--ink); white-space: nowrap; overflow: hidden; @@ -215,7 +252,7 @@ select { .unit__title { font-family: var(--display); font-weight: 600; - font-size: 12.5px; + font-size: 125%; color: var(--ink); white-space: nowrap; overflow: hidden; @@ -223,15 +260,12 @@ select { } .unit__kind { - font-size: 9px; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-dim); - margin: 2px 0 6px; } .unit__resources { - font-size: 11px; background: #171613; border: 1px solid var(--line); border-radius: 3px; @@ -241,7 +275,6 @@ select { } .unit__links { - font-size: 11px; background: #171613; border: 1px solid var(--line); border-radius: 3px; @@ -250,6 +283,37 @@ select { white-space: nowrap; } +.unit__converter__progress { + font-size: 75%; + background: #171613; + border: 1px solid var(--line); + border-radius: 3px; + padding: 1px 6px; + letter-spacing: 0.02em; + white-space: nowrap; + position: relative; + overflow: hidden; + min-height: 10px; + display: flex; + align-items: center; + justify-content: center; +} + +.unit__converter__progress_bar { + position: absolute; + inset: 0; + z-index: 1; + background: linear-gradient(90deg, var(--accent-converter), #f0c169); + opacity: 0.3; + transition: width 0.15s ease-out; +} + +.unit__converter__progress_info { + position: relative; + z-index: 2; + color: var(--ink); +} + .unit__sockets { position: absolute; top: 0; @@ -260,8 +324,14 @@ select { justify-content: center; gap: 6px; } -.unit__sockets--input { left: -5px; } -.unit__sockets--output { right: -5px; } + +.unit__sockets--input { + left: -5px; +} + +.unit__sockets--output { + right: -5px; +} .unit__socket { width: 10px; @@ -287,6 +357,7 @@ select { padding: 14px 16px; border-bottom: 1px solid var(--line); } + .sidebar__section--grow { flex: 1; border-bottom: none; @@ -308,6 +379,7 @@ select { max-height: 140px; overflow-y: auto; } + .sidebar__list li { padding: 6px 8px; font-size: 11.5px; @@ -318,8 +390,16 @@ select { overflow: hidden; text-overflow: ellipsis; } -.sidebar__list li:hover { background: var(--panel-2); color: var(--ink); } -.sidebar__list li.is-active { background: var(--panel-2); color: var(--accent-converter); } + +.sidebar__list li:hover { + background: var(--panel-2); + color: var(--ink); +} + +.sidebar__list li.is-active { + background: var(--panel-2); + color: var(--accent-converter); +} #sidebar-content { padding: 16px 0 0; @@ -349,7 +429,9 @@ select { font-size: 11px; color: var(--ink-dim); } -.field input, .field select { + +.field input, +.field select { font-family: var(--mono); font-size: 12px; background: var(--panel-2); @@ -359,13 +441,21 @@ select { padding: 7px 9px; width: 100%; } -.field input:focus-visible, .field select:focus-visible { + +.field input:focus-visible, +.field select:focus-visible { outline: 2px solid var(--accent-converter); outline-offset: 1px; } -.coord-row { display: flex; gap: 8px; } -.coord-row input { width: 50%; } +.coord-row { + display: flex; + gap: 8px; +} + +.coord-row input { + width: 50%; +} .recipe-box { border: 1px solid var(--line); @@ -374,6 +464,7 @@ select { margin-bottom: 12px; background: var(--panel-2); } + .recipe-box__title { font-size: 10.5px; text-transform: uppercase; @@ -381,15 +472,32 @@ select { color: var(--ink-dim); margin-bottom: 8px; } + .recipe-row { display: flex; gap: 6px; margin-bottom: 6px; align-items: center; } -.recipe-row select { flex: 1.4; } -.recipe-row input { flex: 1; } -::-webkit-scrollbar { width: 9px; height: 9px; } -::-webkit-scrollbar-track { background: var(--bg); } -::-webkit-scrollbar-thumb { background: var(--line); border-radius: 5px; } +.recipe-row select { + flex: 1.4; +} + +.recipe-row input { + flex: 1; +} + +::-webkit-scrollbar { + width: 9px; + height: 9px; +} + +::-webkit-scrollbar-track { + background: var(--bg); +} + +::-webkit-scrollbar-thumb { + background: var(--line); + border-radius: 5px; +} \ No newline at end of file diff --git a/src/prefabs/game.ts b/src/prefabs/game.ts index 9e080a3..7980b6c 100644 --- a/src/prefabs/game.ts +++ b/src/prefabs/game.ts @@ -15,7 +15,7 @@ export const defaultGame: () => SerializedGame = () => ({ outputs: { pins: [ { - resource: 'wood', + resource: 'tree', }, { resource: 'stone', @@ -24,7 +24,7 @@ export const defaultGame: () => SerializedGame = () => ({ }, rules: { cycleLength: 1, - outputRates: { wood: 1, stone: 0.1 }, + outputRates: { tree: 1, stone: 0.1 }, } }, }, @@ -37,22 +37,22 @@ export const defaultGame: () => SerializedGame = () => ({ inputs: { pins: [ { - resource: 'wood', - link: { target: 'forest.1', resource: 'wood' }, + resource: 'tree', + link: { target: 'forest.1', resource: 'tree' }, }, ] }, outputs: { pins: [ { - resource: 'plank', + resource: 'wood', }, ], }, rules: { cycleLength: 1.8, - inputRates: { wood: 1 }, - outputRates: { plank: 2 }, + inputRates: { tree: 1 }, + outputRates: { wood: 2 }, }, }, }, @@ -65,8 +65,8 @@ export const defaultGame: () => SerializedGame = () => ({ inputs: { pins: [ { - resource: 'plank', - link: { target: 'sawmill.1', resource: 'plank' }, + resource: 'wood', + link: { target: 'sawmill.1', resource: 'wood' }, }, ], }, @@ -75,7 +75,7 @@ export const defaultGame: () => SerializedGame = () => ({ }, rules: { cycleLength: 1, - inputRates: { plank: 1 }, + inputRates: { wood: 1 }, }, }, }, diff --git a/src/types/resource.ts b/src/types/resource.ts index 557baef..aaf57d9 100644 --- a/src/types/resource.ts +++ b/src/types/resource.ts @@ -1,5 +1,5 @@ import Decimal from "break_eternity.js"; -export type Resource = 'wood' | 'plank' | 'stone'; +export type Resource = 'tree' | 'wood' | 'stone'; export type Resources = Record; diff --git a/src/ui/graph.ts b/src/ui/graph.ts index 24a6b05..3d48c71 100644 --- a/src/ui/graph.ts +++ b/src/ui/graph.ts @@ -4,8 +4,8 @@ import { Behaviour } from "../engine/behaviours/base"; const elk = new ELK(); -const UNIT_WIDTH = 100; -const NODE_HEIGHT = 90; +const UNIT_WIDTH = 200; +const NODE_HEIGHT = 150; export type GraphNode = { position: { x: number, y: number }; @@ -21,7 +21,7 @@ export async function build2dNodes(units: Unit[], area: { width: number, height: layoutOptions: { "elk.algorithm": "layered", "elk.direction": "LEFT", - "elk.spacing.nodeNode": "60", + "elk.spacing.nodeNode": "100", "elk.layered.spacing.nodeNodeBetweenLayers": "100", }, children: units.map((unit) => ({ diff --git a/src/ui/resource.ts b/src/ui/resource.ts index 499868e..304a517 100644 --- a/src/ui/resource.ts +++ b/src/ui/resource.ts @@ -8,8 +8,8 @@ export type ResourceDescription = { } export const ResourceDescriptions: Record = { - wood: { id: 'wood', label: 'Дерево', color: '#b98249', icon: '🌲' }, - plank: { id: 'plank', label: 'Доска', color: '#c9c209', icon: 'P' }, + tree: { id: 'tree', label: 'Дерево', color: '#b98249', icon: '🌲' }, + wood: { id: 'wood', label: 'Бревно', color: '#c9c209', icon: '🪵' }, // iron: { id: 'iron', label: 'Железо', color: '#9aa7b0', icon: '⛓' }, stone: { id: 'stone', label: 'Камень', color: '#8a8a8a', icon: '🪨' }, // gold: { id: 'gold', label: 'Золото', color: '#d9b64e', icon: '💰' },