Files
xAL6-zero-loader/web/static/style.css
T
Claude e4ce930447 Add web/ console — browser-driven wrapper for encrypt / sideload / build
Single-origin localhost-bound Flask app that wraps the three CLI
entry points (Encrypt.py, SideloadGen.py, build.bat) so the operator
drives the whole pipeline from a browser instead of a shell.

Design:
  - Bind 127.0.0.1 only; banner printed at startup; no auth.
  - Three tabs (Encrypt, Sideload, Build) + an artifacts sidebar.
  - Build output streams live via chunked text/plain response.
  - Every compile-time flag (uac, RWX_SHELLCODE, DEBUG,
    ENABLE_SYNTHETIC_STACK) is a checkbox; forwarded to build.bat
    via positional args or the new CFLAGS_EXTRA env var.
  - File uploads land in web/workspace/ (gitignored); downloads
    served from project root with safe-name regex + no traversal.

Supporting changes:
  - build.bat honors CFLAGS_EXTRA so the web UI can inject /DDEBUG
    etc. without editing the batch script.
  - .gitignore excludes web/.venv/ and web/workspace/.
  - README.md gains a collapsible "Web Console" section under
    Quick Start, pointing at web/run.bat.

Stack: Flask only (no build step on frontend); single HTML file +
vanilla JS fetch. Dark-theme CSS matching the project's README
badges.

Not a remote service. Operator is responsible for not exposing the
port — the banner says so at startup and a pill in the UI header
reads "127.0.0.1 only".
2026-04-17 18:44:00 +00:00

293 lines
7.0 KiB
CSS

