parent
c37b172991
commit
2a63573c1b
@ -1,112 +0,0 @@ |
|||||||
<!DOCTYPE html> |
|
||||||
<html lang="en"> |
|
||||||
|
|
||||||
<head> |
|
||||||
<meta charset="UTF-8" /> |
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> |
|
||||||
<title>MonoDisplay Editor</title> |
|
||||||
<script>!function(){var t=localStorage.getItem('theme');t&&document.documentElement.setAttribute('data-theme',t)}();</script> |
|
||||||
<link href="style.css" rel="stylesheet"/> |
|
||||||
</head> |
|
||||||
|
|
||||||
<body> |
|
||||||
|
|
||||||
<div id="topbar"> |
|
||||||
<span class="app-name">MonoDisplay</span> |
|
||||||
<div class="tb-sep"></div> |
|
||||||
<button class="tb-btn" onclick="document.getElementById('file-input').click()"> |
|
||||||
<svg viewBox="0 0 24 24"> |
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> |
|
||||||
<polyline points="17 8 12 3 7 8" /> |
|
||||||
<line x1="12" y1="3" x2="12" y2="15" /> |
|
||||||
</svg> |
|
||||||
Load .bin |
|
||||||
</button> |
|
||||||
<input type="file" id="file-input" accept=".bin" onchange="loadBin(this)" /> |
|
||||||
<button class="tb-btn" onclick="exportBin()"> |
|
||||||
<svg viewBox="0 0 24 24"> |
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> |
|
||||||
<polyline points="7 10 12 15 17 10" /> |
|
||||||
<line x1="12" y1="15" x2="12" y2="3" /> |
|
||||||
</svg> |
|
||||||
Export .bin |
|
||||||
</button> |
|
||||||
<div class="tb-sep"></div> |
|
||||||
<button class="tb-btn" onclick="clearAll()"> |
|
||||||
<svg viewBox="0 0 24 24"> |
|
||||||
<polyline points="3 6 5 6 21 6" /> |
|
||||||
<path d="M19 6l-1 14H6L5 6" /> |
|
||||||
<path d="M10 11v6M14 11v6" /> |
|
||||||
</svg> |
|
||||||
Clear all |
|
||||||
</button> |
|
||||||
<button class="tb-btn" onclick="addSection()"> |
|
||||||
<svg viewBox="0 0 24 24"> |
|
||||||
<line x1="12" y1="5" x2="12" y2="19" /> |
|
||||||
<line x1="5" y1="12" x2="19" y2="12" /> |
|
||||||
</svg> |
|
||||||
Add section |
|
||||||
</button> |
|
||||||
<span id="filename">untitled</span> |
|
||||||
<div style="flex:1"></div> |
|
||||||
<div class="tb-sep"></div> |
|
||||||
<button class="tb-btn" id="theme-toggle" onclick="cycleTheme()">⊙ auto</button> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div id="main"> |
|
||||||
<div id="left"> |
|
||||||
<span class="pv-label">preview · 120 × 60</span> |
|
||||||
<div id="display-box"> |
|
||||||
<canvas id="canvas_root" width="120" height="60"></canvas> |
|
||||||
</div> |
|
||||||
<span class="pv-label">demos</span> |
|
||||||
<div id="demo-btns"></div> |
|
||||||
<div id="sec-meta"> |
|
||||||
<div class="meta-row"> |
|
||||||
<label>Selected section</label> |
|
||||||
<span class="meta-val green" id="meta-name">-</span> |
|
||||||
</div> |
|
||||||
<div class="meta-row"> |
|
||||||
<label>Elements</label> |
|
||||||
<span class="meta-val" id="meta-count">-</span> |
|
||||||
</div> |
|
||||||
<div class="meta-row"> |
|
||||||
<label>Section flags</label> |
|
||||||
<label class="flag-check"> |
|
||||||
<input type="checkbox" id="flag-drawFront" onchange="setSectionFlag('drawFront',this.checked)" /> |
|
||||||
drawFront |
|
||||||
</label> |
|
||||||
<label class="flag-check" style="margin-left:-8px"> |
|
||||||
<input type="checkbox" id="flag-drawBack" onchange="setSectionFlag('drawBack',this.checked)" /> |
|
||||||
drawBack |
|
||||||
</label> |
|
||||||
<label class="flag-check" style="margin-left:-8px"> |
|
||||||
<input type="checkbox" id="flag-clearBuffer" onchange="setSectionFlag('clearBuffer',this.checked)" /> |
|
||||||
clearBuffer |
|
||||||
</label> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div id="right"> |
|
||||||
<div id="right-hdr"> |
|
||||||
<span class="rh-title">sections</span> |
|
||||||
</div> |
|
||||||
<div id="sections-wrap"> |
|
||||||
<div class="empty-state">No sections yet.<br />Use <b>Add section</b> to get started.</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<!-- confirm dialog --> |
|
||||||
<div id="confirm-overlay"> |
|
||||||
<div id="confirm-box"> |
|
||||||
<div id="confirm-msg"></div> |
|
||||||
<div id="confirm-btns"></div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<script src="./public/mono-display.js"></script> |
|
||||||
</body> |
|
||||||
|
|
||||||
</html> |
|
||||||
@ -1,215 +0,0 @@ |
|||||||
<?php |
|
||||||
session_start(); |
|
||||||
|
|
||||||
define('PASSWORD', 'password'); |
|
||||||
define('FILES_DIR', __DIR__ . '/avj2305'); |
|
||||||
define('ACTIVE_FILE', __DIR__ . '/active.txt'); |
|
||||||
|
|
||||||
// ============================================================================= |
|
||||||
// HELPERS |
|
||||||
// ============================================================================= |
|
||||||
|
|
||||||
function redirect(string $url): never { |
|
||||||
header('Location: ' . $url); |
|
||||||
exit; |
|
||||||
} |
|
||||||
|
|
||||||
function require_auth(): void { |
|
||||||
if (empty($_SESSION['auth'])) redirect('?login'); |
|
||||||
} |
|
||||||
|
|
||||||
function get_active(): string { |
|
||||||
if (!file_exists(ACTIVE_FILE)) return ''; |
|
||||||
$v = trim(file_get_contents(ACTIVE_FILE)); |
|
||||||
return $v !== '' ? $v : ''; |
|
||||||
} |
|
||||||
|
|
||||||
function set_active(string $filename): void { |
|
||||||
file_put_contents(ACTIVE_FILE, $filename); |
|
||||||
} |
|
||||||
|
|
||||||
function get_bin_files(): array { |
|
||||||
$files = glob(FILES_DIR . '/*.bin'); |
|
||||||
return $files ? array_map('basename', $files) : []; |
|
||||||
} |
|
||||||
|
|
||||||
function safe_filename(string $name): bool { |
|
||||||
return $name === basename($name) |
|
||||||
&& str_ends_with($name, '.bin') |
|
||||||
&& !str_contains($name, "\0"); |
|
||||||
} |
|
||||||
|
|
||||||
function has_png(string $bin_basename): bool { |
|
||||||
$png = substr($bin_basename, 0, -4) . '.png'; |
|
||||||
return file_exists(FILES_DIR . '/' . $png); |
|
||||||
} |
|
||||||
|
|
||||||
function png_path(string $bin_basename): string { |
|
||||||
return FILES_DIR . '/' . substr($bin_basename, 0, -4) . '.png'; |
|
||||||
} |
|
||||||
|
|
||||||
// ============================================================================= |
|
||||||
// ACTIONS (POST handlers - redirect, never render) |
|
||||||
// ============================================================================= |
|
||||||
|
|
||||||
function action_login(): never { |
|
||||||
if (hash_equals(PASSWORD, $_POST['password'] ?? '')) { |
|
||||||
$_SESSION['auth'] = true; |
|
||||||
redirect('?edit'); |
|
||||||
} |
|
||||||
redirect('?login&err=1'); |
|
||||||
} |
|
||||||
|
|
||||||
function action_set_file(): never { |
|
||||||
require_auth(); |
|
||||||
$f = $_POST['file'] ?? ''; |
|
||||||
if (safe_filename($f) && file_exists(FILES_DIR . '/' . $f)) { |
|
||||||
set_active($f); |
|
||||||
} |
|
||||||
redirect('?edit'); |
|
||||||
} |
|
||||||
|
|
||||||
// ============================================================================= |
|
||||||
// RENDERERS (GET handlers - output HTML, never redirect) |
|
||||||
// ============================================================================= |
|
||||||
|
|
||||||
function render_login(): never { |
|
||||||
$err = !empty($_GET['err']); ?> |
|
||||||
<!DOCTYPE html> |
|
||||||
<html lang="en"> |
|
||||||
<head><meta charset="utf-8"><title>Login</title> |
|
||||||
<style> |
|
||||||
body{font:14px monospace;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#111;color:#eee} |
|
||||||
form{display:flex;flex-direction:column;gap:8px;width:220px} |
|
||||||
input[type=password]{padding:6px;background:#222;border:1px solid #555;color:#eee} |
|
||||||
button{padding:6px;background:#444;border:none;color:#eee;cursor:pointer} |
|
||||||
button:hover{background:#555} |
|
||||||
.err{color:#f66;font-size:12px} |
|
||||||
</style> |
|
||||||
</head> |
|
||||||
<body> |
|
||||||
<form method="post" action="?login"> |
|
||||||
<label>Password</label> |
|
||||||
<input type="password" name="password" autofocus> |
|
||||||
<button type="submit">Login</button> |
|
||||||
<?php if ($err): ?><span class="err">Wrong password.</span><?php endif; ?> |
|
||||||
</form> |
|
||||||
</body></html> |
|
||||||
<?php exit; } |
|
||||||
|
|
||||||
function render_edit(): never { |
|
||||||
require_auth(); |
|
||||||
$files = get_bin_files(); |
|
||||||
$active = get_active(); ?> |
|
||||||
<!DOCTYPE html> |
|
||||||
<html lang="en"> |
|
||||||
<head><meta charset="utf-8"><title>Select File</title> |
|
||||||
<style> |
|
||||||
body{font:14px monospace;background:#111;color:#eee;padding:24px;margin:0} |
|
||||||
h2{margin:0 0 16px} |
|
||||||
ul{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:6px} |
|
||||||
li form{margin:0} |
|
||||||
.row{display:flex;align-items:center;gap:10px} |
|
||||||
button{padding:6px 12px;background:#333;border:1px solid #555;color:#eee;cursor:pointer;flex:1;text-align:left} |
|
||||||
button:hover{background:#444} |
|
||||||
button.active{border-color:#6af;color:#6af} |
|
||||||
.thumb{width:480px;height:270px;object-fit:cover;border:1px solid #444;background:#222;flex-shrink:0} |
|
||||||
.nothumb{width:64px;height:64px;flex-shrink:0} |
|
||||||
.none{color:#888} |
|
||||||
</style> |
|
||||||
</head> |
|
||||||
<body> |
|
||||||
<h2>Live file selector</h2> |
|
||||||
<p>Active: <strong><?= $active !== '' ? htmlspecialchars($active) : '<span class="none">none</span>' ?></strong></p>
|
|
||||||
<?php if ($files): ?> |
|
||||||
<ul> |
|
||||||
<?php foreach ($files as $f): ?> |
|
||||||
<li> |
|
||||||
<form method="post" action="?edit"> |
|
||||||
<div class="row"> |
|
||||||
<?php if (has_png($f)): ?> |
|
||||||
<img class="thumb" src="?img=<?= urlencode($f) ?>" alt="">
|
|
||||||
<?php else: ?> |
|
||||||
<div class="nothumb"></div> |
|
||||||
<?php endif; ?> |
|
||||||
<input type="hidden" name="file" value="<?= htmlspecialchars($f) ?>">
|
|
||||||
<button type="submit"<?= $f === $active ? ' class="active"' : '' ?>><?= htmlspecialchars($f) ?></button>
|
|
||||||
</div> |
|
||||||
</form> |
|
||||||
</li> |
|
||||||
<?php endforeach; ?> |
|
||||||
</ul> |
|
||||||
<?php else: ?> |
|
||||||
<p class="none">No .bin files found in avj2305/</p> |
|
||||||
<?php endif; ?> |
|
||||||
</body></html> |
|
||||||
<?php exit; } |
|
||||||
|
|
||||||
// ============================================================================= |
|
||||||
// FILE SERVER (default route) |
|
||||||
// ============================================================================= |
|
||||||
|
|
||||||
function serve_png(): never { |
|
||||||
require_auth(); |
|
||||||
$f = $_GET['img'] ?? ''; |
|
||||||
if (!safe_filename($f)) { |
|
||||||
http_response_code(400); exit('Invalid filename.'); |
|
||||||
} |
|
||||||
$path = png_path($f); |
|
||||||
if (!file_exists($path)) { |
|
||||||
http_response_code(404); exit('No preview.'); |
|
||||||
} |
|
||||||
header('Content-Type: image/png'); |
|
||||||
header('Content-Length: ' . filesize($path)); |
|
||||||
readfile($path); |
|
||||||
exit; |
|
||||||
} |
|
||||||
|
|
||||||
function serve_active_file(): never { |
|
||||||
$active = get_active(); |
|
||||||
|
|
||||||
if ($active === '') { |
|
||||||
http_response_code(404); exit('No active file set.'); |
|
||||||
} |
|
||||||
if (!safe_filename($active)) { |
|
||||||
http_response_code(500); exit('Invalid filename.'); |
|
||||||
} |
|
||||||
|
|
||||||
$path = FILES_DIR . '/' . $active; |
|
||||||
|
|
||||||
if (!file_exists($path)) { |
|
||||||
http_response_code(404); exit('File not found.'); |
|
||||||
} |
|
||||||
|
|
||||||
header('Content-Type: application/octet-stream'); |
|
||||||
header('Content-Disposition: inline; filename="' . addslashes($active) . '"'); |
|
||||||
header('Content-Length: ' . filesize($path)); |
|
||||||
readfile($path); |
|
||||||
exit; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================= |
|
||||||
// ROUTES |
|
||||||
// ============================================================================= |
|
||||||
// |
|
||||||
// GET /live.php -> serve active .bin file |
|
||||||
// GET /live.php?login -> login page |
|
||||||
// POST /live.php?login -> process login |
|
||||||
// GET /live.php?edit -> file picker [auth required] |
|
||||||
// POST /live.php?edit -> set active file [auth required] |
|
||||||
// GET /live.php?img=x.bin -> serve x.png preview [auth required] |
|
||||||
// |
|
||||||
// ============================================================================= |
|
||||||
|
|
||||||
$method = $_SERVER['REQUEST_METHOD']; |
|
||||||
$route = array_key_first($_GET) ?? ''; |
|
||||||
|
|
||||||
match (true) { |
|
||||||
$method === 'POST' && $route === 'login' => action_login(), |
|
||||||
$method === 'POST' && $route === 'edit' => action_set_file(), |
|
||||||
$method === 'GET' && $route === 'login' => render_login(), |
|
||||||
$method === 'GET' && $route === 'edit' => render_edit(), |
|
||||||
$method === 'GET' && $route === 'img' => serve_png(), |
|
||||||
default => serve_active_file(), |
|
||||||
}; |
|
||||||
@ -1,682 +0,0 @@ |
|||||||
:root { |
|
||||||
--bg: #111; |
|
||||||
--bg-bar: #1a1a1a; |
|
||||||
--bg-raised: #161616; |
|
||||||
--bg-hover: #1b1b1b; |
|
||||||
--bg-hover2: #222; |
|
||||||
--bg-sunken: #141414; |
|
||||||
--bg-accent: #141e14; |
|
||||||
--bg-active: #1e2e1e; |
|
||||||
--bg-deep: #0f0f0f; |
|
||||||
--bg-input: #0a0a0a; |
|
||||||
--bg-canvas: #000; |
|
||||||
--bg-xhover: #1e1010; |
|
||||||
--bg-overlay: rgba(0,0,0,.6); |
|
||||||
--bd: #2a2a2a; |
|
||||||
--bd-inner: #1e1e1e; |
|
||||||
--bd-deep: #1c1c1c; |
|
||||||
--bd-card: #222; |
|
||||||
--bd-btn: #2d2d2d; |
|
||||||
--bd-type: #252525; |
|
||||||
--bd-active: #2b3d2b; |
|
||||||
--bd-hover: #444; |
|
||||||
--bd-strong: #333; |
|
||||||
--tx: #ccc; |
|
||||||
--tx-hover: #bbb; |
|
||||||
--tx-sub: #888; |
|
||||||
--tx-label: #777; |
|
||||||
--tx-meta: #666; |
|
||||||
--tx-dim: #555; |
|
||||||
--tx-faint: #444; |
|
||||||
--tx-ghost: #333; |
|
||||||
--tx-file: #3a3a3a; |
|
||||||
--tx-empty: #2e2e2e; |
|
||||||
--ac: #33ff66; |
|
||||||
--dirty: #886622 |
|
||||||
} |
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) { |
|
||||||
:root:not([data-theme="dark"]) { |
|
||||||
--bg: #f5f5f5; |
|
||||||
--bg-bar: #e8e8e8; |
|
||||||
--bg-raised: #efefef; |
|
||||||
--bg-hover: #e5e5e5; |
|
||||||
--bg-hover2: #e0e0e0; |
|
||||||
--bg-sunken: #ebebeb; |
|
||||||
--bg-accent: #e8f5e8; |
|
||||||
--bg-active: #dff0df; |
|
||||||
--bg-deep: #f0f0f0; |
|
||||||
--bg-input: #fff; |
|
||||||
--bg-canvas: #fff; |
|
||||||
--bg-xhover: #ffe8e8; |
|
||||||
--bg-overlay: rgba(0,0,0,.4); |
|
||||||
--bd: #d0d0d0; |
|
||||||
--bd-inner: #ddd; |
|
||||||
--bd-deep: #e0e0e0; |
|
||||||
--bd-card: #ddd; |
|
||||||
--bd-btn: #ccc; |
|
||||||
--bd-type: #d8d8d8; |
|
||||||
--bd-active: #7dc87d; |
|
||||||
--bd-hover: #bbb; |
|
||||||
--bd-strong: #ccc; |
|
||||||
--tx: #222; |
|
||||||
--tx-hover: #333; |
|
||||||
--tx-sub: #555; |
|
||||||
--tx-label: #666; |
|
||||||
--tx-meta: #777; |
|
||||||
--tx-dim: #888; |
|
||||||
--tx-faint: #999; |
|
||||||
--tx-ghost: #aaa; |
|
||||||
--tx-file: #aaa; |
|
||||||
--tx-empty: #bbb; |
|
||||||
--ac: #1a9940; |
|
||||||
--dirty: #b37a00 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
[data-theme="light"] { |
|
||||||
--bg: #f5f5f5; |
|
||||||
--bg-bar: #e8e8e8; |
|
||||||
--bg-raised: #efefef; |
|
||||||
--bg-hover: #e5e5e5; |
|
||||||
--bg-hover2: #e0e0e0; |
|
||||||
--bg-sunken: #ebebeb; |
|
||||||
--bg-accent: #e8f5e8; |
|
||||||
--bg-active: #dff0df; |
|
||||||
--bg-deep: #f0f0f0; |
|
||||||
--bg-input: #fff; |
|
||||||
--bg-canvas: #fff; |
|
||||||
--bg-xhover: #ffe8e8; |
|
||||||
--bg-overlay: rgba(0,0,0,.4); |
|
||||||
--bd: #d0d0d0; |
|
||||||
--bd-inner: #ddd; |
|
||||||
--bd-deep: #e0e0e0; |
|
||||||
--bd-card: #ddd; |
|
||||||
--bd-btn: #ccc; |
|
||||||
--bd-type: #d8d8d8; |
|
||||||
--bd-active: #7dc87d; |
|
||||||
--bd-hover: #bbb; |
|
||||||
--bd-strong: #ccc; |
|
||||||
--tx: #222; |
|
||||||
--tx-hover: #333; |
|
||||||
--tx-sub: #555; |
|
||||||
--tx-label: #666; |
|
||||||
--tx-meta: #777; |
|
||||||
--tx-dim: #888; |
|
||||||
--tx-faint: #999; |
|
||||||
--tx-ghost: #aaa; |
|
||||||
--tx-file: #aaa; |
|
||||||
--tx-empty: #bbb; |
|
||||||
--ac: #1a9940; |
|
||||||
--dirty: #b37a00 |
|
||||||
} |
|
||||||
|
|
||||||
*, |
|
||||||
*::before, |
|
||||||
*::after { |
|
||||||
box-sizing: border-box; |
|
||||||
margin: 0; |
|
||||||
padding: 0 |
|
||||||
} |
|
||||||
|
|
||||||
body { |
|
||||||
background: var(--bg); |
|
||||||
color: var(--tx); |
|
||||||
font-family: monospace; |
|
||||||
height: 100vh; |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
overflow: hidden; |
|
||||||
font-size: 12px |
|
||||||
} |
|
||||||
|
|
||||||
#topbar { |
|
||||||
height: 36px; |
|
||||||
background: var(--bg-bar); |
|
||||||
border-bottom: 1px solid var(--bd); |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
gap: 8px; |
|
||||||
padding: 0 12px; |
|
||||||
flex-shrink: 0 |
|
||||||
} |
|
||||||
|
|
||||||
#topbar .app-name { |
|
||||||
color: var(--tx-dim); |
|
||||||
font-size: 11px; |
|
||||||
letter-spacing: .12em; |
|
||||||
text-transform: uppercase; |
|
||||||
margin-right: 4px |
|
||||||
} |
|
||||||
|
|
||||||
.tb-btn { |
|
||||||
background: var(--bg-raised); |
|
||||||
color: var(--tx-sub); |
|
||||||
border: 1px solid var(--bd-btn); |
|
||||||
border-radius: 3px; |
|
||||||
padding: 3px 10px; |
|
||||||
font-family: monospace; |
|
||||||
font-size: 11px; |
|
||||||
cursor: pointer; |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
gap: 5px; |
|
||||||
white-space: nowrap |
|
||||||
} |
|
||||||
|
|
||||||
.tb-btn:hover { |
|
||||||
background: var(--bg-hover2); |
|
||||||
color: var(--tx-hover); |
|
||||||
border-color: var(--bd-hover) |
|
||||||
} |
|
||||||
|
|
||||||
.tb-btn svg { |
|
||||||
width: 13px; |
|
||||||
height: 13px; |
|
||||||
stroke: currentColor; |
|
||||||
fill: none; |
|
||||||
stroke-width: 1.8; |
|
||||||
stroke-linecap: round; |
|
||||||
stroke-linejoin: round; |
|
||||||
flex-shrink: 0 |
|
||||||
} |
|
||||||
|
|
||||||
#file-input { |
|
||||||
display: none |
|
||||||
} |
|
||||||
|
|
||||||
#filename { |
|
||||||
color: var(--tx-file); |
|
||||||
font-size: 11px; |
|
||||||
margin-left: 2px; |
|
||||||
max-width: 200px; |
|
||||||
overflow: hidden; |
|
||||||
text-overflow: ellipsis; |
|
||||||
white-space: nowrap |
|
||||||
} |
|
||||||
|
|
||||||
.tb-sep { |
|
||||||
width: 1px; |
|
||||||
height: 18px; |
|
||||||
background: var(--bd) |
|
||||||
} |
|
||||||
|
|
||||||
.dirty { |
|
||||||
color: var(--dirty) !important |
|
||||||
} |
|
||||||
|
|
||||||
#main { |
|
||||||
flex: 1; |
|
||||||
display: flex; |
|
||||||
min-height: 0 |
|
||||||
} |
|
||||||
|
|
||||||
#left { |
|
||||||
width: 48%; |
|
||||||
border-right: 1px solid var(--bd-inner); |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
padding: 14px; |
|
||||||
gap: 10px; |
|
||||||
overflow-y: auto; |
|
||||||
flex-shrink: 0 |
|
||||||
} |
|
||||||
|
|
||||||
#display-box { |
|
||||||
width: 100%; |
|
||||||
aspect-ratio: 2/1; |
|
||||||
background: var(--bg-canvas); |
|
||||||
border: 1px solid var(--bd); |
|
||||||
border-radius: 3px; |
|
||||||
overflow: hidden |
|
||||||
} |
|
||||||
|
|
||||||
#canvas_root { |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
display: block; |
|
||||||
image-rendering: pixelated; |
|
||||||
image-rendering: crisp-edges |
|
||||||
} |
|
||||||
|
|
||||||
.pv-label { |
|
||||||
font-size: 10px; |
|
||||||
color: var(--tx-ghost); |
|
||||||
letter-spacing: .1em; |
|
||||||
text-transform: uppercase |
|
||||||
} |
|
||||||
|
|
||||||
#demo-btns { |
|
||||||
display: flex; |
|
||||||
flex-wrap: wrap; |
|
||||||
gap: 5px; |
|
||||||
margin-top: 6px |
|
||||||
} |
|
||||||
#demo-btns > div { |
|
||||||
display: contents; |
|
||||||
} |
|
||||||
|
|
||||||
canvas.pixel-editor { |
|
||||||
display: block; |
|
||||||
cursor: crosshair; |
|
||||||
image-rendering: pixelated; |
|
||||||
max-width: 100%; |
|
||||||
border: 1px solid var(--bd-inner); |
|
||||||
} |
|
||||||
.anim-editor { |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
gap: 4px; |
|
||||||
} |
|
||||||
.anim-frame-tabs { |
|
||||||
display: flex; |
|
||||||
flex-wrap: wrap; |
|
||||||
gap: 3px; |
|
||||||
align-items: center; |
|
||||||
} |
|
||||||
.anim-frame-tab { |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
gap: 2px; |
|
||||||
padding: 2px 6px; |
|
||||||
border: 1px solid var(--bd-inner); |
|
||||||
border-radius: 3px; |
|
||||||
cursor: pointer; |
|
||||||
font-size: 11px; |
|
||||||
user-select: none; |
|
||||||
} |
|
||||||
.anim-frame-tab.active { |
|
||||||
border-color: var(--accent, #EC0); |
|
||||||
color: var(--accent, #EC0); |
|
||||||
} |
|
||||||
.x-btn-sm { |
|
||||||
background: none; |
|
||||||
border: none; |
|
||||||
color: inherit; |
|
||||||
cursor: pointer; |
|
||||||
padding: 0 2px; |
|
||||||
font-size: 13px; |
|
||||||
line-height: 1; |
|
||||||
opacity: 0.6; |
|
||||||
} |
|
||||||
.x-btn-sm:hover { opacity: 1; } |
|
||||||
|
|
||||||
#sec-meta { |
|
||||||
background: var(--bg-sunken); |
|
||||||
border: 1px solid var(--bd-inner); |
|
||||||
border-radius: 3px; |
|
||||||
padding: 8px 10px; |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
gap: 6px |
|
||||||
} |
|
||||||
|
|
||||||
.meta-row { |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
gap: 8px; |
|
||||||
font-size: 11px |
|
||||||
} |
|
||||||
|
|
||||||
.meta-row label { |
|
||||||
color: var(--tx-faint); |
|
||||||
min-width: 90px |
|
||||||
} |
|
||||||
|
|
||||||
.meta-val { |
|
||||||
color: var(--tx-meta) |
|
||||||
} |
|
||||||
|
|
||||||
.green { |
|
||||||
color: var(--ac) |
|
||||||
} |
|
||||||
|
|
||||||
select.meta-val { |
|
||||||
background: var(--bg-input); |
|
||||||
color: var(--ac); |
|
||||||
border: 1px solid var(--bd-inner); |
|
||||||
border-radius: 3px; |
|
||||||
padding: 1px 4px; |
|
||||||
} |
|
||||||
|
|
||||||
.flag-check { |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
gap: 5px; |
|
||||||
font-size: 11px; |
|
||||||
color: var(--tx-dim); |
|
||||||
cursor: pointer |
|
||||||
} |
|
||||||
|
|
||||||
.flag-check input { |
|
||||||
accent-color: var(--ac); |
|
||||||
cursor: pointer |
|
||||||
} |
|
||||||
|
|
||||||
#right { |
|
||||||
flex: 1; |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
min-height: 0; |
|
||||||
overflow: hidden |
|
||||||
} |
|
||||||
|
|
||||||
#right-hdr { |
|
||||||
height: 36px; |
|
||||||
background: var(--bg-sunken); |
|
||||||
border-bottom: 1px solid var(--bd-inner); |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
padding: 0 10px; |
|
||||||
gap: 6px; |
|
||||||
flex-shrink: 0 |
|
||||||
} |
|
||||||
|
|
||||||
#right-hdr .rh-title { |
|
||||||
flex: 1; |
|
||||||
color: var(--tx-faint); |
|
||||||
font-size: 11px; |
|
||||||
letter-spacing: .08em; |
|
||||||
text-transform: uppercase |
|
||||||
} |
|
||||||
|
|
||||||
#sections-wrap { |
|
||||||
flex: 1; |
|
||||||
overflow-y: auto; |
|
||||||
padding: 8px |
|
||||||
} |
|
||||||
|
|
||||||
.empty-state { |
|
||||||
color: var(--tx-empty); |
|
||||||
font-size: 11px; |
|
||||||
padding: 28px 16px; |
|
||||||
text-align: center; |
|
||||||
line-height: 2 |
|
||||||
} |
|
||||||
|
|
||||||
/* section card */ |
|
||||||
.sec-card { |
|
||||||
border: 1px solid var(--bd-card); |
|
||||||
border-radius: 3px; |
|
||||||
margin-bottom: 5px; |
|
||||||
overflow: hidden |
|
||||||
} |
|
||||||
|
|
||||||
.sec-card.active { |
|
||||||
border-color: var(--bd-active) |
|
||||||
} |
|
||||||
|
|
||||||
.sec-hdr { |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
gap: 7px; |
|
||||||
padding: 6px 8px; |
|
||||||
cursor: pointer; |
|
||||||
user-select: none; |
|
||||||
background: var(--bg-raised); |
|
||||||
position: relative |
|
||||||
} |
|
||||||
|
|
||||||
.sec-hdr:hover { |
|
||||||
background: var(--bg-hover) |
|
||||||
} |
|
||||||
|
|
||||||
.sec-card.active .sec-hdr, |
|
||||||
.sec-hdr.active { |
|
||||||
background: var(--bg-active) |
|
||||||
} |
|
||||||
|
|
||||||
.sec-arrow { |
|
||||||
color: var(--tx-ghost); |
|
||||||
font-size: 13px; |
|
||||||
line-height: 1; |
|
||||||
transition: transform .12s; |
|
||||||
flex-shrink: 0 |
|
||||||
} |
|
||||||
|
|
||||||
.sec-card.open .sec-arrow { |
|
||||||
transform: rotate(90deg) |
|
||||||
} |
|
||||||
|
|
||||||
.sec-label { |
|
||||||
flex: 1; |
|
||||||
color: var(--tx-label); |
|
||||||
font-size: 11px |
|
||||||
} |
|
||||||
|
|
||||||
.sec-badge { |
|
||||||
font-size: 10px; |
|
||||||
color: var(--ac); |
|
||||||
border: 1px solid var(--bd-active); |
|
||||||
background: var(--bg-accent); |
|
||||||
border-radius: 2px; |
|
||||||
padding: 1px 6px; |
|
||||||
flex-shrink: 0 |
|
||||||
} |
|
||||||
|
|
||||||
/* red × - section & element */ |
|
||||||
.x-btn { |
|
||||||
background: none; |
|
||||||
border: none; |
|
||||||
color: var(--bd); |
|
||||||
cursor: pointer; |
|
||||||
font-size: 15px; |
|
||||||
line-height: 1; |
|
||||||
padding: 2px 5px; |
|
||||||
border-radius: 2px; |
|
||||||
flex-shrink: 0; |
|
||||||
transition: color .1s, background .1s |
|
||||||
} |
|
||||||
|
|
||||||
.x-btn:hover { |
|
||||||
color: #ff4444; |
|
||||||
background: var(--bg-xhover) |
|
||||||
} |
|
||||||
|
|
||||||
/* element */ |
|
||||||
.el-list { |
|
||||||
background: var(--bg-deep); |
|
||||||
border-top: 1px solid var(--bd-deep); |
|
||||||
padding: 6px |
|
||||||
} |
|
||||||
|
|
||||||
.el-item { |
|
||||||
border: 1px solid var(--bd-inner); |
|
||||||
border-radius: 3px; |
|
||||||
background: var(--bg-sunken); |
|
||||||
margin-bottom: 4px; |
|
||||||
overflow: hidden |
|
||||||
} |
|
||||||
|
|
||||||
.el-item.active { |
|
||||||
border-color: var(--ac) |
|
||||||
} |
|
||||||
|
|
||||||
.el-item-hdr { |
|
||||||
display: flex; |
|
||||||
align-items: center; |
|
||||||
gap: 6px; |
|
||||||
padding: 5px 8px; |
|
||||||
cursor: pointer |
|
||||||
} |
|
||||||
|
|
||||||
.el-item-hdr:hover { |
|
||||||
background: var(--bg-hover) |
|
||||||
} |
|
||||||
|
|
||||||
.el-type { |
|
||||||
font-size: 10px; |
|
||||||
color: var(--tx-meta); |
|
||||||
border: 1px solid var(--bd-type); |
|
||||||
border-radius: 2px; |
|
||||||
padding: 1px 6px; |
|
||||||
flex-shrink: 0 |
|
||||||
} |
|
||||||
|
|
||||||
.el-item.active .el-type { |
|
||||||
color: var(--ac); |
|
||||||
border-color: var(--bd-active) |
|
||||||
} |
|
||||||
|
|
||||||
.el-name { |
|
||||||
flex: 1; |
|
||||||
color: var(--tx-dim); |
|
||||||
font-size: 11px; |
|
||||||
overflow: hidden; |
|
||||||
text-overflow: ellipsis; |
|
||||||
white-space: nowrap; |
|
||||||
max-width: 120px |
|
||||||
} |
|
||||||
|
|
||||||
.el-fields { |
|
||||||
padding: 6px 8px 8px; |
|
||||||
border-top: 1px solid var(--bg-bar); |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
gap: 5px |
|
||||||
} |
|
||||||
|
|
||||||
.fields-grid { |
|
||||||
display: grid; |
|
||||||
grid-template-columns: 1fr 1fr; |
|
||||||
gap: 4px 10px |
|
||||||
} |
|
||||||
|
|
||||||
.field { |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
gap: 2px |
|
||||||
} |
|
||||||
|
|
||||||
.field.full { |
|
||||||
grid-column: 1/-1 |
|
||||||
} |
|
||||||
|
|
||||||
.field>label { |
|
||||||
font-size: 10px; |
|
||||||
color: var(--tx-faint) |
|
||||||
} |
|
||||||
|
|
||||||
.field input[type=text], |
|
||||||
.field input[type=number], |
|
||||||
.field input[type=datetime-local], |
|
||||||
.field select, |
|
||||||
.field textarea { |
|
||||||
background: var(--bg-input); |
|
||||||
color: var(--tx-sub); |
|
||||||
border: 1px solid var(--bd-card); |
|
||||||
border-radius: 2px; |
|
||||||
padding: 3px 6px; |
|
||||||
font-family: monospace; |
|
||||||
font-size: 11px; |
|
||||||
width: 100%; |
|
||||||
outline: none |
|
||||||
} |
|
||||||
|
|
||||||
.field input:focus, |
|
||||||
.field select:focus, |
|
||||||
.field textarea:focus { |
|
||||||
border-color: var(--ac); |
|
||||||
color: var(--tx) |
|
||||||
} |
|
||||||
|
|
||||||
.field textarea { |
|
||||||
resize: vertical; |
|
||||||
min-height: 44px; |
|
||||||
line-height: 1.4 |
|
||||||
} |
|
||||||
|
|
||||||
.field select option { |
|
||||||
background: var(--bg-bar); |
|
||||||
color: var(--tx-ghost) |
|
||||||
} |
|
||||||
|
|
||||||
.flags-row { |
|
||||||
display: flex; |
|
||||||
flex-wrap: wrap; |
|
||||||
gap: 10px; |
|
||||||
padding: 2px 0 |
|
||||||
} |
|
||||||
|
|
||||||
.add-el { |
|
||||||
background: transparent; |
|
||||||
color: var(--tx-ghost); |
|
||||||
border: 1px dashed var(--bd-card); |
|
||||||
border-radius: 3px; |
|
||||||
padding: 5px 8px; |
|
||||||
font-family: monospace; |
|
||||||
font-size: 11px; |
|
||||||
cursor: pointer; |
|
||||||
width: 100%; |
|
||||||
text-align: center; |
|
||||||
margin-top: 2px |
|
||||||
} |
|
||||||
|
|
||||||
.add-el:hover { |
|
||||||
color: var(--tx-sub); |
|
||||||
border-color: var(--bd-hover) |
|
||||||
} |
|
||||||
|
|
||||||
/* confirm dialog */ |
|
||||||
#confirm-overlay { |
|
||||||
display: none; |
|
||||||
position: fixed; |
|
||||||
inset: 0; |
|
||||||
background: var(--bg-overlay); |
|
||||||
z-index: 100; |
|
||||||
align-items: center; |
|
||||||
justify-content: center |
|
||||||
} |
|
||||||
|
|
||||||
#confirm-overlay.show { |
|
||||||
display: flex |
|
||||||
} |
|
||||||
|
|
||||||
#confirm-box { |
|
||||||
background: var(--bg-bar); |
|
||||||
border: 1px solid var(--bd-strong); |
|
||||||
border-radius: 4px; |
|
||||||
padding: 20px 24px; |
|
||||||
max-width: 340px; |
|
||||||
width: 90%; |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
gap: 14px |
|
||||||
} |
|
||||||
|
|
||||||
#confirm-msg { |
|
||||||
color: var(--tx-ghost); |
|
||||||
font-size: 12px; |
|
||||||
line-height: 1.6 |
|
||||||
} |
|
||||||
|
|
||||||
#confirm-btns { |
|
||||||
display: flex; |
|
||||||
gap: 8px; |
|
||||||
justify-content: flex-end |
|
||||||
} |
|
||||||
|
|
||||||
.cb { |
|
||||||
background: var(--bg-raised); |
|
||||||
color: var(--tx-sub); |
|
||||||
border: 1px solid var(--bd-btn); |
|
||||||
border-radius: 3px; |
|
||||||
padding: 4px 14px; |
|
||||||
font-family: monospace; |
|
||||||
font-size: 11px; |
|
||||||
cursor: pointer |
|
||||||
} |
|
||||||
|
|
||||||
.cb:hover { |
|
||||||
background: var(--bg-hover2); |
|
||||||
color: var(--tx-hover) |
|
||||||
} |
|
||||||
|
|
||||||
.cb.primary { |
|
||||||
border-color: var(--bd-active); |
|
||||||
color: var(--ac) |
|
||||||
} |
|
||||||
|
|
||||||
.cb.primary:hover { |
|
||||||
background: var(--bg-active) |
|
||||||
} |
|
||||||
Loading…
Reference in new issue