1809 lines
76 KiB
HTML
1809 lines
76 KiB
HTML
<!doctype html>
|
|
<html lang="en" data-theme="azure">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>TES:VIII Map Editor Wiki</title>
|
|
<style>
|
|
:root {
|
|
color-scheme: dark;
|
|
--editor-shell-bg: #0A1020;
|
|
--editor-shell-fg: #D8E8FF;
|
|
--editor-menu-grad-1: #152645;
|
|
--editor-menu-grad-2: #10203C;
|
|
--editor-sidebar-bg: #0E1A33;
|
|
--editor-stage-bg: #060A14;
|
|
--editor-border: #2E426C;
|
|
--editor-border-strong: #3C5E95;
|
|
--editor-panel-bg: #121F3B;
|
|
--editor-panel-bg-alt: #132B4F;
|
|
--editor-panel-bg-elevated: #10284B;
|
|
--editor-panel-bg-hover: #1A3F6D;
|
|
--editor-control-bg: #1A345E;
|
|
--editor-control-bg-hover: #214679;
|
|
--editor-control-bg-active: #1E4B82;
|
|
--editor-control-border: #3C5E95;
|
|
--editor-control-fg: #D6E7FF;
|
|
--editor-muted: #9FB8E5;
|
|
--editor-muted-strong: #CFE2FF;
|
|
--editor-accent: #64AAF8;
|
|
--editor-accent-strong: #8FD0FF;
|
|
--editor-accent-soft: #22466E;
|
|
--editor-warn: #FFD166;
|
|
--editor-danger: #3C1A1A;
|
|
--editor-danger-border: #7F4C4C;
|
|
--editor-danger-hover: #5A2323;
|
|
--editor-preview-bg: #0D1B34;
|
|
--editor-drop-line: #64AAF8;
|
|
--editor-drop-shadow: rgba(100, 170, 248, 0.3);
|
|
--editor-status-ok: #B9CFEF;
|
|
--editor-status-error: #FF9E9E;
|
|
--editor-tooltip-shadow: rgba(0, 0, 20, 0.8);
|
|
--editor-tab-shadow: rgba(3, 8, 18, 0.8);
|
|
}
|
|
|
|
html[data-theme="verdant"] {
|
|
--editor-shell-bg: #081510;
|
|
--editor-shell-fg: #DDF7EA;
|
|
--editor-menu-grad-1: #16352B;
|
|
--editor-menu-grad-2: #0E251E;
|
|
--editor-sidebar-bg: #0D221B;
|
|
--editor-stage-bg: #06100D;
|
|
--editor-border: #305847;
|
|
--editor-border-strong: #46806A;
|
|
--editor-panel-bg: #122A22;
|
|
--editor-panel-bg-alt: #17362C;
|
|
--editor-panel-bg-elevated: #133127;
|
|
--editor-panel-bg-hover: #21503F;
|
|
--editor-control-bg: #1B4335;
|
|
--editor-control-bg-hover: #245844;
|
|
--editor-control-bg-active: #2D7258;
|
|
--editor-control-border: #46806A;
|
|
--editor-control-fg: #DDF7EA;
|
|
--editor-muted: #A5D2BE;
|
|
--editor-muted-strong: #D2F0E0;
|
|
--editor-accent: #70D8A6;
|
|
--editor-accent-strong: #8AE8BE;
|
|
--editor-accent-soft: #275845;
|
|
--editor-warn: #F5D66D;
|
|
--editor-danger: #472123;
|
|
--editor-danger-border: #8B5559;
|
|
--editor-danger-hover: #633034;
|
|
--editor-preview-bg: #10271F;
|
|
--editor-drop-line: #70D8A6;
|
|
--editor-drop-shadow: rgba(112, 216, 166, 0.3);
|
|
--editor-status-ok: #C0E9D5;
|
|
--editor-status-error: #FFADAD;
|
|
--editor-tooltip-shadow: rgba(0, 12, 8, 0.78);
|
|
--editor-tab-shadow: rgba(2, 10, 8, 0.76);
|
|
}
|
|
|
|
html[data-theme="ember"] {
|
|
--editor-shell-bg: #160C0B;
|
|
--editor-shell-fg: #FFE8D9;
|
|
--editor-menu-grad-1: #432018;
|
|
--editor-menu-grad-2: #24110F;
|
|
--editor-sidebar-bg: #21110E;
|
|
--editor-stage-bg: #100706;
|
|
--editor-border: #6A3B33;
|
|
--editor-border-strong: #9A5A4D;
|
|
--editor-panel-bg: #311A16;
|
|
--editor-panel-bg-alt: #3F211B;
|
|
--editor-panel-bg-elevated: #341B17;
|
|
--editor-panel-bg-hover: #5A2E26;
|
|
--editor-control-bg: #4A261F;
|
|
--editor-control-bg-hover: #67352B;
|
|
--editor-control-bg-active: #8B4937;
|
|
--editor-control-border: #9A5A4D;
|
|
--editor-control-fg: #FFE8D9;
|
|
--editor-muted: #E2B6A2;
|
|
--editor-muted-strong: #FFE0CF;
|
|
--editor-accent: #FFB36C;
|
|
--editor-accent-strong: #FFD08E;
|
|
--editor-accent-soft: #684133;
|
|
--editor-warn: #FFE17A;
|
|
--editor-danger: #512225;
|
|
--editor-danger-border: #A16063;
|
|
--editor-danger-hover: #6A2E32;
|
|
--editor-preview-bg: #281311;
|
|
--editor-drop-line: #FFB36C;
|
|
--editor-drop-shadow: rgba(255, 179, 108, 0.32);
|
|
--editor-status-ok: #F6C8AF;
|
|
--editor-status-error: #FFB1A3;
|
|
--editor-tooltip-shadow: rgba(20, 6, 0, 0.76);
|
|
--editor-tab-shadow: rgba(16, 6, 2, 0.76);
|
|
}
|
|
|
|
html[data-theme="amethyst"] {
|
|
--editor-shell-bg: #0F0B19;
|
|
--editor-shell-fg: #F0E6FF;
|
|
--editor-menu-grad-1: #2A1F45;
|
|
--editor-menu-grad-2: #171125;
|
|
--editor-sidebar-bg: #17112A;
|
|
--editor-stage-bg: #0A0712;
|
|
--editor-border: #4E4474;
|
|
--editor-border-strong: #7662A9;
|
|
--editor-panel-bg: #211A39;
|
|
--editor-panel-bg-alt: #2A2149;
|
|
--editor-panel-bg-elevated: #241D41;
|
|
--editor-panel-bg-hover: #3B3066;
|
|
--editor-control-bg: #35295D;
|
|
--editor-control-bg-hover: #473678;
|
|
--editor-control-bg-active: #5B4594;
|
|
--editor-control-border: #7662A9;
|
|
--editor-control-fg: #F0E6FF;
|
|
--editor-muted: #C6B3E6;
|
|
--editor-muted-strong: #E5D9FF;
|
|
--editor-accent: #C38BFF;
|
|
--editor-accent-strong: #DDB5FF;
|
|
--editor-accent-soft: #493C72;
|
|
--editor-warn: #F7D37E;
|
|
--editor-danger: #4A213F;
|
|
--editor-danger-border: #935C8A;
|
|
--editor-danger-hover: #632D56;
|
|
--editor-preview-bg: #1A1430;
|
|
--editor-drop-line: #C38BFF;
|
|
--editor-drop-shadow: rgba(195, 139, 255, 0.32);
|
|
--editor-status-ok: #D9C5FF;
|
|
--editor-status-error: #FFB6DE;
|
|
--editor-tooltip-shadow: rgba(10, 4, 24, 0.8);
|
|
--editor-tab-shadow: rgba(10, 6, 20, 0.78);
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html, body {
|
|
margin: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: var(--editor-shell-bg);
|
|
color: var(--editor-shell-fg);
|
|
font-family: Segoe UI, Arial, sans-serif;
|
|
}
|
|
|
|
body {
|
|
overflow: hidden;
|
|
}
|
|
|
|
a {
|
|
color: var(--editor-accent-strong);
|
|
text-decoration: none;
|
|
}
|
|
|
|
a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
code, pre {
|
|
font-family: Consolas, "Courier New", monospace;
|
|
}
|
|
|
|
.shell {
|
|
display: grid;
|
|
grid-template-rows: 40px 1fr;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
}
|
|
|
|
.menu-bar {
|
|
width: 100%;
|
|
height: 40px;
|
|
min-height: 40px;
|
|
max-height: 40px;
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 4px 8px;
|
|
border-bottom: 1px solid var(--editor-border);
|
|
background: linear-gradient(180deg, var(--editor-menu-grad-1) 0%, var(--editor-menu-grad-2) 100%);
|
|
overflow: hidden;
|
|
user-select: none;
|
|
}
|
|
|
|
.menu-btn {
|
|
height: 30px;
|
|
padding: 0 12px;
|
|
border: 1px solid var(--editor-control-border);
|
|
border-radius: 8px;
|
|
background: var(--editor-control-bg);
|
|
color: var(--editor-control-fg);
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.menu-btn:hover {
|
|
background: var(--editor-control-bg-hover);
|
|
}
|
|
|
|
.menu-bar-center {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-width: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.menu-bar-center > * {
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.menu-title {
|
|
color: var(--editor-muted-strong);
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.menu-bar-right {
|
|
margin-left: auto;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.theme-preset-bar {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 0 2px;
|
|
}
|
|
|
|
.theme-preset-btn {
|
|
width: 40px;
|
|
height: 40px;
|
|
padding: 0;
|
|
border: none;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.theme-preset-swatch {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--editor-control-border);
|
|
background:
|
|
linear-gradient(135deg,
|
|
var(--theme-swatch-a) 0%,
|
|
var(--theme-swatch-a) 50%,
|
|
var(--theme-swatch-b) 50%,
|
|
var(--theme-swatch-b) 100%);
|
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
|
position: relative;
|
|
transition: transform 140ms ease, box-shadow 140ms ease, border-color 140ms ease;
|
|
}
|
|
|
|
.theme-preset-swatch::before,
|
|
.theme-preset-swatch::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: 8px;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.theme-preset-swatch::before {
|
|
background:
|
|
linear-gradient(135deg, transparent 0 50%, var(--theme-swatch-c) 50% 75%, transparent 75% 100%);
|
|
opacity: 0.95;
|
|
}
|
|
|
|
.theme-preset-swatch::after {
|
|
background:
|
|
linear-gradient(315deg, transparent 0 52%, var(--theme-swatch-d) 52% 100%);
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.theme-preset-btn.active .theme-preset-swatch,
|
|
.theme-preset-btn:hover .theme-preset-swatch {
|
|
transform: scale(1.14);
|
|
border-color: var(--editor-accent);
|
|
box-shadow: 0 0 0 1px var(--editor-accent), inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
|
}
|
|
|
|
.menu-status {
|
|
color: var(--editor-status-ok);
|
|
font-size: 12px;
|
|
white-space: nowrap;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.body {
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: 325px 1fr;
|
|
}
|
|
|
|
.sidebar {
|
|
min-height: 0;
|
|
border-right: 1px solid var(--editor-border);
|
|
background: var(--editor-sidebar-bg);
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 10px;
|
|
overflow-y: scroll;
|
|
overflow-x: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.sidebar-tabs {
|
|
display: grid;
|
|
gap: 6px;
|
|
margin-bottom: 10px;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 30;
|
|
background: var(--editor-sidebar-bg);
|
|
border: 1px solid var(--editor-border);
|
|
border-radius: 10px;
|
|
padding: 6px;
|
|
box-shadow: 0 8px 14px var(--editor-tab-shadow);
|
|
isolation: isolate;
|
|
}
|
|
|
|
.sidebar-tabs::before {
|
|
content: "";
|
|
position: absolute;
|
|
inset: -1px;
|
|
background: var(--editor-sidebar-bg);
|
|
border-radius: 10px;
|
|
z-index: -1;
|
|
}
|
|
|
|
.sidebar-tab-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 6px;
|
|
}
|
|
|
|
.sidebar-tab-btn {
|
|
height: 28px;
|
|
width: 100%;
|
|
padding: 0 8px;
|
|
border: 1px solid var(--editor-control-border);
|
|
border-radius: 7px;
|
|
background: var(--editor-control-bg);
|
|
color: var(--editor-control-fg);
|
|
font-size: 12px;
|
|
line-height: 1;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.sidebar-tab-btn.active {
|
|
background: var(--editor-control-bg-active);
|
|
border-color: var(--editor-accent);
|
|
color: var(--editor-shell-fg);
|
|
}
|
|
|
|
.sidebar-copy {
|
|
display: grid;
|
|
gap: 10px;
|
|
}
|
|
|
|
.sidebar-card,
|
|
.wiki-panel,
|
|
.wiki-card,
|
|
.callout,
|
|
.modal-card {
|
|
border: 1px solid var(--editor-border);
|
|
border-radius: 8px;
|
|
background: var(--editor-panel-bg);
|
|
color: var(--editor-shell-fg);
|
|
}
|
|
|
|
.sidebar-card {
|
|
padding: 10px;
|
|
display: grid;
|
|
gap: 8px;
|
|
}
|
|
|
|
.eyebrow {
|
|
margin: 0;
|
|
color: var(--editor-accent-strong);
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.sidebar-title {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.sidebar-text,
|
|
.muted {
|
|
margin: 0;
|
|
color: var(--editor-muted);
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.chip-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
|
|
.chip {
|
|
border: 1px solid var(--editor-control-border);
|
|
border-radius: 999px;
|
|
background: var(--editor-panel-bg-alt);
|
|
color: var(--editor-muted-strong);
|
|
font-size: 11px;
|
|
padding: 4px 8px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.sidebar-actions {
|
|
display: grid;
|
|
gap: 6px;
|
|
}
|
|
|
|
.stage {
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--editor-stage-bg);
|
|
}
|
|
|
|
.meta {
|
|
height: 28px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 0 10px;
|
|
border-bottom: 1px solid var(--editor-border);
|
|
font-size: 12px;
|
|
color: var(--editor-muted);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.meta-main {
|
|
min-width: 0;
|
|
flex: 1 1 auto;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.meta-stats {
|
|
margin-left: auto;
|
|
color: var(--editor-muted-strong);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.wiki-scroll {
|
|
min-height: 0;
|
|
flex: 1 1 auto;
|
|
overflow-y: auto;
|
|
padding: 14px;
|
|
}
|
|
|
|
.wiki-view {
|
|
display: none;
|
|
gap: 12px;
|
|
}
|
|
|
|
.wiki-view.active {
|
|
display: grid;
|
|
}
|
|
|
|
.wiki-panel {
|
|
padding: 14px;
|
|
display: grid;
|
|
gap: 10px;
|
|
}
|
|
|
|
.wiki-panel-header {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
padding-bottom: 8px;
|
|
}
|
|
|
|
.wiki-panel h1,
|
|
.wiki-panel h2,
|
|
.wiki-panel h3 {
|
|
margin: 0;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.wiki-panel h1 { font-size: 26px; }
|
|
.wiki-panel h2 { font-size: 18px; }
|
|
.wiki-panel h3 { font-size: 14px; }
|
|
|
|
.wiki-panel p,
|
|
.wiki-panel li,
|
|
.wiki-panel td,
|
|
.wiki-panel th {
|
|
font-size: 13px;
|
|
line-height: 1.55;
|
|
}
|
|
|
|
.wiki-grid {
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.wiki-grid.two {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
|
|
.wiki-grid.three {
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
}
|
|
|
|
.wiki-card {
|
|
padding: 12px;
|
|
display: grid;
|
|
gap: 8px;
|
|
background: linear-gradient(180deg, var(--editor-panel-bg) 0%, var(--editor-panel-bg-elevated) 100%);
|
|
}
|
|
|
|
.wiki-card-title {
|
|
margin: 0;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
color: var(--editor-muted-strong);
|
|
}
|
|
|
|
.wiki-card p,
|
|
.wiki-card ul,
|
|
.wiki-card ol {
|
|
margin: 0;
|
|
}
|
|
|
|
.callout {
|
|
padding: 10px 12px;
|
|
background: var(--editor-panel-bg-alt);
|
|
}
|
|
|
|
.callout.warn {
|
|
border-color: var(--editor-warn);
|
|
background: var(--editor-accent-soft);
|
|
}
|
|
|
|
.callout.good {
|
|
border-color: var(--editor-accent);
|
|
}
|
|
|
|
.details-list {
|
|
display: grid;
|
|
gap: 8px;
|
|
}
|
|
|
|
details {
|
|
border: 1px solid var(--editor-border);
|
|
border-radius: 8px;
|
|
background: var(--editor-panel-bg-elevated);
|
|
overflow: hidden;
|
|
}
|
|
|
|
summary {
|
|
cursor: pointer;
|
|
padding: 10px 12px;
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
color: var(--editor-muted-strong);
|
|
background: rgba(255, 255, 255, 0.02);
|
|
}
|
|
|
|
details > div {
|
|
padding: 0 12px 12px;
|
|
display: grid;
|
|
gap: 8px;
|
|
}
|
|
|
|
.docs-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
border: 1px solid var(--editor-border);
|
|
background: var(--editor-panel-bg-elevated);
|
|
}
|
|
|
|
.docs-table th,
|
|
.docs-table td {
|
|
border: 1px solid var(--editor-border);
|
|
padding: 8px 9px;
|
|
text-align: left;
|
|
vertical-align: top;
|
|
}
|
|
|
|
.docs-table th {
|
|
color: var(--editor-muted-strong);
|
|
background: rgba(255, 255, 255, 0.03);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.code-block {
|
|
margin: 0;
|
|
padding: 12px;
|
|
border: 1px solid var(--editor-border);
|
|
border-radius: 8px;
|
|
background: var(--editor-preview-bg);
|
|
color: var(--editor-shell-fg);
|
|
overflow: auto;
|
|
font-size: 12px;
|
|
line-height: 1.55;
|
|
white-space: pre;
|
|
}
|
|
|
|
.tree {
|
|
white-space: pre-wrap;
|
|
font-size: 12px;
|
|
line-height: 1.55;
|
|
}
|
|
|
|
.flowchart-shell {
|
|
padding: 12px;
|
|
border: 1px solid var(--editor-border);
|
|
border-radius: 8px;
|
|
background: var(--editor-preview-bg);
|
|
overflow: auto;
|
|
}
|
|
|
|
.flowchart {
|
|
display: block;
|
|
width: 100%;
|
|
min-width: 880px;
|
|
height: auto;
|
|
}
|
|
|
|
.footer-note {
|
|
font-size: 12px;
|
|
color: var(--editor-muted);
|
|
}
|
|
|
|
.modal-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(4, 10, 20, 0.7);
|
|
display: grid;
|
|
place-items: center;
|
|
padding: 24px;
|
|
z-index: 80;
|
|
}
|
|
|
|
.modal-backdrop.hidden {
|
|
display: none;
|
|
}
|
|
|
|
.modal-card {
|
|
width: min(900px, 100%);
|
|
max-height: min(82vh, 900px);
|
|
overflow: auto;
|
|
padding: 14px;
|
|
box-shadow: 0 16px 40px var(--editor-tooltip-shadow);
|
|
background: linear-gradient(180deg, var(--editor-panel-bg) 0%, var(--editor-panel-bg-elevated) 100%);
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.modal-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
}
|
|
|
|
.modal-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
}
|
|
|
|
@media (max-width: 1080px) {
|
|
.body {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.sidebar {
|
|
border-right: none;
|
|
border-bottom: 1px solid var(--editor-border);
|
|
max-height: 42vh;
|
|
}
|
|
|
|
.wiki-grid.two,
|
|
.wiki-grid.three {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 760px) {
|
|
.menu-bar-center {
|
|
display: none;
|
|
}
|
|
|
|
.menu-bar {
|
|
gap: 6px;
|
|
}
|
|
|
|
.menu-bar-right {
|
|
gap: 6px;
|
|
}
|
|
|
|
.theme-preset-bar {
|
|
gap: 4px;
|
|
}
|
|
|
|
.theme-preset-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
}
|
|
|
|
.theme-preset-swatch {
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
.sidebar-tab-row {
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="shell">
|
|
<div class="menu-bar">
|
|
<button class="menu-btn" type="button" onclick="window.location.href='/'">Main Editor</button>
|
|
<button class="menu-btn" id="quickRefBtn" type="button">Quick Ref</button>
|
|
<button class="menu-btn" type="button" onclick="window.location.href='/wiki-assets/world-chunk-manual.html'">World Chunk Manual</button>
|
|
<button class="menu-btn" type="button" onclick="window.location.href='/wiki-assets/dialogue-builder.html'">Dialogue Builder</button>
|
|
<div class="menu-bar-center">
|
|
<span class="menu-title">TES:VIII Map Editor Wiki</span>
|
|
</div>
|
|
<div class="menu-bar-right">
|
|
<div class="theme-preset-bar" role="group" aria-label="Wiki theme presets">
|
|
<button class="theme-preset-btn active" data-theme="azure" type="button" title="Azure theme" aria-label="Switch to Azure theme">
|
|
<span class="theme-preset-swatch" style="--theme-swatch-a:#17325d; --theme-swatch-b:#244c84; --theme-swatch-c:#7fd1ff; --theme-swatch-d:#10203c;"></span>
|
|
</button>
|
|
<button class="theme-preset-btn" data-theme="verdant" type="button" title="Verdant theme" aria-label="Switch to Verdant theme">
|
|
<span class="theme-preset-swatch" style="--theme-swatch-a:#17372E; --theme-swatch-b:#285B4A; --theme-swatch-c:#7CE0AF; --theme-swatch-d:#0E251E;"></span>
|
|
</button>
|
|
<button class="theme-preset-btn" data-theme="ember" type="button" title="Ember theme" aria-label="Switch to Ember theme">
|
|
<span class="theme-preset-swatch" style="--theme-swatch-a:#4F231C; --theme-swatch-b:#87412F; --theme-swatch-c:#FFB36C; --theme-swatch-d:#24110F;"></span>
|
|
</button>
|
|
<button class="theme-preset-btn" data-theme="amethyst" type="button" title="Amethyst theme" aria-label="Switch to Amethyst theme">
|
|
<span class="theme-preset-swatch" style="--theme-swatch-a:#342456; --theme-swatch-b:#5A3B8A; --theme-swatch-c:#D3A8FF; --theme-swatch-d:#171125;"></span>
|
|
</button>
|
|
</div>
|
|
<span class="menu-status">/wiki static manual</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="body">
|
|
<aside class="sidebar">
|
|
<div class="sidebar-tabs">
|
|
<div class="sidebar-tab-row">
|
|
<button class="sidebar-tab-btn active" data-view-target="overview" type="button">Overview</button>
|
|
<button class="sidebar-tab-btn" data-view-target="workflows" type="button">How-To</button>
|
|
</div>
|
|
<div class="sidebar-tab-row">
|
|
<button class="sidebar-tab-btn" data-view-target="features" type="button">Features</button>
|
|
<button class="sidebar-tab-btn" data-view-target="systems" type="button">Systems</button>
|
|
</div>
|
|
<div class="sidebar-tab-row">
|
|
<button class="sidebar-tab-btn" data-view-target="api" type="button">API + Storage</button>
|
|
<button class="sidebar-tab-btn" data-view-target="troubleshooting" type="button">Fixes</button>
|
|
</div>
|
|
<div class="sidebar-tab-row">
|
|
<button class="sidebar-tab-btn" data-view-target="future" type="button">Future</button>
|
|
<button class="sidebar-tab-btn" type="button" onclick="document.getElementById('quickRefBtn').click()">Modal Ref</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-copy">
|
|
<div class="sidebar-card">
|
|
<p class="eyebrow">Standalone App</p>
|
|
<h1 class="sidebar-title">Map Editor Manual</h1>
|
|
<p class="sidebar-text">
|
|
This wiki documents the popup map editor as it exists today: its daily workflows,
|
|
runtime systems, save flow, file layout, communication contract, and where it should go next.
|
|
</p>
|
|
<div class="chip-row">
|
|
<span class="chip">Menu Bar</span>
|
|
<span class="chip">Tools</span>
|
|
<span class="chip">Canvas</span>
|
|
<span class="chip">Warm Minimap</span>
|
|
<span class="chip">Per-map History</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-card">
|
|
<p class="eyebrow">What This Covers</p>
|
|
<p class="muted">- Launch and save lifecycle</p>
|
|
<p class="muted">- Layer, tile, and instance authoring</p>
|
|
<p class="muted">- Performance and rendering strategy</p>
|
|
<p class="muted">- API endpoints, postMessage events, and file layout</p>
|
|
<p class="muted">- Roadmap ideas that would push the tool much further</p>
|
|
</div>
|
|
|
|
<div class="sidebar-card">
|
|
<p class="eyebrow">Current Reality</p>
|
|
<p class="muted">
|
|
Triggers, Paths, and Transitions already have tool panels and folder UI,
|
|
but they are still prototype surfaces. The documentation below calls those out clearly
|
|
so the manual stays aligned with the actual tool instead of promising hidden behavior.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<section class="stage">
|
|
<div class="meta">
|
|
<div class="meta-main" id="metaMain">Overview - purpose, terminology, lifecycle, and the core editing model.</div>
|
|
<div class="meta-stats" id="metaStats">Served from <code>/wiki</code></div>
|
|
</div>
|
|
|
|
<div class="wiki-scroll">
|
|
<article class="wiki-view active" data-view="overview" data-meta="Overview - purpose, terminology, lifecycle, and the core editing model.">
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<div>
|
|
<p class="eyebrow">Map Editor</p>
|
|
<h1>What This Tool Is</h1>
|
|
</div>
|
|
<div class="chip-row">
|
|
<span class="chip">Popup editor</span>
|
|
<span class="chip">Theme-aware</span>
|
|
<span class="chip">JSON-backed</span>
|
|
</div>
|
|
</div>
|
|
<p>
|
|
The map editor is a standalone popup app launched from the main content editor. It is not a thin
|
|
form field sitting on top of map JSON. It owns its own state, controls, render loop, history stack,
|
|
and save workflow, then syncs its results back into the host editor through API writes and
|
|
postMessage events.
|
|
</p>
|
|
<div class="wiki-grid three">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Menu Bar</p>
|
|
<p>Undo, redo, save, quick layer selection, and theme switching live here. This is the stable command layer.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Tools</p>
|
|
<p>The left tool panel hosts Information, Maps, History, Instances, Tiles, Layers, and prototype placement tabs.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Canvas</p>
|
|
<p>The main viewport is a tile-grid world with snapping, drag placement, context menus, zoom, pan, selection, and the minimap drawer.</p>
|
|
</div>
|
|
</div>
|
|
<div class="callout good">
|
|
The editor is intentionally moving toward becoming the main map-authoring platform. It already has enough local behavior,
|
|
persistence, and rendering independence to be treated as a real app rather than a popup form.
|
|
</div>
|
|
</section>
|
|
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h2>Terminology and Mental Model</h2>
|
|
</div>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Naming</p>
|
|
<ul>
|
|
<li><strong>Menu Bar</strong> - the top command strip.</li>
|
|
<li><strong>Tools</strong> - the left-side panel with tabs and lists.</li>
|
|
<li><strong>Canvas</strong> - the world viewport where tiles and instances are edited.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Authored entities</p>
|
|
<ul>
|
|
<li><strong>Tile</strong> - a sprite-backed paintable map cell resource.</li>
|
|
<li><strong>Template</strong> - a reusable instance source used like a stamp.</li>
|
|
<li><strong>Instance</strong> - a placed or unplaced map-local entity created from a template by value, not by live linkage.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Layer numbering</p>
|
|
<p>
|
|
Internal layer <code>0</code> is the anchored <strong>Background</strong>. The first user-facing non-background
|
|
paint layer is displayed as <strong>Layer 0</strong>, because its internal id is <code>1</code>.
|
|
</p>
|
|
<p>
|
|
In short: Background is special, anchored at the bottom, and every other displayed layer name is offset by one.
|
|
</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Ownership boundary</p>
|
|
<ul>
|
|
<li>The <strong>host editor</strong> owns dataset loading, popup launch, and background refresh after save.</li>
|
|
<li>The <strong>popup editor</strong> owns editing state, rendering, history, drag logic, and the final composed save payload.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h2>Core Principles</h2>
|
|
</div>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Edit local, save explicit</p>
|
|
<p>The popup mutates local runtime state first. Nothing is persisted until Save writes maps and NPCs back through the API.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">History is per map</p>
|
|
<p>Undo and redo belong to the active map, not the whole app. Branches are truncated if you edit after undoing.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Templates are stamps</p>
|
|
<p>Selecting a template keeps stamp mode active so repeated clicks place new instances. A created instance is then independent.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Rendering is viewport-first</p>
|
|
<p>The editor draws only what the current viewport needs, then uses cached preview surfaces for pan, zoom, scroll, and the minimap.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Background is optimized</p>
|
|
<p>The editor can compress fully implicit background rows so huge maps do not waste JSON storing the same map-wide fill repeatedly.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Prototype tabs are honest</p>
|
|
<p>Monsters, Triggers, Paths, and Transitions already share selector and folder UI, but placement logic is intentionally not claimed yet.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</article>
|
|
|
|
<article class="wiki-view" data-view="workflows" data-meta="How-To - launch, paint, place, save, import, and navigate inside the popup editor.">
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h1>Daily Workflows</h1>
|
|
</div>
|
|
<p>
|
|
This section is written as direct operating procedure. If you want to use the tool instead of study it,
|
|
start here.
|
|
</p>
|
|
<div class="details-list">
|
|
<details open>
|
|
<summary>Open the editor for a map</summary>
|
|
<div>
|
|
<ol>
|
|
<li>Open the main content editor and select <strong>Maps</strong>.</li>
|
|
<li>Select the map record you want to edit.</li>
|
|
<li>Use the dedicated launch button to open the popup editor.</li>
|
|
<li>The host assembles bootstrap data, opens <code>map-editor-popup.html?token=...</code>, and hands the popup a full in-memory startup package.</li>
|
|
</ol>
|
|
</div>
|
|
</details>
|
|
|
|
<details open>
|
|
<summary>Paint tiles</summary>
|
|
<div>
|
|
<ol>
|
|
<li>Go to <strong>Tiles</strong> in Tools.</li>
|
|
<li>Select a tile brush entry from the palette list.</li>
|
|
<li>Choose your active layer from the Layers tab or the menu-bar layer selector.</li>
|
|
<li>Click-drag on the canvas to paint.</li>
|
|
<li>Use <code>Alt + Drag</code> to erase on the active layer.</li>
|
|
<li>Use <code>L Shift + Drag</code> to line-lock after leaving the origin tile.</li>
|
|
<li>Use <code>L Ctrl + Drag</code> for a rectangle outline, or <code>R Ctrl + Drag</code> for a circle outline.</li>
|
|
</ol>
|
|
</div>
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Fill the background layer</summary>
|
|
<div>
|
|
<ol>
|
|
<li>Switch to <strong>Background</strong> or set the current editable layer so drawing resolves to the background layer.</li>
|
|
<li>Right-click a tile in the tile palette.</li>
|
|
<li>Choose <strong>Fill Background</strong>.</li>
|
|
<li>The editor stores the background tile id at map level and can compress fully implicit background rows on save.</li>
|
|
</ol>
|
|
<p class="muted">Roomwide fill is intentionally restricted to Background.</p>
|
|
</div>
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Place instances from templates</summary>
|
|
<div>
|
|
<ol>
|
|
<li>Open <strong>Instances</strong>.</li>
|
|
<li>In the <strong>Templates</strong> section, select the template you want.</li>
|
|
<li>Click the canvas to stamp a new instance.</li>
|
|
<li>The template remains active so each click keeps creating a fresh instance.</li>
|
|
<li>Each created instance copies template values by value and is no longer live-linked to the template record.</li>
|
|
</ol>
|
|
</div>
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Select, center, and place an instance record</summary>
|
|
<div>
|
|
<ol>
|
|
<li>In the <strong>Instances</strong> list, click an existing instance.</li>
|
|
<li>If it is already placed, the camera recenters toward it.</li>
|
|
<li>If it is unplaced, the editor enters ghost placement mode and shows the silhouette under the cursor.</li>
|
|
<li>Click the canvas to drop it with grid snapping.</li>
|
|
</ol>
|
|
<p class="muted">Placeholder instances can exist without placement. That is part of the authoring model, not a bug.</p>
|
|
</div>
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Reorder layers</summary>
|
|
<div>
|
|
<ol>
|
|
<li>Open <strong>Layers</strong>.</li>
|
|
<li>Drag a non-background layer by its handle.</li>
|
|
<li>Dropping changes its draw depth and remaps tile and instance layer references accordingly.</li>
|
|
<li>The background layer is anchored and cannot be dragged below or above other layers.</li>
|
|
</ol>
|
|
</div>
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Use folders in selector lists</summary>
|
|
<div>
|
|
<ol>
|
|
<li>Use the folder button at the top of a supported panel.</li>
|
|
<li>Create folders for Tiles, Templates, Instances, Monsters, Triggers, Paths, or Transitions.</li>
|
|
<li>Drag selectors into a folder to group them.</li>
|
|
<li>Drag them back out to return them to the root of that panel.</li>
|
|
</ol>
|
|
<p class="muted">Folder data is editor UI persistence only. It changes organization, not gameplay payloads.</p>
|
|
</div>
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Navigate the room quickly</summary>
|
|
<div>
|
|
<ul>
|
|
<li><code>MMB + Drag</code> pans the room.</li>
|
|
<li><code>Ctrl + Wheel</code> zooms around the pointer anchor.</li>
|
|
<li>The minimap drawer provides click-to-center navigation and a live viewport rectangle.</li>
|
|
<li>Dragging an instance near the viewport edge auto-pans the camera in that direction.</li>
|
|
<li><code>R Shift</code> temporarily hides the grid so you can inspect the runtime-like composition.</li>
|
|
</ul>
|
|
</div>
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Import sprites or tiles from another editor build</summary>
|
|
<div>
|
|
<ol>
|
|
<li>Open <strong>Information</strong>.</li>
|
|
<li>Expand <strong>Experimental Imports</strong>.</li>
|
|
<li>Import from file, or open the JSON paste modal with the writing-pad button.</li>
|
|
<li>The import pipeline accepts a single entry or a whole compatible gallery payload.</li>
|
|
<li>Known resources are deduped by normalized dimensions, pixel scale, and row content signature.</li>
|
|
</ol>
|
|
</div>
|
|
</details>
|
|
|
|
<details open>
|
|
<summary>Save without surprises</summary>
|
|
<div>
|
|
<ol>
|
|
<li>Use Save in the menu bar when the save button is enabled.</li>
|
|
<li>The popup writes <code>maps</code> first, then <code>npcs</code>.</li>
|
|
<li>After success it posts <code>map-editor-saved</code> to the opener so the host editor can refresh quietly.</li>
|
|
<li>If you undo, then make a new edit, the impossible future branch is discarded and history continues from the new point.</li>
|
|
</ol>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</section>
|
|
</article>
|
|
|
|
<article class="wiki-view" data-view="features" data-meta="Features - every user-facing system, what it does, and how it behaves.">
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h1>Feature Reference</h1>
|
|
</div>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Menu Bar</p>
|
|
<ul>
|
|
<li>Undo and Redo are bound to toolbar buttons and <code>Ctrl+Z</code> / <code>Ctrl+Y</code>.</li>
|
|
<li>Save reflects dirty history state and is disabled when nothing changed or a save is running.</li>
|
|
<li>The centered layer selector mirrors the Layers tab and stays in sync with it.</li>
|
|
<li>Theme preset buttons apply editor-wide palette swaps through <code>/api/editor-settings</code>.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Information Tab</p>
|
|
<ul>
|
|
<li>Locked map id.</li>
|
|
<li>Editable map name.</li>
|
|
<li>Width and height with explicit apply/cancel controls.</li>
|
|
<li>Map background color and background brush mode.</li>
|
|
<li>Experimental sprite/tile import tools.</li>
|
|
<li>In-editor controls reference and footer links.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Maps Tab</p>
|
|
<p>
|
|
Switch maps, create maps, and delete maps. Switching away with unsaved changes prompts first.
|
|
Creating a new map seeds a background layer and a first editable layer.
|
|
</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Layers Tab</p>
|
|
<ul>
|
|
<li>All Layers mode for draw-depth inspection.</li>
|
|
<li>Visibility toggle per layer.</li>
|
|
<li>Layer reordering for non-background layers.</li>
|
|
<li>Context-menu rename support.</li>
|
|
<li>Background layer anchored at the bottom.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Tiles Tab</p>
|
|
<ul>
|
|
<li>Sprite-backed tile selector list instead of simple swatches.</li>
|
|
<li>Right-click actions: select tile, fill background, replace on current layer, inspect id/symbol.</li>
|
|
<li>Transparency honors <code>.</code> as no-color data.</li>
|
|
<li>Selection reticle scales with grid size.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Instances Tab</p>
|
|
<ul>
|
|
<li>Templates stamp fresh records repeatedly.</li>
|
|
<li>Placed instances recenter the camera when selected.</li>
|
|
<li>Unplaced instances enter ghost placement mode.</li>
|
|
<li>Placeholder markers use a clashing multi-color orb so they remain visible on mixed backgrounds.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h2>Canvas Interaction Set</h2>
|
|
</div>
|
|
<div class="wiki-grid three">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Selection</p>
|
|
<p>Tile and instance selection uses a reusable reticle with directional markers so selected cells read clearly across different grid sizes.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Dragging</p>
|
|
<p>Instance dragging snaps to the grid, previews the destination, and now auto-pans near the viewport edges so long repositioning feels continuous.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Context Menus</p>
|
|
<p>The reusable right-click panel can be attached across the editor. It already powers layer actions, tile actions, and canvas entity actions.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Minimap Drawer</p>
|
|
<p>The minimap is a docked drawer with a live maintained surface. Opening it reveals current state immediately rather than taking a fresh snapshot first.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Hotkey Cursor Feedback</p>
|
|
<p>Shift, Alt, and inspect modes swap the cursor so the canvas communicates line draw, erase, and no-grid inspection states without needing extra text.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Warm Preview Modes</p>
|
|
<p>Zoom, drag-pan, and wheel-scroll all use cached low-res preview frames before a sharper redraw lands, which keeps movement feeling much smoother.</p>
|
|
</div>
|
|
</div>
|
|
<div class="callout warn">
|
|
Prototype panels for Monsters, Triggers, Paths, and Transitions already reuse selector, folder, and panel framing. They are scaffolding for future map-local authoring, not finished gameplay editors yet.
|
|
</div>
|
|
</section>
|
|
</article>
|
|
|
|
<article class="wiki-view" data-view="systems" data-meta="Systems - internal architecture, render strategy, history model, imports, and persistence rules.">
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h1>Technical Systems</h1>
|
|
</div>
|
|
<table class="docs-table">
|
|
<thead>
|
|
<tr>
|
|
<th>File</th>
|
|
<th>Role</th>
|
|
<th>What It Owns</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td><code>src/components/MapEditorPanels.tsx</code></td><td>Host bridge</td><td>Launches the popup, assembles bootstrap payload, handles save/open postMessage events, and persists popup bounds.</td></tr>
|
|
<tr><td><code>src/mapEditorPopup/bootstrap.ts</code></td><td>Popup handoff</td><td>Token generation, opener registry, sessionStorage fallback, and bootstrap retrieval.</td></tr>
|
|
<tr><td><code>src/mapEditorPopup/main.ts</code></td><td>Popup boot</td><td>Loads the bootstrap, applies editor theme settings, injects popup HTML/CSS, and starts the runtime.</td></tr>
|
|
<tr><td><code>src/mapEditorPopup/runtime.ts</code></td><td>State root</td><td>Global editor state, DOM lookup, layer helpers, data catalogs, and controller wiring.</td></tr>
|
|
<tr><td><code>src/mapEditorPopup/renderController.ts</code></td><td>Render loop</td><td>Viewport drawing, tile surface cache, minimap surface, preview frames, overlay drawing, and meta telemetry.</td></tr>
|
|
<tr><td><code>src/mapEditorPopup/interactionController.ts</code></td><td>Input system</td><td>Mouse, wheel, keyboard, paint strokes, shape tools, drag logic, auto-pan, and context menu triggers.</td></tr>
|
|
<tr><td><code>src/mapEditorPopup/sidebarController.ts</code></td><td>Tools UI</td><td>Tab switching, layer list, information panel logic, palette lists, folder rendering, and inline status text.</td></tr>
|
|
<tr><td><code>src/mapEditorPopup/npcController.ts</code></td><td>Instance semantics</td><td>Template assignment, instance centering, placeholder handling, sprite binding, and instance selection behavior.</td></tr>
|
|
<tr><td><code>src/mapEditorPopup/historyController.ts</code></td><td>Undo/redo engine</td><td>State capture, branch truncation, persistence, restore, toolbar dirty state, and preview diffs.</td></tr>
|
|
<tr><td><code>src/mapEditorPopup/persistenceController.ts</code></td><td>Save pipeline</td><td>Payload rebuild, map compression rules, dual-save ordering, and host notification after save.</td></tr>
|
|
<tr><td><code>src/mapEditorPopup/importController.ts</code></td><td>Resource import</td><td>Sprite/tile import normalization, dedupe signatures, JSON modal import, and content save for imported art.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h2>Rendering Strategy</h2>
|
|
</div>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Viewport-local canvas</p>
|
|
<p>
|
|
The main canvas only sizes itself to the current viewport, while a spacer tracks total world dimensions.
|
|
This keeps actual draw cost tied to what the user can see rather than full map size.
|
|
</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Tile surface cache</p>
|
|
<p>
|
|
<code>tileSurfaceCanvas</code> stores the current visible tile result. Painting can patch single cells instead
|
|
of forcing a full layer redraw every time.
|
|
</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Frame preview cache</p>
|
|
<p>
|
|
<code>framePreviewCanvas</code> is a cached snapshot of the viewport used during pan, wheel scroll,
|
|
and zoom-preview motion so interaction stays smooth before the sharp redraw completes.
|
|
</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Warm minimap surface</p>
|
|
<p>
|
|
<code>minimapSurfaceCanvas</code> is maintained continuously in the background. Opening the drawer reveals
|
|
current state instantly, and tile edits patch the minimap instead of waking it from scratch.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="callout">
|
|
The rendering pipeline is intentionally doing the least honest work possible per frame: draw only the current viewport,
|
|
reuse cached surfaces while moving, patch individual cells when feasible, and reserve full refreshes for bigger invalidations.
|
|
</div>
|
|
</section>
|
|
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h2>State and History Model</h2>
|
|
</div>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Captured state</p>
|
|
<p>History snapshots include map dimensions, map name, background color, background tile id, room layers, tile instances, NPC overlays, and editor UI folder layout state.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Branch behavior</p>
|
|
<p>If you undo and then perform a new edit, all future states beyond the current point are discarded. The new action becomes the forward branch.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Per-map storage</p>
|
|
<p>History persistence is scoped to the active map through a map-specific localStorage key, so switching maps does not smear history across rooms.</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Save awareness</p>
|
|
<p>The toolbar compares the current history id to the last saved history id. That is what drives dirty-state messaging and save enablement.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h2>Data Models That Matter</h2>
|
|
</div>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Templates vs instances</p>
|
|
<p>
|
|
Templates are reusable creation sources. Selecting a template is a stamp tool. Once an instance is created, it copies the template values
|
|
it needs and becomes a separate record. This is deliberate so authored rooms do not rewire themselves unexpectedly when a template changes.
|
|
</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Tile identity</p>
|
|
<p>
|
|
Tile placement is stored by tile id, not just visible symbol. Symbol compatibility still exists, but the authoritative authored resource is the tile record id.
|
|
</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Background compression</p>
|
|
<p>
|
|
If the map uses a background tile id and the background layer is fully implicit, the save pipeline can store empty background rows and reconstruct them from map metadata.
|
|
</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Folder persistence</p>
|
|
<p>
|
|
Panel folder layouts are saved under <code>editorUi.panelLayouts</code>. They affect selector presentation only and do not change runtime gameplay data.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h2>Import Pipeline</h2>
|
|
</div>
|
|
<p>
|
|
Experimental imports accept either a single compatible record or a full gallery payload from another build of this editor.
|
|
The import controller normalizes width, height, pixelScale, and row data before signature comparison.
|
|
</p>
|
|
<table class="docs-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Step</th>
|
|
<th>What happens</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td>Normalize</td><td>Rows are padded, width/height are inferred or clamped, and records with no valid pixel content are rejected.</td></tr>
|
|
<tr><td>Signature</td><td>The editor builds a deterministic signature from width, height, pixelScale, and serialized rows.</td></tr>
|
|
<tr><td>Deduplicate</td><td>Existing signatures and same-batch signatures are skipped.</td></tr>
|
|
<tr><td>Generate ids</td><td>New sprite ids or tile ids are generated. Imported tiles also receive the next free tile symbol.</td></tr>
|
|
<tr><td>Persist</td><td>The updated <code>sprites</code> or <code>tiles</code> payload is posted to the same content API used elsewhere in the app.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
</article>
|
|
|
|
<article class="wiki-view" data-view="api" data-meta="API + Storage - popup launch flow, endpoints, postMessage contract, and on-disk file layout.">
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h1>API and Communication Flow</h1>
|
|
</div>
|
|
<p>
|
|
The map editor is a cross-window system. The host editor launches it, the popup owns editing, the API persists data,
|
|
and postMessage closes the loop for save and map-switch events.
|
|
</p>
|
|
<div class="flowchart-shell">
|
|
<svg class="flowchart" viewBox="0 0 1120 620" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Map editor API and communication flow">
|
|
<defs>
|
|
<marker id="arrow" markerWidth="10" markerHeight="10" refX="7" refY="3" orient="auto">
|
|
<polygon points="0 0, 8 3, 0 6" fill="#64AAF8"></polygon>
|
|
</marker>
|
|
</defs>
|
|
<rect x="40" y="40" width="240" height="86" rx="12" fill="#121F3B" stroke="#3C5E95" stroke-width="2" />
|
|
<text x="60" y="70" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Main editor shell</text>
|
|
<text x="60" y="98" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">MapEditorPanels.tsx</text>
|
|
|
|
<rect x="340" y="40" width="250" height="86" rx="12" fill="#121F3B" stroke="#3C5E95" stroke-width="2" />
|
|
<text x="360" y="70" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Bootstrap handoff</text>
|
|
<text x="360" y="98" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">token + opener registry + sessionStorage fallback</text>
|
|
|
|
<rect x="650" y="40" width="250" height="86" rx="12" fill="#121F3B" stroke="#3C5E95" stroke-width="2" />
|
|
<text x="670" y="70" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Popup startup</text>
|
|
<text x="670" y="98" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">main.ts -> runtime.ts</text>
|
|
|
|
<rect x="340" y="190" width="250" height="96" rx="12" fill="#132B4F" stroke="#64AAF8" stroke-width="2" />
|
|
<text x="360" y="220" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Local edit loop</text>
|
|
<text x="360" y="248" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">tiles, instances, folders, history, minimap</text>
|
|
<text x="360" y="268" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">render + interaction + sidebar controllers</text>
|
|
|
|
<rect x="650" y="190" width="250" height="96" rx="12" fill="#132B4F" stroke="#64AAF8" stroke-width="2" />
|
|
<text x="670" y="220" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Save pipeline</text>
|
|
<text x="670" y="248" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">saveCurrentState()</text>
|
|
<text x="670" y="268" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">POST maps, then POST npcs</text>
|
|
|
|
<rect x="650" y="360" width="250" height="120" rx="12" fill="#121F3B" stroke="#3C5E95" stroke-width="2" />
|
|
<text x="670" y="392" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">Express API</text>
|
|
<text x="670" y="420" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">/api/content/maps</text>
|
|
<text x="670" y="440" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">/api/content/npcs</text>
|
|
<text x="670" y="460" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">/api/editor-settings</text>
|
|
|
|
<rect x="40" y="360" width="240" height="120" rx="12" fill="#121F3B" stroke="#3C5E95" stroke-width="2" />
|
|
<text x="60" y="392" fill="#D8E8FF" font-size="20" font-family="Segoe UI, Arial">On-disk content</text>
|
|
<text x="60" y="420" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">content/maps.json</text>
|
|
<text x="60" y="440" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">content/maps/<mapId>/...</text>
|
|
<text x="60" y="460" fill="#9FB8E5" font-size="14" font-family="Segoe UI, Arial">content/npcs.json</text>
|
|
|
|
<rect x="340" y="520" width="250" height="60" rx="12" fill="#132B4F" stroke="#64AAF8" stroke-width="2" />
|
|
<text x="360" y="555" fill="#D8E8FF" font-size="18" font-family="Segoe UI, Arial">postMessage back to host</text>
|
|
|
|
<line x1="280" y1="83" x2="340" y2="83" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
|
|
<line x1="590" y1="83" x2="650" y2="83" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
|
|
<line x1="465" y1="126" x2="465" y2="190" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
|
|
<line x1="590" y1="238" x2="650" y2="238" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
|
|
<line x1="775" y1="286" x2="775" y2="360" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
|
|
<line x1="650" y1="420" x2="280" y2="420" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
|
|
<line x1="650" y1="480" x2="590" y2="520" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
|
|
<line x1="340" y1="550" x2="160" y2="480" stroke="#64AAF8" stroke-width="3" marker-end="url(#arrow)" />
|
|
<line x1="160" y1="360" x2="160" y2="126" stroke="#64AAF8" stroke-width="3" stroke-dasharray="8 8" marker-end="url(#arrow)" />
|
|
<text x="182" y="242" fill="#9FB8E5" font-size="13" font-family="Segoe UI, Arial">refresh maps + npc data</text>
|
|
</svg>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h2>Endpoints and Messages</h2>
|
|
</div>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Important GET endpoints</p>
|
|
<table class="docs-table">
|
|
<thead>
|
|
<tr><th>Endpoint</th><th>Used for</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td><code>/api/content/maps</code></td><td>Host editor loads and refreshes map records.</td></tr>
|
|
<tr><td><code>/api/content/npcs</code></td><td>Host editor loads NPC instances and templates needed by the popup.</td></tr>
|
|
<tr><td><code>/api/content/tiles</code></td><td>Tile resource catalog.</td></tr>
|
|
<tr><td><code>/api/content/sprites</code></td><td>Sprite resource catalog used for previews and overlays.</td></tr>
|
|
<tr><td><code>/api/editor-settings</code></td><td>Theme preset load for the popup editor.</td></tr>
|
|
<tr><td><code>/api/images</code></td><td>UI image slug catalog for small editor icons.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Important POST endpoints</p>
|
|
<table class="docs-table">
|
|
<thead>
|
|
<tr><th>Endpoint</th><th>Used for</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td><code>/api/content/maps</code></td><td>Persist the rebuilt map payload and per-map storage files.</td></tr>
|
|
<tr><td><code>/api/content/npcs</code></td><td>Persist map-local NPC instances and compatibility data.</td></tr>
|
|
<tr><td><code>/api/content/tiles</code></td><td>Persist imported or edited tile resources.</td></tr>
|
|
<tr><td><code>/api/content/sprites</code></td><td>Persist imported sprite resources.</td></tr>
|
|
<tr><td><code>/api/editor-settings</code></td><td>Persist selected editor theme preset.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<table class="docs-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Message</th>
|
|
<th>Sender</th>
|
|
<th>Receiver</th>
|
|
<th>Effect</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td><code>map-editor-saved</code></td><td>Popup</td><td>Main editor</td><td>Triggers a quiet refresh of map, NPC, and template background data after save.</td></tr>
|
|
<tr><td><code>map-editor-open-map</code></td><td>Popup</td><td>Main editor</td><td>Requests that the host reload and reopen a different map record.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h2>Storage Layout</h2>
|
|
</div>
|
|
<pre class="code-block tree">content/
|
|
maps.json
|
|
npcs.json
|
|
npc_templates.json
|
|
sprites.json
|
|
tiles.json
|
|
maps/
|
|
<mapId>/
|
|
tiles.json
|
|
layer_0.json
|
|
layer_1.json
|
|
...
|
|
instances.json
|
|
</pre>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Why split map files exist</p>
|
|
<p>
|
|
The split per-map layout keeps large rooms scalable, makes layer files addressable on their own,
|
|
and sets up cleaner future systems for chunking, streaming, and non-tile authoring data.
|
|
</p>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Compatibility mirrors</p>
|
|
<p>
|
|
The server still composes payloads into the shapes older editor flows expect. That lets the storage model evolve
|
|
without forcing every existing authoring surface to change at once.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="callout warn">
|
|
If save looks successful in the UI but files do not change where you expect, check the actual content root on the server.
|
|
This project supports a writable local content folder beside <code>server.js</code> and can also be overridden by environment configuration.
|
|
</div>
|
|
</section>
|
|
</article>
|
|
|
|
<article class="wiki-view" data-view="troubleshooting" data-meta="Fixes - common failure modes, save issues, content-root problems, and performance notes.">
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h1>Troubleshooting</h1>
|
|
</div>
|
|
<div class="details-list">
|
|
<details open>
|
|
<summary>Save appears to work but data is missing after reload</summary>
|
|
<div>
|
|
<ul>
|
|
<li>Check the actual content root being written by the server, not just the route that served the page.</li>
|
|
<li>Confirm the host page and the popup are pointing at the same API base.</li>
|
|
<li>Inspect <code>/api/content/maps</code> and <code>/api/content/npcs</code> responses if needed.</li>
|
|
</ul>
|
|
</div>
|
|
</details>
|
|
<details>
|
|
<summary>Popup opens but content or previews look wrong</summary>
|
|
<div>
|
|
<ul>
|
|
<li>Verify sprite ids and tile ids exist in their catalogs.</li>
|
|
<li>Check case-sensitive image paths on Linux or VPS deployments.</li>
|
|
<li>Make sure the popup received a valid bootstrap token and did not fall back to an empty opener state.</li>
|
|
</ul>
|
|
</div>
|
|
</details>
|
|
<details>
|
|
<summary>Background fill is unavailable</summary>
|
|
<div>
|
|
<ul>
|
|
<li>Roomwide fill belongs to the Background layer only.</li>
|
|
<li>If you are on a non-background layer, the context action is intentionally disabled.</li>
|
|
</ul>
|
|
</div>
|
|
</details>
|
|
<details>
|
|
<summary>Large rooms feel sluggish</summary>
|
|
<div>
|
|
<ul>
|
|
<li>The editor already uses viewport-local rendering, surface caching, and preview frames.</li>
|
|
<li>Very large rooms still cost more when many distinct tiles are visible at once.</li>
|
|
<li>Best future wins are chunk-aware tile surfaces, coarser minimap sampling on giant maps, and brush batching tuned for extremely dense edits.</li>
|
|
</ul>
|
|
</div>
|
|
</details>
|
|
<details>
|
|
<summary>History feels odd after undo</summary>
|
|
<div>
|
|
<p>
|
|
This is usually intentional. If you undo and then make a different edit, the old future branch is removed.
|
|
The history system is branch-truncating by design.
|
|
</p>
|
|
</div>
|
|
</details>
|
|
<details>
|
|
<summary>Selector folders are not affecting runtime data</summary>
|
|
<div>
|
|
<p>
|
|
Correct. Folder layout lives in editor UI state so authors can organize selectors without mutating gameplay data contracts.
|
|
</p>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</section>
|
|
</article>
|
|
|
|
<article class="wiki-view" data-view="future" data-meta="Future - practical next steps and moonshot ideas that would push the project much further.">
|
|
<section class="wiki-panel">
|
|
<div class="wiki-panel-header">
|
|
<h1>Future - What Pushes This Project To The Moon</h1>
|
|
</div>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Authoring power</p>
|
|
<ul>
|
|
<li>Brush presets and saved tool loadouts.</li>
|
|
<li>Tile stamp prefabs and multi-tile pattern brushes.</li>
|
|
<li>Selection transform tools for copy, move, rotate, mirror, and flood replace.</li>
|
|
<li>Real trigger, path, and transition placement layers with visual handles on the canvas.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Runtime confidence</p>
|
|
<ul>
|
|
<li>Live runtime preview mode that uses the same asset and draw rules as the game.</li>
|
|
<li>Validation overlays for missing sprite ids, orphaned references, and impossible layer combinations.</li>
|
|
<li>One-click audit reports for map-local dependencies.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Performance ceiling</p>
|
|
<ul>
|
|
<li>Chunked tile surfaces instead of a single viewport tile cache.</li>
|
|
<li>Background surface baking with selective dirty regions.</li>
|
|
<li>Multi-resolution minimap sampling for giant rooms.</li>
|
|
<li>Optional worker-backed serialization and save prep for very heavy rooms.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">World scale</p>
|
|
<ul>
|
|
<li>Map-to-map travel graph editing.</li>
|
|
<li>Biome and region metadata surfaces.</li>
|
|
<li>Cross-map search for instances, tile ids, and scripted references.</li>
|
|
<li>World atlas view that treats maps as navigable nodes instead of isolated records.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Editor maturity</p>
|
|
<ul>
|
|
<li>Graduating the popup into the primary map platform while the older shell becomes a host and archive tool.</li>
|
|
<li>Shared command palette, unified modal system, and global settings panel.</li>
|
|
<li>More visual inline documentation surfaced directly inside the editor where decisions happen.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Moonshot ideas</p>
|
|
<ul>
|
|
<li>Collaborative sessions with author locks and merge-safe map diffs.</li>
|
|
<li>Rule-driven procedural placement helpers that still keep authored intent visible.</li>
|
|
<li>Playback scrubbing for event layers once triggers and transitions go live.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="callout good">
|
|
The biggest strategic move is still the same one the editor has already started: treat the map editor like the main product.
|
|
It already has the strongest identity, the richest interaction model, and the clearest path to becoming the center of world authoring.
|
|
</div>
|
|
</section>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-backdrop hidden" id="quickRefModal">
|
|
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="quickRefTitle">
|
|
<div class="modal-head">
|
|
<div>
|
|
<p class="eyebrow">Quick Ref</p>
|
|
<h2 id="quickRefTitle">Map Editor Fast Reference</h2>
|
|
</div>
|
|
<button class="menu-btn" id="closeQuickRefBtn" type="button">Close</button>
|
|
</div>
|
|
<div class="wiki-grid two">
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Hotkeys</p>
|
|
<ul>
|
|
<li><code>Ctrl + Wheel</code> - zoom canvas</li>
|
|
<li><code>MMB + Drag</code> - pan room view</li>
|
|
<li><code>Alt + Drag</code> - erase on active layer</li>
|
|
<li><code>L Shift + Drag</code> - straight line draw</li>
|
|
<li><code>L Ctrl + Drag</code> - rectangle outline</li>
|
|
<li><code>R Ctrl + Drag</code> - circle outline</li>
|
|
<li><code>R Shift</code> - hide grid while held</li>
|
|
<li><code>Escape</code> - clear canvas selection / collapse cells</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Messages + writes</p>
|
|
<ul>
|
|
<li><code>map-editor-saved</code> - popup tells host save is complete</li>
|
|
<li><code>map-editor-open-map</code> - popup asks host to reopen another map</li>
|
|
<li><code>POST /api/content/maps</code> - map payload and split map files</li>
|
|
<li><code>POST /api/content/npcs</code> - map-local NPC instance payload</li>
|
|
<li><code>POST /api/editor-settings</code> - theme preset persistence</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Files</p>
|
|
<ul>
|
|
<li><code>content/maps.json</code> - map index</li>
|
|
<li><code>content/maps/<mapId>/tiles.json</code> - base tile payload</li>
|
|
<li><code>content/maps/<mapId>/layer_<n>.json</code> - room layers</li>
|
|
<li><code>content/maps/<mapId>/instances.json</code> - map-local entities</li>
|
|
</ul>
|
|
</div>
|
|
<div class="wiki-card">
|
|
<p class="wiki-card-title">Current prototypes</p>
|
|
<ul>
|
|
<li>Monsters panel - selector UI scaffold</li>
|
|
<li>Triggers panel - selector UI scaffold</li>
|
|
<li>Paths panel - selector UI scaffold</li>
|
|
<li>Transitions panel - selector UI scaffold</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="menu-btn" type="button" onclick="window.location.href='/'">Open Main Editor</button>
|
|
<button class="menu-btn" id="closeQuickRefBtnFooter" type="button">Done</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
const THEME_KEY = "tes8-map-editor-wiki-theme";
|
|
const metaMain = document.getElementById("metaMain");
|
|
const quickRefModal = document.getElementById("quickRefModal");
|
|
const quickRefBtn = document.getElementById("quickRefBtn");
|
|
const closeQuickRefBtn = document.getElementById("closeQuickRefBtn");
|
|
const closeQuickRefBtnFooter = document.getElementById("closeQuickRefBtnFooter");
|
|
const viewButtons = Array.from(document.querySelectorAll("[data-view-target]"));
|
|
const views = Array.from(document.querySelectorAll(".wiki-view"));
|
|
const themeButtons = Array.from(document.querySelectorAll(".theme-preset-btn"));
|
|
|
|
function applyTheme(themeId) {
|
|
const nextTheme = String(themeId || "azure");
|
|
document.documentElement.setAttribute("data-theme", nextTheme);
|
|
themeButtons.forEach((button) => {
|
|
const isActive = button.getAttribute("data-theme") === nextTheme;
|
|
button.classList.toggle("active", isActive);
|
|
button.setAttribute("aria-pressed", isActive ? "true" : "false");
|
|
});
|
|
try {
|
|
window.localStorage.setItem(THEME_KEY, nextTheme);
|
|
} catch (_err) {}
|
|
}
|
|
|
|
function setView(viewId) {
|
|
const nextView = String(viewId || "overview");
|
|
viewButtons.forEach((button) => {
|
|
button.classList.toggle("active", button.getAttribute("data-view-target") === nextView);
|
|
});
|
|
views.forEach((view) => {
|
|
const isActive = view.getAttribute("data-view") === nextView;
|
|
view.classList.toggle("active", isActive);
|
|
if (isActive && metaMain) {
|
|
metaMain.textContent = view.getAttribute("data-meta") || "Map editor documentation";
|
|
}
|
|
});
|
|
}
|
|
|
|
function openQuickRef() {
|
|
quickRefModal.classList.remove("hidden");
|
|
}
|
|
|
|
function closeQuickRef() {
|
|
quickRefModal.classList.add("hidden");
|
|
}
|
|
|
|
viewButtons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
setView(button.getAttribute("data-view-target"));
|
|
});
|
|
});
|
|
|
|
themeButtons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
applyTheme(button.getAttribute("data-theme"));
|
|
});
|
|
});
|
|
|
|
quickRefBtn.addEventListener("click", openQuickRef);
|
|
closeQuickRefBtn.addEventListener("click", closeQuickRef);
|
|
closeQuickRefBtnFooter.addEventListener("click", closeQuickRef);
|
|
quickRefModal.addEventListener("click", (event) => {
|
|
if (event.target === quickRefModal) {
|
|
closeQuickRef();
|
|
}
|
|
});
|
|
window.addEventListener("keydown", (event) => {
|
|
if (event.key === "Escape" && !quickRefModal.classList.contains("hidden")) {
|
|
closeQuickRef();
|
|
}
|
|
});
|
|
|
|
let initialTheme = "azure";
|
|
try {
|
|
initialTheme = window.localStorage.getItem(THEME_KEY) || "azure";
|
|
} catch (_err) {}
|
|
applyTheme(initialTheme);
|
|
setView("overview");
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|