diff --git a/libs/jieba-wasm/jieba_rs_wasm.js b/libs/jieba-wasm/jieba_rs_wasm.js
deleted file mode 100644
index d6af3f7..0000000
--- a/libs/jieba-wasm/jieba_rs_wasm.js
+++ /dev/null
@@ -1,390 +0,0 @@
-
-let wasm;
-
-let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
-
-cachedTextDecoder.decode();
-
-let cachegetUint8Memory0 = null;
-function getUint8Memory0() {
- if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
- cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
- }
- return cachegetUint8Memory0;
-}
-
-function getStringFromWasm0(ptr, len) {
- return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
-}
-
-const heap = new Array(32).fill(undefined);
-
-heap.push(undefined, null, true, false);
-
-let heap_next = heap.length;
-
-function addHeapObject(obj) {
- if (heap_next === heap.length) heap.push(heap.length + 1);
- const idx = heap_next;
- heap_next = heap[idx];
-
- heap[idx] = obj;
- return idx;
-}
-
-function getObject(idx) { return heap[idx]; }
-
-function dropObject(idx) {
- if (idx < 36) return;
- heap[idx] = heap_next;
- heap_next = idx;
-}
-
-function takeObject(idx) {
- const ret = getObject(idx);
- dropObject(idx);
- return ret;
-}
-
-function debugString(val) {
- // primitive types
- const type = typeof val;
- if (type == 'number' || type == 'boolean' || val == null) {
- return `${val}`;
- }
- if (type == 'string') {
- return `"${val}"`;
- }
- if (type == 'symbol') {
- const description = val.description;
- if (description == null) {
- return 'Symbol';
- } else {
- return `Symbol(${description})`;
- }
- }
- if (type == 'function') {
- const name = val.name;
- if (typeof name == 'string' && name.length > 0) {
- return `Function(${name})`;
- } else {
- return 'Function';
- }
- }
- // objects
- if (Array.isArray(val)) {
- const length = val.length;
- let debug = '[';
- if (length > 0) {
- debug += debugString(val[0]);
- }
- for(let i = 1; i < length; i++) {
- debug += ', ' + debugString(val[i]);
- }
- debug += ']';
- return debug;
- }
- // Test for built-in
- const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
- let className;
- if (builtInMatches.length > 1) {
- className = builtInMatches[1];
- } else {
- // Failed to match the standard '[object ClassName]'
- return toString.call(val);
- }
- if (className == 'Object') {
- // we're a user defined class or Object
- // JSON.stringify avoids problems with cycles, and is generally much
- // easier than looping through ownProperties of `val`.
- try {
- return 'Object(' + JSON.stringify(val) + ')';
- } catch (_) {
- return 'Object';
- }
- }
- // errors
- if (val instanceof Error) {
- return `${val.name}: ${val.message}\n${val.stack}`;
- }
- // TODO we could test for more things here, like `Set`s and `Map`s.
- return className;
-}
-
-let WASM_VECTOR_LEN = 0;
-
-let cachedTextEncoder = new TextEncoder('utf-8');
-
-const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
- ? function (arg, view) {
- return cachedTextEncoder.encodeInto(arg, view);
-}
- : function (arg, view) {
- const buf = cachedTextEncoder.encode(arg);
- view.set(buf);
- return {
- read: arg.length,
- written: buf.length
- };
-});
-
-function passStringToWasm0(arg, malloc, realloc) {
-
- if (realloc === undefined) {
- const buf = cachedTextEncoder.encode(arg);
- const ptr = malloc(buf.length);
- getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
- WASM_VECTOR_LEN = buf.length;
- return ptr;
- }
-
- let len = arg.length;
- let ptr = malloc(len);
-
- const mem = getUint8Memory0();
-
- let offset = 0;
-
- for (; offset < len; offset++) {
- const code = arg.charCodeAt(offset);
- if (code > 0x7F) break;
- mem[ptr + offset] = code;
- }
-
- if (offset !== len) {
- if (offset !== 0) {
- arg = arg.slice(offset);
- }
- ptr = realloc(ptr, len, len = offset + arg.length * 3);
- const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
- const ret = encodeString(arg, view);
-
- offset += ret.written;
- }
-
- WASM_VECTOR_LEN = offset;
- return ptr;
-}
-
-let cachegetInt32Memory0 = null;
-function getInt32Memory0() {
- if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
- cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
- }
- return cachegetInt32Memory0;
-}
-
-let cachegetUint32Memory0 = null;
-function getUint32Memory0() {
- if (cachegetUint32Memory0 === null || cachegetUint32Memory0.buffer !== wasm.memory.buffer) {
- cachegetUint32Memory0 = new Uint32Array(wasm.memory.buffer);
- }
- return cachegetUint32Memory0;
-}
-
-function getArrayJsValueFromWasm0(ptr, len) {
- const mem = getUint32Memory0();
- const slice = mem.subarray(ptr / 4, ptr / 4 + len);
- const result = [];
- for (let i = 0; i < slice.length; i++) {
- result.push(takeObject(slice[i]));
- }
- return result;
-}
-/**
-* @param {string} text
-* @param {boolean} hmm
-* @returns {any[]}
-*/
-export function cut(text, hmm) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- var ptr0 = passStringToWasm0(text, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- wasm.cut(retptr, ptr0, len0, hmm);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var v1 = getArrayJsValueFromWasm0(r0, r1).slice();
- wasm.__wbindgen_free(r0, r1 * 4);
- return v1;
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-/**
-* @param {string} text
-* @returns {any[]}
-*/
-export function cut_all(text) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- var ptr0 = passStringToWasm0(text, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- wasm.cut_all(retptr, ptr0, len0);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var v1 = getArrayJsValueFromWasm0(r0, r1).slice();
- wasm.__wbindgen_free(r0, r1 * 4);
- return v1;
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-/**
-* @param {string} text
-* @param {boolean} hmm
-* @returns {any[]}
-*/
-export function cut_for_search(text, hmm) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- var ptr0 = passStringToWasm0(text, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- wasm.cut_for_search(retptr, ptr0, len0, hmm);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var v1 = getArrayJsValueFromWasm0(r0, r1).slice();
- wasm.__wbindgen_free(r0, r1 * 4);
- return v1;
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-/**
-* @param {string} text
-* @param {string} mode
-* @param {boolean} hmm
-* @returns {any[]}
-*/
-export function tokenize(text, mode, hmm) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- var ptr0 = passStringToWasm0(text, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- var ptr1 = passStringToWasm0(mode, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len1 = WASM_VECTOR_LEN;
- wasm.tokenize(retptr, ptr0, len0, ptr1, len1, hmm);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var v2 = getArrayJsValueFromWasm0(r0, r1).slice();
- wasm.__wbindgen_free(r0, r1 * 4);
- return v2;
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-function isLikeNone(x) {
- return x === undefined || x === null;
-}
-/**
-* @param {string} word
-* @param {number | undefined} freq
-* @param {string | undefined} tag
-* @returns {number}
-*/
-export function add_word(word, freq, tag) {
- var ptr0 = passStringToWasm0(word, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- var ptr1 = isLikeNone(tag) ? 0 : passStringToWasm0(tag, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len1 = WASM_VECTOR_LEN;
- var ret = wasm.add_word(ptr0, len0, !isLikeNone(freq), isLikeNone(freq) ? 0 : freq, ptr1, len1);
- return ret >>> 0;
-}
-
-async function load(module, imports) {
- if (typeof Response === 'function' && module instanceof Response) {
- if (typeof WebAssembly.instantiateStreaming === 'function') {
- try {
- return await WebAssembly.instantiateStreaming(module, imports);
-
- } catch (e) {
- if (module.headers.get('Content-Type') != 'application/wasm') {
- console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
-
- } else {
- throw e;
- }
- }
- }
-
- const bytes = await module.arrayBuffer();
- return await WebAssembly.instantiate(bytes, imports);
-
- } else {
- const instance = await WebAssembly.instantiate(module, imports);
-
- if (instance instanceof WebAssembly.Instance) {
- return { instance, module };
-
- } else {
- return instance;
- }
- }
-}
-
-async function init(input) {
- if (typeof input === 'undefined') {
- input = new URL('jieba_rs_wasm_bg.wasm', import.meta.url);
- }
- const imports = {};
- imports.wbg = {};
- imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
- var ret = getStringFromWasm0(arg0, arg1);
- return addHeapObject(ret);
- };
- imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
- takeObject(arg0);
- };
- imports.wbg.__wbg_new_68adb0d58759a4ed = function() {
- var ret = new Object();
- return addHeapObject(ret);
- };
- imports.wbg.__wbindgen_number_new = function(arg0) {
- var ret = arg0;
- return addHeapObject(ret);
- };
- imports.wbg.__wbg_set_2e79e744454afade = function(arg0, arg1, arg2) {
- getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
- };
- imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
- var ret = getObject(arg0);
- return addHeapObject(ret);
- };
- imports.wbg.__wbg_new_7031805939a80203 = function(arg0, arg1) {
- var ret = new Error(getStringFromWasm0(arg0, arg1));
- return addHeapObject(ret);
- };
- imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
- var ret = debugString(getObject(arg1));
- var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- getInt32Memory0()[arg0 / 4 + 1] = len0;
- getInt32Memory0()[arg0 / 4 + 0] = ptr0;
- };
- imports.wbg.__wbindgen_throw = function(arg0, arg1) {
- throw new Error(getStringFromWasm0(arg0, arg1));
- };
- imports.wbg.__wbindgen_rethrow = function(arg0) {
- throw takeObject(arg0);
- };
-
- if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
- input = fetch(input);
- }
-
-
-
- const { instance, module } = await load(await input, imports);
-
- wasm = instance.exports;
- init.__wbindgen_wasm_module = module;
-
- return wasm;
-}
-
-export default init;
-
diff --git a/libs/jieba-wasm/jieba_rs_wasm_bg.wasm b/libs/jieba-wasm/jieba_rs_wasm_bg.wasm
deleted file mode 100644
index d78f47c..0000000
Binary files a/libs/jieba-wasm/jieba_rs_wasm_bg.wasm and /dev/null differ
diff --git a/libs/jieba-wasm/jieba_rs_wasm_bg.wasm.d.ts b/libs/jieba-wasm/jieba_rs_wasm_bg.wasm.d.ts
deleted file mode 100644
index 0e1390d..0000000
--- a/libs/jieba-wasm/jieba_rs_wasm_bg.wasm.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/* tslint:disable */
-/* eslint-disable */
-export const memory: WebAssembly.Memory;
-export function cut(a: number, b: number, c: number, d: number): void;
-export function cut_all(a: number, b: number, c: number): void;
-export function cut_for_search(a: number, b: number, c: number, d: number): void;
-export function tokenize(a: number, b: number, c: number, d: number, e: number, f: number): void;
-export function add_word(a: number, b: number, c: number, d: number, e: number, f: number): number;
-export function __wbindgen_malloc(a: number): number;
-export function __wbindgen_realloc(a: number, b: number, c: number): number;
-export function __wbindgen_add_to_stack_pointer(a: number): number;
-export function __wbindgen_free(a: number, b: number): void;
diff --git a/modules/story-outline/story-outline.html b/modules/story-outline/story-outline.html
index e5e68fa..ae0f476 100644
--- a/modules/story-outline/story-outline.html
+++ b/modules/story-outline/story-outline.html
@@ -7,8 +7,2501 @@
小白板
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box
+ }
+
+ :root {
+ --bg: #FDFDF9;
+ --bg2: #FFFFFF;
+ --bg3: #F0F0EE;
+ --c: #1C1917;
+ --c2: #57534E;
+ --c3: #78716C;
+ --bd: #E5E5E4;
+ --r4: 8px;
+ --r6: 12px;
+ --r8: 16px;
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+ --accent: #6366f1;
+ --accent2: #8b5cf6;
+ --glass: rgba(255, 255, 255, 0.8);
+ --glass-border: rgba(255, 255, 255, 0.5)
+ }
+
+ html.dark {
+ --bg: #0f0f12;
+ --bg2: #18181b;
+ --bg3: #27272a;
+ --c: #fafafa;
+ --c2: #a1a1aa;
+ --c3: #71717a;
+ --bd: #3f3f46;
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
+ --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.3);
+ --accent: #818cf8;
+ --accent2: #a78bfa;
+ --glass: rgba(24, 24, 27, 0.85);
+ --glass-border: rgba(63, 63, 70, 0.6)
+ }
+
+ html,
+ body {
+ transition: background .3s, color .3s
+ }
+
+ html,
+ body {
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ background: var(--bg);
+ color: var(--c)
+ }
+
+ .fc,
+ .fcc {
+ display: flex;
+ align-items: center
+ }
+
+ .fcc {
+ justify-content: center
+ }
+
+ .g4 {
+ gap: 4px
+ }
+
+ .g6 {
+ gap: 6px
+ }
+
+ .g10 {
+ gap: 10px
+ }
+
+ .g20 {
+ gap: 20px
+ }
+
+ .p12 {
+ padding: 12px
+ }
+
+ .r6 {
+ border-radius: var(--r6)
+ }
+
+ .bd {
+ border: 1px solid var(--bd)
+ }
+
+ .bg3 {
+ background: var(--bg3)
+ }
+
+ .fs12 {
+ font-size: 12px
+ }
+
+ .fw6 {
+ font-weight: 600
+ }
+
+ .c2 {
+ color: var(--c2)
+ }
+
+ .usn {
+ user-select: none
+ }
+
+ .f1 {
+ flex: 1
+ }
+
+ .jsb {
+ justify-content: space-between
+ }
+
+ .mt6 {
+ margin-top: 6px
+ }
+
+ .mt8 {
+ margin-top: 8px
+ }
+
+ .mt10 {
+ margin-top: 10px
+ }
+
+ .mt12 {
+ margin-top: 12px
+ }
+
+ .mt16 {
+ margin-top: 16px
+ }
+
+ .mb4 {
+ margin-bottom: 4px
+ }
+
+ .mb8 {
+ margin-bottom: 8px
+ }
+
+ .mb10 {
+ margin-bottom: 10px
+ }
+
+ .mb12 {
+ margin-bottom: 12px
+ }
+
+ .lh15 {
+ line-height: 1.5
+ }
+
+ .lh165 {
+ line-height: 1.65
+ }
+
+ .w100 {
+ width: 100px
+ }
+
+ .w140 {
+ width: 140px
+ }
+
+ .btn {
+ padding: 8px 16px;
+ background: var(--bg2);
+ font-size: 13px;
+ font-weight: 500;
+ cursor: pointer;
+ border: 1px solid var(--bd);
+ border-radius: var(--r4);
+ color: var(--c);
+ transition: all .2s;
+ box-shadow: var(--shadow-sm);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px
+ }
+
+ .btn:hover {
+ background: #fafafa;
+ border-color: #dcdcdc;
+ box-shadow: var(--shadow);
+ transform: translateY(-1px)
+ }
+
+ .btn:active {
+ transform: translateY(0)
+ }
+
+ .btn:disabled {
+ opacity: .5;
+ cursor: not-allowed;
+ box-shadow: none;
+ transform: none
+ }
+
+ .btn-p {
+ background: var(--c);
+ color: #fff;
+ border-color: var(--c)
+ }
+
+ .btn-p:hover {
+ background: #2a2a28;
+ color: #fff;
+ border-color: #2a2a28
+ }
+
+ .btn-due {
+ background: #ffe1e1 !important;
+ border-color: #ff9b9b !important;
+ color: #7a1f1f !important
+ }
+
+ .btn-s {
+ padding: 6px 12px;
+ font-size: 12px
+ }
+
+ .btn-c,
+ .btn-add {
+ padding: 0;
+ border-radius: 50%;
+ flex-shrink: 0
+ }
+
+ .btn-c {
+ width: 32px;
+ height: 32px;
+ background: var(--bg3);
+ border-color: var(--bd);
+ color: var(--c2)
+ }
+
+ .btn-c:hover {
+ background: #e7e5e4;
+ color: var(--c)
+ }
+
+ .btn-add {
+ width: 36px;
+ height: 36px;
+ background: var(--bg2);
+ color: var(--c);
+ box-shadow: var(--shadow-sm)
+ }
+
+ .fold {
+ background: var(--bg2);
+ border: 1px solid var(--bd);
+ border-radius: var(--r6);
+ margin-bottom: 8px;
+ overflow: hidden;
+ box-shadow: var(--shadow-sm);
+ transition: box-shadow .2s
+ }
+
+ .fold:hover {
+ box-shadow: var(--shadow)
+ }
+
+ .fold-h {
+ padding: 12px 16px;
+ cursor: pointer;
+ display: flex;
+ justify-content: space-between;
+ align-items: center
+ }
+
+ .fold-a {
+ color: var(--c3);
+ font-size: 10px;
+ transition: transform .15s
+ }
+
+ .fold.exp .fold-a {
+ transform: rotate(180deg)
+ }
+
+ .fold-b {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height .2s cubic-bezier(0, 1, 0, 1)
+ }
+
+ .fold.exp .fold-b {
+ max-height: 500px;
+ transition: max-height .3s ease-in-out
+ }
+
+ .side-nav-wrap {
+ position: fixed;
+ left: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 500;
+ display: flex;
+ flex-direction: column;
+ gap: 8px
+ }
+
+ .side-glass {
+ background: rgba(255, 255, 255, .8);
+ backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.5);
+ box-shadow: var(--shadow)
+ }
+
+ .side-nav,
+ .side-menu {
+ display: flex;
+ flex-direction: column;
+ border-radius: 24px
+ }
+
+ .side-nav {
+ gap: 6px;
+ padding: 8px 6px
+ }
+
+ .side-menu {
+ position: relative;
+ padding: 6px 6px;
+ align-items: center
+ }
+
+ .side-menu-btn,
+ .nav-i {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ color: var(--c3);
+ transition: all .2s
+ }
+
+ .side-menu-btn {
+ width: 32px;
+ height: 32px;
+ font-size: 12px;
+ border-radius: 10px
+ }
+
+ .nav-i {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%
+ }
+
+ .nav-i i {
+ font-size: 14px
+ }
+
+ .side-menu-btn:hover,
+ .nav-i:hover {
+ background: var(--bg3);
+ color: var(--c)
+ }
+
+ .side-menu-btn.act,
+ .nav-i.act {
+ background: var(--c);
+ color: #fff;
+ box-shadow: var(--shadow-sm)
+ }
+
+ .side-menu-panel {
+ position: absolute;
+ left: 100%;
+ top: 50%;
+ transform: translateY(-50%);
+ margin-left: 12px;
+ display: none;
+ flex-direction: column;
+ gap: 6px;
+ padding: 8px;
+ background: var(--bg2);
+ border-radius: var(--r6);
+ border: 1px solid var(--bd);
+ white-space: nowrap;
+ z-index: 600;
+ box-shadow: var(--shadow)
+ }
+
+ .side-menu-panel.show {
+ display: flex
+ }
+
+ .side-menu-panel .btn {
+ font-size: 11px;
+ padding: 6px 12px;
+ width: 100%;
+ justify-content: flex-start
+ }
+
+ .toolbar {
+ height: 52px;
+ background: var(--bg2);
+ border-bottom: 1px solid var(--bd);
+ padding: 0 16px 0 64px;
+ position: relative;
+ z-index: 200;
+ box-shadow: var(--shadow-sm)
+ }
+
+ .toolbar-t {
+ font-size: 16px;
+ font-weight: 700;
+ margin-right: auto;
+ letter-spacing: -0.5px;
+ color: var(--c)
+ }
+
+ .toolbar-t span {
+ font-weight: 400;
+ color: var(--c3);
+ margin-left: 8px;
+ font-size: 12px;
+ letter-spacing: 0
+ }
+
+ .main-wrap {
+ width: 100%;
+ height: calc(100% - 52px);
+ position: relative
+ }
+
+ .page {
+ position: absolute;
+ inset: 0;
+ background: var(--bg);
+ display: none;
+ overflow: hidden
+ }
+
+ .page.act {
+ display: block
+ }
+
+ .page-pad {
+ padding: 24px 24px 24px 72px;
+ overflow-y: auto;
+ height: 100%
+ }
+
+ .banner {
+ width: 100%;
+ height: 180px;
+ border-radius: var(--r8);
+ overflow: hidden;
+ margin-bottom: 24px;
+ position: relative;
+ background: var(--bg3);
+ box-shadow: var(--shadow-sm)
+ }
+
+ .banner img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover
+ }
+
+ .banner-ov {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(transparent 40%, rgba(0, 0, 0, .6));
+ display: flex;
+ align-items: flex-end;
+ padding: 20px
+ }
+
+ .banner-ov div {
+ color: #fff;
+ font-size: 14px;
+ font-weight: 500;
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3)
+ }
+
+ .sec-t {
+ font-size: 13px;
+ color: var(--c3);
+ margin-bottom: 12px;
+ font-weight: 600;
+ letter-spacing: 0.5px;
+ text-transform: uppercase
+ }
+
+ .sec-hd {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px
+ }
+
+ .sec-hd .sec-t {
+ margin-bottom: 0
+ }
+
+ .news-sec {
+ margin-bottom: 32px
+ }
+
+ .news-t {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--c)
+ }
+
+ .news-time {
+ font-size: 11px;
+ color: var(--c3)
+ }
+
+ .fold.exp .news-b {
+ padding: 0 16px 16px
+ }
+
+ .news-b p {
+ font-size: 13px;
+ color: var(--c2);
+ line-height: 1.7
+ }
+
+ .user-guide {
+ padding: 20px;
+ background: var(--bg2);
+ border-radius: var(--r6);
+ border: 1px solid var(--bd);
+ margin-bottom: 24px;
+ box-shadow: var(--shadow-sm)
+ }
+
+ .user-guide-state {
+ font-size: 14px;
+ font-weight: 500;
+ margin-bottom: 12px;
+ color: var(--c)
+ }
+
+ .user-guide-actions {
+ display: flex;
+ flex-direction: column;
+ gap: 8px
+ }
+
+ .user-guide-action {
+ padding: 10px 14px;
+ background: var(--bg);
+ border: 1px solid var(--bd);
+ border-radius: var(--r4);
+ font-size: 13px;
+ color: var(--c2);
+ cursor: pointer;
+ transition: all .2s
+ }
+
+ .user-guide-action:hover {
+ background: var(--bg2);
+ border-color: var(--c3)
+ }
+
+ #mapWrap {
+ width: 100%;
+ height: 100%;
+ background: var(--bg);
+ cursor: grab;
+ overflow: hidden;
+ position: relative;
+ touch-action: none
+ }
+
+ #inner,
+ #lines {
+ position: absolute;
+ width: 4000px;
+ height: 4000px;
+ transform-origin: 0 0
+ }
+
+ #lines {
+ pointer-events: none
+ }
+
+ .item {
+ position: absolute;
+ padding: 8px 16px;
+ background: var(--bg2);
+ border: 1px solid var(--bd);
+ border-radius: 20px;
+ font-size: 12px;
+ font-weight: 600;
+ white-space: nowrap;
+ cursor: pointer;
+ user-select: none;
+ box-shadow: var(--shadow-sm);
+ color: var(--c);
+ transition: transform .1s, border-color .1s
+ }
+
+ .item.node-main {
+ background: var(--c);
+ color: #fff;
+ border-color: var(--c);
+ z-index: 10
+ }
+
+ .item.node-home {
+ background: #fff;
+ color: var(--c);
+ border: 2px solid var(--c);
+ z-index: 11
+ }
+
+ .item.node-sub {
+ background: var(--bg2)
+ }
+
+ .item:hover {
+ transform: scale(1.05);
+ z-index: 20
+ }
+
+ .item.hl {
+ border-color: var(--c);
+ box-shadow: 0 0 0 3px rgba(68, 64, 60, 0.2);
+ z-index: 20
+ }
+
+ .map-act {
+ position: absolute;
+ top: 16px;
+ right: 16px;
+ z-index: 100
+ }
+
+ #btn-goto {
+ display: none;
+ background: var(--c);
+ color: #fff;
+ border: none;
+ box-shadow: var(--shadow)
+ }
+
+ #btn-goto.show {
+ display: flex
+ }
+
+ .map-lbl {
+ position: absolute;
+ top: 16px;
+ left: 72px;
+ z-index: 100;
+ background: var(--bg2);
+ border: 1px solid var(--bd);
+ border-radius: 20px;
+ padding: 8px 16px;
+ font-size: 12px;
+ color: var(--c);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ box-shadow: var(--shadow-sm)
+ }
+
+ .map-lbl i:first-child {
+ margin-right: 8px;
+ color: var(--c3)
+ }
+
+ .map-lbl .fa-chevron-down {
+ margin-left: 8px;
+ font-size: 9px;
+ color: var(--c3)
+ }
+
+ .map-lbl-sel {
+ position: absolute;
+ opacity: 0;
+ cursor: pointer;
+ inset: 0;
+ border: none;
+ background: transparent;
+ -webkit-appearance: none;
+ appearance: none
+ }
+
+ .panel {
+ position: fixed;
+ background: var(--bg2);
+ padding: 12px 16px;
+ border-radius: var(--r6);
+ font-size: 12px;
+ color: var(--c2);
+ border: 1px solid var(--bd);
+ box-shadow: var(--shadow)
+ }
+
+ #zoom-ind {
+ bottom: 20px;
+ right: 20px;
+ font-family: monospace
+ }
+
+ #tip {
+ bottom: 20px;
+ left: 72px;
+ max-width: 280px;
+ line-height: 1.6;
+ max-height: 220px;
+ overflow-y: auto;
+ display: none
+ }
+
+ #tip .desc {
+ display: none
+ }
+
+ #tip .info-w {
+ display: block
+ }
+
+ #tip.show {
+ display: block
+ }
+
+ .loc-lk {
+ color: var(--c);
+ font-weight: 600;
+ cursor: pointer;
+ border-bottom: 1px dashed var(--c3);
+ transition: color .2s
+ }
+
+ .loc-lk:hover {
+ color: #000;
+ border-bottom-style: solid
+ }
+
+ .local-map-title {
+ font-size: 15px;
+ font-weight: 700;
+ color: var(--c);
+ margin-bottom: 12px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid var(--bd)
+ }
+
+ .info-h {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px
+ }
+
+ .info-t {
+ font-weight: 700;
+ color: var(--c);
+ font-size: 14px
+ }
+
+ .info-bk {
+ font-size: 11px;
+ color: var(--c3);
+ cursor: pointer;
+ padding: 4px 8px;
+ border-radius: 4px;
+ background: var(--bg3)
+ }
+
+ .info-c {
+ color: var(--c2);
+ font-size: 13px;
+ line-height: 1.6
+ }
+
+ .comm-hd {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 20px
+ }
+
+ .comm-tabs {
+ display: flex;
+ gap: 4px;
+ background: var(--bg3);
+ padding: 4px;
+ border-radius: var(--r6)
+ }
+
+ .comm-tab {
+ padding: 8px 20px;
+ text-align: center;
+ border-radius: var(--r4);
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--c3);
+ transition: all .2s;
+ border: none
+ }
+
+ .comm-tab:first-child {
+ border-radius: var(--r4)
+ }
+
+ .comm-tab:last-child {
+ border-radius: var(--r4);
+ border-left: none
+ }
+
+ .comm-tab.act {
+ background: var(--bg2);
+ color: var(--c);
+ box-shadow: var(--shadow-sm)
+ }
+
+ .comm-sec {
+ display: none
+ }
+
+ .comm-sec.act {
+ display: block
+ }
+
+ .ct-hd {
+ gap: 12px
+ }
+
+ .ct-av,
+ .chat-av {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-weight: 600;
+ font-size: 14px;
+ flex-shrink: 0;
+ box-shadow: var(--shadow-sm)
+ }
+
+ .ct-info {
+ flex: 1;
+ min-width: 0
+ }
+
+ .ct-name {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--c)
+ }
+
+ .ct-st {
+ font-size: 12px;
+ color: var(--c3);
+ margin-top: 2px
+ }
+
+ .ct-det {
+ padding: 0 16px 16px
+ }
+
+ .ct-info-text {
+ font-size: 13px;
+ color: var(--c2);
+ line-height: 1.7;
+ margin: 0 0 16px;
+ background: var(--bg);
+ padding: 10px;
+ border-radius: var(--r4)
+ }
+
+ .ct-acts {
+ display: flex;
+ gap: 10px
+ }
+
+ .ct-acts .btn {
+ flex: 1;
+ justify-content: center;
+ font-size: 12px;
+ height: 32px
+ }
+
+ .empty {
+ text-align: center;
+ padding: 60px 20px;
+ color: var(--c3);
+ font-size: 13px;
+ background: var(--bg3);
+ border-radius: var(--r6);
+ border: 1px dashed var(--bd)
+ }
+
+ .modal {
+ position: fixed;
+ inset: 0;
+ z-index: 10000;
+ display: none;
+ justify-content: center;
+ align-items: center;
+ backdrop-filter: blur(2px);
+ transition: all .3s
+ }
+
+ .modal.act {
+ display: flex
+ }
+
+ .modal-bd {
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, .15)
+ }
+
+ .modal-p {
+ position: relative;
+ width: 90%;
+ max-width: 700px;
+ max-height: 85vh;
+ background: var(--bg2);
+ border: 1px solid var(--bd);
+ border-radius: var(--r8);
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ box-shadow: var(--shadow-lg)
+ }
+
+ .modal-p.sm {
+ max-width: 400px
+ }
+
+ .modal-p.lg {
+ max-width: 800px
+ }
+
+ .modal-hd,
+ .modal-ft {
+ padding: 16px 20px;
+ background: var(--bg2)
+ }
+
+ .modal-hd {
+ justify-content: space-between;
+ border-bottom: 1px solid var(--bd)
+ }
+
+ .modal-hd h2 {
+ font-size: 16px;
+ font-weight: 700;
+ color: var(--c)
+ }
+
+ .modal-ft {
+ justify-content: flex-end;
+ gap: 12px;
+ border-top: 1px solid var(--bd);
+ background: #fafaf9
+ }
+
+ .modal-x {
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ cursor: pointer;
+ border: none;
+ border-radius: 50%;
+ color: var(--c3);
+ font-size: 16px
+ }
+
+ .modal-x:hover {
+ background: var(--bg3);
+ color: var(--c)
+ }
+
+ .modal-by {
+ flex: 1;
+ overflow-y: auto;
+ padding: 24px
+ }
+
+ .form-g {
+ margin-bottom: 20px
+ }
+
+ .form-l {
+ display: block;
+ font-size: 12px;
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: var(--c2);
+ text-transform: uppercase;
+ letter-spacing: 0.5px
+ }
+
+ .form-in {
+ width: 100%;
+ padding: 10px 14px;
+ border: 1px solid var(--bd);
+ border-radius: var(--r4);
+ font-size: 14px;
+ outline: none;
+ background: var(--bg);
+ color: var(--c);
+ transition: all .2s;
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.03)
+ }
+
+ .form-in:focus {
+ background: #fff;
+ border-color: var(--c3);
+ box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.05)
+ }
+
+ .form-ta {
+ min-height: 100px;
+ resize: vertical;
+ font-family: inherit;
+ line-height: 1.6
+ }
+
+ .goto-d {
+ font-size: 14px;
+ color: var(--c);
+ font-weight: 500;
+ padding: 12px 16px;
+ background: var(--bg3);
+ border-radius: var(--r4);
+ margin-bottom: 16px;
+ border: 1px solid var(--bd)
+ }
+
+ .ed-ta {
+ width: 100%;
+ min-height: 120px;
+ padding: 16px;
+ background: var(--bg);
+ border: 1px solid var(--bd);
+ border-radius: var(--r4);
+ font-family: "SF Mono", Monaco, Consolas, monospace;
+ font-size: 12px;
+ line-height: 1.6;
+ color: var(--c);
+ resize: vertical;
+ outline: 0;
+ overflow: auto
+ }
+
+ .ed-ta:focus {
+ background: #fff;
+ border-color: var(--c3)
+ }
+
+ .ed-preview {
+ margin-top: 12px;
+ padding: 16px;
+ background: var(--bg3);
+ border: 1px solid var(--bd);
+ border-radius: var(--r4);
+ font-size: 11px;
+ font-family: "SF Mono", Monaco, Consolas, monospace;
+ white-space: pre-wrap;
+ display: none;
+ color: var(--c2)
+ }
+
+ .ed-err {
+ padding: 12px;
+ background: #fef2f2;
+ border: 1px solid #fecaca;
+ border-radius: var(--r4);
+ color: #b91c1c;
+ font-size: 13px;
+ margin-top: 12px;
+ display: none
+ }
+
+ .ed-err.vis {
+ display: block
+ }
+
+ .chat {
+ position: fixed;
+ top: 0;
+ right: -420px;
+ width: 380px;
+ height: 100%;
+ background: var(--bg2);
+ border-left: 1px solid var(--bd);
+ z-index: 600;
+ display: flex;
+ flex-direction: column;
+ box-shadow: var(--shadow-lg);
+ transition: right .3s cubic-bezier(0.16, 1, 0.3, 1)
+ }
+
+ .chat.act {
+ right: 0
+ }
+
+ .chat-hd {
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--bd);
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ flex-shrink: 0
+ }
+
+ .chat-t {
+ flex: 1;
+ min-width: 0
+ }
+
+ .chat-nm {
+ font-size: 15px;
+ font-weight: 700;
+ color: var(--c)
+ }
+
+ .chat-st {
+ font-size: 12px;
+ color: var(--c3);
+ margin-top: 2px
+ }
+
+ .chat-hd-acts {
+ display: flex;
+ align-items: center;
+ gap: 6px
+ }
+
+ .chat-compress,
+ .chat-clr,
+ .chat-x {
+ width: 36px;
+ height: 36px;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--c3);
+ transition: background .2s
+ }
+
+ .chat-compress:hover,
+ .chat-clr:hover,
+ .chat-x:hover {
+ background: var(--bg3);
+ color: var(--c)
+ }
+
+ .chat-compress:disabled {
+ opacity: .4;
+ cursor: not-allowed
+ }
+
+ .chat-divider {
+ width: 100%;
+ text-align: center;
+ padding: 12px 0;
+ color: var(--c3);
+ font-size: 11px;
+ position: relative
+ }
+
+ .chat-divider:before,
+ .chat-divider:after {
+ content: "";
+ position: absolute;
+ top: 50%;
+ width: 30%;
+ height: 1px;
+ background: var(--bd)
+ }
+
+ .chat-divider:before {
+ left: 0
+ }
+
+ .chat-divider:after {
+ right: 0
+ }
+
+ .chat-msgs {
+ flex: 1;
+ overflow-y: auto;
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ background: var(--bg)
+ }
+
+ .chat-msg {
+ max-width: 85%;
+ padding: 12px 16px;
+ border-radius: 18px;
+ font-size: 14px;
+ line-height: 1.6;
+ white-space: pre-wrap;
+ box-shadow: var(--shadow-sm)
+ }
+
+ .chat-msg.sent {
+ align-self: flex-end;
+ background: var(--c);
+ color: #fff;
+ border-bottom-right-radius: 4px
+ }
+
+ .chat-msg.recv {
+ align-self: flex-start;
+ background: var(--bg2);
+ border-bottom-left-radius: 4px;
+ border: 1px solid var(--bd);
+ color: var(--c)
+ }
+
+ .chat-msg.typing {
+ font-style: italic;
+ opacity: .7;
+ background: transparent;
+ box-shadow: none;
+ border: none;
+ padding: 0
+ }
+
+ .chat-in-w {
+ padding: 16px;
+ border-top: 1px solid var(--bd);
+ display: flex;
+ gap: 12px;
+ flex-shrink: 0;
+ background: var(--bg2)
+ }
+
+ .chat-in {
+ flex: 1;
+ padding: 12px 16px;
+ border: 1px solid var(--bd);
+ border-radius: 24px;
+ font-size: 14px;
+ outline: none;
+ background: var(--bg);
+ transition: all .2s
+ }
+
+ .chat-in:focus {
+ border-color: var(--c);
+ background: #fff;
+ box-shadow: var(--shadow-sm)
+ }
+
+ .chat-in:disabled,
+ .chat-send:disabled {
+ opacity: .5;
+ cursor: not-allowed
+ }
+
+ .chat-send {
+ width: 40px;
+ height: 40px;
+ border: none;
+ background: var(--c);
+ color: #fff;
+ border-radius: 50%;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: var(--shadow-sm);
+ transition: transform .2s
+ }
+
+ .chat-send:hover {
+ transform: scale(1.05)
+ }
+
+ .chat-emp {
+ text-align: center;
+ color: var(--c3);
+ font-size: 13px;
+ padding: 60px 20px
+ }
+
+ .loc-list {
+ max-height: 300px;
+ overflow-y: auto
+ }
+
+ .loc-i {
+ padding: 14px 16px;
+ border: 1px solid var(--bd);
+ border-radius: var(--r6);
+ margin-bottom: 10px;
+ cursor: pointer;
+ background: var(--bg2);
+ transition: all .2s
+ }
+
+ .loc-i:hover {
+ border-color: var(--c3);
+ transform: translateY(-1px)
+ }
+
+ .loc-i.sel {
+ border-color: var(--c);
+ background: var(--c);
+ color: #fff;
+ box-shadow: var(--shadow)
+ }
+
+ .loc-i.sel .loc-i-info {
+ color: rgba(255, 255, 255, 0.8)
+ }
+
+ .loc-i-nm {
+ font-size: 14px;
+ font-weight: 600
+ }
+
+ .loc-i-info {
+ font-size: 12px;
+ opacity: .7;
+ margin-top: 4px
+ }
+
+ .mob-pop {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: var(--bg2);
+ border-top: 1px solid var(--bd);
+ border-radius: 20px 20px 0 0;
+ z-index: 101;
+ display: none;
+ flex-direction: column;
+ box-shadow: var(--shadow-lg)
+ }
+
+ .mob-pop.act {
+ display: flex
+ }
+
+ .pop-hd {
+ padding: 12px;
+ cursor: grab;
+ touch-action: none;
+ flex-shrink: 0
+ }
+
+ .pop-handle {
+ width: 40px;
+ height: 5px;
+ background: var(--bd);
+ border-radius: 3px;
+ margin: 0 auto
+ }
+
+ .pop-ct {
+ flex: 1;
+ overflow-y: auto;
+ padding: 0 24px 24px;
+ -webkit-overflow-scrolling: touch
+ }
+
+ .pop-desc {
+ display: none
+ }
+
+ .pop-info {
+ display: block
+ }
+
+ .pop-info-h {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px
+ }
+
+ .pop-info-t {
+ font-weight: 700;
+ font-size: 16px;
+ color: var(--c)
+ }
+
+ .pop-info-bk {
+ font-size: 12px;
+ color: var(--c3);
+ cursor: pointer;
+ padding: 6px 12px;
+ border-radius: var(--r4);
+ background: var(--bg3)
+ }
+
+ .pop-h-ind {
+ position: absolute;
+ top: 50%;
+ right: 8px;
+ transform: translateY(-50%);
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ opacity: 0
+ }
+
+ .mob-pop.drag .pop-h-ind {
+ opacity: 1
+ }
+
+ .pop-h-ind span {
+ width: 4px;
+ height: 10px;
+ background: var(--bd);
+ border-radius: 2px
+ }
+
+ .pop-h-ind span.act {
+ background: var(--c)
+ }
+
+ .side-pop {
+ position: fixed;
+ top: 44px;
+ right: 0;
+ bottom: 0;
+ background: var(--bg2);
+ border-left: 1px solid var(--bd);
+ z-index: 90;
+ display: none;
+ width: 12px;
+ transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ overflow: hidden
+ }
+
+ .side-pop.show {
+ display: flex
+ }
+
+ .side-pop.act {
+ width: 332px;
+ box-shadow: var(--shadow-lg)
+ }
+
+ .side-pop-handle {
+ width: 12px;
+ background: var(--bg3);
+ cursor: pointer;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ touch-action: none;
+ border-right: 1px solid var(--bd);
+ transition: background 0.2s
+ }
+
+ .side-pop-handle:hover {
+ background: var(--bd)
+ }
+
+ .side-pop-bar {
+ width: 3px;
+ height: 36px;
+ background: var(--bd);
+ border-radius: 2px
+ }
+
+ .side-pop-ct {
+ flex: 1;
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding: 16px;
+ margin-bottom: 30vh;
+ min-width: 0
+ }
+
+ .side-pop-hd {
+ font-size: 11px;
+ color: var(--c3);
+ margin-bottom: 10px
+ }
+
+ .side-pop-desc {
+ font-size: 13px;
+ color: var(--c2);
+ line-height: 1.7;
+ margin-top: 2em
+ }
+
+ .set-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 10px
+ }
+
+ .set-row .form-in {
+ flex: 1
+ }
+
+ .set-row .btn {
+ flex-shrink: 0
+ }
+
+ .set-hint {
+ font-size: 11px;
+ color: var(--c3);
+ margin-top: 4px
+ }
+
+ .set-test {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-top: 8px
+ }
+
+ .set-test-res {
+ font-size: 12px;
+ padding: 6px 10px;
+ border-radius: var(--r4);
+ display: none
+ }
+
+ .set-test-res.ok {
+ display: block;
+ background: #dcfce7;
+ color: #166534;
+ border: 1px solid #86efac
+ }
+
+ .set-test-res.err {
+ display: block;
+ background: #fef2f2;
+ color: #b91c1c;
+ border: 1px solid #fecaca
+ }
+
+ .warn {
+ color: #f80
+ }
+
+ .settings-body {
+ height: 480px;
+ overflow-y: auto;
+ background: var(--bg2);
+ padding: 24px
+ }
+
+ .settings-nav {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ background: var(--bg3);
+ padding: 4px;
+ border-radius: var(--r6);
+ height: 40px
+ }
+
+ .set-nav-item {
+ padding: 0 16px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ border-radius: var(--r4);
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--c3);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ transition: all .2s;
+ white-space: nowrap
+ }
+
+ .set-nav-item:hover {
+ color: var(--c2)
+ }
+
+ .set-nav-item.act {
+ background: var(--bg2);
+ color: var(--c);
+ box-shadow: var(--shadow-sm);
+ font-weight: 600
+ }
+
+ .set-tab-page {
+ display: none;
+ animation: fadeIn .3s
+ }
+
+ .set-tab-page.act {
+ display: block
+ }
+
+ .set-group {
+ background: #fff;
+ border: 1px solid var(--bd);
+ border-radius: var(--r6);
+ padding: 20px;
+ margin-bottom: 20px;
+ box-shadow: var(--shadow-sm)
+ }
+
+ .set-group-t {
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--c);
+ margin-bottom: 16px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding-bottom: 10px;
+ border-bottom: 1px dashed var(--bd)
+ }
+
+ .tips-box {
+ background: var(--bg3);
+ padding: 10px 12px;
+ border-radius: var(--r4);
+ font-size: 12px;
+ color: var(--c2);
+ line-height: 1.5;
+ margin-top: 8px
+ }
+
+ .btn.w100 {
+ width: 100%
+ }
+
+ #data-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ gap: 12px
+ }
+
+ .data-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 12px;
+ background: var(--bg);
+ border: 1px solid var(--bd);
+ border-radius: var(--r6);
+ cursor: pointer;
+ transition: all .2s;
+ position: relative;
+ min-height: 60px
+ }
+
+ .data-item:hover {
+ border-color: var(--c3);
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-sm)
+ }
+
+ .data-item.sel {
+ background: #fff;
+ border-color: var(--c);
+ box-shadow: 0 0 0 1px var(--c) inset
+ }
+
+ .data-ck {
+ width: 20px;
+ height: 20px;
+ border: 1px solid var(--bd);
+ background: var(--bg3);
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: transparent;
+ font-size: 10px;
+ flex-shrink: 0
+ }
+
+ .data-item.sel .data-ck {
+ background: var(--c);
+ border-color: var(--c);
+ color: #fff
+ }
+
+ .data-info {
+ flex: 1;
+ min-width: 0
+ }
+
+ .data-nm {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--c);
+ margin-bottom: 2px
+ }
+
+ .data-desc {
+ font-size: 11px;
+ color: var(--c3);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis
+ }
+
+ .data-edit {
+ width: 28px;
+ height: 28px;
+ border-radius: 4px;
+ border: 1px solid transparent;
+ background: transparent;
+ color: var(--c3);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ transition: all .2s;
+ cursor: pointer
+ }
+
+ .data-edit:hover {
+ background: var(--bg3);
+ color: var(--c);
+ border-color: var(--bd)
+ }
+
+ @keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(4px)
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0)
+ }
+ }
+
+ @media (max-width:600px) {
+ .modal-p.lg {
+ width: 100%;
+ height: 100%;
+ max-height: 100%;
+ border-radius: 0
+ }
+
+ .settings-body {
+ height: auto;
+ flex: 1;
+ padding: 16px
+ }
+
+ .modal-hd {
+ padding: 10px 16px
+ }
+
+ .set-nav-item {
+ padding: 0 10px;
+ font-size: 12px
+ }
+
+ .set-nav-item i {
+ display: none
+ }
+
+ #data-list {
+ grid-template-columns: 1fr
+ }
+
+ .set-row {
+ flex-wrap: wrap
+ }
+
+ .mt-mob-8 {
+ margin-top: 8px
+ }
+ }
+
+ .data-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ padding: 12px;
+ background: var(--bg);
+ border: 1px solid var(--bd);
+ border-radius: var(--r6);
+ margin-bottom: 8px;
+ cursor: pointer
+ }
+
+ .data-item.sel {
+ border-color: var(--c);
+ background: rgba(34, 34, 34, .05)
+ }
+
+ .data-ck {
+ width: 18px;
+ height: 18px;
+ border: 2px solid var(--bd);
+ border-radius: var(--r4);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ margin-top: 2px
+ }
+
+ .data-item.sel .data-ck {
+ background: var(--c);
+ border-color: var(--c);
+ color: #fff
+ }
+
+ .data-ck i {
+ font-size: 10px;
+ opacity: 0
+ }
+
+ .data-item.sel .data-ck i {
+ opacity: 1
+ }
+
+ .data-info {
+ flex: 1;
+ min-width: 0
+ }
+
+ .data-nm {
+ font-size: 13px;
+ font-weight: 500;
+ margin-bottom: 4px
+ }
+
+ .data-desc {
+ font-size: 11px;
+ color: var(--c3);
+ line-height: 1.4
+ }
+
+ .data-edit {
+ width: 28px;
+ height: 28px;
+ border: 1px solid var(--bd);
+ border-radius: var(--r4);
+ background: var(--bg2);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ color: var(--c3)
+ }
+
+ #set-model-list {
+ display: none;
+ margin-top: 8px
+ }
+
+ #world-gen-status,
+ #world-sim-status {
+ display: none;
+ color: #4a9
+ }
+
+ #adv-u1,
+ #adv-a1,
+ #adv-u2,
+ #adv-a2 {
+ height: 85px
+ }
+
+ #adv-json {
+ min-height: 140px
+ }
+
+ #data-edit-ta {
+ min-height: 300px
+ }
+
+ #res-msg {
+ margin-bottom: 10px;
+ line-height: 1.5
+ }
+
+ #res-record-box {
+ display: none
+ }
+
+ #res-action {
+ display: none
+ }
+
+ #res-record {
+ max-height: 200px;
+ overflow-y: auto;
+ background: var(--bg3);
+ padding: 8px;
+ border-radius: var(--r4);
+ font-size: 12px;
+ white-space: pre-wrap;
+ word-break: break-all
+ }
+
+ .adv-modal .modal-p {
+ max-width: 900px;
+ height: 80vh;
+ max-height: 800px;
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+ background: var(--bg2);
+ border-radius: var(--r8);
+ box-shadow: var(--shadow-lg);
+ border: 1px solid var(--bd)
+ }
+
+ .adv-modal .modal-hd {
+ padding: 16px 24px;
+ border-bottom: 1px solid var(--bd);
+ background: var(--bg2);
+ flex-shrink: 0
+ }
+
+ .adv-modal .modal-hd h2 {
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--c);
+ letter-spacing: -0.5px
+ }
+
+ .adv-layout {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+ height: 100%
+ }
+
+ .adv-sidebar {
+ width: 260px;
+ background: var(--bg3);
+ border-right: 1px solid var(--bd);
+ padding: 20px;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ flex-shrink: 0
+ }
+
+ .adv-sidebar .form-g {
+ margin-bottom: 0
+ }
+
+ .adv-sidebar .set-hint {
+ font-size: 12px;
+ color: var(--c3);
+ line-height: 1.6
+ }
+
+ .adv-main {
+ flex: 1;
+ padding: 24px 32px;
+ overflow-y: auto;
+ background: var(--bg2);
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+ min-width: 0
+ }
+
+ .adv-var-group-title {
+ font-size: 12px;
+ font-weight: 700;
+ color: var(--c2);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin: 16px 0 8px 0;
+ padding-bottom: 4px;
+ border-bottom: 1px solid var(--bd)
+ }
+
+ .adv-var-item {
+ font-size: 12px;
+ color: var(--c2);
+ padding: 4px 0;
+ display: flex;
+ flex-direction: column;
+ gap: 2px
+ }
+
+ .adv-var-item code {
+ font-family: "SF Mono", Monaco, Consolas, monospace;
+ background: rgba(0, 0, 0, 0.06);
+ padding: 2px 5px;
+ border-radius: 4px;
+ color: var(--c);
+ font-size: 11px;
+ align-self: flex-start;
+ border: 1px solid rgba(0, 0, 0, 0.05)
+ }
+
+ .adv-var-desc {
+ color: var(--c3);
+ font-size: 11px;
+ line-height: 1.4;
+ padding-left: 2px
+ }
+
+ .adv-section {
+ background: var(--bg);
+ border: 1px solid var(--bd);
+ border-radius: var(--r6);
+ padding: 20px;
+ box-shadow: var(--shadow-sm)
+ }
+
+ .adv-h-title {
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--c);
+ margin-bottom: 16px;
+ display: flex;
+ align-items: center;
+ gap: 8px
+ }
+
+ .adv-h-title::before {
+ content: '';
+ width: 4px;
+ height: 16px;
+ background: var(--c);
+ border-radius: 2px;
+ display: block
+ }
+
+ .uaua-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px
+ }
+
+ .ed-ta-adv {
+ font-family: "SF Mono", Monaco, Consolas, monospace;
+ font-size: 12px;
+ line-height: 1.5;
+ padding: 12px;
+ border: 1px solid var(--bd);
+ border-radius: var(--r4);
+ background: #fff;
+ width: 100%;
+ resize: vertical;
+ min-height: 120px;
+ transition: border-color 0.2s;
+ outline: none
+ }
+
+ .ed-ta-adv:focus {
+ border-color: var(--c);
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05)
+ }
+
+ .adv-tag-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ margin-bottom: 12px
+ }
+
+ .adv-tag {
+ font-size: 10px;
+ padding: 4px 8px;
+ background: var(--bg2);
+ border: 1px solid var(--bd);
+ border-radius: 12px;
+ color: var(--c2);
+ cursor: pointer;
+ transition: all 0.2s;
+ user-select: none
+ }
+
+ .adv-tag:hover {
+ border-color: var(--c3);
+ color: var(--c);
+ background: #fff
+ }
+
+ @media(max-width:768px) {
+ .adv-layout {
+ flex-direction: column;
+ overflow-y: auto
+ }
+
+ .adv-sidebar {
+ width: 100%;
+ height: auto;
+ border-right: none;
+ border-bottom: 1px solid var(--bd);
+ padding: 16px
+ }
+
+ .adv-main {
+ overflow: visible;
+ padding: 16px
+ }
+
+ .uaua-grid {
+ grid-template-columns: 1fr
+ }
+ }
+
+ @media(max-width:550px) {
+ .chat {
+ width: 100%;
+ right: -100%
+ }
+
+ .side-pop {
+ bottom: 0
+ }
+
+ .side-nav-wrap {
+ left: 6px
+ }
+
+ .side-nav {
+ padding: 6px 4px
+ }
+
+ .side-menu {
+ padding: 5px 4px
+ }
+
+ .nav-i {
+ width: 28px;
+ height: 28px
+ }
+
+ .nav-i i {
+ font-size: 11px
+ }
+
+ .toolbar {
+ padding: 0 12px 0 56px
+ }
+
+ .toolbar-t span {
+ display: none
+ }
+
+ .btn {
+ padding: 6px 10px;
+ font-size: 11px
+ }
+
+ .modal-p {
+ max-width: 100%;
+ max-height: 100%;
+ border-radius: 0
+ }
+
+ #tip {
+ display: none !important
+ }
+
+ .page-pad {
+ padding: 14px 14px 14px 48px
+ }
+
+ .map-lbl {
+ left: 48px
+ }
+ }
+
+ /* 600px 响应式布局 */
+ @media (max-width: 600px) {
+ .modal-hd {
+ padding: 10px 16px;
+ display: flex;
+ justify-content: flex-end
+ }
+ }
+
+ /* 主题切换按钮样式 */
+ .theme-toggle {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ border: 1px solid var(--bd);
+ background: var(--bg3);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--c2);
+ transition: all .3s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
+ overflow: hidden
+ }
+
+ .theme-toggle:hover {
+ background: var(--bd);
+ color: var(--c);
+ transform: scale(1.05)
+ }
+
+ .theme-toggle i {
+ font-size: 14px;
+ transition: all .3s cubic-bezier(0.4, 0, 0.2, 1)
+ }
+
+ .theme-toggle .fa-sun {
+ position: absolute;
+ opacity: 0;
+ transform: rotate(-90deg) scale(0)
+ }
+
+ .theme-toggle .fa-moon {
+ opacity: 1;
+ transform: rotate(0) scale(1)
+ }
+
+ html.dark .theme-toggle .fa-sun {
+ opacity: 1;
+ transform: rotate(0) scale(1)
+ }
+
+ html.dark .theme-toggle .fa-moon {
+ opacity: 0;
+ transform: rotate(90deg) scale(0)
+ }
+
+ /* 暗色模式额外样式修复 */
+
+ /* 按钮样式修复 */
+ html.dark .btn:hover {
+ background: var(--bg3);
+ border-color: var(--c3)
+ }
+
+ html.dark .btn-c:hover {
+ background: var(--bg3);
+ color: var(--c)
+ }
+
+ html.dark .btn-p {
+ background: var(--accent);
+ border-color: var(--accent);
+ color: #fff
+ }
+
+ html.dark .btn-p:hover {
+ background: var(--accent2);
+ border-color: var(--accent2);
+ color: #fff
+ }
+
+ html.dark .modal-ft {
+ background: var(--bg3)
+ }
+
+ /* 输入框样式修复 */
+ html.dark .form-in {
+ background: var(--bg);
+ color: var(--c)
+ }
+
+ html.dark .form-in:focus {
+ background: var(--bg2);
+ box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.2)
+ }
+
+ html.dark .ed-ta:focus,
+ html.dark .ed-ta-adv:focus {
+ background: var(--bg);
+ box-shadow: 0 0 0 2px rgba(129, 140, 248, 0.2)
+ }
+
+ html.dark .ed-ta-adv {
+ background: var(--bg)
+ }
+
+ html.dark .set-group {
+ background: var(--bg2)
+ }
+
+ /* 聊天界面修复 */
+ html.dark .chat-in {
+ background: var(--bg);
+ color: var(--c)
+ }
+
+ html.dark .chat-in:focus {
+ background: var(--bg2);
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.2)
+ }
+
+ html.dark .chat-send {
+ background: var(--accent);
+ color: #fff
+ }
+
+ html.dark .chat-send:hover {
+ background: var(--accent2)
+ }
+
+ html.dark .chat-msg.sent {
+ background: var(--accent);
+ color: #fff
+ }
+
+ html.dark .chat-msg.recv {
+ background: var(--bg2);
+ border-color: var(--bd);
+ color: var(--c)
+ }
+
+ html.dark .chat-av {
+ border: 2px solid var(--accent)
+ }
+
+ /* 地图节点修复 */
+ html.dark .item {
+ background: var(--bg2);
+ border-color: var(--bd);
+ color: var(--c)
+ }
+
+ html.dark .item.node-main {
+ background: var(--accent);
+ border-color: var(--accent);
+ color: #fff
+ }
+
+ html.dark .item.node-home {
+ background: var(--bg);
+ border-color: var(--accent);
+ color: var(--c)
+ }
+
+ html.dark .item.node-sub {
+ background: var(--bg2);
+ color: var(--c)
+ }
+
+ html.dark .item.hl {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.3)
+ }
+
+ html.dark #btn-goto {
+ background: var(--accent);
+ color: #fff
+ }
+
+ /* 标签和导航修复 */
+ html.dark .nav-i.act {
+ background: var(--accent);
+ color: #fff
+ }
+
+ html.dark .side-menu-btn.act {
+ background: var(--accent);
+ color: #fff
+ }
+
+ html.dark .comm-tab.act {
+ background: var(--accent);
+ color: #fff;
+ border-color: var(--accent)
+ }
+
+ /* 其他元素修复 */
+ html.dark .adv-tag:hover {
+ background: var(--bg3)
+ }
+
+ html.dark .adv-var-item code {
+ background: rgba(255, 255, 255, 0.08);
+ border-color: rgba(255, 255, 255, 0.1)
+ }
+
+ html.dark .side-glass {
+ background: var(--glass);
+ border-color: var(--glass-border)
+ }
+
+ html.dark .loc-lk {
+ color: var(--accent)
+ }
+
+ html.dark .loc-lk:hover {
+ color: var(--accent2)
+ }
+
+ html.dark .data-item.sel {
+ background: rgba(129, 140, 248, 0.1);
+ border-color: var(--accent)
+ }
+
+ html.dark .loc-i.sel {
+ background: rgba(129, 140, 248, 0.1);
+ border-color: var(--accent)
+ }
+
+ html.dark .fold-h:hover {
+ background: var(--bg3)
+ }
+
+ html.dark .ct-av {
+ border: 2px solid rgba(255, 255, 255, 0.1)
+ }
+
+ html.dark .map-lbl {
+ background: var(--bg2);
+ border-color: var(--bd);
+ color: var(--c)
+ }
+
+ html.dark #zoom-ind {
+ background: var(--bg2);
+ border-color: var(--bd);
+ color: var(--c2)
+ }
+
+ html.dark #tip {
+ background: var(--bg2);
+ border-color: var(--bd)
+ }
+
+ html.dark .set-nav-item.act {
+ color: var(--accent);
+ border-color: var(--accent)
+ }
+
+ html.dark .btn-add {
+ border-color: var(--bd);
+ color: var(--c3)
+ }
+
+ html.dark .btn-add:hover {
+ background: var(--bg3);
+ color: var(--c)
+ }
+
+ /* 选择框修复 */
+ html.dark select.form-in {
+ background: var(--bg);
+ color: var(--c)
+ }
+
+ html.dark select.form-in option {
+ background: var(--bg2);
+ color: var(--c)
+ }
+
+ /* 复选框修复 */
+ html.dark input[type="checkbox"] {
+ accent-color: var(--accent)
+ }
+
+ /* SVG 地图线条修复 */
+ html.dark #lines line {
+ stroke: #71717a !important
+ }
+
+ html.dark #lines line[stroke="#333"] {
+ stroke: var(--accent) !important
+ }
+
@@ -35,6 +2528,8 @@
小白板预测试
+
@@ -518,8 +3013,28 @@
+ const D = { stage: 0, deviationScore: 0, simulationTarget: 5, meta: { truth: null, onion_layers: null, timeline: null, user_guide: null }, world: {}, maps: { outdoor: { nodes: [] }, indoor: null }, sceneSetup: null, contacts: { strangers: [], contacts: [{ name: "{{characterName}}", avatar: "", color: "#555", location: "在线", info: "角色卡联络人", online: !0, worldbookUid: "__CHARACTER_CARD__", messages: [], summarizedCount: 0 }] } }; let charSmsHistory = { messages: [], summarizedCount: 0, summaries: {} }; const $ = t => document.getElementById(t), $$ = t => document.querySelectorAll(t), isMob = () => innerWidth <= 550, escHtml = t => t.replace(/[&<>"']/g, (t => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[t]))), h = t => escHtml(String(t ?? "")), stripXml = t => t ? t.replace(/<(\w+)[^>]*>[\s\S]*?<\/\1>/g, "").replace(/<[^>]+\/?>/g, "").trim() : "", parseLinks = t => h(t).replace(/\*\*([^*]+)\*\*/g, '$1'), PARENT_ORIGIN = (() => { try { return new URL(document.referrer).origin } catch { return window.location.origin } })(), post = (t, e = {}) => parent.postMessage({ source: "LittleWhiteBox-OutlineFrame", type: t, ...e }, PARENT_ORIGIN), syncSimDueUI = () => { const t = (Number(D.simulationTarget) || 0) <= 0; $("btn-simulate")?.classList.toggle("btn-due", t), $("world-sim-ok")?.classList.toggle("btn-due", t) }, BtnState = { load: (t, e) => { t.disabled = !0, t._o = t.innerHTML, t.innerHTML = ` ${e}` }, reset: (t, e) => { t.disabled = !1, t.innerHTML = e || t._o } }, Req = { _p: {}, _id: 0, create(t) { const e = t + "_" + ++this._id; return this._p[t] = { id: e }, e }, get(t) { return this._p[t] }, match(t) { const e = t?.split("_")[0]; return this._p[e]?.id === t ? this._p[e] : null }, set(t, e) { Object.assign(this._p[t] || {}, e) }, clear(t) { delete this._p[t] } }, openM = t => $(t).classList.add("act"), closeM = t => $(t).classList.remove("act"), dirMap = { north: [0, -1], south: [0, 1], east: [1, 0], west: [-1, 0], northeast: [1, -1], northwest: [-1, -1], southeast: [1, 1], southwest: [-1, 1] }; let playerLocation = "未知", curNode = null, drag = !1, scale = 1, offX = 0, offY = 0, seed = 123456789, nodes = [], lines = [], anim = !1, chatTgt = null, invTgt = null, selLoc = null, smsGen = !1, selectedMapValue = "current"; const inner = $("inner"), svg = $("lines"), mapWrap = $("mapWrap"), popup = $("mob-pop"), chat = $("chat"), sidePop = $("side-pop"), rand = () => (seed = (9301 * seed + 49297) % 233280) / 233280, getCurInside = () => D.maps?.indoor?.[playerLocation] || null, snaps = () => [$("pop-hd")?.offsetHeight || 0, .3 * innerHeight, .65 * innerHeight]; let popH = 0, popDrag = !1, popSY = 0, popSH = 0, popLv = 1; const inds = popup.querySelectorAll(".pop-h-ind span"), setPopH = t => { const e = snaps(); popH = Math.max(e[0], Math.min(.85 * innerHeight, t)), popup.style.height = popH + "px", popLv = e.map(((t, e) => [e, Math.abs(popH - t)])).sort(((t, e) => t[1] - e[1]))[0][0], inds.forEach(((t, e) => t.classList.toggle("act", e === popLv))) }, snapTo = t => { popLv = Math.max(0, Math.min(2, t)), setPopH(snaps()[popLv]) }, openPop = (t = 1) => { popup.classList.add("act"), snapTo(t) }; $("side-pop-handle").onclick = () => sidePop.classList.toggle("act"), $("pop-hd").onmousedown = t => { popDrag = !0, popSY = t.clientY, popSH = popH || snaps()[1], popup.classList.add("drag"), t.preventDefault() }, $("pop-hd").ontouchstart = t => { t.preventDefault(), popDrag = !0, popSY = t.touches[0].clientY, popSH = popH || snaps()[1], popup.classList.add("drag") }, document.onmousemove = t => { popDrag && setPopH(popSH + popSY - t.clientY) }, document.ontouchmove = t => { popDrag && t.touches.length && (t.preventDefault(), setPopH(popSH + popSY - t.touches[0].clientY)) }; const endDrag = () => { popDrag && (popDrag = !1, popup.classList.remove("drag"), snapTo(popLv)) }; document.onmouseup = endDrag, document.ontouchend = endDrag, document.ontouchcancel = endDrag; const bindLinks = t => t.querySelectorAll(".loc-lk").forEach((t => t.onclick = e => { e.stopPropagation(); const s = t.dataset.loc, o = nodes.find((t => t.name === s)); if (o) return panTo(o), void showInfo(o); const a = getCurInside(), n = a?.nodes?.find((t => t.name === s)); n && ($("info-t").textContent = n.name, $("info-c").textContent = n.info || "暂无信息...", $("tip").classList.add("show"), $("btn-goto").classList.remove("show"), isMob() && ($("mob-info-t").textContent = n.name, $("mob-info-c").textContent = n.info || "暂无信息...", popup.classList.contains("act") || openPop(1))) })), bindFold = t => t.querySelector(".fold-h").onclick = () => t.classList.toggle("exp"); $$(".modal-bd,.modal-x,.m-cancel").forEach((t => t.onclick = () => t.closest(".modal").classList.remove("act"))); const openChat = t => { chatTgt = t, $("chat-av").textContent = t.avatar, $("chat-av").style.background = t.color, $("chat-nm").textContent = t.name, $("chat-st").textContent = t.online ? "● 在线" : t.location, !t.worldbookUid || t.messages && t.messages.length ? renderMsgs() : post("LOAD_SMS_HISTORY", { worldbookUid: t.worldbookUid }), chat.classList.add("act"), $("chat-in").focus() }, closeChat = () => { chat.classList.remove("act"), chatTgt = null, smsGen = !1 }, renderMsgs = () => { if (!chatTgt) return; const t = chatTgt.messages || [], e = chatTgt.summarizedCount || 0; let s = ""; t.length ? t.forEach(((t, o) => { e > 0 && o === e && (s += '—— 以上为已总结消息 ——
'), s += `${escHtml(stripXml(t.text))}
` })) : s = '暂无消息,开始聊天吧
', $("chat-msgs").innerHTML = s, $("chat-msgs").scrollTop = $("chat-msgs").scrollHeight, $("chat-compress").disabled = t.length - e < 2 || smsGen, $("chat-back").disabled = !t.length || smsGen }, sendMsg = () => { const t = $("chat-in").value.trim(); if (!t || !chatTgt || smsGen) return; chatTgt.messages = chatTgt.messages || [], chatTgt.messages.push({ type: "sent", text: t }), $("chat-in").value = "", renderMsgs(), smsGen = !0, $("chat-in").disabled = $("chat-send").disabled = $("chat-back").disabled = !0, chatTgt.messages.push({ type: "received", text: "正在输入...", typing: !0 }), renderMsgs(); const e = Req.create("sms"); Req.set("sms", { tgt: chatTgt }), post("SEND_SMS", { requestId: e, contactName: chatTgt.name, worldbookUid: chatTgt.worldbookUid, userMessage: t, chatHistory: chatTgt.messages.filter((t => !t.typing)).slice(-20), summarizedCount: chatTgt.summarizedCount || 0 }) }, isCharCardContact = t => "__CHARACTER_CARD__" === t?.worldbookUid, contactsForSave = () => (D.contacts.contacts || []).filter((t => !isCharCardContact(t))), saveCt = () => post("SAVE_CONTACTS", { contacts: contactsForSave(), strangers: D.contacts.strangers }), saveChat = t => post("SAVE_SMS_HISTORY", { worldbookUid: t.worldbookUid, contactName: t.name, messages: t.messages.filter((t => !t.typing)), summarizedCount: t.summarizedCount || 0 }); $("chat-x").onclick = closeChat, $("chat-back").onclick = () => { if (!chatTgt || smsGen) return; const t = chatTgt.messages || []; if (t.length) { for (t.pop(); t[t.length - 1]?.typing;)t.pop(); chatTgt.summarizedCount = Math.min(chatTgt.summarizedCount || 0, t.length), renderMsgs(), saveCt(), chatTgt.worldbookUid && saveChat(chatTgt) } }, $("chat-clr").onclick = () => { chatTgt && confirm(`确定要清空与 ${chatTgt.name} 的所有聊天记录吗?`) && (chatTgt.messages = [], chatTgt.summarizedCount = 0, renderMsgs(), saveCt(), chatTgt.worldbookUid && saveChat(chatTgt)) }, $("chat-compress").onclick = () => { if (!chatTgt || smsGen) return; const t = chatTgt.messages || [], e = chatTgt.summarizedCount || 0, s = t.slice(e); if (s.length < 2) return void alert("至少需要2条未总结的消息才能压缩"); if (!confirm(`确定要压缩总结 ${s.length} 条消息吗?`)) return; smsGen = !0, $("chat-compress").disabled = $("chat-in").disabled = $("chat-send").disabled = $("chat-back").disabled = !0; const o = Req.create("compress"); Req.set("compress", { tgt: chatTgt }), post("COMPRESS_SMS", { requestId: o, contactName: chatTgt.name, worldbookUid: chatTgt.worldbookUid, messages: t.filter((t => !t.typing)), summarizedCount: e }) }, $("chat-send").onclick = sendMsg; const chatIn = $("chat-in");["keydown", "keypress", "keyup"].forEach((t => chatIn.addEventListener(t, (t => t.stopPropagation())))), chatIn.addEventListener("keydown", (t => { "Enter" !== t.key || t.shiftKey || (t.preventDefault(), sendMsg()) })); const openInv = t => { invTgt = t, selLoc = null, $("inv-t").textContent = `邀请:${t.name}`, $("loc-list").innerHTML = D.maps.outdoor.nodes.map((t => `${h(t.name)}
${h(t.info || "")}
`)).join(""), $$("#loc-list .loc-i").forEach((t => t.onclick = () => { $$("#loc-list .loc-i").forEach((t => t.classList.remove("sel"))), t.classList.add("sel"), selLoc = t.dataset.n })), openM("m-invite") }; $("inv-ok").onclick = () => { if (!selLoc || !invTgt) return; const t = $("inv-ok"); BtnState.load(t, "询问中..."); const e = Req.create("invite"), s = canonicalLoc(selLoc); Req.set("invite", { contact: invTgt, loc: s, btn: t }), post("SEND_INVITE", { requestId: e, contactName: invTgt.name, contactUid: invTgt.worldbookUid, targetLocation: s, smsHistory: (invTgt.messages || []).map((t => "sent" === t.type ? `{{user}}: ${t.text}` : `${invTgt.name}: ${t.text}`)).join("\n") }) }; let addCtState = { uid: "", name: "", keys: [] }; const resetAddCt = () => { addCtState = { uid: "", name: "", keys: [] }, $("add-uid").value = "", $("add-name").value = "", $("add-name").innerHTML = '', $("name-select-group").style.display = "none", $("uid-check-err").classList.remove("vis"), $("add-ct-ok").disabled = !0, BtnState.reset($("btn-check-uid"), ' 检查') }, showUidErr = t => { $("uid-check-err").textContent = t, $("uid-check-err").classList.add("vis") }; $("btn-check-uid").onclick = () => { const t = $("add-uid").value.trim(); t ? ($("uid-check-err").classList.remove("vis"), BtnState.load($("btn-check-uid"), "检查中"), $("name-select-group").style.display = "none", $("add-ct-ok").disabled = !0, addCtState.uid = t, post("CHECK_WORLDBOOK_UID", { uid: t, requestId: Req.create("uidck") })) : showUidErr("请输入UID") }, $("btn-add-ct").onclick = () => { resetAddCt(), openM("m-add-ct") }, $("add-ct-ok").onclick = () => { const t = addCtState.uid || $("add-uid").value.trim(), e = addCtState.name || $("add-name").value.trim(); t && e && (D.contacts.contacts.some((e => e.worldbookUid === t)) ? showUidErr("该联络人已存在") : (D.contacts.contacts.push({ name: e, avatar: e[0], color: "#" + Math.floor(16777215 * Math.random()).toString(16).padStart(6, "0"), location: "未知", worldbookUid: t, messages: [] }), saveCt(), closeM("m-add-ct"), render())) }; const genAddCt = (t, e, s) => { BtnState.load(s, "检查中"); const o = Req.create("stgwb"); Req.set("stgwb", { name: t, info: e, btn: s }), post("CHECK_STRANGER_WORLDBOOK", { requestId: o, strangerName: t }) }; function canonicalLoc(t) { return String(t || "").trim().replace(/^\u90ae\u8f6e/, "") } $("btn-refresh-strangers").onclick = () => { const t = $("btn-refresh-strangers"); BtnState.load(t, ""); const e = Req.create("extract"); Req.set("extract", { btn: t }), post("EXTRACT_STRANGERS", { requestId: e, existingContacts: D.contacts.contacts, existingStrangers: D.contacts.strangers }) }, $("world-gen-ok").onclick = () => { const t = $("world-gen-ok"), e = $("world-gen-status"); BtnState.load(t, "生成中"), e.style.display = "block", e.textContent = "正在生成世界数据,请稍候...", post("GENERATE_WORLD", { requestId: Req.create("wgen"), playerRequests: $("world-gen-req").value.trim() }) }, $("world-sim-ok").onclick = () => { if (!D.meta && !D.timeline && !D.maps?.outdoor) return void alert("请先生成世界数据,再进行推演"); const t = $("world-sim-ok"), e = $("world-sim-status"); BtnState.load(t, "推演中"), e.style.display = "block", e.style.color = "#4a9", e.textContent = "正在分析玩家行为并推演世界变化...", post("SIMULATE_WORLD", { requestId: id, currentData: JSON.stringify({ meta: D.meta, timeline: D.timeline, world: D.world, maps: D.maps }, null, 2) }) }, $("btn-deduce").onclick = () => openM("m-world-gen"), $("btn-simulate").onclick = () => openM("m-world-sim"), $("btn-side-menu-toggle").onclick = () => { const t = $("side-menu-panel"), e = $("btn-side-menu-toggle"); t.classList.toggle("show"), e.classList.toggle("act", t.classList.contains("show")) }, document.addEventListener("click", (t => { t.target.closest(".side-menu") || ($("side-menu-panel")?.classList.remove("show"), $("btn-side-menu-toggle")?.classList.remove("act")) })); const getWaitingContacts = t => { const e = canonicalLoc(t); return e ? (D.contacts.contacts || []).filter((t => t?.waitingAt && canonicalLoc(t.waitingAt) === e)) : [] }, finishTravel = (t, e) => { playerLocation = t, selectedMapValue = "current"; const s = getWaitingContacts(t); s.forEach((t => delete t.waitingAt)), saveAll(), render(), hideInfo(); const o = $("goto-task")?.value; let a = `{{user}}离开了${e || "上一地点"},来到${t}。${o ? "意图:" + o : ""}`; s.length && (a += ` ${s.map((t => t.name)).join("、")}已经在这里等你了。`), post("EXECUTE_SLASH_COMMAND", { command: `/sendas name="剧情任务" ${a}` }) }; $("goto-ok").onclick = () => { if (!curNode) return; const t = $("goto-ok"), e = D.maps?.outdoor?.nodes?.find((t => t.name === playerLocation)), s = { name: playerLocation, info: e?.info || "" }, o = curNode.name, a = D.maps?.indoor?.[o]; if (a?.description) return BtnState.reset(t, "确认前往"), closeM("m-goto"), finishTravel(o, s.name), void switchMapView("current"); let n = "main" === curNode.type ? "main" : "home" === curNode.type ? "home" : "sub"; const r = Req.create("scene"); Req.set("scene", { node: curNode, prev: s.name }), BtnState.load(t, "生成中"), post("SCENE_SWITCH", { requestId: r, prevLocationName: s.name, prevLocationInfo: s.info, targetLocationName: curNode.name, targetLocationType: n, targetLocationInfo: curNode.data?.info || "", playerAction: $("goto-task").value || "" }) }, $("btn-gen-local-map").onclick = () => { const t = $("btn-gen-local-map"); BtnState.load(t, "生成中"); const e = Req.create("localmap"); Req.set("localmap", { btn: t }), post("GENERATE_LOCAL_MAP", { requestId: e, outdoorDescription: D.maps?.outdoor?.description || "" }) }, $("btn-refresh-local-map").onclick = () => { if (!playerLocation) return void showResultModal("提示", "请先生成世界数据", !0); const t = $("btn-refresh-local-map"), e = D.maps?.indoor?.[playerLocation], s = e; if (!s) return void showResultModal("提示", "当前区域没有局部地图,请先生成", !0); BtnState.load(t, "刷新中"); const o = Req.create("localmaprf"); Req.set("localmaprf", { btn: t, loc: playerLocation }), post("REFRESH_LOCAL_MAP", { requestId: o, locationName: playerLocation, currentLocalMap: s, outdoorDescription: D.maps?.outdoor?.description || "" }) }, $("btn-gen-local-scene").onclick = () => { if (!playerLocation || "未知" === playerLocation) return void showResultModal("提示", "请先生成世界数据", !0); const t = $("btn-gen-local-scene"); BtnState.load(t, "生成中"); const e = D.maps?.outdoor?.nodes?.find((t => t.name === playerLocation)), s = D.maps?.indoor?.[playerLocation], o = s?.description || e?.info || e?.data?.info || "", a = Req.create("localscene"); Req.set("localscene", { btn: t, loc: playerLocation }), post("GENERATE_LOCAL_SCENE", { requestId: a, locationName: playerLocation, locationInfo: o }) }, $("btn-refresh-world-news").onclick = () => { const t = $("btn-refresh-world-news"); if (!t) return; t.disabled = !0, t._o = t._o ?? t.innerHTML, t.innerHTML = ''; const e = Req.create("newsrf"); Req.set("newsrf", { btn: t }), post("REFRESH_WORLD_NEWS", { requestId: e }) }; const saveAll = () => post("SAVE_ALL_DATA", { allData: { meta: D.meta, world: D.world, outdoor: D.maps.outdoor, indoor: D.maps.indoor, sceneSetup: D.sceneSetup, strangers: D.contacts.strangers, contacts: contactsForSave() }, playerLocation }), dataKeys = [["meta", "大纲", "核心真相、洋葱结构、时间线、用户指南", () => D.meta, t => D.meta = t], ["world", "世界资讯", "世界新闻等信息", () => D.world, t => D.world = t], ["outdoor", "大地图", "室外区域的地点和路线", () => D.maps.outdoor, t => D.maps.outdoor = t], ["indoor", "局部地图", "隐藏的室内/局部场景地图", () => D.maps.indoor, t => D.maps.indoor = t], ["sceneSetup", "区域剧情", "当前区域的 Side Story", () => D.sceneSetup, t => D.sceneSetup = t], ["characterContactSms", "角色卡短信", "角色卡联络人的短信记录", () => ({ messages: charSmsHistory?.messages || [], summarizedCount: charSmsHistory?.summarizedCount || 0, summaries: charSmsHistory?.summaries || {} }), t => { t && "object" == typeof t && (charSmsHistory = { messages: charSmsHistory?.messages || [], summarizedCount: charSmsHistory?.summarizedCount || 0, ...t || {} }) }], ["strangers", "陌路人", "已遇见但未建立联系的角色", () => D.contacts.strangers, t => D.contacts.strangers = t], ["contacts", "联络人", "已添加的联系人", () => contactsForSave(), t => { const e = (D.contacts.contacts || []).find(isCharCardContact); D.contacts.contacts = (e ? [e] : []).concat(Array.isArray(t) ? t : []) }]]; let gSet = { apiUrl: "", apiKey: "", model: "", mode: "assist" }, dataCk = {}, editCtx = null, commSet = { historyCount: 50, npcPosition: 0, npcOrder: 100, stream: !1 }, promptDefaults = { jsonTemplates: {}, promptSources: {} }, promptStores = { global: { jsonTemplates: {}, promptSources: {} }, character: { jsonTemplates: {}, promptSources: {} } }; const reqSet = () => post("GET_SETTINGS"), renderDataList = () => { $("data-list").innerHTML = dataKeys.map((([t, e, s]) => ``)).join(""), $$("#data-list .data-item").forEach((t => t.onclick = e => { if (e.target.closest(".data-edit")) return; const s = t.dataset.k; dataCk[s] = !dataCk[s], t.classList.toggle("sel", dataCk[s]) })), $$("#data-list .data-edit").forEach((t => t.onclick = e => { e.stopPropagation(), openDataEdit(t.dataset.k) })) }, ADV_PROMPT_ITEMS = [["sms", "短信回复"], ["invite", "邀请回复"], ["npc", "NPC 生成"], ["stranger", "提取陌路人"], ["worldGenStep1", "大纲生成"], ["worldGenStep2", "世界生成"], ["worldSim", "世界推演(故事模式)"], ["worldSimAssist", "世界推演(辅助模式)"], ["worldNewsRefresh", "世界新闻刷新"], ["sceneSwitch", "场景切换"], ["localMapGen", "局部地图生成"], ["localMapRefresh", "局部地图刷新"], ["localSceneGen", "局部剧情生成"], ["summary", "总结压缩"]], advHasJsonTemplate = t => { const e = promptDefaults?.jsonTemplates || {}; return Object.prototype.hasOwnProperty.call(e, t) }, advGetScope = () => "global" === $("adv-scope")?.value ? "global" : "character", advStoreHasKey = (t, e) => { const s = ("global" === t ? promptStores.global : promptStores.character) || {}; return !(!Object.prototype.hasOwnProperty.call(s?.promptSources || {}, e) && !Object.prototype.hasOwnProperty.call(s?.jsonTemplates || {}, e)) }, advGetPromptObj = (t, e = "character") => { const s = promptDefaults?.promptSources || {}, o = promptStores?.global?.promptSources || {}, a = promptStores?.character?.promptSources || {}, n = ("global" === e ? o[t] || s[t] : a[t] || o[t] || s[t]) || {}; return { u1: "string" == typeof n.u1 ? n.u1 : "", a1: "string" == typeof n.a1 ? n.a1 : "", u2: "string" == typeof n.u2 ? n.u2 : "", a2: "string" == typeof n.a2 ? n.a2 : "" } }, advNormalizeDisplayText = t => { let e = String(t ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n"); return !e.includes("\n") && e.includes("\\n") && (e = e.replaceAll("\\n", "\n")), e.includes("\\t") && (e = e.replaceAll("\\t", "\t")), e.includes("\\`") && (e = e.replaceAll("\\`", "`")), e }, advNormalizeSaveText = t => String(t ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n"), advGetJsonTemplate = (t, e = "character") => { const s = promptDefaults?.jsonTemplates || {}, o = promptStores?.global?.jsonTemplates || {}, a = promptStores?.character?.jsonTemplates || {}; if (!(advHasJsonTemplate(t) || Object.prototype.hasOwnProperty.call(o, t) || Object.prototype.hasOwnProperty.call(a, t))) return ""; const n = "global" === e ? Object.prototype.hasOwnProperty.call(o, t) ? o[t] : s[t] : Object.prototype.hasOwnProperty.call(a, t) ? a[t] : Object.prototype.hasOwnProperty.call(o, t) ? o[t] : s[t]; return "string" == typeof n ? n : "" }, advApplyToUI = (t, e = "character") => { const s = advGetPromptObj(t, e), o = (t, e) => { const s = $(t); s && (s.value = advNormalizeDisplayText(e)) }; o("adv-u1", s.u1), o("adv-a1", s.a1), o("adv-u2", s.u2), o("adv-a2", s.a2), $("adv-json-wrap").style.display = ""; const a = advGetJsonTemplate(t, e), n = $("adv-json"); n && (n.value = String(a ?? "")), advUpdateVarHelp(t) }, advUpdateVarHelp = t => { $$(".adv-vars-group").forEach((e => { const s = String(e.dataset.advFor || "").split(",").map((t => t.trim())).filter(Boolean); e.style.display = !s.length || s.includes(t) ? "" : "none" })) }, advBuildEdits = () => { const t = t => advNormalizeSaveText($(t)?.value ?? ""); return { prompt: { u1: t("adv-u1"), a1: t("adv-a1"), u2: t("adv-u2"), a2: t("adv-a2") }, jsonTemplate: advNormalizeSaveText($("adv-json")?.value ?? "") } }, advInit = () => { const t = $("adv-key"); if (!t || t._inited) return; t._inited = !0, t.innerHTML = ADV_PROMPT_ITEMS.map((([t, e]) => ``)).join(""), t.onchange = () => advApplyToUI(t.value, advGetScope()); const e = $("adv-scope"); e && !e._inited && (e._inited = !0, e.onchange = () => { const t = $("adv-key")?.value; t && advApplyToUI(t, advGetScope()) }) }, advOpen = () => { advInit(); const t = $("adv-key"), e = t?.value || ADV_PROMPT_ITEMS[0]?.[0]; if (e) { const t = $("adv-scope"); t && (t.value = advStoreHasKey("character", e) ? "character" : "global"), advApplyToUI(e, advGetScope()) } openM("m-adv-prompts") }, advSaveTo = t => { advInit(); const e = $("adv-key")?.value; if (!e) return; const { prompt: s, jsonTemplate: o } = advBuildEdits(); post("SAVE_PROMPTS", { scope: t, key: e, prompt: s, jsonTemplate: o }), closeM("m-adv-prompts") }, advReset = () => { advInit(); const t = $("adv-key")?.value; t && (post("SAVE_PROMPTS", { scope: advGetScope(), key: t, reset: !0 }), closeM("m-adv-prompts")) }, parseJsonLoose = t => { const e = String(t ?? "").trim(); if (!e) throw new Error("空内容"); try { return JSON.parse(e) } catch { } const s = e.match(/```[^\n]*\n([\s\S]*?)\n```/); if (s?.[1]) { const t = s[1].trim(); try { return JSON.parse(t) } catch { } } const o = (t, s) => { const o = e.indexOf(t), a = e.lastIndexOf(s); return -1 === o || -1 === a || a <= o ? null : e.slice(o, a + 1) }, a = o("{", "}") ?? o("[", "]"); return a ? JSON.parse(a) : JSON.parse(e) }, updateEditPreview = () => { const t = $("data-edit-preview"); t && (t.style.display = "none", t.textContent = "") }, setEditContent = (t, e) => { $("data-edit-title").textContent = t, $("data-edit-ta").value = e, $("data-edit-err").classList.remove("vis"), updateEditPreview(), openM("m-data-edit") }, openDataEdit = t => { const e = dataKeys.find((([e]) => e === t)); e && (editCtx = { type: "characterContactSms" === t ? "charSms" : "data", key: t }, setEditContent(`编辑 - ${e[1]}`, JSON.stringify(e[3](), null, 2))) }; $("data-edit-save").onclick = () => { if (editCtx) try { const t = parseJsonLoose($("data-edit-ta").value); if ("data" === editCtx.type) { const e = dataKeys.find((([t]) => t === editCtx.key)); if (!e) return; e[4](t), render(), saveAll() } else if ("charSms" === editCtx.type) { const e = t?.summaries ?? t; if (!e || "object" != typeof e || Array.isArray(e)) throw new Error("需要 summaries 对象"); charSmsHistory.summaries = e, post("SAVE_CHAR_SMS_HISTORY", { summaries: e }) } closeM("m-data-edit"), editCtx = null } catch (t) { $("data-edit-err").textContent = `JSON错误: ${t.message}`, $("data-edit-err").classList.add("vis") } }, $("data-edit-ta").addEventListener("input", updateEditPreview); const showTestRes = (t, e) => { const s = $("test-res"); s.textContent = e, s.className = "set-test-res " + (t ? "ok" : "err") }, showResultModal = (t, e, s = !1, o = null) => { $("res-title").textContent = t, $("res-title").style.color = s ? "#ff6b6b" : "", $("res-msg").textContent = e; const a = $("res-record-box"), n = $("res-record"); o ? (a.style.display = "block", n.textContent = "object" == typeof o ? JSON.stringify(o, null, 2) : String(o)) : (a.style.display = "none", n.textContent = ""); const r = $("res-action"); r.style.display = "none", r.textContent = "", r.onclick = null, openM("m-result") }; function render() { const t = D.world?.news || []; $("news-list").innerHTML = t.length ? t.map((t => `${h(t.title)}
${h(t.time || "")}
`)).join("") : '暂无新闻
', $$("#news-list .fold").forEach(bindFold); const e = D.meta?.user_guide; e && ($("ug-state").textContent = e.current_state || "未知状态", $("ug-actions").innerHTML = (e.guides || []).map(((t, e) => `${e + 1}. ${h(t)}
`)).join("") || '暂无行动指南
'); const s = (t, e) => (t || []).length ? t.map((t => `${h(t.avatar || "")}
${h(t.name || "")}
${t.online ? "● 在线" : h(t.location)}
${t.info ? `
${h(t.info)}
` : ""}
${e ? `` : ``}
`)).join("") : '暂无
'; if ($("sec-stranger").innerHTML = s(D.contacts.strangers, !0), $("sec-contact").innerHTML = s(D.contacts.contacts, !1), $$(".comm-sec .fold").forEach(bindFold), $$(".add-btn").forEach((t => t.onclick = e => { e.stopPropagation(), genAddCt(t.dataset.name, t.dataset.info || "", t) })), $$(".ignore-btn").forEach((t => t.onclick = e => { e.stopPropagation(); const s = D.contacts.strangers.findIndex((e => e.name === t.dataset.name)); s > -1 && (D.contacts.strangers.splice(s, 1), saveCt(), render()) })), $$(".msg-btn").forEach((t => t.onclick = e => { e.stopPropagation(); const s = D.contacts.contacts.find((e => e.worldbookUid === t.dataset.uid)); s && openChat(s) })), $$(".inv-btn").forEach((t => t.onclick = e => { e.stopPropagation(); const s = D.contacts.contacts.find((e => e.worldbookUid === t.dataset.uid)); s && openInv(s) })), "current" === selectedMapValue) { const t = getCurInside(); $("side-desc").innerHTML = t?.description ? `📍 ${h(playerLocation)}
` + parseLinks(t.description) : parseLinks(D.maps?.outdoor?.description || "") } else $("side-desc").innerHTML = parseLinks(D.maps?.outdoor?.description || ""); bindLinks($("side-desc")), $("mob-desc").innerHTML = "", renderMapSelector(), renderMap() } function renderMap() { const t = D.maps?.outdoor; if (seed = 123456789, inner.querySelectorAll(".item").forEach((t => t.remove())), svg.innerHTML = "", nodes = [], lines = [], !t?.nodes?.length) return; t.nodes.forEach(((t, e) => { const s = dirMap[t.position] || [0, 0], o = Math.hypot(s[0], s[1]) || 1, a = 120 * (t.distant || 1); nodes.push({ id: "n" + e, name: t.name, type: t.type || "sub", pos: t.position, distant: t.distant || 1, x: s[0] / o * a, y: s[1] / o * a, data: t }) })); const e = {}; nodes.forEach((t => (e[t.pos] = e[t.pos] || []).push(t))); for (let t in e) { const s = e[t]; if (s.length <= 1) continue; const o = dirMap[t] || [0, 0], a = Math.hypot(o[0], o[1]) || 1, n = -o[1] / a, r = o[0] / a, i = (s.length - 1) / 2; s.sort(((t, e) => t.distant - e.distant)).forEach(((t, e) => { t.x += n * (e - i) * 50, t.y += r * (e - i) * 50 })) } for (let t = 0; t < 15; t++)for (let t = 0; t < nodes.length; t++)for (let e = t + 1; e < nodes.length; e++) { const s = nodes[t], o = nodes[e], a = s.x - o.x, n = s.y - o.y, r = Math.hypot(a, n); if (r < 80 && r > 0) { const t = (80 - r) / r * .5; s.x += a * t, s.y += n * t, o.x -= a * t, o.y -= n * t } } nodes.forEach((t => { const e = document.createElement("div"); e.className = `item node-${t.type}`, e.id = t.id, e.textContent = "home" === t.type ? "🏠 " + t.name : t.name, e.style.cssText = `left:${t.x + 2e3}px;top:${t.y + 2e3}px`, e.onclick = e => { e.stopPropagation(), curNode?.id === t.id ? hideInfo() : showInfo(t) }, inner.appendChild(e) })); for (let t in e) { const s = e[t].sort(((t, e) => t.distant - e.distant)); for (let t = 0; t < s.length - 1; t++)lines.push([s[t], s[t + 1]]) } const s = Object.values(e).map((t => t.sort(((t, e) => t.distant - e.distant))[0])).sort(((t, e) => Math.atan2(t.y, t.x) - Math.atan2(e.y, e.x))); s.forEach(((t, e) => lines.push([t, s[(e + 1) % s.length]]))); const o = (t, e) => lines.some((([s, o]) => s === t && o === e || s === e && o === t)); for (let t = 0, e = 0; e < Math.floor(nodes.length / 5) && t < 200; t++) { const t = nodes[Math.floor(rand() * nodes.length)], s = nodes[Math.floor(rand() * nodes.length)]; t === s || o(t, s) || (lines.push([t, s]), e++) } drawLines() } function drawLines() { svg.innerHTML = ""; const t = mapWrap.getBoundingClientRect(); lines.forEach((([e, s]) => { const o = $(e.id), a = $(s.id); if (!o || !a) return; const n = o.getBoundingClientRect(), r = a.getBoundingClientRect(), i = document.createElementNS("http://www.w3.org/2000/svg", "line"); i.setAttribute("x1", (n.left + n.width / 2 - t.left) / scale - offX / scale), i.setAttribute("y1", (n.top + n.height / 2 - t.top) / scale - offY / scale), i.setAttribute("x2", (r.left + r.width / 2 - t.left) / scale - offX / scale), i.setAttribute("y2", (r.top + r.height / 2 - t.top) / scale - offY / scale); const c = "main" === e.type && "main" === s.type || "home" === e.type || "home" === s.type; i.setAttribute("stroke", c ? "#333" : "#aaa"), i.setAttribute("stroke-width", c ? "2" : "1"), c || i.setAttribute("stroke-dasharray", "4 3"), svg.appendChild(i) })) } $("btn-settings").onclick = () => { reqSet(), $("set-api-url").value = gSet.apiUrl || "", $("set-api-key").value = gSet.apiKey || "", $("set-model").value = gSet.model || "", $("set-model-list").style.display = "none", $("test-res").className = "set-test-res", $("set-stage").value = D.stage || 0, $("set-deviation").value = D.deviationScore || 0, $("set-sim-target").value = D.simulationTarget ?? 5, $("set-mode").value = gSet.mode || "story", $("set-history-count").value = commSet.historyCount || 50, $("set-use-stream").checked = !!commSet.stream, $("set-npc-position").value = commSet.npcPosition || 0, $("set-npc-order").value = commSet.npcOrder || 100, renderDataList(), syncSimDueUI(), openM("m-settings") }, $("btn-adv-prompts").onclick = () => advOpen(), $("adv-save-global").onclick = () => advSaveTo("global"), $("adv-save-char").onclick = () => advSaveTo("character"), $("adv-reset").onclick = () => advReset(), $("btn-fetch-models").onclick = () => { BtnState.load($("btn-fetch-models"), "加载"), post("FETCH_MODELS", { apiUrl: $("set-api-url").value.trim(), apiKey: $("set-api-key").value.trim() }) }, $("btn-test-conn").onclick = () => { $("test-res").className = "set-test-res", BtnState.load($("btn-test-conn"), "测试"), post("TEST_CONNECTION", { apiUrl: $("set-api-url").value.trim(), apiKey: $("set-api-key").value.trim(), model: $("set-model").value.trim() }) }, $("set-save").onclick = () => { gSet = { apiUrl: $("set-api-url").value.trim(), apiKey: $("set-api-key").value.trim(), model: $("set-model").value.trim(), mode: $("set-mode").value || "story" }, D.stage = Math.max(0, Math.min(10, parseInt($("set-stage").value, 10) || 0)), D.deviationScore = Math.max(0, Math.min(100, parseInt($("set-deviation").value, 10) || 0)), D.simulationTarget = parseInt($("set-sim-target").value, 10), Number.isNaN(D.simulationTarget) && (D.simulationTarget = 5), commSet = { historyCount: Math.max(0, Math.min(200, parseInt($("set-history-count").value, 10) || 50)), stream: !!$("set-use-stream").checked, npcPosition: parseInt($("set-npc-position").value, 10) || 0, npcOrder: Math.max(0, Math.min(1e3, parseInt($("set-npc-order").value, 10) || 100)) }; const t = {}; dataKeys.forEach((([e, , , s]) => { dataCk[e] && (t[e] = s()) })), syncSimDueUI(), post("SAVE_SETTINGS", { globalSettings: gSet, commSettings: commSet, stage: D.stage, deviationScore: D.deviationScore, simulationTarget: D.simulationTarget, playerLocation, dataChecked: dataCk, outlineData: t, allData: { meta: D.meta, timeline: D.timeline, world: D.world, outdoor: D.maps.outdoor, indoor: D.maps.indoor, strangers: D.contacts.strangers, contacts: contactsForSave() } }), closeM("m-settings") }, $("btn-close").onclick = () => post("CLOSE_PANEL"), window.addEventListener("message", (t => { if (t.origin !== PARENT_ORIGIN || t.source !== parent) return; if ("LittleWhiteBox" !== t.data?.source) return; const e = t.data, s = e.type; if ("LOAD_SETTINGS" === s) { if (e.globalSettings && (gSet = e.globalSettings), void 0 !== e.stage && (D.stage = e.stage), void 0 !== e.deviationScore && (D.deviationScore = e.deviationScore), void 0 !== e.simulationTarget && (D.simulationTarget = e.simulationTarget), e.playerLocation && (playerLocation = e.playerLocation), e.commSettings && (commSet = { historyCount: e.commSettings.historyCount ?? 50, npcPosition: e.commSettings.npcPosition ?? 0, npcOrder: e.commSettings.npcOrder ?? 100, stream: !!e.commSettings.stream }), e.dataChecked && (dataCk = e.dataChecked), e.promptConfig && (promptDefaults = e.promptConfig.defaults || promptDefaults, e.promptConfig.stores ? (promptStores.global = e.promptConfig.stores.global || { jsonTemplates: {}, promptSources: {} }, promptStores.character = e.promptConfig.stores.character || { jsonTemplates: {}, promptSources: {} }) : (promptStores.global = e.promptConfig.current || { jsonTemplates: {}, promptSources: {} }, promptStores.character = promptStores.character || { jsonTemplates: {}, promptSources: {} })), e.outlineData) { const t = e.outlineData; t.meta && (D.meta = t.meta), t.world && (D.world = t.world), t.outdoor && (D.maps.outdoor = t.outdoor), t.indoor && (D.maps.indoor = t.indoor), t.sceneSetup && (D.sceneSetup = t.sceneSetup), t.strangers && (D.contacts.strangers = t.strangers), t.contacts && (D.contacts.contacts = t.contacts) } { const t = e.characterContactSmsHistory || {}; charSmsHistory = { messages: Array.isArray(t.messages) ? t.messages : [], summarizedCount: t.summarizedCount || 0, summaries: t.summaries || {} } } let t = D.contacts.contacts.find((t => "__CHARACTER_CARD__" === t.worldbookUid)); t || (t = D.contacts.contacts.find((t => !t.worldbookUid && "炒饭智能" === t.name)), t ? (t.worldbookUid = "__CHARACTER_CARD__", t.info = "角色卡联络人", t.location = "在线", t.online = !0) : (D.contacts.contacts.unshift({ name: e.characterCardName || "{{characterName}}", avatar: "", color: "#555", location: "在线", info: "角色卡联络人", online: !0, worldbookUid: "__CHARACTER_CARD__", messages: [], summarizedCount: 0 }), t = D.contacts.contacts[0])), t && e.characterCardName && (t.name = e.characterCardName, t.avatar = (e.characterCardName || "")[0] || t.avatar || ""), render(), syncSimDueUI(), $("m-settings").classList.contains("act") && ($("set-api-url").value = gSet.apiUrl || "", $("set-api-key").value = gSet.apiKey || "", $("set-model").value = gSet.model || "", $("set-stage").value = D.stage, $("set-deviation").value = D.deviationScore, $("set-sim-target").value = D.simulationTarget ?? 5, $("set-mode").value = gSet.mode || "story", $("set-history-count").value = commSet.historyCount, $("set-use-stream").checked = !!commSet.stream, $("set-npc-position").value = commSet.npcPosition, $("set-npc-order").value = commSet.npcOrder, renderDataList()) } else if ("PROMPT_CONFIG_UPDATED" === s) { if (e.promptConfig && (promptDefaults = e.promptConfig.defaults || promptDefaults, e.promptConfig.stores ? (promptStores.global = e.promptConfig.stores.global || { jsonTemplates: {}, promptSources: {} }, promptStores.character = e.promptConfig.stores.character || { jsonTemplates: {}, promptSources: {} }) : (promptStores.global = e.promptConfig.current || { jsonTemplates: {}, promptSources: {} }, promptStores.character = promptStores.character || { jsonTemplates: {}, promptSources: {} }), $("m-adv-prompts").classList.contains("act"))) { const t = $("adv-key")?.value; t && advApplyToUI(t, advGetScope()) } } else if ("FETCH_MODELS_RESULT" === s) { BtnState.reset($("btn-fetch-models"), "获取"); const t = $("set-model-list"); if (e.error) return t.style.display = "none", void showTestRes(!1, "获取模型失败: " + e.error); if (!e.models?.length) return t.style.display = "none", void showTestRes(!1, "未找到可用模型"); t.innerHTML = '' + e.models.map((t => ``)).join(""), t.style.display = "block", t.onchange = () => { t.value && ($("set-model").value = t.value) }, showTestRes(!0, `找到 ${e.models.length} 个模型`) } else if ("TEST_CONN_RESULT" === s) BtnState.reset($("btn-test-conn"), "测试连接"), showTestRes(e.success, e.message); else if ("CHECK_WORLDBOOK_UID_RESULT" === s) { if (BtnState.reset($("btn-check-uid"), ' 检查'), !Req.match(e.requestId)) return; if (e.error) return void showUidErr(e.error); if (!e.primaryKeys?.length) return void showUidErr("该条目没有主要关键字"); addCtState.keys = e.primaryKeys; const t = $("add-name"); t.innerHTML = '' + e.primaryKeys.map((t => ``)).join(""), t.onchange = () => { addCtState.name = t.value, $("add-ct-ok").disabled = !t.value }, $("name-select-group").style.display = "block", 1 === e.primaryKeys.length && (addCtState.name = e.primaryKeys[0], t.value = addCtState.name, $("add-ct-ok").disabled = !1) } else if ("SMS_RESULT" === s) { const t = Req.get("sms"); if (!t || t.id !== e.requestId) return; if (Req.clear("sms"), smsGen = !1, $("chat-in").disabled = $("chat-send").disabled = $("chat-back").disabled = !1, !chatTgt) return; chatTgt.messages = chatTgt.messages.filter((t => !t.typing)), e.error ? chatTgt.messages.push({ type: "received", text: `[错误] ${e.error}` }) : e.reply && chatTgt.messages.push({ type: "received", text: stripXml(e.reply) }), renderMsgs(), saveCt(), chatTgt.worldbookUid && saveChat(chatTgt) } else if ("SMS_STREAM" === s) { const t = Req.get("sms"); if (!t || t.id !== e.requestId || !chatTgt) return; const s = chatTgt.messages.find((t => t.typing)); s && e.text && (s.text = e.text, renderMsgs()) } else if ("LOAD_SMS_HISTORY_RESULT" === s) { if (!chatTgt || chatTgt.worldbookUid !== e.worldbookUid) return; e.messages?.length && (chatTgt.messages = e.messages, chatTgt.summarizedCount = e.summarizedCount || 0, saveCt()), renderMsgs() } else if ("COMPRESS_SMS_RESULT" === s) { const t = Req.get("compress"); if (!t || t.id !== e.requestId) return; if (Req.clear("compress"), smsGen = !1, $("chat-compress").disabled = $("chat-in").disabled = $("chat-send").disabled = $("chat-back").disabled = !1, !chatTgt) return; if (e.error) return void alert(`压缩失败: ${e.error}`); void 0 !== e.newSummarizedCount && (chatTgt.summarizedCount = e.newSummarizedCount, renderMsgs(), saveCt()) } else if ("CHECK_STRANGER_WORLDBOOK_RESULT" === s) { const t = Req.get("stgwb"); if (!t || t.id !== e.requestId) return; const { name: s, info: o, btn: a } = t; if (Req.clear("stgwb"), e.found && e.worldbookUid) { BtnState.reset(a, ' 添加'); const t = D.contacts.strangers.findIndex((t => t.name === s)); if (t > -1) { const s = D.contacts.strangers.splice(t, 1)[0]; D.contacts.contacts.push({ name: s.name, avatar: s.avatar || s.name[0], color: s.color || "#" + Math.floor(16777215 * Math.random()).toString(16).padStart(6, "0"), location: s.location || "未知", info: s.info || "", worldbookUid: e.worldbookUid, messages: [] }), saveCt(), render() } } else { BtnState.load(a, "生成中"); const t = Req.create("npcgen"); Req.set("npcgen", { name: s, info: o, btn: a }), post("GENERATE_NPC", { requestId: t, strangerName: s, strangerInfo: o }) } } else if ("GENERATE_NPC_RESULT" === s) { const t = Req.get("npcgen"); if (!t || t.id !== e.requestId) return; const { name: s, btn: o } = t; if (Req.clear("npcgen"), BtnState.reset(o, ' 添加'), e.error) return void showResultModal("生成角色失败", "生成 NPC 失败", !0, e.error); if (e.success && e.worldbookUid) { const t = D.contacts.strangers.findIndex((t => t.name === s)); if (t > -1) { const o = D.contacts.strangers.splice(t, 1)[0], a = e.npcData || {}; D.contacts.contacts.push({ name: a.name || o.name, avatar: (a.name || o.name)[0], color: o.color || "#" + Math.floor(16777215 * Math.random()).toString(16).padStart(6, "0"), location: o.location || "未知", info: a.intro || o.info || "", worldbookUid: e.worldbookUid, messages: [] }), saveCt(), render(), showResultModal("生成成功", `NPC ${s} 已生成并添加到联络人`, !1, e.npcData) } } } else if ("EXTRACT_STRANGERS_RESULT" === s) { const t = Req.get("extract"); if (!t || t.id !== e.requestId) return; const { btn: s } = t; if (Req.clear("extract"), BtnState.reset(s, ''), e.error) return void showResultModal("提取失败", "提取陌路人失败", !0, e.error); if (e.success && Array.isArray(e.strangers)) { if (!e.strangers.length) return void showResultModal("提取结果", "没有发现新的陌路人"); const t = [...D.contacts.contacts.map((t => t.name)), ...D.contacts.strangers.map((t => t.name))], s = e.strangers.filter((e => !t.includes(e.name))); if (!s.length) return void showResultModal("提取结果", "提取到的角色都已存在"); D.contacts.strangers = D.contacts.strangers.concat(s), saveCt(), render(), showResultModal("提取成功", `成功提取 ${s.length} 个新陌路人`, !1, s) } } else if ("GENERATE_WORLD_STATUS" === s) { if (!Req.match(e.requestId)) return; const t = $("world-gen-status"); t.style.display = "block", t.style.color = "#4a9", t.textContent = e.message } else if ("GENERATE_WORLD_RESULT" === s) { if (!Req.match(e.requestId)) return; Req.clear("wgen"); const t = $("world-gen-ok"), s = $("world-gen-status"); if (BtnState.reset(t, ' 开始生成'), e.error) { if (s.style.display = "none", showResultModal("生成失败", "世界生成失败", !0, e.error), String(e.error || "").includes("Step 2")) { const e = $("res-action"); e.style.display = "inline-block", e.textContent = "重试 Step2", e.onclick = () => { closeM("m-result"); const e = Req.create("wgen"); BtnState.load(t, "重试中"), s.style.display = "block", s.style.color = "#4a9", s.textContent = "准备重试 Step 2/2...", post("RETRY_WORLD_GEN_STEP2", { requestId: e }) } } return } if (e.success && e.worldData) { s.style.color = "#4a9", s.textContent = "生成成功!正在应用数据..."; const t = e.worldData; if (t.meta && (D.meta = t.meta), t.world && (D.world = t.world), t.maps?.outdoor && (D.maps.outdoor = t.maps.outdoor), D.stage = 0, D.deviationScore = 0, t.playerLocation) playerLocation = t.playerLocation; else { const t = D.maps?.outdoor?.nodes?.find((t => "home" === t.type)); playerLocation = t?.name || D.maps?.outdoor?.nodes?.[0]?.name || "未知" } t.maps?.inside && playerLocation && (D.maps.indoor = D.maps.indoor || {}, D.maps.indoor[playerLocation] = t.maps.inside), selectedMapValue = "current", saveAll(), render(), setTimeout((() => { closeM("m-world-gen"), s.style.display = "none", $("world-gen-req").value = "", showResultModal("生成成功", "世界数据生成完成!Stage 和 Deviation 已重置为 0", !1, e.worldData) }), 500) } } else if ("SIMULATE_WORLD_RESULT" === s) { if (!e.isAuto && !Req.match(e.requestId)) return; if (!e.isAuto) { Req.clear("wsim"); const t = $("world-sim-ok"), s = $("world-sim-status"); if (BtnState.reset(t, ' 开始推演'), e.error) return s.style.display = "none", void showResultModal("推演失败", "世界推演失败", !0, e.error) } if (e.success && e.simData) { if (!e.isAuto) { const t = $("world-sim-status"); t.style.color = "#4a9", t.textContent = "推演成功!正在应用数据..." } const t = e.simData; t.meta && (D.meta = t.meta), t.world && (D.world = t.world), t.maps?.outdoor && (D.maps.outdoor = t.maps.outdoor), D.stage = (D.stage || 0) + 1, saveAll(), render(), e.isAuto || setTimeout((() => { closeM("m-world-sim"), $("world-sim-status").style.display = "none", showResultModal("推演成功", `世界推演完成!Stage 已推进到 ${D.stage}`, !1, e.simData) }), 500) } } else if ("REFRESH_WORLD_NEWS_RESULT" === s) { const t = Req.get("newsrf"); if (!t || t.id !== e.requestId) return; const { btn: s } = t; if (Req.clear("newsrf"), s && (s.disabled = !1, s.innerHTML = s._o ?? ''), e.error) return void showResultModal("刷新失败", "世界新闻刷新失败", !0, e.error); e.success && Array.isArray(e.news) && (D.world = D.world || {}, D.world.news = e.news, saveAll(), render(), showResultModal("刷新成功", `已更新世界新闻(${e.news.length} 条)`, !1, e.news)) } else if ("SCENE_SWITCH_RESULT" === s) { const t = Req.get("scene"); if (!t || t.id !== e.requestId) return; const { node: s, prev: o } = t; if (Req.clear("scene"), BtnState.reset($("goto-ok"), "确认前往"), closeM("m-goto"), e.error) return void showResultModal("切换失败", "场景切换失败", !0, e.error); if (e.success && e.sceneData) { const t = e.sceneData; if ("number" == typeof t.newScore && (D.deviationScore = t.newScore), t.localMap && (D.maps.indoor = D.maps.indoor || {}, D.maps.indoor[s.name] = t.localMap), t.strangers?.length) { const e = new Set((D.contacts.strangers || []).map((t => t.name))), s = t.strangers.filter((t => !e.has(t.name))); D.contacts.strangers = [...D.contacts.strangers || [], ...s] } finishTravel(s.name, o), 0 !== t.scoreDelta && showResultModal("切换成功", `场景切换完成!\n偏差值变化: ${t.scoreDelta > 0 ? "+" : ""}${t.scoreDelta} (当前: ${t.newScore})`, !1, e.sceneData) } } else if ("REFRESH_LOCAL_MAP_RESULT" === s) { const t = Req.get("localmaprf"); if (!t || t.id !== e.requestId) return; const { btn: s, loc: o } = t; if (Req.clear("localmaprf"), BtnState.reset(s, '刷新'), e.error) return void showResultModal("刷新失败", "刷新局部地图失败", !0, e.error); if (e.success && e.localMapData) { const t = e.localMapData, s = t.name || o || playerLocation || "当前位置"; D.maps.indoor = D.maps.indoor || {}, D.maps.indoor[s] = t, playerLocation = s, selectedMapValue = "current", saveAll(), render(), t.description && ($("side-desc").innerHTML = `📍 ${h(s)}
` + parseLinks(t.description), bindLinks($("side-desc"))), showResultModal("刷新成功", `局部地图已刷新!当前位置: ${s}`, !1, e.localMapData) } } else if ("GENERATE_LOCAL_SCENE_RESULT" === s) { const t = Req.get("localscene"); if (!t || t.id !== e.requestId) return; const { btn: s, loc: o } = t; if (Req.clear("localscene"), BtnState.reset(s, '局部剧情'), e.error) return void showResultModal("生成失败", "局部剧情生成失败", !0, e.error); if (e.success && e.sceneSetup) { D.sceneSetup = { ...D.sceneSetup || {}, ...e.sceneSetup || {} }, saveAll(), render(); const t = String(e.introduce || "").replace(/^\s*(?:\/?\s*(?:sendas|as)\s+name\s*=\s*(?:"[^"]*"|'[^']*'|\S+)\s+)/i, "").replace(/\s+/g, " ").trim(); t && post("EXECUTE_SLASH_COMMAND", { command: `/sendas name="剧情任务" ${t}` }), showResultModal("生成成功", `局部剧情已生成:${o || playerLocation}`, !1, e.sceneSetup) } } else if ("SEND_INVITE_RESULT" === s) { const t = Req.get("invite"); if (!t || t.id !== e.requestId) return; const { contact: s, loc: o, btn: a } = t; if (Req.clear("invite"), BtnState.reset(a, "发送邀请"), e.error) return void showResultModal("邀请失败", "邀请发送失败", !0, e.error); if (e.success && e.inviteData) { const t = e.inviteData, a = canonicalLoc(t.targetLocation || o || ""), n = D.contacts.contacts.find((t => t && s && t.worldbookUid && s.worldbookUid && t.worldbookUid === s.worldbookUid)) || D.contacts.contacts.find((t => t && s && t.name === s.name)) || s; if (n.messages = n.messages || [], n.messages.push({ type: "sent", text: `我邀请你前往「${a}」` }), n.messages.push({ type: "received", text: t.reply }), t.accepted) { const e = canonicalLoc(playerLocation); n.location = a, e && a && e === a ? (delete n.waitingAt, post("EXECUTE_SLASH_COMMAND", { command: `/sendas name="剧情任务" ${n.name}过来了。` })) : n.waitingAt = a, showResultModal("邀请成功", `${n.name} 接受了邀请!\n回复: ${t.reply}`, !1, t) } else showResultModal("邀请被拒", `${n.name} 拒绝了邀请。\n回复: ${t.reply}`, !1, t); saveAll(), n.worldbookUid && saveChat(n), closeM("m-invite"), render(), openChat(n) } } else if ("GENERATE_LOCAL_MAP_RESULT" === s) { const t = Req.get("localmap"); if (!t || t.id !== e.requestId) return; const { btn: s } = t; if (Req.clear("localmap"), BtnState.reset(s, '局部地图'), e.error) return void showResultModal("生成失败", "局部地图生成失败", !0, e.error); if (e.success && e.localMapData) { const t = e.localMapData, s = t.name || "当前位置"; D.sceneSetup = null, D.maps.indoor = D.maps.indoor || {}, D.maps.indoor[s] = t, playerLocation = s, selectedMapValue = "current", saveAll(), render(), t.description && ($("side-desc").innerHTML = `📍 ${h(s)}
` + parseLinks(t.description), bindLinks($("side-desc"))), showResultModal("生成成功", `局部地图生成完成!当前位置: ${s}`, !1, t) } } })); const updateTf = () => { inner.style.transform = `translate(${offX}px,${offY}px) scale(${scale})`, $("zoom-ind").textContent = Math.round(100 * scale) + "%", requestAnimationFrame(drawLines) }, initPos = () => { offX = mapWrap.clientWidth / 2 - 2e3, offY = mapWrap.clientHeight / 2 - 2e3, scale = 1, updateTf() }; function panTo(t, e = 350) { if (!t || anim) return; if (!$(t.id)) return; const s = -(t.x + 2e3) * scale + mapWrap.clientWidth / 2, o = -(t.y + 2e3) * scale + .25 * mapWrap.clientHeight, a = offX, n = offY, r = performance.now(); anim = !0, function t(i) { const c = Math.min((i - r) / e, 1), l = 1 - Math.pow(1 - c, 3); offX = a + (s - a) * l, offY = n + (o - n) * l, updateTf(), c < 1 ? requestAnimationFrame(t) : anim = !1 }(r) } function showInfo(t) { if (!t?.data) return; curNode = t, inner.querySelectorAll(".item").forEach((t => t.classList.remove("hl"))), $(t.id)?.classList.add("hl"); const e = t.name === playerLocation; $("btn-goto").classList.toggle("show", !e), e || ($("goto-t").textContent = `前往 ${t.name}`); const s = D.maps?.indoor?.[t.name]; e && s?.description ? ($("side-desc").innerHTML = `📍 ${h(t.name)}
` + parseLinks(s.description), bindLinks($("side-desc"))) : ($("side-desc").innerHTML = parseLinks(D.maps?.outdoor?.description || ""), bindLinks($("side-desc"))), isMob() ? ($("mob-info-t").textContent = t.name, $("mob-info-c").textContent = e ? s?.description || t.data.info || "暂无信息..." : t.data.info || "暂无信息...", popup.classList.contains("act") || openPop(1)) : ($("info-t").textContent = t.name, $("info-c").textContent = t.data.info || "暂无信息...", $("tip").classList.add("show")) } const hideInfo = () => { curNode = null, inner.querySelectorAll(".item").forEach((t => t.classList.remove("hl"))), $("btn-goto").classList.remove("show"), $("tip").classList.remove("show") }; function renderMapSelector() { const t = $("map-lbl-select"); t.innerHTML = ''; const e = D.maps?.outdoor?.nodes?.findIndex((t => t.name === playerLocation)), s = D.maps?.indoor && D.maps.indoor[playerLocation]; if ((e >= 0 || s) && (t.innerHTML += ``), t.innerHTML += "", D.maps?.outdoor?.nodes?.length && D.maps.outdoor.nodes.forEach(((e, s) => { e.name !== playerLocation && (t.innerHTML += ``) })), D.maps?.indoor) { const e = Object.keys(D.maps.indoor).filter((t => t !== playerLocation && !D.maps?.outdoor?.nodes?.some((e => e.name === t)))); e.length && (t.innerHTML += "", e.forEach((e => t.innerHTML += ``))) } t.value = selectedMapValue, updateMapLabel() } function updateMapLabel() { const t = $("map-lbl-select").value; if ("overview" === t) $("map-lbl-t").textContent = "大地图"; else if ("current" === t) $("map-lbl-t").textContent = playerLocation + "(你)"; else if (t.startsWith("node:")) { const e = parseInt(t.split(":")[1]); $("map-lbl-t").textContent = D.maps?.outdoor?.nodes?.[e]?.name || "未知" } else t.startsWith("indoor:") && ($("map-lbl-t").textContent = t.replace("indoor:", "")) } function switchMapView(t) { if (selectedMapValue = t, hideInfo(), "overview" === t) $("btn-goto").classList.remove("show"), $("side-desc").innerHTML = parseLinks(D.maps?.outdoor?.description || ""), bindLinks($("side-desc")), initPos(); else if ("current" === t) { $("btn-goto").classList.remove("show"); const t = getCurInside(); t?.description ? ($("side-desc").innerHTML = `📍 ${h(playerLocation)}
` + parseLinks(t.description), bindLinks($("side-desc"))) : ($("side-desc").innerHTML = parseLinks(D.maps?.outdoor?.description || ""), bindLinks($("side-desc"))); const e = nodes.find((t => t.name === playerLocation)); e && (panTo(e), showInfo(e)) } else if (t.startsWith("node:")) { const e = parseInt(t.split(":")[1]), s = D.maps?.outdoor?.nodes?.[e]; $("side-desc").innerHTML = parseLinks(D.maps?.outdoor?.description || ""), bindLinks($("side-desc")); const o = nodes.find((t => t.name === s?.name)); o && (panTo(o), showInfo(o)) } else if (t.startsWith("indoor:")) { const e = t.replace("indoor:", ""), s = D.maps?.indoor?.[e]; e !== playerLocation ? ($("side-desc").innerHTML = parseLinks(D.maps?.outdoor?.description || ""), bindLinks($("side-desc")), curNode = { name: e, type: "sub", data: { info: stripXml(s?.description || "") }, isIndoor: !0 }, $("btn-goto").classList.add("show"), $("goto-t").textContent = `前往 ${e}`) : ($("btn-goto").classList.remove("show"), s?.description && ($("side-desc").innerHTML = `🏠 ${h(e)}
` + parseLinks(s.description), bindLinks($("side-desc")))) } updateMapLabel() } $("info-bk").onclick = hideInfo, $("mob-info-bk").onclick = () => popup.classList.remove("act"), $("map-lbl-select").onchange = t => switchMapView(t.target.value); let sx, sy, lastDist = 0, lastCX = 0, lastCY = 0; mapWrap.onmousedown = t => { anim || t.target.closest(".map-act,.map-lbl") || (t.target.classList.contains("item") || hideInfo(), drag = !0, sx = t.clientX, sy = t.clientY, mapWrap.style.cursor = "grabbing") }, mapWrap.onmousemove = t => { drag && (offX += t.clientX - sx, offY += t.clientY - sy, sx = t.clientX, sy = t.clientY, updateTf()) }, mapWrap.onmouseup = mapWrap.onmouseleave = () => { drag = !1, mapWrap.style.cursor = "grab" }, mapWrap.onwheel = t => { if (anim) return; t.preventDefault(); const e = Math.max(.3, Math.min(3, scale + (t.deltaY > 0 ? -.1 : .1))), s = mapWrap.getBoundingClientRect(), o = t.clientX - s.left, a = t.clientY - s.top, n = e / scale; offX = o - (o - offX) * n, offY = a - (a - offY) * n, scale = e, updateTf() }, mapWrap.ontouchstart = t => { if (anim || t.target.closest(".map-act,.map-lbl")) return; const e = t.target.closest(".item"); e ? e._ts = { x: t.touches[0].clientX, y: t.touches[0].clientY, t: Date.now() } : (hideInfo(), 1 === t.touches.length ? (drag = !0, sx = t.touches[0].clientX, sy = t.touches[0].clientY) : 2 === t.touches.length && (drag = !1, lastDist = Math.hypot(t.touches[0].clientX - t.touches[1].clientX, t.touches[0].clientY - t.touches[1].clientY), lastCX = (t.touches[0].clientX + t.touches[1].clientX) / 2, lastCY = (t.touches[0].clientY + t.touches[1].clientY) / 2)) }, mapWrap.ontouchmove = t => { const e = t.target.closest(".item"); if (e && e._ts) Math.hypot(t.touches[0].clientX - e._ts.x, t.touches[0].clientY - e._ts.y) > 10 && (delete e._ts, drag = !0, sx = t.touches[0].clientX, sy = t.touches[0].clientY); else if (1 === t.touches.length && drag) t.preventDefault(), offX += t.touches[0].clientX - sx, offY += t.touches[0].clientY - sy, sx = t.touches[0].clientX, sy = t.touches[0].clientY, updateTf(); else if (2 === t.touches.length) { t.preventDefault(); const e = Math.hypot(t.touches[0].clientX - t.touches[1].clientX, t.touches[0].clientY - t.touches[1].clientY), s = (t.touches[0].clientX + t.touches[1].clientX) / 2, o = (t.touches[0].clientY + t.touches[1].clientY) / 2, a = Math.max(.3, Math.min(3, scale * (e / lastDist))), n = mapWrap.getBoundingClientRect(), r = s - n.left, i = o - n.top, c = a / scale; offX = r - (r - offX) * c, offY = i - (i - offY) * c, offX += s - lastCX, offY += o - lastCY, scale = a, lastDist = e, lastCX = s, lastCY = o, updateTf() } }, mapWrap.ontouchend = t => { const e = t.target.closest(".item"); if (e && e._ts) { const s = Date.now() - e._ts.t; if (delete e._ts, s < 300) { const s = nodes.find((t => t.id === e.id)); s && (t.preventDefault(), curNode?.id === s.id ? hideInfo() : showInfo(s)) } } drag = !1 }, $$(".nav-i").forEach((t => t.onclick = () => { $$(".nav-i").forEach((t => t.classList.remove("act"))), $$(".page").forEach((t => t.classList.remove("act"))), t.classList.add("act"), $(`page-${t.dataset.p}`).classList.add("act"); const e = "map" === t.dataset.p; sidePop.classList.toggle("show", e), isMob() && (e ? openPop(1) : popup.classList.remove("act")), e && setTimeout((() => { initPos(), drawLines() }), 50) })), $$(".comm-tab").forEach((t => t.onclick = () => { $$(".comm-tab").forEach((t => t.classList.remove("act"))), $$(".comm-sec").forEach((t => t.classList.remove("act"))), t.classList.add("act"), $(`sec-${t.dataset.t}`).classList.add("act") })), $("btn-goto").onclick = t => { t.stopPropagation(), curNode && ($("goto-d").textContent = `目的地:${curNode.name}`, $("goto-task").value = "", openM("m-goto")) }, addEventListener("resize", (() => requestAnimationFrame(drawLines))), window.clickTab = (t, e) => { const s = t.closest(".settings-modal"); if (!s) return; s.querySelectorAll(".set-nav-item").forEach((t => t.classList.remove("act"))), t.classList.add("act"), s.querySelectorAll(".set-tab-page").forEach((t => t.classList.remove("act"))); const o = document.getElementById(e); o && (o.classList.add("act"), o.style.animation = "none", o.offsetHeight, o.style.animation = null) }, document.addEventListener("DOMContentLoaded", (() => { render(), initPos(), sidePop.classList.add("show"), sidePop.classList.add("act"), isMob() && openPop(1), post("FRAME_READY"), setTimeout((() => { "current" === selectedMapValue && switchMapView("current") }), 100) }));
+ // 主题切换逻辑
+ const initTheme = () => {
+ const saved = localStorage.getItem('lwb-theme');
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+ if (saved === 'dark' || (!saved && prefersDark)) {
+ document.documentElement.classList.add('dark');
+ }
+ };
+
+ const toggleTheme = () => {
+ const html = document.documentElement;
+ const isDark = html.classList.toggle('dark');
+ localStorage.setItem('lwb-theme', isDark ? 'dark' : 'light');
+ };
+
+ // 初始化主题
+ initTheme();
+
+ // 绑定主题切换按钮
+ $("btn-theme-toggle").onclick = toggleTheme;
+