diff options
author | MetroWind <chris.corsair@gmail.com> | 2025-09-25 16:16:58 -0700 |
---|---|---|
committer | MetroWind <chris.corsair@gmail.com> | 2025-09-25 16:16:58 -0700 |
commit | 5f168246cca42ba54b67bd84fbd69fd6a5b9f820 (patch) | |
tree | ace07dfcb83ea1baac5062a02fe6d0a81ea4bf22 /nms-freighter-planner | |
parent | a1fa9840be9ca1b092f722be7b61c142cf80d8e8 (diff) |
This repo will be used for all small web apps.
The NMS Freighter Planner is moved into its own dir.
Diffstat (limited to 'nms-freighter-planner')
-rw-r--r-- | nms-freighter-planner/index.html | 25 | ||||
-rw-r--r-- | nms-freighter-planner/scripts/lib.js | 40 | ||||
-rw-r--r-- | nms-freighter-planner/scripts/main.js | 256 | ||||
-rw-r--r-- | nms-freighter-planner/scripts/tiles.js | 347 | ||||
-rw-r--r-- | nms-freighter-planner/styles.css | 332 |
5 files changed, 1000 insertions, 0 deletions
diff --git a/nms-freighter-planner/index.html b/nms-freighter-planner/index.html new file mode 100644 index 0000000..31f6a28 --- /dev/null +++ b/nms-freighter-planner/index.html | |||
@@ -0,0 +1,25 @@ | |||
1 | <!doctype html> | ||
2 | <html lang="en-US"> | ||
3 | <head> | ||
4 | <meta charset="utf-8" /> | ||
5 | <meta name="viewport" | ||
6 | content="width=device-width, initial-scale=1" /> | ||
7 | <link rel="stylesheet" href="styles.css" /> | ||
8 | <script src="https://cdnjs.cloudflare.com/ajax/libs/preact/10.27.1/preact.min.js" | ||
9 | integrity="sha512-tJqZlYpxIwLhPlIXYqzFfeEzWsb0eBbDJbAJcnfIhFKkF1pQ6Q5SkB+gFvZi0jt+7BPY69WrWyxBu5PLn1BJpw==" | ||
10 | crossorigin="anonymous" | ||
11 | referrerpolicy="no-referrer"></script> | ||
12 | <script src="https://cdnjs.cloudflare.com/ajax/libs/preact/10.27.1/hooks.umd.min.js" integrity="sha512-mODWC9TZn8iSfmbGnNgHErn0TQSFstq5vEBnkKJK7HPDgt9MpTECcYg50whbqW2yAHIYVnvpnW+owcEx/FnR3g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> | ||
13 | <script src="scripts/lib.js"></script> | ||
14 | <script src="scripts/tiles.js"></script> | ||
15 | <script type="module" defer src="scripts/main.js"></script> | ||
16 | <title>NMS Freighter Planner</title> | ||
17 | </head> | ||
18 | <body> | ||
19 | <div class="Window Dialog"> | ||
20 | <div class="Titlebar">NMS Freighter Planner</div> | ||
21 | <div id="Plan"> | ||
22 | </div> | ||
23 | </div> | ||
24 | </body> | ||
25 | </html> | ||
diff --git a/nms-freighter-planner/scripts/lib.js b/nms-freighter-planner/scripts/lib.js new file mode 100644 index 0000000..9a5e957 --- /dev/null +++ b/nms-freighter-planner/scripts/lib.js | |||
@@ -0,0 +1,40 @@ | |||
1 | const h = preact.h; | ||
2 | const CELL_SIZE = 32; | ||
3 | const GRID_SIZE_X = 21; | ||
4 | const GRID_SIZE_Y = 21; | ||
5 | const FLOOR_COUNT = 15; | ||
6 | |||
7 | const ASSET_SVG_PROPS = {"width": CELL_SIZE, "height": CELL_SIZE, | ||
8 | "version": "1.1"}; | ||
9 | const ASSET_WHITE_BG = h("rect", {"x": 0, "y": 0, "width": CELL_SIZE, | ||
10 | "height": CELL_SIZE, "fill": "white", | ||
11 | "stroke-width": 0 }); | ||
12 | |||
13 | // Bytes is a Uint8Array | ||
14 | async function compressBytes(bytes) | ||
15 | { | ||
16 | let blob = new Blob([bytes]); | ||
17 | const ds = new CompressionStream("deflate"); | ||
18 | const compressed = blob.stream().pipeThrough(ds); | ||
19 | let compressed_blob = await new Response(compressed).blob(); | ||
20 | const reader = new FileReader(); | ||
21 | return new Promise((resolve, _) => { | ||
22 | reader.onloadend = (event) => { | ||
23 | const result = event.target.result; | ||
24 | resolve(result.replace(/^data:.+;base64,/, '') | ||
25 | .replaceAll("/", "-").replaceAll("+", "_")); | ||
26 | } | ||
27 | reader.readAsDataURL(compressed_blob); | ||
28 | }); | ||
29 | } | ||
30 | |||
31 | // Decompress into Uint8Array | ||
32 | function decompressBytes(s) | ||
33 | { | ||
34 | const decoded = window.atob(s.replaceAll("_", "+").replaceAll("-", "/")); | ||
35 | const decoded_array = Uint8Array.from(decoded, c => c.charCodeAt(0)); | ||
36 | let blob = new Blob([decoded_array]); | ||
37 | const cs = new DecompressionStream("deflate"); | ||
38 | const decompressed = blob.stream().pipeThrough(cs); | ||
39 | return new Response(decompressed).bytes(); | ||
40 | } | ||
diff --git a/nms-freighter-planner/scripts/main.js b/nms-freighter-planner/scripts/main.js new file mode 100644 index 0000000..d6b6787 --- /dev/null +++ b/nms-freighter-planner/scripts/main.js | |||
@@ -0,0 +1,256 @@ | |||
1 | const DEFAULT_APP_STATE = { | ||
2 | floor: 0, // This is 0-based. | ||
3 | plan: new Array(FLOOR_COUNT).fill(null).map(() => | ||
4 | new Array(GRID_SIZE_X * GRID_SIZE_Y).fill(0)), | ||
5 | // mouse_state can be “normal”, or “dnd”. | ||
6 | mouse_state: "normal", | ||
7 | dragged_asset_index: null, | ||
8 | plan_code: "", | ||
9 | }; | ||
10 | |||
11 | async function serializePlan(app_state) | ||
12 | { | ||
13 | return compressBytes(Uint8Array.from(app_state.plan.flat())); | ||
14 | } | ||
15 | |||
16 | function genGrid(x_count, y_count, cell_size) | ||
17 | { | ||
18 | let lines = []; | ||
19 | for(let x = 0; x < x_count+1; x++) | ||
20 | { | ||
21 | lines.push(h("line", { | ||
22 | "x1": x * cell_size + 0.5, "x2": x * cell_size + 0.5, "y1": 0.5, | ||
23 | "y2": y_count * cell_size + 0.5, | ||
24 | })); | ||
25 | } | ||
26 | for(let y = 0; y < y_count+1; y++) | ||
27 | { | ||
28 | lines.push(h("line", { | ||
29 | "y1": y * cell_size + 0.5, "y2": y * cell_size + 0.5, "x1": 0.5, | ||
30 | "x2": x_count * cell_size + 0.5, | ||
31 | })); | ||
32 | } | ||
33 | return h("g", {"stroke": "black", "stroke-width": 1}, lines); | ||
34 | } | ||
35 | |||
36 | function AssetView({asset_index, on_drag_begin, on_drag_end}) | ||
37 | { | ||
38 | let bg = ASSET_WHITE_BG; | ||
39 | if(TILES[asset_index].bg === null) | ||
40 | { | ||
41 | bg = null; | ||
42 | } | ||
43 | return h("div", {"class": "Asset", "draggable": true, | ||
44 | "data-asset-index": asset_index, | ||
45 | "ondragstart": on_drag_begin, | ||
46 | "ondragend": on_drag_end, | ||
47 | "title": TILES[asset_index].name,}, | ||
48 | h("svg", ASSET_SVG_PROPS, bg, TILES[asset_index].inner_svg)); | ||
49 | } | ||
50 | |||
51 | function AssetsView({on_drag_begin, on_drag_end}) | ||
52 | { | ||
53 | let views = TILES.map((t, i) => | ||
54 | h(AssetView, {asset_index: i, on_drag_begin: on_drag_begin, | ||
55 | on_drag_end: on_drag_end})); | ||
56 | return h("div", {}, views); | ||
57 | } | ||
58 | |||
59 | function PlanGridView({content, mouse_state, on_drop_asset}) | ||
60 | { | ||
61 | const [mouse_coord, setMouseCoord] = preactHooks.useState(null); | ||
62 | |||
63 | function onDragOver(e) | ||
64 | { | ||
65 | e.preventDefault(); | ||
66 | let b = e.target.getBoundingClientRect(); | ||
67 | let x = e.clientX - b.left; | ||
68 | let y = e.clientY - b.top; | ||
69 | setMouseCoord([x, y]); | ||
70 | } | ||
71 | |||
72 | function onDragLeave(e) | ||
73 | { | ||
74 | setMouseCoord(null); | ||
75 | } | ||
76 | |||
77 | let hilight_box = null; | ||
78 | |||
79 | if(mouse_coord !== null && mouse_state != "dnd") | ||
80 | { | ||
81 | setMouseCoord(null); | ||
82 | } | ||
83 | |||
84 | if(mouse_coord !== null && mouse_state == "dnd") | ||
85 | { | ||
86 | let cell_x = Math.floor(mouse_coord[0] / (CELL_SIZE + 1)); | ||
87 | let cell_y = Math.floor(mouse_coord[1] / (CELL_SIZE + 1)); | ||
88 | hilight_box = h("rect", { | ||
89 | "x": cell_x * (CELL_SIZE + 1) + 0.5, "y": cell_y * (CELL_SIZE + 1) + 0.5, | ||
90 | "width": CELL_SIZE + 1, "height": CELL_SIZE + 1, | ||
91 | "stroke": "#2ed573", "stroke-width": 3, "fill": "transparent"}); | ||
92 | } | ||
93 | let grid_content = content.map((idx, i) => { | ||
94 | if(idx == 0) return null; | ||
95 | let cell_x = i % GRID_SIZE_X; | ||
96 | let cell_y = Math.floor(i / GRID_SIZE_Y); | ||
97 | let bg = ASSET_WHITE_BG; | ||
98 | if(TILES[idx].bg === null) | ||
99 | { | ||
100 | bg = null; | ||
101 | } | ||
102 | return h("g", {"transform": `translate(${cell_x * (CELL_SIZE + 1) + 1} \ | ||
103 | ${cell_y * (CELL_SIZE + 1) + 1})`}, bg, TILES[idx].inner_svg); | ||
104 | }); | ||
105 | |||
106 | let img_width = GRID_SIZE_X * (CELL_SIZE + 1) + 1; | ||
107 | let img_height = GRID_SIZE_Y * (CELL_SIZE + 1) + 1; | ||
108 | return h("svg", {"width": img_width, "height": img_height, | ||
109 | "version": "1.1", "ondragover": onDragOver, | ||
110 | "ondrop": on_drop_asset, | ||
111 | "id": "Grid", "ondragleave": onDragLeave,}, | ||
112 | h("rect", {x: 0, y: 0, width: img_width, height: img_height, | ||
113 | fill: "#c0c0c0", "stroke-width": 0}), | ||
114 | grid_content, genGrid(GRID_SIZE_X, GRID_SIZE_Y, CELL_SIZE + 1), | ||
115 | hilight_box); | ||
116 | } | ||
117 | |||
118 | function PlanView({plan, mouse_state, on_drop_asset}) | ||
119 | { | ||
120 | return h("div", {"id": "PlanView"}, | ||
121 | h("div", {"id": "EntryIndicator"}, "⬇️"), | ||
122 | h(PlanGridView, {content: plan, mouse_state: mouse_state, | ||
123 | on_drop_asset: on_drop_asset})); | ||
124 | } | ||
125 | |||
126 | // on_floor_change takes one argument, which is the 0-based floor number. | ||
127 | function FloorSelector({floor, on_floor_change}) | ||
128 | { | ||
129 | return h("div", {"id": "FloorSelectorWrapper", "class": "ButtonRowLeft"}, | ||
130 | h("label", {}, "Floor"), | ||
131 | h("input", {"id": "InputFloor", "type": "number", "step": 1, "min": 1, | ||
132 | "max": 15, "value": floor, | ||
133 | onchange: e => on_floor_change(e.target.value - 1),},)); | ||
134 | } | ||
135 | |||
136 | function App({initial_state}) | ||
137 | { | ||
138 | const [state, setState] = preactHooks.useState(initial_state); | ||
139 | |||
140 | // This event is targeted at the dragged asset. | ||
141 | function onDragAssetBegin(e) | ||
142 | { | ||
143 | let new_state = structuredClone(state); | ||
144 | new_state.mouse_state = "dnd"; | ||
145 | new_state.dragged_asset_index = | ||
146 | parseInt(e.target.getAttribute("data-asset-index")); | ||
147 | setState(new_state); | ||
148 | } | ||
149 | |||
150 | // This event is targeted at the grid. | ||
151 | function onDropAssetOnGrid(e) | ||
152 | { | ||
153 | if(e.dragEffect === "none") return; | ||
154 | let grid = document.getElementById("Grid"); | ||
155 | let b = grid.getBoundingClientRect(); | ||
156 | let x = e.clientX - b.left; | ||
157 | let y = e.clientY - b.top; | ||
158 | let cell_x = Math.floor(x / (CELL_SIZE + 1)); | ||
159 | let cell_y = Math.floor(y / (CELL_SIZE + 1)); | ||
160 | let cell_index = cell_y * GRID_SIZE_X + cell_x; | ||
161 | let new_state = structuredClone(state); | ||
162 | new_state.plan[new_state.floor][cell_index] = state.dragged_asset_index; | ||
163 | new_state.mouse_state = "normal"; | ||
164 | new_state.dragged_asset_index = null; | ||
165 | serializePlan(new_state).then((s) => { | ||
166 | new_state.plan_code = s; | ||
167 | setState(new_state); | ||
168 | const url = new URL(location); | ||
169 | url.searchParams.set("plan", s); | ||
170 | window.history.pushState({}, "", url); | ||
171 | }); | ||
172 | } | ||
173 | |||
174 | function onClickSaveImg() | ||
175 | { | ||
176 | let grid_width = GRID_SIZE_X * (CELL_SIZE + 1) + 1; | ||
177 | let grid_height = GRID_SIZE_Y * (CELL_SIZE + 1) + 1; | ||
178 | let canvas = document.createElement('canvas'); | ||
179 | canvas.width = grid_width; | ||
180 | canvas.height = grid_height; | ||
181 | let context = canvas.getContext("2d"); | ||
182 | var svg_str = new XMLSerializer().serializeToString( | ||
183 | document.getElementById("Grid")); | ||
184 | var img = new Image(); | ||
185 | img.onload = function() | ||
186 | { | ||
187 | context.drawImage(this, 0, 0); | ||
188 | // Open PNG image in new tab. | ||
189 | var dataURL = canvas.toDataURL("image/png"); | ||
190 | var new_tab = window.open('about:blank', 'image from canvas'); | ||
191 | new_tab.document.write("<img src='" + dataURL + "' alt='from canvas'/>"); | ||
192 | } | ||
193 | img.src = 'data:image/svg+xml; charset=utf8, ' + | ||
194 | encodeURIComponent(svg_str); | ||
195 | } | ||
196 | |||
197 | // new_floor is 0-based. | ||
198 | function onFloorChange(new_floor) | ||
199 | { | ||
200 | let new_state = structuredClone(state); | ||
201 | new_state.floor = new_floor; | ||
202 | setState(new_state); | ||
203 | } | ||
204 | |||
205 | return h("div", {}, | ||
206 | h("div", {"class": "MainWrapper"}, | ||
207 | h("aside", {}, | ||
208 | h(FloorSelector, {floor: state.floor + 1, | ||
209 | on_floor_change: onFloorChange}), | ||
210 | h("div", {"class": "LabeledPanel"}, | ||
211 | h("h2", {}, "Rooms"), | ||
212 | h(AssetsView, {on_drag_begin: onDragAssetBegin,}))), | ||
213 | h(PlanView, {plan: state.plan[state.floor], | ||
214 | mouse_state: state.mouse_state, | ||
215 | on_drop_asset: onDropAssetOnGrid,})), | ||
216 | h("hr", {}), | ||
217 | h("div", {}, | ||
218 | h("textarea", {readonly: true}, state.plan_code)), | ||
219 | h("div", {"class": "ButtonRow"}, | ||
220 | h("button", {onclick: onClickSaveImg, type: "button"}, | ||
221 | "Open as Image!")), | ||
222 | ); | ||
223 | } | ||
224 | |||
225 | function render(app_state) | ||
226 | { | ||
227 | preact.render(h(App, {initial_state: app_state}), | ||
228 | document.getElementById("Plan")); | ||
229 | } | ||
230 | |||
231 | const paramsString = window.location.search; | ||
232 | const searchParams = new URLSearchParams(paramsString); | ||
233 | const encoded_plan = searchParams.get("plan"); | ||
234 | if(encoded_plan === null) | ||
235 | { | ||
236 | render(DEFAULT_APP_STATE); | ||
237 | } | ||
238 | else | ||
239 | { | ||
240 | decompressBytes(encoded_plan).then(a => { | ||
241 | let state = structuredClone(DEFAULT_APP_STATE); | ||
242 | let concated_plan = Array.from(a); | ||
243 | console.debug(concated_plan); | ||
244 | state.plan = []; | ||
245 | for(let i = 0; i < GRID_SIZE_X * GRID_SIZE_Y * FLOOR_COUNT; | ||
246 | i += GRID_SIZE_X * GRID_SIZE_Y) | ||
247 | { | ||
248 | let floor_slice = | ||
249 | concated_plan.slice(i, i + GRID_SIZE_X * GRID_SIZE_Y); | ||
250 | console.debug(floor_slice); | ||
251 | state.plan.push(floor_slice); | ||
252 | } | ||
253 | state.plan_code = encoded_plan; | ||
254 | render(state); | ||
255 | }); | ||
256 | } | ||
diff --git a/nms-freighter-planner/scripts/tiles.js b/nms-freighter-planner/scripts/tiles.js new file mode 100644 index 0000000..2ed5691 --- /dev/null +++ b/nms-freighter-planner/scripts/tiles.js | |||
@@ -0,0 +1,347 @@ | |||
1 | // TODO: how does internal walls & ladder work?? | ||
2 | const TILES = [ | ||
3 | { | ||
4 | id: "null", | ||
5 | name: "Nothing", | ||
6 | bg: null, | ||
7 | inner_svg: null, | ||
8 | }, { | ||
9 | id: "blank", | ||
10 | name: "Generic", | ||
11 | inner_svg: null, | ||
12 | }, { | ||
13 | id: "corridor_ns", | ||
14 | name: "Corridor N-S", | ||
15 | bg: null, | ||
16 | inner_svg: h("rect", { | ||
17 | x: CELL_SIZE / 4, y: 0, width: CELL_SIZE / 2, height: CELL_SIZE, | ||
18 | fill: "white"}), | ||
19 | }, { | ||
20 | id: "corridor_we", | ||
21 | name: "Corridor W-E", | ||
22 | bg: null, | ||
23 | inner_svg: h("rect", { | ||
24 | y: CELL_SIZE / 4, x: 0, height: CELL_SIZE / 2, width: CELL_SIZE, | ||
25 | fill: "white"}), | ||
26 | }, { | ||
27 | id: "corridor_ne", | ||
28 | name: "Corridor N-E", | ||
29 | bg: null, | ||
30 | inner_svg: h("path", { | ||
31 | d: `M ${CELL_SIZE * 0.5} 0 \ | ||
32 | A ${CELL_SIZE * 0.5} ${CELL_SIZE * 0.5} 0 0 0 \ | ||
33 | ${CELL_SIZE} ${CELL_SIZE * 0.5}`, | ||
34 | stroke: "white", "stroke-width": CELL_SIZE * 0.5}), | ||
35 | }, { | ||
36 | id: "corridor_se", | ||
37 | name: "Corridor S-E", | ||
38 | bg: null, | ||
39 | inner_svg: h("path", { | ||
40 | d: `M ${CELL_SIZE * 0.5} ${CELL_SIZE} \ | ||
41 | A ${CELL_SIZE * 0.5} ${CELL_SIZE * 0.5} 0 0 1 \ | ||
42 | ${CELL_SIZE} ${CELL_SIZE * 0.5}`, | ||
43 | stroke: "white", "stroke-width": CELL_SIZE * 0.5}), | ||
44 | }, { | ||
45 | id: "corridor_sw", | ||
46 | name: "Corridor S-W", | ||
47 | bg: null, | ||
48 | inner_svg: h("path", { | ||
49 | d: `M ${CELL_SIZE * 0.5} ${CELL_SIZE} \ | ||
50 | A ${CELL_SIZE * 0.5} ${CELL_SIZE * 0.5} 0 0 0 \ | ||
51 | 0 ${CELL_SIZE * 0.5}`, | ||
52 | stroke: "white", "stroke-width": CELL_SIZE * 0.5}), | ||
53 | }, { | ||
54 | id: "corridor_nw", | ||
55 | name: "Corridor N-W", | ||
56 | bg: null, | ||
57 | inner_svg: h("path", { | ||
58 | d: `M ${CELL_SIZE * 0.5} 0 \ | ||
59 | A ${CELL_SIZE * 0.5} ${CELL_SIZE * 0.5} 0 0 1 \ | ||
60 | 0 ${CELL_SIZE * 0.5}`, | ||
61 | stroke: "white", "stroke-width": CELL_SIZE * 0.5}), | ||
62 | }, { | ||
63 | id: "corridor_3n", | ||
64 | name: "Three-way corridor 1", | ||
65 | bg: null, | ||
66 | inner_svg: | ||
67 | h(preact.Fragment, {}, | ||
68 | h("rect", { | ||
69 | y: CELL_SIZE / 4, x: 0, height: CELL_SIZE / 2, width: CELL_SIZE, | ||
70 | fill: "white"}), | ||
71 | h("rect", { | ||
72 | x: CELL_SIZE / 4, y: 0, width: CELL_SIZE * 0.5, | ||
73 | height: CELL_SIZE * 0.5, fill: "white"})), | ||
74 | }, { | ||
75 | id: "corridor_3e", | ||
76 | name: "Three-way corridor 2", | ||
77 | bg: null, | ||
78 | inner_svg: | ||
79 | h(preact.Fragment, {}, | ||
80 | h("rect", { | ||
81 | x: CELL_SIZE / 4, y: 0, width: CELL_SIZE / 2, height: CELL_SIZE, | ||
82 | fill: "white"}), | ||
83 | h("rect", { | ||
84 | y: CELL_SIZE / 4, x: CELL_SIZE * 0.5, height: CELL_SIZE / 2, | ||
85 | width: CELL_SIZE * 0.5, fill: "white"})) | ||
86 | }, { | ||
87 | id: "corridor_3s", | ||
88 | name: "Three-way corridor 3", | ||
89 | bg: null, | ||
90 | inner_svg: | ||
91 | h(preact.Fragment, {}, | ||
92 | h("rect", { | ||
93 | y: CELL_SIZE / 4, x: 0, height: CELL_SIZE / 2, width: CELL_SIZE, | ||
94 | fill: "white"}), | ||
95 | h("rect", { | ||
96 | x: CELL_SIZE / 4, y: CELL_SIZE * 0.5, width: CELL_SIZE * 0.5, | ||
97 | height: CELL_SIZE * 0.5, fill: "white"})), | ||
98 | }, { | ||
99 | id: "corridor_3e", | ||
100 | name: "Three-way corridor 4", | ||
101 | bg: null, | ||
102 | inner_svg: | ||
103 | h(preact.Fragment, {}, | ||
104 | h("rect", { | ||
105 | x: CELL_SIZE / 4, y: 0, width: CELL_SIZE / 2, height: CELL_SIZE, | ||
106 | fill: "white"}), | ||
107 | h("rect", { | ||
108 | y: CELL_SIZE / 4, x: 0, height: CELL_SIZE / 2, | ||
109 | width: CELL_SIZE * 0.5, fill: "white"})) | ||
110 | }, { | ||
111 | id: "stair_up", | ||
112 | name: "Stair up", | ||
113 | inner_svg: | ||
114 | h(preact.Fragment, {}, | ||
115 | h("polygon", { | ||
116 | points: `5, ${CELL_SIZE} \ | ||
117 | 5, ${5 + (CELL_SIZE - 5) / 3 * 2} \ | ||
118 | ${5 + (CELL_SIZE - 5) / 3}, ${5 + (CELL_SIZE - 5) / 3 * 2} \ | ||
119 | ${5 + (CELL_SIZE - 5) / 3}, ${5 + (CELL_SIZE - 5) / 3} \ | ||
120 | ${5 + (CELL_SIZE - 5) / 3 * 2}, ${5 + (CELL_SIZE - 5) / 3} \ | ||
121 | ${5 + (CELL_SIZE - 5) / 3 * 2}, 5 \ | ||
122 | ${CELL_SIZE}, 5 \ | ||
123 | ${CELL_SIZE}, ${CELL_SIZE}`, | ||
124 | fill: "black", "stroke-width": 0}), | ||
125 | h("line", {x1: 3, y1: CELL_SIZE - 15, x2: CELL_SIZE - 15, y2: 3, | ||
126 | stroke: "black", "stroke-width": 2}), | ||
127 | h("polyline", {points: `${CELL_SIZE - 15 - 6}, 3 \ | ||
128 | ${CELL_SIZE - 15}, 3 \ | ||
129 | ${CELL_SIZE - 15}, ${3 + 6}`, | ||
130 | stroke: "black", "stroke-width": 2})), | ||
131 | |||
132 | }, { | ||
133 | id: "stair_down", | ||
134 | name: "Stair down", | ||
135 | inner_svg: | ||
136 | h(preact.Fragment, {}, | ||
137 | h("polygon", { | ||
138 | points: `5, ${CELL_SIZE} \ | ||
139 | 5, ${5 + (CELL_SIZE - 5) / 3 * 2} \ | ||
140 | ${5 + (CELL_SIZE - 5) / 3}, ${5 + (CELL_SIZE - 5) / 3 * 2} \ | ||
141 | ${5 + (CELL_SIZE - 5) / 3}, ${5 + (CELL_SIZE - 5) / 3} \ | ||
142 | ${5 + (CELL_SIZE - 5) / 3 * 2}, ${5 + (CELL_SIZE - 5) / 3} \ | ||
143 | ${5 + (CELL_SIZE - 5) / 3 * 2}, 5 \ | ||
144 | ${CELL_SIZE}, 5 \ | ||
145 | ${CELL_SIZE}, ${CELL_SIZE}`, | ||
146 | fill: "black", "stroke-width": 0}), | ||
147 | h("line", {x1: 3, y1: CELL_SIZE - 15, x2: CELL_SIZE - 15, y2: 3, | ||
148 | stroke: "black", "stroke-width": 2}), | ||
149 | h("polyline", {points: `3, ${CELL_SIZE - 15 - 6} \ | ||
150 | 3, ${CELL_SIZE - 15} \ | ||
151 | ${3 + 6}, ${CELL_SIZE - 15}`, | ||
152 | stroke: "black", "stroke-width": 2})), | ||
153 | |||
154 | }, { | ||
155 | id: "tech", | ||
156 | name: "Technology room", | ||
157 | inner_svg: h("text", {x: CELL_SIZE * 0.5, y: CELL_SIZE * 0.5, dy: 3, | ||
158 | "font-family": "monospace", "font-weight": "bold", | ||
159 | "font-size": 32, "text-anchor": "middle", | ||
160 | "dominant-baseline": "middle", fill: "#c0c0c0", }, | ||
161 | "T"), | ||
162 | }, { | ||
163 | id: "industry", | ||
164 | name: "Industrial room", | ||
165 | inner_svg: h("text", {x: CELL_SIZE * 0.5, y: CELL_SIZE * 0.5, dy: 3, | ||
166 | "font-family": "monospace", "font-weight": "bold", | ||
167 | "font-size": 32, "text-anchor": "middle", | ||
168 | "dominant-baseline": "middle", fill: "#c0c0c0", }, | ||
169 | "I"), | ||
170 | }, { | ||
171 | id: "biology", | ||
172 | name: "Biological room", | ||
173 | inner_svg: h("text", {x: CELL_SIZE * 0.5, y: CELL_SIZE * 0.5, dy: 3, | ||
174 | "font-family": "monospace", "font-weight": "bold", | ||
175 | "font-size": 32, "text-anchor": "middle", | ||
176 | "dominant-baseline": "middle", fill: "#c0c0c0", }, | ||
177 | "B"), | ||
178 | }, { | ||
179 | id: "appearance", | ||
180 | name: "Appearance modifier", | ||
181 | inner_svg: h("g", {"fill": "black", "stroke-width": 0, | ||
182 | "transform": "translate(2.921 1.0547)"}, | ||
183 | h("path", {d: "m28.151 27.442 4e-3 -5e-3c-2.0135-2.2928-4.2222-1.0005-6.0958-0.27575-1.651 0.721-3.213 1.402-4.92 0.749-0.871-0.33-1.423-0.809-1.645-1.419-0.184-0.502-0.135-1.045-0.028-1.488l1.228 0.22c0.619 0.109 1.218-0.307 1.326-0.925l1.508-8.447c3.688-0.714 6.474-3.955 6.474-7.851 0-4.419-3.582-8-8-8-2.524 0-4.772 1.173-6.239 3h-7.763v9h7.079c0.565 0.977 1.34 1.813 2.25 2.474l-1.556 8.71c-0.11 0.618 0.306 1.216 0.925 1.326l1.081 0.191c-0.15 0.686-0.201 1.527 0.099 2.362 0.394 1.104 1.287 1.931 2.647 2.451 2.365 0.899 4.412 6e-3 6.218-0.78 1.475-0.6585 2.9852-1.5128 4.1748-0.11225l6e-3 -7e-3c0.15 0.136 0.35 0.218 0.566 0.218 0.473 0 0.854-0.382 0.854-0.852 0-0.203-0.072-0.391-0.193-0.539zm-10.151-23.442c2.206 0 4 1.794 4 4 0 2.205-1.794 4-4 4s-4-1.795-4-4c0-2.206 1.794-4 4-4z"}), | ||
184 | h("circle", {cx: 18, cy: 8, r: 2}), | ||
185 | h("path", {d: "m 0,4.001 v 6.997 C 0,11.552 0.448,12 1.001,12 H 3 V 3 H 1.001 C 0.448,3 0,3.448 0,4.001 Z"})), | ||
186 | }, { | ||
187 | id: "construction", | ||
188 | name: "Construction specialist’s room", | ||
189 | inner_svg: h("text", {x: CELL_SIZE * 0.5, y: CELL_SIZE * 0.5, dy: 3, | ||
190 | "font-family": "monospace", "font-weight": "bold", | ||
191 | "font-size": 32, "text-anchor": "middle", | ||
192 | "dominant-baseline": "middle", fill: "black",}, | ||
193 | "C"), | ||
194 | }, { | ||
195 | id: "science", | ||
196 | name: "Scientist’s room", | ||
197 | inner_svg: h("text", {x: CELL_SIZE * 0.5, y: CELL_SIZE * 0.5, dy: 3, | ||
198 | "font-family": "monospace", "font-weight": "bold", | ||
199 | "font-size": 32, "text-anchor": "middle", | ||
200 | "dominant-baseline": "middle", fill: "black",}, | ||
201 | "S"), | ||
202 | }, { | ||
203 | id: "agricultural", | ||
204 | name: "Agricultural specialist’s room", | ||
205 | inner_svg: h("text", {x: CELL_SIZE * 0.5, y: CELL_SIZE * 0.5, dy: 3, | ||
206 | "font-family": "monospace", "font-weight": "bold", | ||
207 | "font-size": 32, "text-anchor": "middle", | ||
208 | "dominant-baseline": "middle", fill: "black",}, | ||
209 | "A"), | ||
210 | }, { | ||
211 | id: "exocrafter", | ||
212 | name: "Exocraft specialist’s room", | ||
213 | inner_svg: h("text", {x: CELL_SIZE * 0.5, y: CELL_SIZE * 0.5, dy: 3, | ||
214 | "font-family": "monospace", "font-weight": "bold", | ||
215 | "font-size": 32, "text-anchor": "middle", | ||
216 | "dominant-baseline": "middle", fill: "black",}, | ||
217 | "E"), | ||
218 | }, { | ||
219 | id: "trade", | ||
220 | name: "Galaxtic trade", | ||
221 | inner_svg: h("text", {x: CELL_SIZE * 0.5, y: CELL_SIZE * 0.5, dy: 3, | ||
222 | "font-family": "monospace", "font-weight": "bold", | ||
223 | "font-size": 28, "text-anchor": "middle", | ||
224 | "dominant-baseline": "middle", fill: "black",}, | ||
225 | "$"), | ||
226 | }, { | ||
227 | id: "teleporter", | ||
228 | name: "Teleporter", | ||
229 | inner_svg: h("circle", { | ||
230 | "cx": CELL_SIZE * 0.5, "cy": CELL_SIZE * 0.5, | ||
231 | "r": (CELL_SIZE - 10) * 0.5, "stroke": "black", | ||
232 | "fill": "transparent", "stroke-width": 6, | ||
233 | }), | ||
234 | }, { | ||
235 | id: "fleet_command", | ||
236 | name: "Fleet command", | ||
237 | inner_svg: h("polyline", { | ||
238 | "points": `3, ${CELL_SIZE} \ | ||
239 | 3, ${CELL_SIZE - 15} \ | ||
240 | 13, ${CELL_SIZE - 25} \ | ||
241 | 13, ${CELL_SIZE - 5} \ | ||
242 | ${CELL_SIZE - 13}, ${CELL_SIZE - 5} \ | ||
243 | ${CELL_SIZE - 13}, ${CELL_SIZE - 25} \ | ||
244 | ${CELL_SIZE - 3}, ${CELL_SIZE - 15} \ | ||
245 | ${CELL_SIZE - 3}, ${CELL_SIZE}`, | ||
246 | "fill": "black", "stroke-width": 0, | ||
247 | }), | ||
248 | }, { | ||
249 | id: "scanner", | ||
250 | name: "Scanner", | ||
251 | inner_svg: | ||
252 | h(preact.Fragment, {}, | ||
253 | h("circle", {"cx": CELL_SIZE * 0.5, "cy": CELL_SIZE * 0.5, | ||
254 | "r": CELL_SIZE * 0.5 - 2, "fill": "transparent", | ||
255 | "stroke": "black", "stroke-width": 2}), | ||
256 | h("circle", {"cx": CELL_SIZE * 0.5, "cy": CELL_SIZE * 0.5, | ||
257 | "r": CELL_SIZE * 0.25, "fill": "transparent", | ||
258 | "stroke": "black", "stroke-width": 2}), | ||
259 | h("line", {"x1": 2, "y1": CELL_SIZE * 0.5, | ||
260 | "x2": CELL_SIZE - 2, "y2": CELL_SIZE * 0.5, | ||
261 | "stroke": "black", "stroke-width": 2}), | ||
262 | h("line", {"y1": 2, "x1": CELL_SIZE * 0.5, | ||
263 | "y2": CELL_SIZE - 2, "x2": CELL_SIZE * 0.5, | ||
264 | "stroke": "black", "stroke-width": 2}), | ||
265 | h("line", {"x1": CELL_SIZE * 0.5, "y1": CELL_SIZE * 0.5, | ||
266 | "x2": CELL_SIZE * 0.5 + (CELL_SIZE * 0.5 - 2) * Math.cos(Math.PI / 6), | ||
267 | "y2": CELL_SIZE * 0.5 - (CELL_SIZE * 0.5 - 2) * Math.sin(Math.PI / 6), | ||
268 | "stroke": "black", "stroke-width": 2}), | ||
269 | h("circle", {"cx": 22, "cy": 6, "r": 2, "fill": "black", | ||
270 | "stroke-width": 0}), | ||
271 | h("circle", {"cx": 10, "cy": 22, "r": 4, "fill": "black", | ||
272 | "stroke-width": 0}), | ||
273 | |||
274 | ), | ||
275 | }, { | ||
276 | id: "extractor", | ||
277 | name: "Stellar extractor", | ||
278 | inner_svg: | ||
279 | h(preact.Fragment, {}, | ||
280 | h("circle", {"cx": CELL_SIZE * 0.5, "cy": 21, | ||
281 | "r": 10, "fill": "black", "stroke-width": 0}), | ||
282 | h("polygon", {"points": `${CELL_SIZE * 0.5 - 4}, 20 \ | ||
283 | ${CELL_SIZE * 0.5 - 4}, 10 \ | ||
284 | ${CELL_SIZE * 0.5 - 8}, 10 \ | ||
285 | ${CELL_SIZE * 0.5}, 3 \ | ||
286 | ${CELL_SIZE * 0.5 + 8}, 10 \ | ||
287 | ${CELL_SIZE * 0.5 + 4}, 10 \ | ||
288 | ${CELL_SIZE * 0.5 + 4}, 20`, | ||
289 | "stroke": "black", "stroke-width": 2, | ||
290 | "fill": "white"}), | ||
291 | ), | ||
292 | }, { | ||
293 | id: "storage", | ||
294 | name: "Storage", | ||
295 | inner_svg: | ||
296 | h(preact.Fragment, {}, | ||
297 | h("rect", {"x": 4, "y": 4, "width": CELL_SIZE - 8, | ||
298 | "height": CELL_SIZE - 8, "rx": 2, "ry": 2, | ||
299 | "fill": "black", "stroke-width": 0}), | ||
300 | h("line", {"x1": 4, "y1": 14, "x2": CELL_SIZE - 4, "y2": 14, | ||
301 | "stroke": "white", "stroke-width": 2}), | ||
302 | h("rect", {"x": CELL_SIZE * 0.5 - 4, "y": 10, "width": 8, "height": 8, | ||
303 | "fill": "white", "stroke-width": 0}), | ||
304 | ), | ||
305 | }, { | ||
306 | id: "plant", | ||
307 | name: "Cultivation chamber", | ||
308 | inner_svg: h("path", {"d": "m3.02 17.7a13 13 0 0013 13 13 13 0 00-13-13m13 13a13 13 0 0013-13 13 13 0 00-13 13m8.66-27.4v7.21a8.66 8.66 0 01-8.66 8.66 8.66 8.66 0 01-8.66-8.66v-7.21c1.07 0 2.12.173 3.12.563.793.332 1.5.821 2.09 1.44l3.43-3.43 3.43 3.43c.591-.622 1.3-1.11 2.09-1.44.996-.388 2.05-.563 3.12-.563z", | ||
309 | "fill": "black", "stroke-width": 0}), | ||
310 | }, { | ||
311 | id: "refiner", | ||
312 | name: "Refiner", | ||
313 | inner_svg: | ||
314 | h(preact.Fragment, {}, | ||
315 | h("polyline", { | ||
316 | points: `5, 5 \ | ||
317 | ${CELL_SIZE * 0.5}, 5 \ | ||
318 | ${CELL_SIZE * 0.5}, ${CELL_SIZE - 5} \ | ||
319 | 5, ${CELL_SIZE - 5}`, fill: "transparent", stroke: "black", "stroke-width": 2}), | ||
320 | h("line", {x1: 5, y1: CELL_SIZE * 0.5, x2: CELL_SIZE - 5, | ||
321 | y2: CELL_SIZE * 0.5, stroke: "black", "stroke-width": 2}), | ||
322 | h("circle", {cx: 5, cy: CELL_SIZE * 0.5, r: 3, fill: "black", | ||
323 | "stroke-width": 0}), | ||
324 | h("circle", {cx: CELL_SIZE - 5, cy: CELL_SIZE * 0.5, r: 3, | ||
325 | fill: "black", "stroke-width": 0}), | ||
326 | h("circle", {cx: 5, cy: 5, r: 3, fill: "black", "stroke-width": 0}), | ||
327 | h("circle", {cx: 5, cy: CELL_SIZE - 5, r: 3, fill: "black", | ||
328 | "stroke-width": 0}), | ||
329 | ), | ||
330 | }, { | ||
331 | id: "nutrition", | ||
332 | name: "Nutrition", | ||
333 | inner_svg: | ||
334 | h("path", { | ||
335 | d: "M 11 10 C 11.2727 7.45014 13.3891 5.34782 16 5.34782 C 18.6108 5.30432 20.7272 7.40664 20.7272 10 L 21.9091 10 C 24.5199 10.0435 26.6363 12.1457 26.6363 14.7392 C 26.6363 16.8978 24.5199 19 21.9091 19 L 21 19 L 21 23 L 11 23 L 11 19 L 10.0909 19 C 7.48012 19.4347 5.36364 17.3325 5.36364 14.7392 C 5.36364 12.1457 7.48012 10.0435 10.0909 10.0435 Z M 9.29722 7.73927 C 10.2581 4.98084 12.8962 3 16 3 C 19.1037 3 21.7419 4.98084 22.7028 7.73929 C 26.2454 8.1312 29 11.1157 29 14.7392 C 28.9091 18.229 26.3537 21.1263 23 21.6852 L 23 28.826 L 21.9091 30 L 10.0909 30 L 9 28.826 L 9 21.6852 C 5.55544 21.1263 3 18.229 3 14.7392 C 3 11.1157 5.7546 8.1312 9.29722 7.73927 Z M 11 27.6521 L 11 25 L 21 25 L 21 27.6521 Z", | ||
336 | fill: "black", "stroke-width": 0, "fill-rule": "evenodd"}), | ||
337 | }, { | ||
338 | id: "exocraft", | ||
339 | name: "Exocraft materialiser", | ||
340 | inner_svg: h("path", {d: "m 11.71,23.3 h 9.95 m -9.95,0 c 0,1.37 -1.11,2.49 -2.49,2.49 -1.37,0 -2.49,-1.11 -2.49,-2.49 m 4.98,0 c 0,-1.37 -1.11,-2.49 -2.49,-2.49 -1.37,0 -2.49,1.11 -2.49,2.49 m 14.93,0 c 0,1.37 1.11,2.49 2.49,2.49 1.37,0 2.49,-1.11 2.49,-2.49 m -4.98,0 c 0,-1.37 1.11,-2.49 2.49,-2.49 1.37,0 2.49,1.11 2.49,2.49 M 14.2,8.37 v 7.47 m -7.47,0 0.41,-2.47 C 7.44,11.59 7.59,10.7 8.03,10.04 8.42,9.45 8.97,8.98 9.61,8.7 10.34,8.37 11.24,8.37 13.04,8.37 h 4.19 c 1.17,0 1.75,0 2.28,0.16 0.47,0.14 0.91,0.38 1.29,0.69 0.43,0.35 0.75,0.84 1.4,1.81 l 3.2,4.81 M 6.74,23.3 H 6.24 c -0.7,0 -1.05,0 -1.31,-0.14 C 4.69,23.05 4.5,22.86 4.38,22.62 4.25,22.36 4.25,22.01 4.25,21.31 v -1.49 c 0,-1.39 0,-2.09 0.27,-2.62 0.24,-0.47 0.62,-0.85 1.09,-1.09 0.53,-0.27 1.23,-0.27 2.62,-0.27 h 14.93 c 0.92,0 1.39,0 1.77,0.06 2.13,0.34 3.8,2.01 4.14,4.14 0.06,0.39 0.06,0.85 0.06,1.77 0,0.23 0,0.35 -0.02,0.44 -0.08,0.53 -0.5,0.95 -1.03,1.03 -0.1,0.02 -0.21,0.02 -0.44,0.02 H 26.64", | ||
341 | "stroke": "black", | ||
342 | "fill": "transparent", "stroke-width": 2.48853, | ||
343 | }), | ||
344 | }, | ||
345 | ]; | ||
346 | |||
347 | const TILE_MAP = Object.fromEntries(TILES.slice(1).map(t => [t.id, t])); | ||
diff --git a/nms-freighter-planner/styles.css b/nms-freighter-planner/styles.css new file mode 100644 index 0000000..2c02554 --- /dev/null +++ b/nms-freighter-planner/styles.css | |||
@@ -0,0 +1,332 @@ | |||
1 | /* http://meyerweb.com/eric/tools/css/reset/ | ||
2 | v2.0 | 20110126 | ||
3 | License: none (public domain) | ||
4 | */ | ||
5 | |||
6 | html, body, div, span, applet, object, iframe, | ||
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, | ||
8 | a, abbr, acronym, address, big, cite, code, | ||
9 | del, dfn, em, img, ins, kbd, q, s, samp, | ||
10 | small, strike, strong, sub, sup, tt, var, | ||
11 | b, u, i, center, | ||
12 | dl, dt, dd, ol, ul, li, | ||
13 | fieldset, form, label, legend, | ||
14 | table, caption, tbody, tfoot, thead, tr, th, td, | ||
15 | article, aside, canvas, details, embed, | ||
16 | figure, figcaption, footer, header, hgroup, | ||
17 | menu, nav, output, ruby, section, summary, | ||
18 | time, mark, audio, video { | ||
19 | margin: 0; | ||
20 | padding: 0; | ||
21 | border: 0; | ||
22 | font-size: unset; | ||
23 | font: inherit; | ||
24 | vertical-align: baseline; | ||
25 | } | ||
26 | /* HTML5 display-role reset for older browsers */ | ||
27 | article, aside, details, figcaption, figure, | ||
28 | footer, header, hgroup, menu, nav, section { | ||
29 | display: block; | ||
30 | } | ||
31 | body { | ||
32 | line-height: 1; | ||
33 | } | ||
34 | ol, ul { | ||
35 | list-style: none; | ||
36 | } | ||
37 | blockquote, q { | ||
38 | quotes: none; | ||
39 | } | ||
40 | blockquote:before, blockquote:after, | ||
41 | q:before, q:after { | ||
42 | content: ''; | ||
43 | content: none; | ||
44 | } | ||
45 | table { | ||
46 | border-collapse: collapse; | ||
47 | border-spacing: 0; | ||
48 | } | ||
49 | |||
50 | /* ========== Actual styles ======================================> */ | ||
51 | |||
52 | :root | ||
53 | { | ||
54 | --color-bg: #c0c0c0; | ||
55 | --color-fg: black; | ||
56 | --color-border-dark-1: #808080; | ||
57 | --color-border-dark-2: black; | ||
58 | --color-border-light-1: #dfdfdf; | ||
59 | --color-border-light-2: white; | ||
60 | --color-titlebar-1: navy; | ||
61 | --color-titlebar-2: #1084d0; | ||
62 | } | ||
63 | |||
64 | * | ||
65 | { | ||
66 | box-sizing: border-box; | ||
67 | } | ||
68 | |||
69 | html | ||
70 | { | ||
71 | background: #008080; | ||
72 | color: var(--color-fg); | ||
73 | font-family: sans-serif; | ||
74 | font-size: 12px; | ||
75 | padding: 12px; | ||
76 | } | ||
77 | |||
78 | .Window | ||
79 | { | ||
80 | box-shadow: inset -1px -1px var(--color-border-dark-2), | ||
81 | inset 1px 1px var(--color-border-light-1), | ||
82 | inset -2px -2px var(--color-border-dark-1), | ||
83 | inset 2px 2px var(--color-border-light-2); | ||
84 | background: var(--color-bg); | ||
85 | padding: 10px; | ||
86 | |||
87 | .Titlebar | ||
88 | { | ||
89 | background: linear-gradient(90deg, var(--color-titlebar-1), var(--color-titlebar-2)); | ||
90 | margin: -6px -6px 10px -6px; | ||
91 | color: white; | ||
92 | padding: 3px; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | .Dialog | ||
97 | { | ||
98 | max-width: 910px; | ||
99 | margin: 0px auto; | ||
100 | } | ||
101 | |||
102 | .LabeledPanel | ||
103 | { | ||
104 | border-top: solid var(--color-border-light-1) 1px; | ||
105 | border-right: solid var(--color-border-dark-1) 1px; | ||
106 | border-bottom: solid var(--color-border-dark-1) 1px; | ||
107 | border-left: solid var(--color-border-light-1) 1px; | ||
108 | |||
109 | box-shadow: 1px 1px var(--color-border-light-1), | ||
110 | -1px -1px var(--color-border-dark-1); | ||
111 | padding: 1ex; | ||
112 | padding-top: 0px; | ||
113 | margin: 2ex 0px; | ||
114 | } | ||
115 | |||
116 | .LabeledPanel > h3, .LabeledPanel > h2 | ||
117 | { | ||
118 | font-weight: normal; | ||
119 | display: inline-block; | ||
120 | position: relative; | ||
121 | top: calc(-0.5em); | ||
122 | left: 1ex; | ||
123 | background-color: var(--color-bg); | ||
124 | padding: 0px 1ex 0px 1ex; | ||
125 | margin: 0px; | ||
126 | } | ||
127 | |||
128 | input[type="number"] | ||
129 | { | ||
130 | appearance: none; | ||
131 | border: none; | ||
132 | outline: none; | ||
133 | box-shadow: inset -1px -1px var(--color-border-light-2), | ||
134 | inset 1px 1px var(--color-border-dark-1), | ||
135 | inset -2px -2px var(--color-border-light-1), | ||
136 | inset 2px 2px var(--color-border-dark-2); | ||
137 | padding: 4px 0px 4px 6px; | ||
138 | font-family: monospace; | ||
139 | font-size: 0.9rem; | ||
140 | } | ||
141 | |||
142 | input[type="text"], input[type="url"], input[type="email"], textarea | ||
143 | { | ||
144 | appearance: none; | ||
145 | border: none; | ||
146 | outline: none; | ||
147 | box-shadow: inset -1px -1px var(--color-border-light-2), | ||
148 | inset 1px 1px var(--color-border-dark-1), | ||
149 | inset -2px -2px var(--color-border-light-1), | ||
150 | inset 2px 2px var(--color-border-dark-2); | ||
151 | width: 100%; | ||
152 | padding: 4px 6px; | ||
153 | font-family: monospace; | ||
154 | font-size: 0.9rem; | ||
155 | } | ||
156 | |||
157 | a.Button, a.FloatButton | ||
158 | { | ||
159 | color: black; | ||
160 | text-decoration: none; | ||
161 | } | ||
162 | |||
163 | button, input[type="submit"] | ||
164 | { | ||
165 | appearance: none; | ||
166 | border: none; | ||
167 | outline: none; | ||
168 | box-shadow: inset -1px -1px var(--color-border-dark-2), | ||
169 | inset 1px 1px var(--color-border-light-1), | ||
170 | inset -2px -2px var(--color-border-dark-1), | ||
171 | inset 2px 2px var(--color-border-light-2); | ||
172 | background: var(--color-bg); | ||
173 | min-height: 23px; | ||
174 | min-width: 75px; | ||
175 | padding: 0 12px; | ||
176 | text-align: center; | ||
177 | } | ||
178 | |||
179 | .IconButton | ||
180 | { | ||
181 | padding: 4px; | ||
182 | min-width: unset; | ||
183 | min-height: unset; | ||
184 | } | ||
185 | |||
186 | .FloatButton | ||
187 | { | ||
188 | padding: 4px; | ||
189 | min-width: unset; | ||
190 | min-height: unset; | ||
191 | box-shadow: none; | ||
192 | } | ||
193 | |||
194 | .FloatButton:hover | ||
195 | { | ||
196 | padding: 4px; | ||
197 | min-width: unset; | ||
198 | min-height: unset; | ||
199 | box-shadow: inset 1px 1px var(--color-border-light-1), | ||
200 | inset -1px -1px var(--color-border-dark-1); | ||
201 | } | ||
202 | |||
203 | button:active, .FloatButton:active | ||
204 | { | ||
205 | box-shadow: inset -1px -1px var(--color-border-light-2), | ||
206 | inset 1px 1px var(--color-border-dark-1), | ||
207 | inset -2px -2px var(--color-border-light-1), | ||
208 | inset 2px 2px var(--color-border-dark-2); | ||
209 | } | ||
210 | |||
211 | hr | ||
212 | { | ||
213 | border: none; | ||
214 | border-top: solid var(--color-border-dark-1) 1px; | ||
215 | border-bottom: solid var(--color-border-light-1) 1px; | ||
216 | } | ||
217 | |||
218 | .ButtonRow, .ButtonRowLeft | ||
219 | { | ||
220 | display: flex; | ||
221 | gap: 10px; | ||
222 | margin: 10px 0; | ||
223 | align-items: baseline; | ||
224 | } | ||
225 | |||
226 | .ButtonRow | ||
227 | { | ||
228 | justify-content: right; | ||
229 | } | ||
230 | |||
231 | .Toolbar | ||
232 | { | ||
233 | display: flex; | ||
234 | border-bottom: solid var(--color-border-dark-1) 1px; | ||
235 | box-shadow: 0px 1px var(--color-border-light-1); | ||
236 | position: relative; | ||
237 | left: -10px; | ||
238 | top: -10px; | ||
239 | width: calc(100% + 2ex); | ||
240 | padding: 2px 8px; | ||
241 | } | ||
242 | |||
243 | table.InputFields | ||
244 | { | ||
245 | border-collapse: collapse; | ||
246 | width: 100%; | ||
247 | table-layout: auto; | ||
248 | |||
249 | td | ||
250 | { | ||
251 | padding: 2px 2px; | ||
252 | } | ||
253 | |||
254 | td:nth-child(2) | ||
255 | { | ||
256 | width: 100%; | ||
257 | } | ||
258 | |||
259 | } | ||
260 | |||
261 | .StatusBar | ||
262 | { | ||
263 | margin: 10px -6px -7px -6px; | ||
264 | display: flex; | ||
265 | } | ||
266 | |||
267 | .StatusCell | ||
268 | { | ||
269 | box-shadow: inset -2px -2px var(--color-border-light-2), | ||
270 | inset 2px 2px var(--color-border-dark-1); | ||
271 | padding: 4px; | ||
272 | flex-grow: 1; | ||
273 | } | ||
274 | |||
275 | #Links | ||
276 | { | ||
277 | box-shadow: inset -1px -1px var(--color-border-light-2), | ||
278 | inset 1px 1px var(--color-border-dark-1), | ||
279 | inset -2px -2px var(--color-border-light-1), | ||
280 | inset 2px 2px var(--color-border-dark-2); | ||
281 | padding: 2px; | ||
282 | } | ||
283 | |||
284 | table.TableView | ||
285 | { | ||
286 | background-color: white; | ||
287 | border-collapse: collapse; | ||
288 | width: 100%; | ||
289 | |||
290 | thead th | ||
291 | { | ||
292 | box-shadow: inset -1px -1px var(--color-border-dark-2), | ||
293 | inset 1px 1px var(--color-border-light-1), | ||
294 | inset -2px -2px var(--color-border-dark-1), | ||
295 | inset 2px 2px var(--color-border-light-2); | ||
296 | background-color: var(--color-bg); | ||
297 | padding: 2px 4px 4px 4px; | ||
298 | text-align: left; | ||
299 | } | ||
300 | |||
301 | td | ||
302 | { | ||
303 | padding: 2px; | ||
304 | } | ||
305 | } | ||
306 | |||
307 | .MainWrapper | ||
308 | { | ||
309 | display: flex; | ||
310 | gap: 10px; | ||
311 | } | ||
312 | |||
313 | .Asset | ||
314 | { | ||
315 | display: inline-block; | ||
316 | border: solid black 1px; | ||
317 | width: 34px; | ||
318 | height: 34px; | ||
319 | } | ||
320 | |||
321 | #EntryIndicator | ||
322 | { | ||
323 | font-size: 150%; | ||
324 | text-align: center; | ||
325 | margin-bottom: 10px; | ||
326 | } | ||
327 | |||
328 | /* Prevent SVG elements from interferring with drag n drop. */ | ||
329 | #Grid * | ||
330 | { | ||
331 | pointer-events: none; | ||
332 | } | ||