From f0680ce0c413d9e99ca94291e5a3aaa5f4b63034 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Wed, 2 Dec 2020 13:54:32 -0700 Subject: [PATCH] Cleaned up several tile properties; added railroad adjusting --- js/defs.js | 2 + js/editor-tile-overlays.js | 113 ++++++++++++++++++++++++++++++++----- js/format-c2g.js | 68 ++++++++++++++++------ js/game.js | 6 +- js/main-editor.js | 52 ++++++++++------- js/tileset.js | 98 +++++++++++++++++--------------- js/tiletypes.js | 41 +++++++------- style.css | 38 ++++++++++++- 8 files changed, 296 insertions(+), 122 deletions(-) diff --git a/js/defs.js b/js/defs.js index a20d0a2..da7f095 100644 --- a/js/defs.js +++ b/js/defs.js @@ -34,6 +34,8 @@ export const DIRECTIONS = { opposite: 'west', }, }; +// Should match the bit ordering above, and CC2's order +export const DIRECTION_ORDER = ['north', 'east', 'south', 'west']; // TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile) export const DRAW_LAYERS = { diff --git a/js/editor-tile-overlays.js b/js/editor-tile-overlays.js index 0ce909b..27ba96f 100644 --- a/js/editor-tile-overlays.js +++ b/js/editor-tile-overlays.js @@ -1,11 +1,10 @@ import { TransientOverlay } from './main-base.js'; -import { mk } from './util.js'; +import { mk, mk_svg } from './util.js'; // FIXME could very much stand to have a little animation when appearing class TileEditorOverlay extends TransientOverlay { constructor(conductor) { let root = mk('form.editor-popup-tile-editor'); - root.append(mk('span.popup-chevron')); super(conductor, root); this.editor = conductor.editor; this.tile = null; @@ -24,34 +23,41 @@ class LetterTileEditor extends TileEditorOverlay { constructor(conductor) { super(conductor); + this.root.append(mk('h3', "Letter tile")); let list = mk('ol.editor-letter-tile-picker'); this.root.append(list); this.glyph_elements = {}; - for (let c = 32; c < 128; c++) { - let glyph = String.fromCharCode(c); + let add = glyph => { let input = mk('input', {type: 'radio', name: 'glyph', value: glyph}); this.glyph_elements[glyph] = input; let item = mk('li', mk('label', input, mk('span.-glyph', glyph))); list.append(item); + }; + let arrows = ["⬆", "➡", "⬇", "⬅"]; + for (let c = 32; c < 96; c++) { + let glyph = String.fromCharCode(c); + add(glyph); + // Add the arrows to the ends of the rows + if (c % 16 === 15) { + add(arrows[(c - 47) / 16]); + } } list.addEventListener('change', ev => { - let glyph = this.root.elements['glyph'].value; if (this.tile) { - this.tile.ascii_code = glyph.charCodeAt(0); - // FIXME should be able to mark tiles as dirty, also this is sure a mouthful - this.conductor.editor.renderer.draw(); + this.tile.overlaid_glyph = this.root.elements['glyph'].value; + this.editor.mark_tile_dirty(this.tile); } }); } edit_tile(tile) { super.edit_tile(tile); - this.root.elements['glyph'].value = String.fromCharCode(tile.ascii_code); + this.root.elements['glyph'].value = tile.overlaid_glyph; } static configure_tile_defaults(tile) { - tile.ascii_code = 32; + tile.type.populate_defaults(tile); } } @@ -59,29 +65,110 @@ class HintTileEditor extends TileEditorOverlay { constructor(conductor) { super(conductor); + this.root.append(mk('h3', "Hint text")); this.text = mk('textarea.editor-hint-tile-text'); this.root.append(this.text); this.text.addEventListener('input', ev => { if (this.tile) { - this.tile.specific_hint = this.text.value; + this.tile.hint_text = this.text.value; } }); } edit_tile(tile) { super.edit_tile(tile); - this.text.value = tile.specific_hint ?? ""; + this.text.value = tile.hint_text ?? ""; } static configure_tile_defaults(tile) { - tile.specific_hint = ""; + tile.hint_text = ""; } } +class RailroadTileEditor extends TileEditorOverlay { + constructor(conductor) { + super(conductor); + + let svg_icons = []; + for (let center of [[16, 0], [16, 16], [0, 16], [0, 0]]) { + let symbol = mk_svg('svg', {viewBox: '0 0 16 16'}, + mk_svg('circle', {cx: center[0], cy: center[1], r: 3}), + mk_svg('circle', {cx: center[0], cy: center[1], r: 13}), + ); + svg_icons.push(symbol); + } + svg_icons.push(mk_svg('svg', {viewBox: '0 0 16 16'}, + mk_svg('rect', {x: -2, y: 3, width: 20, height: 10}), + )); + svg_icons.push(mk_svg('svg', {viewBox: '0 0 16 16'}, + mk_svg('rect', {x: 3, y: -2, width: 10, height: 20}), + )); + + this.root.append(mk('h3', "Tracks")); + let track_list = mk('ul.editor-railroad-tile-tracks'); + // Shown as two rows, this puts the straight parts first and the rest in a circle + let track_order = [4, 1, 2, 5, 0, 3]; + for (let i of track_order) { + let input = mk('input', {type: 'checkbox', name: 'track', value: i}); + track_list.append(mk('li', mk('label', input, svg_icons[i]))); + } + track_list.addEventListener('change', ev => { + if (this.tile) { + let bit = 1 << ev.target.value; + if (ev.target.checked) { + this.tile.tracks |= bit; + } + else { + this.tile.tracks &= ~bit; + } + this.editor.mark_tile_dirty(this.tile); + } + }); + this.root.append(track_list); + + this.root.append(mk('h3', "Switch")); + let switch_list = mk('ul.editor-railroad-tile-tracks.--switch'); + for (let i of track_order) { + let input = mk('input', {type: 'radio', name: 'switch', value: i}); + switch_list.append(mk('li', mk('label', input, svg_icons[i].cloneNode(true)))); + } + // TODO if they remove a track it should change the switch + // TODO if they pick a track that's missing it should add it + switch_list.addEventListener('change', ev => { + if (this.tile) { + this.tile.track_switch = ev.target.value; + this.editor.mark_tile_dirty(this.tile); + } + }); + this.root.append(switch_list); + + // TODO need a way to set no actor at all + // TODO initial actor facing (maybe only if there's an actor in the cell) + } + + edit_tile(tile) { + super.edit_tile(tile); + + for (let input of this.root.elements['track']) { + input.checked = !! (tile.tracks & (1 << input.value)); + } + + if (tile.track_switch === null) { + this.root.elements['switch'].value = ''; + } + else { + this.root.elements['switch'].value = tile.track_switch; + } + } + + static configure_tile_defaults(tile) { + } +} export const TILES_WITH_PROPS = { floor_letter: LetterTileEditor, hint: HintTileEditor, + railroad: RailroadTileEditor, // TODO various wireable tiles // TODO initial value of counter // TODO cloner arrows diff --git a/js/format-c2g.js b/js/format-c2g.js index 8e65cbc..998ff1f 100644 --- a/js/format-c2g.js +++ b/js/format-c2g.js @@ -1,4 +1,4 @@ -import { DIRECTIONS } from './defs.js'; +import { DIRECTIONS, DIRECTION_ORDER } from './defs.js'; import * as format_base from './format-base.js'; import TILE_TYPES from './tiletypes.js'; import * as util from './util.js'; @@ -374,11 +374,28 @@ const TILE_ENCODING = { modifier: { _parts: ['ne', 'se', 'sw', 'ne', 'ew', 'ns'], decode(tile, mask) { - tile.railroad_bits = mask; + // Leave the track parts alone as a bitmask; the type has a list of them + tile.tracks = mask & 0x3f; + // Check for a switch, which is a bit number in the above mask + if (mask & 0x40) { + tile.track_switch = (mask >> 8) & 0x0f; + } + else { + tile.track_switch = null; + } + // Initial actor facing is in the highest nybble + tile.entered_direction = (mask >> 12) & 0x03; }, encode(tile) { - // TODO - return 0; + let ret = tile.tracks & 0x3f; + if (tile.track_switch !== null) { + ret |= 0x40; + ret |= tile.track_switch << 8; + } + if (tile.entered_direction) { + ret |= DIRECTION_ORDER.indexOf(tile.entered_direction) << 12; + } + return ret; }, }, }, @@ -519,10 +536,25 @@ const TILE_ENCODING = { name: 'floor_letter', modifier: { decode(tile, ascii_code) { - tile.ascii_code = ascii_code; + if (ascii_code < 28 || ascii_code >= 96) { + // Invalid + tile.overlaid_glyph = "?"; + } + else if (ascii_code < 32) { + // Arrows are stored goofily + tile.overlaid_glyph = ["⬆", "➡", "⬇", "⬅"][ascii_code - 28]; + } + else { + tile.overlaid_glyph = String.fromCharCode(ascii_code); + } }, encode(tile) { - return tile.ascii_code; + let arrow_index = ["⬆", "➡", "⬇", "⬅"].indexOf(tile.overlaid_glyph); + if (arrow_index >= 0) { + return arrow_index + 28; + } + + return tile.overlaid_glyph.charCodeAt(0); }, }, }, @@ -798,11 +830,10 @@ export function parse_level(buf, number = 1) { level.hint = str; } else if (type === 'NOTE') { - // Author's comments... but might also include multiple hints - // for levels with multiple hint tiles, delineated by [CLUE]. - // For my purposes, extra hints are associated with the - // individual tiles, so we'll map those later - [level.comment, ...extra_hints] = str.split(/^\[CLUE\]$/mg); + // Author's comments... but might also include multiple hints for levels with + // multiple hint tiles, delineated by [CLUE] (anywhere in the line (!)). + // LL treats extra hints as tile properties, so store them for later + [level.comment, ...extra_hints] = str.split(/\n?^.*\[CLUE\].*$\n?/mg); } continue; } @@ -1018,13 +1049,14 @@ export function parse_level(buf, number = 1) { } // Connect extra hints - let h = 0; - for (let tile of hint_tiles) { - if (h > extra_hints.length) - break; - - tile.specific_hint = extra_hints[h]; - h++; + for (let [i, tile] of hint_tiles.entries()) { + if (i < extra_hints.length) { + tile.hint_text = extra_hints[i]; + } + else { + // Fall back to regular hint + tile.hint_text = null; + } } return level; diff --git a/js/game.js b/js/game.js index f769897..6e31a9b 100644 --- a/js/game.js +++ b/js/game.js @@ -297,7 +297,7 @@ export class Level { let tile = Tile.from_template(template_tile); if (tile.type.is_hint) { // Copy over the tile-specific hint, if any - tile.specific_hint = template_tile.specific_hint ?? null; + tile.hint_text = template_tile.hint_text ?? null; } if (tile.type.is_power_source) { @@ -390,7 +390,7 @@ export class Level { tile.type.on_ready(tile, this); } if (cell === this.player.cell && tile.type.is_hint) { - this.hint_shown = tile.specific_hint ?? this.stored_level.hint; + this.hint_shown = tile.hint_text ?? this.stored_level.hint; } } } @@ -1019,7 +1019,7 @@ export class Level { } if (actor === this.player && tile.type.is_hint) { - this.hint_shown = tile.specific_hint ?? this.stored_level.hint; + this.hint_shown = tile.hint_text ?? this.stored_level.hint; } } diff --git a/js/main-editor.js b/js/main-editor.js index e8d8ae1..6397650 100644 --- a/js/main-editor.js +++ b/js/main-editor.js @@ -284,7 +284,7 @@ class TrackOperation extends DrawOperation { // Get the corresponding bit let bit = null; - for (let [i, track] of TILE_TYPES['railroad']._track_order.entries()) { + for (let [i, track] of TILE_TYPES['railroad'].track_order.entries()) { if ((track[0] === this.entry_direction && track[1] === exit_direction) || (track[1] === this.entry_direction && track[0] === exit_direction)) { @@ -300,10 +300,12 @@ class TrackOperation extends DrawOperation { let cell = this.cell(prevx, prevy); let terrain = cell[0]; if (terrain.type.name === 'railroad') { - terrain.railroad_bits |= bit; + terrain.tracks |= bit; } else { - terrain = { type: TILE_TYPES['railroad'], railroad_bits: bit }; + terrain = { type: TILE_TYPES['railroad'] }; + terrain.type.populate_defaults(terrain); + terrain.tracks |= bit; this.editor.place_in_cell(prevx, prevy, terrain); } @@ -364,17 +366,19 @@ class AdjustOperation extends MouseOperation { for (let tile of cell) { // Rotate railroads, which are a bit complicated if (tile.type.name === 'railroad') { - let new_bits = 0; - for (let [i, new_bit] of [1, 2, 3, 0, 5, 4].entries()) { - if (tile.railroad_bits & (1 << i)) { - new_bits |= (1 << new_bit); + let new_tracks = 0; + let rotated_tracks = [1, 2, 3, 0, 5, 4]; + for (let [i, new_bit] of rotated_tracks.entries()) { + if (tile.tracks & (1 << i)) { + new_tracks |= (1 << new_bit); } } - if (tile.railroad_bits & 0x40) { - new_bits |= 0x40; + tile.tracks = new_tracks; + + if (tile.switch_track !== null) { + tile.switch_track = rotated_tracks[tile.switch_track]; } - // TODO high byte also - tile.railroad_bits = new_bits; + tile.entered_direction = DIRECTIONS[tile.entered_direction].right; } // TODO also directional blocks @@ -832,7 +836,6 @@ export class Editor extends PrimaryView { this.selected_tile_el.addEventListener('click', ev => { if (this.palette_selection && TILES_WITH_PROPS[this.palette_selection.type.name]) { // FIXME use tile bounds - // FIXME redraw the tile after editing this.open_tile_prop_overlay(this.palette_selection, ev.clientX, ev.clientY); } }); @@ -973,11 +976,6 @@ export class Editor extends PrimaryView { name = tile.type.name; } - // FIXME should redraw in an existing canvas - this.selected_tile_el.textContent = ''; - // FIXME should draw the actual tile!! - this.selected_tile_el.append(this.renderer.create_tile_type_canvas(name, tile)); - if (this.palette_selection) { let entry = this.palette[this.palette_selection.type.name]; if (entry) { @@ -989,6 +987,8 @@ export class Editor extends PrimaryView { this.palette[name].classList.add('--selected'); } + this.mark_tile_dirty(tile); + // Some tools obviously don't work with a palette selection, in which case changing tiles // should default you back to the pencil if (this.current_tool === 'adjust') { @@ -996,6 +996,18 @@ export class Editor extends PrimaryView { } } + mark_tile_dirty(tile) { + // TODO partial redraws! until then, redraw everything + if (tile === this.palette_selection) { + // FIXME should redraw in an existing canvas + this.selected_tile_el.textContent = ''; + this.selected_tile_el.append(this.renderer.create_tile_type_canvas(tile.type.name, tile)); + } + else { + this.renderer.draw(); + } + } + is_in_bounds(x, y) { return 0 <= x && x < this.stored_level.size_x && 0 <= y && y < this.stored_level.size_y; } @@ -1051,13 +1063,13 @@ export class Editor extends PrimaryView { // Horizontal position: centered, but kept within the screen let left; let margin = 8; // prefer to not quite touch the edges - let halfwidth = root.offsetWidth / 2 + margin; - if (document.body.clientWidth / 2 < halfwidth) { + if (document.body.clientWidth < root.offsetWidth + margin * 2) { // It doesn't fit on the screen at all, so there's nothing we can do; just center it left = (document.body.clientWidth - root.offsetWidth) / 2; } else { - left = Math.max(margin, Math.min(document.body.clientWidth - halfwidth, x0 - halfwidth)); + left = Math.max(margin, Math.min(document.body.clientWidth - root.offsetWidth - margin, + x0 - root.offsetWidth / 2)); } root.style.left = `${left}px`; root.style.setProperty('--chevron-offset', `${x0 - left}px`); diff --git a/js/tileset.js b/js/tileset.js index fd9177f..5ad9229 100644 --- a/js/tileset.js +++ b/js/tileset.js @@ -63,18 +63,25 @@ export const CC2_TILESET_LAYOUT = { wall_invisible: [0, 2], wall_appearing: [0, 2], wall: [1, 2], - floor_letter: [2, 2], - 'floor_letter#ascii': { - x0: 0, - y0: 0, - width: 16, - height: 1, - }, - 'floor_letter#arrows': { - north: [14, 31], - east: [14.5, 31], - south: [15, 31], - west: [15.5, 31], + floor_letter: { + special: 'letter', + base: [2, 2], + letter_glyphs: { + // Arrows + "⬆": [14, 31], + "➡": [14.5, 31], + "⬇": [15, 31], + "⬅": [15.5, 31], + }, + letter_ranges: [{ + // ASCII text (only up through uppercase) + range: [32, 96], + x0: 0, + y0: 0, + w: 0.5, + h: 0.5, + columns: 32, + }], }, thief_tools: [3, 2], socket: [4, 2], @@ -830,6 +837,31 @@ export class Tileset { blit(coords[0], coords[1], ...mask); } + _draw_letter(drawspec, tile, tic, blit) { + this._draw_standard(drawspec.base, tile, tic, blit); + + let glyph = tile.overlaid_glyph; + if (drawspec.letter_glyphs[glyph]) { + let [x, y] = drawspec.letter_glyphs[glyph]; + // XXX size is hardcoded here, but not below, meh + blit(x, y, 0, 0, 0.5, 0.5, 0.25, 0.25); + } + else { + // Look for a range + let u = glyph.charCodeAt(0); + for (let rangedef of drawspec.letter_ranges) { + if (rangedef.range[0] <= u && u < rangedef.range[1]) { + let t = u - rangedef.range[0]; + let x = rangedef.x0 + rangedef.w * (t % rangedef.columns); + let y = rangedef.y0 + rangedef.h * Math.floor(t / rangedef.columns); + blit(x, y, 0, 0, rangedef.w, rangedef.h, + (1 - rangedef.w) / 2, (1 - rangedef.h) / 2); + break; + } + } + } + } + _draw_logic_gate(drawspec, tile, tic, blit) { // Layer 1: wiring state // Always draw the unpowered wire base @@ -876,15 +908,15 @@ export class Tileset { let visible_parts = []; let topmost_part = null; for (let [i, part] of part_order.entries()) { - if (tile && (tile.railroad_bits & (1 << i))) { - if ((tile.railroad_bits >> 8) === i) { + if (tile && (tile.tracks & (1 << i))) { + if (tile.track_switch === i) { topmost_part = part; } visible_parts.push(part); } } - let has_switch = (tile && (tile.railroad_bits & 0x40)); + let has_switch = (tile && tile.track_switch !== null); for (let part of visible_parts) { this._draw_standard(drawspec.railroad_ties[part], tile, tic, blit); } @@ -929,7 +961,11 @@ export class Tileset { // TODO shift everything to use this style, this is ridiculous if (drawspec.special) { - if (drawspec.special === 'logic-gate') { + if (drawspec.special === 'letter') { + this._draw_letter(drawspec, tile, tic, blit); + return; + } + else if (drawspec.special === 'logic-gate') { this._draw_logic_gate(drawspec, tile, tic, blit); return; } @@ -1144,36 +1180,6 @@ export class Tileset { } blit(x, y, x0, y0, x1 - x0, y1 - y0); } - - // Special behavior for special objects - // TODO? hardcode this less? - if (name === 'floor_letter' && tile) { - let n = tile.ascii_code - 32; - let scale = 0.5; - let sx, sy; - if (n < 0) { - // Arrows - if (n < -4) { - // Default to south - n = -2; - } - - let direction = ['north', 'east', 'south', 'west'][n + 4]; - [sx, sy] = this.layout['floor_letter#arrows'][direction]; - } - else { - // ASCII text (only up through uppercase) - let letter_spec = this.layout['floor_letter#ascii']; - if (n > letter_spec.width / scale * letter_spec.height / scale) { - n = 0; - } - let w = letter_spec.width / scale; - sx = (letter_spec.x0 + n % w) * scale; - sy = (letter_spec.y0 + Math.floor(n / w)) * scale; - } - let offset = (1 - scale) / 2; - blit(sx, sy, 0, 0, 0.5, 0.5, offset, offset); - } } _rotate(direction, x0, y0, x1, y1) { diff --git a/js/tiletypes.js b/js/tiletypes.js index 3bc0aa6..b2378d2 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -90,6 +90,9 @@ const TILE_TYPES = { }, floor_letter: { draw_layer: DRAW_LAYERS.terrain, + populate_defaults(me) { + me.overlaid_glyph = "?"; + }, }, // TODO possibly this should be a single tile floor_custom_green: { @@ -316,7 +319,7 @@ const TILE_TYPES = { // Railroad railroad: { draw_layer: DRAW_LAYERS.terrain, - _track_order: [ + track_order: [ ['north', 'east'], ['south', 'east'], ['south', 'west'], @@ -324,15 +327,12 @@ const TILE_TYPES = { ['east', 'west'], ['north', 'south'], ], - // FIXME railroad_bits sucks, split into some useful variables - on_ready(me) { - // If there's already an actor on top of us, assume it entered the way it's already - // facing (which may be illegal, in which case it can't leave) - // FIXME wrong! > Yeah, so in the high byte, the low nibble encodes the active track. What's missing is that the high nibble encodes a direction value, which is required when a mob starts out on top of the track: it represents the direction the mob was going when it entered the tile, which controls which way it can go on the track. - let actor = me.cell.get_actor(); - if (actor) { - me.entered_direction = actor.direction; - } + populate_defaults(me) { + me.tracks = 0; // bitmask of bits 0-5, corresponding to track order above + me.track_switch = null; // null, or 0-5 indicating the active switched track + // If there's already an actor on us, it's treated as though it entered the tile moving + // in this direction, which is given in the save file and defaults to zero i.e. north + me.entered_direction = 'north'; }, // TODO feel like "ignores" was the wrong idea and there should just be some magic flags for // particular objects that can be immune to. or maybe those objects should have their own @@ -345,28 +345,28 @@ const TILE_TYPES = { return true; }, *_iter_tracks(me) { - let order = me.type._track_order; - if (me.railroad_bits & 0x40) { + let order = me.type.track_order; + if (me.track_switch !== null) { // FIXME what happens if the "top" track is not actually a valid track??? - yield order[me.railroad_bits >> 8]; + yield order[me.track_switch]; } else { for (let [i, track] of order.entries()) { - if (me.railroad_bits & (1 << i)) { + if (me.tracks & (1 << i)) { yield track; } } } }, _switch_track(me, level) { - if (me.railroad_bits & 0x40) { - let current = me.railroad_bits >> 8; - for (let i = 0, l = me.type._track_order.length; i < l; i++) { + if (me.track_switch !== null) { + let current = me.track_switch; + for (let i = 0, l = me.type.track_order.length; i < l; i++) { current = (current + 1) % l; - if (me.railroad_bits & (1 << current)) + if (me.tracks & (1 << current)) break; } - level._set_tile_prop(me, 'railroad_bits', (current << 8) | (me.railroad_bits & 0xff)); + level._set_tile_prop(me, 'track_switch', current); } }, has_opening(me, direction) { @@ -1817,6 +1817,9 @@ const TILE_TYPES = { draw_layer: DRAW_LAYERS.terrain, is_hint: true, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, + populate_defaults(me) { + me.hint_text = null; // optional, may use level's hint instead + }, }, socket: { draw_layer: DRAW_LAYERS.terrain, diff --git a/style.css b/style.css index 91733fb..692d465 100644 --- a/style.css +++ b/style.css @@ -1077,7 +1077,16 @@ form.editor-popup-tile-editor { form.editor-popup-tile-editor.--above { margin-top: -1em; } -.popup-chevron { +form.editor-popup-tile-editor h3 { + border-bottom: 1px dotted #606060; +} +form.editor-popup-tile-editor * + h3 { + margin-top: 0.25em; +} +/* Use ::before for a chevron pointing at the tile in question */ +form.editor-popup-tile-editor::before { + content: ''; + display: block; position: absolute; border: 1em solid transparent; left: var(--chevron-offset); @@ -1088,7 +1097,7 @@ form.editor-popup-tile-editor.--above { border-bottom-color: white; filter: drop-shadow(0 -1px 0 black); } -form.editor-popup-tile-editor.--above .popup-chevron { +form.editor-popup-tile-editor.--above::before { top: auto; bottom: -1em; border: 1em solid transparent; @@ -1100,7 +1109,7 @@ form.editor-popup-tile-editor.--above .popup-chevron { * characters are preceded by radio buttons, which we hide for simplicity */ ol.editor-letter-tile-picker { display: grid; - grid: auto-flow 1.5em / repeat(16, 1.5em); + grid: auto-flow 1.5em / repeat(17, 1.5em); text-align: center; font-family: monospace; } @@ -1123,5 +1132,28 @@ textarea.editor-hint-tile-text { height: 20vh; min-width: 15rem; min-height: 5rem; + border: none; font-family: serif; } +/* Railroad tracks are... complicated */ +ul.editor-railroad-tile-tracks { + display: grid; + grid: auto-flow 3em / repeat(3, 3em); + gap: 0.25em; +} +ul.editor-railroad-tile-tracks input { + display: none; +} +ul.editor-railroad-tile-tracks svg { + display: block; + width: 3em; + fill: none; + stroke: #c0c0c0; + stroke-width: 2; +} +ul.editor-railroad-tile-tracks input:checked + svg { + stroke: hsl(225, 90%, 50%); +} +ul.editor-railroad-tile-tracks.--switch input:checked + svg { + stroke: hsl(15, 90%, 50%); +}