/* zero-loader console — minimal dark theme */
:root {
--bg: #0a0d12;
--bg-panel: #10151c;
--bg-elev: #161c25;
--bg-input: #0c1118;
--line: #1f2731;
--line-hot: #2a3341;
--text: #e4e9f0;
--text-dim: #8a95a7;
--text-mute: #5a6479;
--accent: #6ae0d7;
--accent-dim:#3e9891;
--warn: #f2b85a;
--err: #f26a6a;
--ok: #6adc84;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
background: var(--bg);
color: var(--text);
font: 14px/1.55 "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
display: grid;
grid-template-columns: 1fr 320px;
grid-template-rows: auto auto 1fr;
grid-template-areas:
"top top"
"tabs sidebar"
"stage sidebar";
min-height: 100vh;
}
/* ---------- top bar ---------- */
.top {
grid-area: top;
padding: 14px 22px;
border-bottom: 1px solid var(--line);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
background: var(--bg-panel);
}
.brand { display: flex; align-items: baseline; gap: 10px; }
.brand .glyph { color: var(--accent); font-size: 14px; }
.brand .title { font-weight: 600; letter-spacing: .4px; font-size: 15px; }
.brand .subtitle { color: var(--text-mute); font-size: 12px; letter-spacing: .4px; }
.status-bar { display: flex; gap: 8px; }
.pill {
font-size: 11px;
padding: 4px 10px;
border: 1px solid var(--line-hot);
border-radius: 100px;
background: var(--bg-input);
color: var(--text-dim);
letter-spacing: .4px;
}
.pill.ok { color: var(--ok); border-color: #264a32; }
.pill.bad { color: var(--err); border-color: #4a2626; }
.pill.warn { color: var(--warn); border-color: #4a3a1e; }
/* ---------- tabs ---------- */
.tabs {
grid-area: tabs;
display: flex;
gap: 2px;
padding: 0 22px;
background: var(--bg-panel);
border-bottom: 1px solid var(--line);
}
.tab {
background: transparent;
color: var(--text-mute);
border: 0;
padding: 12px 18px;
font: inherit;
letter-spacing: .5px;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color .15s, border-color .15s;
}
.tab:hover { color: var(--text-dim); }
.tab.active { color: var(--accent); border-color: var(--accent); }
/* ---------- stage ---------- */
.stage { grid-area: stage; padding: 26px 22px; overflow: auto; }
.panel { display: none; max-width: 900px; }
.panel.active { display: block; }
.panel-head { margin-bottom: 18px; }
.panel-head h2 { margin: 0 0 6px; font-size: 18px; font-weight: 600; letter-spacing: .3px; }
.panel-head p { margin: 0; color: var(--text-dim); font-size: 13px; }
/* ---------- forms ---------- */
.form { display: flex; flex-direction: column; gap: 16px; }
.field {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 12px;
color: var(--text-dim);
letter-spacing: .3px;
}
.field > span, .field > legend {
text-transform: uppercase;
font-size: 11px;
letter-spacing: .6px;
color: var(--text-mute);
}
.field input[type="file"],
.field input[type="text"],
.field input[type="url"] {
background: var(--bg-input);
border: 1px solid var(--line);
color: var(--text);
padding: 9px 12px;
font: inherit;
font-size: 13px;
border-radius: 4px;
outline: none;
transition: border-color .15s;
}
.field input:focus { border-color: var(--accent-dim); }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.field.radio {
flex-direction: row;
align-items: center;
gap: 8px;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: 4px;
background: var(--bg-input);
cursor: pointer;
font-size: 13px;
color: var(--text);
}
.field.radio:has(input:checked) { border-color: var(--accent-dim); color: var(--accent); }
fieldset.field {
border: 1px solid var(--line);
border-radius: 4px;
padding: 14px 16px;
margin: 0;
}
fieldset.field legend { padding: 0 6px; }
.check {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 0;
font-size: 13px;
color: var(--text);
cursor: pointer;
}
.check input { accent-color: var(--accent); }
/* ---------- actions ---------- */
.actions {
display: flex;
align-items: center;
gap: 14px;
margin-top: 4px;
flex-wrap: wrap;
}
button {
background: var(--bg-elev);
color: var(--text);
border: 1px solid var(--line-hot);
padding: 10px 18px;
font: inherit;
font-size: 13px;
letter-spacing: .4px;
border-radius: 4px;
cursor: pointer;
transition: background .15s, border-color .15s, color .15s;
}
button:hover { border-color: var(--accent-dim); color: var(--accent); }
button.primary {
background: var(--accent-dim);
color: #08100e;
border-color: var(--accent-dim);
font-weight: 600;
}
button.primary:hover { background: var(--accent); color: #08100e; }
button:disabled { opacity: .4; cursor: not-allowed; }
.hint { color: var(--text-mute); font-size: 12px; }
/* ---------- console ---------- */
.console {
margin: 18px 0 0;
background: #06090d;
border: 1px solid var(--line);
border-radius: 4px;
padding: 14px 16px;
color: var(--text-dim);
font-size: 12.5px;
min-height: 80px;
max-height: 380px;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
}
.console.ok { color: var(--ok); }
.console.bad { color: var(--err); border-color: #4a2626; }
.preview-wrap { margin-top: 14px; }
.preview-wrap summary {
cursor: pointer;
color: var(--text-mute);
font-size: 12px;
letter-spacing: .4px;
padding: 6px 0;
}
.preview-wrap summary:hover { color: var(--text-dim); }
.preview {
margin: 8px 0 0;
background: #06090d;
border: 1px solid var(--line);
border-radius: 4px;
padding: 14px 16px;
color: var(--text-dim);
font-size: 12px;
max-height: 280px;
overflow: auto;
white-space: pre;
}
/* ---------- sidebar ---------- */
.sidebar {
grid-area: sidebar;
background: var(--bg-panel);
border-left: 1px solid var(--line);
padding: 22px;
overflow: auto;
}
.sidebar h3 {
margin: 0 0 14px;
font-size: 12px;
font-weight: 600;
letter-spacing: .8px;
text-transform: uppercase;
color: var(--text-mute);
}
.artifacts { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }
.artifact {
padding: 12px 14px;
border: 1px solid var(--line);
border-radius: 4px;
background: var(--bg-input);
}
.artifact .name {
font-size: 13px;
color: var(--text);
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
}
.artifact .meta { font-size: 11px; color: var(--text-mute); margin-top: 4px; }
.artifact a.dl {
color: var(--accent);
text-decoration: none;
font-size: 11px;
letter-spacing: .4px;
padding: 3px 8px;
border: 1px solid var(--line-hot);
border-radius: 3px;
transition: background .1s;
}
.artifact a.dl:hover { background: var(--accent-dim); color: #08100e; }
.footnote { color: var(--text-mute); font-size: 11px; margin-top: 14px; }
.empty-note { color: var(--text-mute); font-size: 12px; padding: 10px 0; }
code {
background: var(--bg-input);
padding: 1px 5px;
border-radius: 3px;
font-size: 11.5px;
color: var(--accent);
}