diff options
| author | historia <[not public]> | 2026-07-15 06:19:05 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-15 06:19:05 -0400 |
| commit | e09e7badbc722f580a9d7674c46d6ff3cc320b26 (patch) | |
| tree | a6beb1ed8a2ed3e2ae2334a619a1e0b40e5030d6 | |
| parent | 84dd8c7557d8b9d92cd8d339afc28a0fd9a3a896 (diff) | |
| download | thehouseoficarus-e09e7badbc722f580a9d7674c46d6ff3cc320b26.tar.gz | |
feat(admin): color picker shows color cube correctly
| -rw-r--r-- | internal/admin/api_colors.go | 53 | ||||
| -rw-r--r-- | internal/admin/static/admin.css | 11 | ||||
| -rw-r--r-- | internal/admin/static/coloreditor.js | 45 | ||||
| -rw-r--r-- | internal/admin/static/colorpicker.js | 35 |
4 files changed, 95 insertions, 49 deletions
diff --git a/internal/admin/api_colors.go b/internal/admin/api_colors.go index 78e5f98..0493b6c 100644 --- a/internal/admin/api_colors.go +++ b/internal/admin/api_colors.go @@ -144,17 +144,15 @@ func buildPreview() []colorSection { sections = append(sections, colorSection{ Title: "Room Look", Lines: []colorLine{ - ln(seg("[", "dim"), seg("*", "map_at"), seg("] ", "dim"), - seg("Test Room", "room_name"), - seg(" (", ""), seg("intro", "room_desc"), seg(", ", ""), - seg("#2001", "room_number"), seg(")", "")), + ln(seg("Test Room", "room_name"), + seg(" (intro, #2001)", "room_number")), txt(""), ln(seg("It's a test room", "room_desc")), txt(""), ln(seg("A tree is here.", "room_desc")), txt(""), - ln(seg("A sentry ", "protected_mob"), seg("(level 4) watches the gate.", "")), - ln(seg("A man ", "mob"), seg("(level 5) scribbles something in a small notebook.", "")), + ln(seg("A sentry ", "protected_mob"), seg("(level 4)", ""), seg(" watches the gate.", "")), + ln(seg("A man ", "mob"), seg("(level 5)", ""), seg(" scribbles something in a small notebook.", "")), txt(""), ln(seg("Icarus", "player_name"), seg(" is here.", "")), txt(""), @@ -167,20 +165,13 @@ func buildPreview() []colorSection { txt(""), txt("Exits:"), ln(seg(" ", ""), seg("south", "exit_direction"), seg(" - ", ""), seg("Test Room", "exit_name")), - ln(seg(" ", ""), seg("east", "exit_direction"), seg(" - ", ""), seg("Room ", "exit_name"), seg("#1005", "room_number")), + ln(seg(" ", ""), seg("east", "exit_direction"), seg(" - ", ""), seg("Processing Station", "exit_name")), ln(seg(" ", ""), seg("west", "exit_direction"), seg(" - ", ""), seg("Inside Transport Shuttle", "exit_name")), txt(""), ln(seg(">", "dim")), - }, - }) - - // --- Movement --------------------------------------------------------- - sections = append(sections, colorSection{ - Title: "Movement", - Lines: []colorLine{ - ln(seg("You walk ", ""), seg("south", "direction"), seg(" to ", ""), - seg("Test Room", "room_name"), seg(" (", ""), seg("#2001", "room_number"), seg(")", "")), - ln(seg("WARNING: that area is exposed to ", ""), seg("radiation", "warning"), seg(".", "")), + txt(""), + ln(seg("> s", "dim")), + ln(seg("You walk ", ""), seg("south", "direction"), seg(".", "")), }, }) @@ -193,23 +184,25 @@ func buildPreview() []colorSection { txt(""), ln(seg("You attack ", ""), seg("the man", "mob"), seg("! Style: accurate (+3 atk)", "")), ln(seg("You hit ", ""), seg("the man", "mob"), seg(" for ", ""), - seg("\u25B8 6 \u25C2 damage. [\u2588\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591] [ 1/7]", "damage_dealt"), + seg("\U0001FB38 6 \U0001FB34 damage. [█░░░░░░░░░] [ 1/7]", "damage_dealt"), seg(" (+18xp acc, +6xp hp)", "xp")), - ln(seg("The man", "mob"), seg(" misses you.", "miss")), + ln(seg("The man misses you.", "miss")), ln(seg("You hit ", ""), seg("the man", "mob"), seg(" for ", ""), - seg("\u25B8 6 \u25C2 damage. [\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591] [ 0/7]", "damage_dealt"), + seg("\U0001FB38 6 \U0001FB34 damage. [░░░░░░░░░░] [ 0/7]", "damage_dealt"), seg(" (+18xp acc, +6xp hp)", "xp")), ln(seg("You have defeated ", ""), seg("the man", "mob"), seg("!", "victory")), ln(seg("*** You are now level 12 melee! ***", "level_up")), txt(""), ln(seg("A scout", "mob"), seg(" hits you for ", ""), - seg("\u25B8 5 \u25C2 damage. [\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591] [22/40]", "damage_taken")), + seg("\U0001FB38 5 \U0001FB34 damage. [██████░░░░] [22/40]", "damage_taken")), ln(seg("A scout's attack is extra effective! Equip ", ""), seg("plated vest", "warning"), seg(" for protection.", "")), ln(seg("You attack ", ""), seg("a scout", "mob"), seg(" with ", ""), seg("laser cutter", "science_mod"), seg("!", "")), - ln(seg("You miss ", ""), seg("a scout", "mob"), seg(".", "miss")), + ln(seg("You miss a scout.", "miss")), ln(seg("Oh dear, you are dead!", "death")), - ln(seg("Error: command not recognized.", "error")), + txt(""), + ln(seg("> help!", "dim")), + ln(seg("Unknown command.", "error")), txt(""), ln(seg("(", ""), seg("damage", "damage"), seg(" is a config-only alt key, not emitted by the engine)", "")), }, @@ -284,13 +277,13 @@ func buildPreview() []colorSection { sections = append(sections, colorSection{ Title: "Mini Map", Lines: []colorLine{ - ln(seg("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557", "dim")), - ln(seg("\u2551", "dim"), seg("\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591", "map_blocked"), seg("\u2551", "dim")), - ln(seg("\u2551", "dim"), seg("\u2591\u2591", "map_blocked"), seg("@", "map_at"), seg("\u2591\u2591", "map_blocked"), seg("\u2502", "map_course"), seg("\u2591\u2591\u2591\u2591", "map_blocked"), seg("\u2551", "dim")), - ln(seg("\u2551", "dim"), seg("\u2591\u2591\u2591\u2591\u2591", "map_blocked"), seg("\u2502", "map_course"), seg("\u2591\u2591\u2591\u2591", "map_blocked"), seg("\u2551", "dim")), - ln(seg("\u2551", "dim"), seg("\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591", "map_blocked"), seg("\u2551", "dim")), - ln(seg("\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d", "dim")), - ln(seg(" ", ""), seg("Room #2002", "room_number")), + ln(seg("╔═════╗", "dim")), + ln(seg("║", "dim"), seg(" ", ""), seg("░", "map_blocked"), seg(" ", ""), seg("║", "dim")), + ln(seg("║", "dim"), seg(" ", ""), seg("║", "dim")), + ln(seg("║", "dim"), seg("■", ""), seg("-", ""), seg("@", "map_at"), seg("-", ""), seg("■", ""), seg("║", "dim")), + ln(seg("║", "dim"), seg(" ", ""), seg("│", "map_course"), seg(" ", ""), seg("║", "dim")), + ln(seg("║", "dim"), seg(" ", ""), seg("■", ""), seg(" ", ""), seg("║", "dim")), + ln(seg("╚═════╝", "dim")), }, }) diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css index 06a640c..d1d3c4d 100644 --- a/internal/admin/static/admin.css +++ b/internal/admin/static/admin.css @@ -182,7 +182,13 @@ details.dir-group summary.dir-header::-webkit-details-marker{display:none} .form-grid .form-group textarea{font-size:11px;padding:4px 6px;min-height:40px} .form-grid .form-group.full{grid-column:1/-1} .form-grid .form-group.full textarea{min-height:60px} -.cp-grid{display:grid;grid-template-columns:repeat(16,1fr);gap:2px} +.cp-grid{display:grid;gap:2px} +.cp-grid.cp-ansi{grid-template-columns:repeat(8,1fr);width:192px} +.cp-grid.cp-cube{grid-template-columns:repeat(6,1fr)} +.cp-grid.cp-gray{grid-template-columns:repeat(12,1fr);width:192px} +.cp-cube-row{display:flex;flex-wrap:wrap;gap:4px;margin:4px 0;width:192px} +.cp-section-label{font-size:10px;color:#888;margin-top:6px;margin-bottom:2px;text-transform:uppercase;letter-spacing:.5px} +.cp-section-label:first-child{margin-top:0} .cp-swatch{aspect-ratio:1;border:1px solid rgba(255,255,255,.15);cursor:pointer;border-radius:2px;min-width:14px} .cp-swatch:hover{transform:scale(1.3);z-index:1;border-color:#fff} .cp-swatch.selected{outline:2px solid #fff;outline-offset:1px;z-index:1} @@ -522,9 +528,8 @@ input:focus-visible,select:focus-visible,textarea:focus-visible,button:focus-vis .color-preview-toolbar select{padding:3px 6px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace} .color-preview-hint{font-size:11px;color:#888} .color-preview-scroll{flex:1;overflow-y:auto;overflow-x:auto;border:1px solid var(--border);border-radius:4px} -.color-preview-pre{margin:0;padding:8px;font-family:ui-monospace,"Cascadia Mono","Cascadia Code","JetBrains Mono","Fira Code",Menlo,Consolas,"DejaVu Sans Mono","Courier New",monospace;font-size:12px;line-height:1;white-space:pre-wrap;color:#e0e0e0;background:#000} +pre.color-preview-pre{margin:0;padding:8px;font-family:ui-monospace,"Cascadia Mono","Cascadia Code","JetBrains Mono","Fira Code",Menlo,Consolas,"DejaVu Sans Mono","Courier New",monospace;font-size:12px;line-height:1;white-space:pre-wrap;color:#e0e0e0;background:#000} .cp-title{color:#9ab;display:block;margin:8px 0 3px 0;font-size:11px;text-transform:uppercase;letter-spacing:.5px;font-weight:bold} .cp-title:first-child{margin-top:0} /* Picker popup tweaks for the themer (richer than bindColorField's) */ -#__cp_popover .cp-grid .cp-swatch{width:14px;height:14px} #__cp_popover .cp-swatch.selected{outline:2px solid #fff;outline-offset:1px} diff --git a/internal/admin/static/coloreditor.js b/internal/admin/static/coloreditor.js index dc219f6..deb7336 100644 --- a/internal/admin/static/coloreditor.js +++ b/internal/admin/static/coloreditor.js @@ -268,11 +268,19 @@ // ── Picker popover (per-row, does not touch bold/dim/bg) ──────────────── var popover = null, boundRow = null, boundCat = null, boundSwatch = null; + function makeSwatch(idx) { + var sw = el('div', 'cp-swatch'); + sw.dataset.idx = idx; + sw.style.background = window.xtermToCss ? xtermToCss(idx) : '#000'; + sw.title = ('0' + idx.toString(16)).slice(-2).toUpperCase() + ' (' + idx + ')'; + return sw; + } + function ensurePicker() { if (popover) return; popover = el('div'); popover.id = '__ct_popover'; - popover.style.cssText = 'display:none;position:fixed;z-index:300;background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:8px;box-shadow:0 4px 20px rgba(0,0,0,.6);max-width:340px'; + popover.style.cssText = 'display:none;position:fixed;z-index:300;background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:8px;box-shadow:0 4px 20px rgba(0,0,0,.6);max-width:400px'; var head = el('div'); head.style.cssText = 'display:flex;align-items:center;gap:6px;margin-bottom:6px'; head.appendChild(el('span', 'color-swatch')); var hexIn = el('input'); hexIn.type = 'text'; hexIn.maxLength = 2; @@ -282,22 +290,33 @@ head.appendChild(hexIn); head.appendChild(offB); head.appendChild(xB); popover.appendChild(head); - var grid = el('div', 'cp-grid'); - for (var i = 0; i < 256; i++) { - var sw = el('div', 'cp-swatch'); - sw.className = 'cp-swatch'; - (function (idx, ee) { - ee.style.background = window.xtermToCss ? xtermToCss(idx) : '#000'; - ee.dataset.idx = idx; - ee.title = ('0' + idx.toString(16)).slice(-2).toUpperCase() + ' (' + idx + ')'; - })(i, sw); - grid.appendChild(sw); + // ANSI colors 0-15 (2 rows of 8) + popover.appendChild(el('div', 'cp-section-label', 'ANSI')); + var ansiGrid = el('div', 'cp-grid cp-ansi'); + for (var i = 0; i < 16; i++) ansiGrid.appendChild(makeSwatch(i)); + popover.appendChild(ansiGrid); + + // Color cube 16-231: six 6x6 slices (G=rows, B=cols, each slice one R level) + popover.appendChild(el('div', 'cp-section-label', 'Color Cube')); + var cubeRow = el('div', 'cp-cube-row'); + for (var r = 0; r < 6; r++) { + var cubeGrid = el('div', 'cp-grid cp-cube'); + for (var g = 0; g < 6; g++) + for (var b = 0; b < 6; b++) + cubeGrid.appendChild(makeSwatch(16 + 36 * r + 6 * g + b)); + cubeRow.appendChild(cubeGrid); } - popover.appendChild(grid); + popover.appendChild(cubeRow); + + // Grayscale 232-255 (2 rows of 12) + popover.appendChild(el('div', 'cp-section-label', 'Grayscale')); + var grayGrid = el('div', 'cp-grid cp-gray'); + for (var i = 232; i < 256; i++) grayGrid.appendChild(makeSwatch(i)); + popover.appendChild(grayGrid); document.body.appendChild(popover); - grid.addEventListener('click', function (e) { + popover.addEventListener('click', function (e) { var s = e.target.closest('.cp-swatch'); if (!s) return; var idx = parseInt(s.dataset.idx, 10); setRowFg(boundRow, boundCat, idx); diff --git a/internal/admin/static/colorpicker.js b/internal/admin/static/colorpicker.js index bba438e..f93b261 100644 --- a/internal/admin/static/colorpicker.js +++ b/internal/admin/static/colorpicker.js @@ -68,7 +68,7 @@ window.cssToXtermIdx = function(css) { if (pickerEl) return; pickerEl = document.createElement('div'); pickerEl.id = '__cp_popover'; - pickerEl.style.cssText = 'display:none;position:fixed;z-index:200;background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:8px;box-shadow:0 4px 20px rgba(0,0,0,.5);min-width:280px'; + pickerEl.style.cssText = 'display:none;position:fixed;z-index:200;background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:8px;box-shadow:0 4px 20px rgba(0,0,0,.5);min-width:208px'; pickerEl.innerHTML = buildPickerHTML(); document.body.appendChild(pickerEl); @@ -91,12 +91,41 @@ window.cssToXtermIdx = function(css) { h += '<input id="__cp_hex" style="flex:1;padding:3px 6px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace;text-transform:uppercase" maxlength="2" placeholder="00">'; h += '<button id="__cp_close" style="background:none;border:none;color:#aaa;cursor:pointer;font-size:14px;padding:0 4px">×</button>'; h += '</div>'; - h += '<div class="cp-grid">'; - for (var i = 0; i < 256; i++) { + + // ANSI colors 0-15 (2 rows of 8) + h += '<div class="cp-section-label">ANSI</div>'; + h += '<div class="cp-grid cp-ansi">'; + for (var i = 0; i < 16; i++) { var css = xtermToCss(i); h += '<div class="cp-swatch" data-idx="'+i+'" style="background:'+css+'" title="'+xtermIdxToHex(i)+' ('+i+')"></div>'; } h += '</div>'; + + // Color cube 16-231: six 6x6 slices (G=rows, B=cols, each slice is one R level) + h += '<div class="cp-section-label">Color Cube</div>'; + h += '<div class="cp-cube-row">'; + for (var r = 0; r < 6; r++) { + h += '<div class="cp-grid cp-cube">'; + for (var g = 0; g < 6; g++) { + for (var b = 0; b < 6; b++) { + var idx = 16 + 36*r + 6*g + b; + var css = xtermToCss(idx); + h += '<div class="cp-swatch" data-idx="'+idx+'" style="background:'+css+'" title="'+xtermIdxToHex(idx)+' ('+idx+')"></div>'; + } + } + h += '</div>'; + } + h += '</div>'; + + // Grayscale 232-255 (2 rows of 12) + h += '<div class="cp-section-label">Grayscale</div>'; + h += '<div class="cp-grid cp-gray">'; + for (var i = 232; i < 256; i++) { + var css = xtermToCss(i); + h += '<div class="cp-swatch" data-idx="'+i+'" style="background:'+css+'" title="'+xtermIdxToHex(i)+' ('+i+')"></div>'; + } + h += '</div>'; + return h; } |
