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