diff --git a/docs/compatibility-validation.md b/docs/compatibility-validation.md index d9f07e6..e7f52a4 100644 --- a/docs/compatibility-validation.md +++ b/docs/compatibility-validation.md @@ -34,3 +34,9 @@ DSH Web:可用 `dsh plugin --profile web add /绝对路径/测试包.tgz` 安 ## Issue / PR 收尾 已确认可收尾的 #2、#3、#6 关闭。#8、#12、#14、#15 与 PR #10、#13 在用户验证前保持开放;PR #9 留待后续处理。 + +## 后续:DSH 0.2.1-rc.2 账号页面 + +已核对[官方固定发布源码](https://github.com/deepseek-ai/deepseek-harness/blob/687f282e8625c6b8d11ff58211a90524be2e4317/packages/client/ui-settings-account/src/client/PlatformOverlay.module.css)及对应 npm 产物:账号用量/充值页覆盖层仍继承背景 token,48px 返回栏没有独立底色。皮肤新增局部规则,仅为 body 直属、带原生拖拽返回栏和 viewport 的模态覆盖层恢复配色背景。 + +验收:分别在浅色和黑金档打开用量/充值页,确认返回栏与加载状态背景不透明、返回按钮清晰可用;普通弹窗、菜单和聊天背景保持原行为。停用皮肤后恢复宿主背景。自动化只使用宿主结构夹具,不登录账号,也不替代原生 Desktop 验收。 diff --git a/lib/client.js b/lib/client.js index 05af77e..af8f5b1 100644 --- a/lib/client.js +++ b/lib/client.js @@ -255,6 +255,13 @@ body[data-liang-skin="on"]:is( top: var(--dsh-desktop-frame-height, 0px); } +/* Native account pages are full-screen body portals. Their header and loading + surface must stay opaque when the chat background token is transparent. */ +body[data-liang-skin="on"] > [role="dialog"][aria-modal="true"][class*="_overlay"]:has(> header[data-window-drag] button[class*="_back"]):has(> [class*="_viewport"]) { + --dsw-alias-bg-base: var(--liang-page, #fff) !important; + background: var(--liang-page, #fff) !important; +} + /* Windows paints the sidebar fill across the host frame, covering the portrait. Match macOS's transparent frame while keeping the sidebar, caption, and conversation surfaces painted by their own host rules. */ diff --git a/lib/client.js.map b/lib/client.js.map index 291d298..f1d7fd9 100644 --- a/lib/client.js.map +++ b/lib/client.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../src/client/index.tsx", "../src/client/skin.css", "../src/client/appearance.ts", "../src/client/portrait-loader.ts", "../src/client/logic.ts"], - "sourcesContent": ["import {\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { Switch } from \"@deepseek-ai/dsh-client-ui-primitives\";\nimport type { Context as ClientContext } from \"@deepseek-ai/cordis\";\nimport type { SnapshotStore } from \"@deepseek-ai/dsh-client-store\";\nimport type {} from \"@deepseek-ai/dsh-client-ui-conversation/client\";\nimport type {} from \"@deepseek-ai/dsh-client-ui-settings/client\";\nimport type {} from \"@deepseek-ai/dsh-client-ui-model-selection/client\";\nimport styles from \"./skin.css\";\nimport { installLiangAppearanceButton } from \"./appearance\";\nimport { createPortraitLoader } from \"./portrait-loader\";\nimport {\n PREVIEW_MAX_FRAME,\n frameForEffort,\n indicatorLabel,\n nearestEffortIndex,\n paletteForFrame,\n selectedEffortIndex,\n type EffortLike,\n} from \"./logic\";\n\nconst PACKAGE_ID = \"dsh-client-liang-intensity-skin\";\nconst LOCALE_NAMESPACE = \"liang.skin\";\nconst ASSET_PREFIX = `/plugins/${PACKAGE_ID}/assets`;\nconst BIND_EFFORT_KEY = \"dsh-liang-intensity-skin.bind-effort\";\n\nconst PORTRAIT_ANCHORS = [\n { level: 0, file: \"stage-00.webp\" },\n { level: 1, file: \"level-01.webp\" },\n { level: 3, file: \"level-03.webp\" },\n { level: 4, file: \"level-04.webp\" },\n { level: 6, file: \"stage-06.webp\" },\n { level: 7, file: \"level-07.webp\" },\n { level: 9, file: \"level-09.webp\" },\n { level: 10, file: \"level-10.webp\" },\n { level: 12, file: \"stage-12.webp\" },\n { level: 13, file: \"level-13.webp\" },\n { level: 14, file: \"level-14.webp\" },\n { level: 15, file: \"bridge-15.webp\" },\n { level: 16, file: \"level-16.webp\" },\n { level: 17, file: \"level-17.webp\" },\n { level: 18, file: \"stage-18.webp\" },\n { level: 19, file: \"level-19.webp\" },\n { level: 21, file: \"level-21.webp\" },\n { level: 22, file: \"level-22.webp\" },\n { level: 24, file: \"stage-24.webp\" },\n { level: 25, file: \"level-25.webp\" },\n { level: 27, file: \"bridge-27.webp\" },\n { level: 28, file: \"level-28.webp\" },\n { level: 29, file: \"level-29.webp\" },\n { level: 30, file: \"stage-30.webp\" },\n] as const;\n\ninterface SkinSettings {\n enabled: boolean;\n bindEffort: boolean;\n}\n\ninterface PreferenceStore {\n getSnapshot(): SkinSettings;\n subscribe(listener: () => void): () => void;\n set(enabled: boolean): Promise;\n setBindEffort(enabled: boolean): Promise;\n dispose(): void;\n}\n\ntype NativeThemeId = \"light\" | \"dark\";\n\ninterface ThemeService {\n getTheme(): { preference: string };\n setTheme(id: NativeThemeId | \"system\"): void;\n}\n\ninterface ModelSelection {\n provider: string;\n model: string;\n reasoningEffort?: string;\n}\n\ninterface CatalogModel {\n id: string;\n reasoning?: {\n efforts: EffortLike[];\n defaultEffort?: string;\n };\n}\n\ninterface ModelDirectoryState {\n current: ModelSelection | null;\n groups: readonly { id: string; models: CatalogModel[] }[];\n status: \"idle\" | \"loading\" | \"ready\" | \"selecting\" | \"error\";\n error: string | null;\n}\n\ninterface ModelDirectory {\n store: SnapshotStore;\n load(): Promise;\n select(selection: ModelSelection): Promise<{ ok: boolean }>;\n}\n\ninterface SliderProps {\n directory: SnapshotStore;\n load: () => void;\n select: (selection: ModelSelection) => Promise;\n presenter: SkinPresenter;\n scope: PreferenceStore;\n}\n\ninterface EffortBindingRowProps {\n scope: PreferenceStore;\n t: (key: string) => string;\n}\n\nconst cssVariables = [\n \"--liang-strength\",\n \"--liang-page\",\n \"--liang-bg-base\",\n \"--liang-layer-1\",\n \"--liang-layer-2\",\n \"--liang-layer-3\",\n \"--liang-sidebar\",\n \"--liang-ink\",\n \"--liang-secondary\",\n \"--liang-tertiary\",\n \"--liang-border\",\n \"--liang-accent\",\n \"--liang-accent-hover\",\n \"--liang-hover\",\n \"--liang-portrait-opacity\",\n] as const;\n\nclass SkinPresenter {\n private readonly scope: PreferenceStore;\n private readonly theme: ThemeService;\n private readonly root: HTMLDivElement;\n private readonly portraits: ReturnType;\n private enabled = true;\n // Default to the max frame so the first paint after load is the dark shell;\n // starting at 0 flashed the light palette before the directory resolved.\n private frame = PREVIEW_MAX_FRAME;\n private pendingFrame = PREVIEW_MAX_FRAME;\n private raf = 0;\n private disposed = false;\n private unsubscribe: () => void;\n\n constructor(scope: PreferenceStore, theme: ThemeService) {\n this.scope = scope;\n this.theme = theme;\n this.root = document.createElement(\"div\");\n this.root.className = \"liang-skin-backdrop\";\n this.root.dataset.plugin = PACKAGE_ID;\n this.root.dataset.media = \"color\";\n this.root.setAttribute(\"aria-hidden\", \"true\");\n this.portraits = createPortraitLoader({\n sources: PORTRAIT_ANCHORS.map(({ level, file }) => ({\n level,\n url: `${ASSET_PREFIX}/portrait-source-v2/${file}`,\n })),\n onChange: ({ image }) => {\n if (this.disposed || !this.enabled) return;\n if (image === null) {\n this.root.dataset.media = \"color\";\n this.root.replaceChildren();\n return;\n }\n // Reuse the decoded node so a frame swap cannot start a second fetch.\n const portrait = image as HTMLImageElement;\n portrait.className = \"liang-skin-sequence-frame\";\n portrait.alt = \"\";\n portrait.draggable = false;\n if (this.root.firstChild !== portrait) this.root.replaceChildren(portrait);\n this.root.dataset.media = \"sequence\";\n },\n });\n\n this.unsubscribe = scope.subscribe(() => this.syncSettings());\n this.syncSettings();\n }\n\n private syncSettings() {\n this.setEnabled(this.scope.getSnapshot().enabled);\n }\n\n isEnabled() {\n return this.enabled;\n }\n\n getFrame() {\n return this.frame;\n }\n\n setEnabled(enabled: boolean) {\n if (this.disposed) return;\n this.enabled = enabled;\n if (enabled) {\n if (!this.root.isConnected) document.body.prepend(this.root);\n document.body.dataset.liangSkin = \"on\";\n this.applyFrame();\n this.portraits.start();\n } else {\n if (this.raf !== 0) cancelAnimationFrame(this.raf);\n this.raf = 0;\n this.root.remove();\n this.portraits.stop();\n this.root.replaceChildren();\n this.root.dataset.media = \"color\";\n delete document.body.dataset.liangSkin;\n delete document.body.dataset.liangStage;\n for (const name of cssVariables) document.body.style.removeProperty(name);\n }\n }\n\n setFrame(frame: number) {\n if (this.disposed) return;\n this.pendingFrame = Math.min(PREVIEW_MAX_FRAME, Math.max(0, Math.round(frame)));\n this.frame = this.pendingFrame;\n if (!this.enabled) return;\n if (this.raf !== 0) return;\n this.raf = requestAnimationFrame(() => {\n this.raf = 0;\n this.frame = this.pendingFrame;\n this.applyFrame();\n });\n }\n\n private applyFrame() {\n if (this.disposed || !this.enabled) return;\n const palette = paletteForFrame(this.frame);\n const body = document.body;\n this.syncNativeTheme(palette.stage === 5 ? \"dark\" : \"light\");\n body.dataset.liangStage = String(palette.stage);\n body.style.setProperty(\"--liang-strength\", String(palette.strength));\n body.style.setProperty(\"--liang-page\", palette.page);\n body.style.setProperty(\"--liang-bg-base\", palette.base);\n body.style.setProperty(\"--liang-layer-1\", palette.layer1);\n body.style.setProperty(\"--liang-layer-2\", palette.layer2);\n body.style.setProperty(\"--liang-layer-3\", palette.layer3);\n body.style.setProperty(\"--liang-sidebar\", palette.sidebar);\n body.style.setProperty(\"--liang-ink\", palette.ink);\n body.style.setProperty(\"--liang-secondary\", palette.secondary);\n body.style.setProperty(\"--liang-tertiary\", palette.tertiary);\n body.style.setProperty(\"--liang-border\", palette.border);\n body.style.setProperty(\"--liang-accent\", palette.accent);\n body.style.setProperty(\"--liang-accent-hover\", palette.accentHover);\n body.style.setProperty(\"--liang-hover\", palette.hover);\n body.style.setProperty(\"--liang-portrait-opacity\", palette.portraitOpacity);\n this.updatePortrait(palette.level);\n }\n\n syncNativeTheme(theme: NativeThemeId = paletteForFrame(this.frame).stage === 5 ? \"dark\" : \"light\") {\n if (!this.enabled || this.theme.getTheme().preference === theme) return;\n this.theme.setTheme(theme);\n }\n\n private updatePortrait(level: number) {\n this.portraits.select(level);\n }\n\n async choose(enabled: boolean) {\n this.setEnabled(enabled);\n try {\n await this.scope.set(enabled);\n } catch (error) {\n this.syncSettings();\n throw error;\n }\n }\n\n dispose() {\n if (this.disposed) return;\n this.disposed = true;\n this.unsubscribe();\n if (this.raf !== 0) cancelAnimationFrame(this.raf);\n this.portraits.dispose();\n this.root.remove();\n document.body.removeAttribute(\"data-liang-skin\");\n delete document.body.dataset.liangStage;\n for (const name of cssVariables) document.body.style.removeProperty(name);\n }\n}\n\nfunction modelReasoning(state: ModelDirectoryState) {\n const current = state.current;\n if (current === null) return null;\n const group = state.groups.find((item) => item.id === current.provider);\n const model = group?.models.find((item) => item.id === current.model);\n if (model?.reasoning === undefined) return null;\n return {\n selection: current,\n efforts: model.reasoning.efforts,\n defaultEffort: model.reasoning.defaultEffort,\n };\n}\n\nfunction LiangEffortSlider({ directory, load, select, presenter, scope }: SliderProps) {\n const state = useSyncExternalStore(\n (listener) => directory.subscribe(listener),\n () => directory.getSnapshot(),\n );\n const skin = useSyncExternalStore(\n (listener) => scope.subscribe(listener),\n () => scope.getSnapshot(),\n );\n const reasoning = useMemo(() => modelReasoning(state), [state]);\n const efforts = reasoning?.efforts ?? [];\n const committedIndex = selectedEffortIndex(\n efforts,\n reasoning?.selection.reasoningEffort,\n reasoning?.defaultEffort,\n );\n // An unknown committed effort defaults to the max frame: a fresh\n // conversation paints the dark shell immediately instead of flashing the\n // light palette until the directory load resolves the real effort.\n const committedFrame = committedIndex < 0\n ? PREVIEW_MAX_FRAME\n : frameForEffort(committedIndex, efforts.length);\n const bindEffort = skin.bindEffort;\n const [frame, setFrame] = useState(() => bindEffort ? committedFrame : presenter.getFrame());\n const [pending, setPending] = useState(false);\n const [failed, setFailed] = useState(false);\n const [interacting, setInteracting] = useState(false);\n const dragging = useRef(false);\n const dragStartFrame = useRef(frame);\n const enabled = skin.enabled;\n\n useEffect(() => {\n if (enabled) load();\n }, [enabled, load]);\n\n useEffect(() => {\n if (!bindEffort || dragging.current || pending) return;\n setFrame(committedFrame);\n presenter.setFrame(committedFrame);\n }, [bindEffort, committedFrame, pending, presenter]);\n\n if (!enabled) return null;\n if (bindEffort && (reasoning === null || efforts.length < 2)) return null;\n\n const previewIndex = nearestEffortIndex(frame, efforts);\n const previewEffort = efforts[previewIndex];\n const progressRatio = frame / PREVIEW_MAX_FRAME;\n const tooltipLabel = indicatorLabel(frame, bindEffort ? efforts : []);\n\n const commit = async (rawFrame: number) => {\n if (!bindEffort) {\n dragging.current = false;\n setFrame(rawFrame);\n presenter.setFrame(rawFrame);\n return;\n }\n const targetIndex = nearestEffortIndex(rawFrame, efforts);\n const target = efforts[targetIndex];\n if (target === undefined) return;\n const targetFrame = frameForEffort(targetIndex, efforts.length);\n dragging.current = false;\n setFrame(targetFrame);\n presenter.setFrame(targetFrame);\n if (targetIndex === committedIndex || pending) return;\n setPending(true);\n setFailed(false);\n const accepted = await select({\n provider: reasoning.selection.provider,\n model: reasoning.selection.model,\n reasoningEffort: target.id,\n });\n setPending(false);\n if (!accepted) {\n setFailed(true);\n setFrame(committedFrame);\n presenter.setFrame(committedFrame);\n }\n };\n\n return (\n \n {interacting && (\n \n {tooltipLabel}\n \n )}\n
\n {efforts.map((effort, index) => (\n \n ))}\n
\n {\n dragging.current = true;\n dragStartFrame.current = frame;\n setInteracting(true);\n }}\n onInput={(event) => {\n dragging.current = true;\n const next = Number(event.currentTarget.value);\n setFrame(next);\n presenter.setFrame(next);\n }}\n onPointerUp={(event) => {\n setInteracting(false);\n void commit(Number(event.currentTarget.value));\n }}\n onPointerCancel={() => {\n setInteracting(false);\n dragging.current = false;\n setFrame(dragStartFrame.current);\n presenter.setFrame(dragStartFrame.current);\n }}\n onKeyUp={(event) => {\n setInteracting(false);\n if (event.key !== \"Escape\") void commit(Number(event.currentTarget.value));\n }}\n onBlur={(event) => {\n setInteracting(false);\n if (dragging.current) void commit(Number(event.currentTarget.value));\n }}\n onKeyDown={(event) => {\n setInteracting(true);\n if (event.key === \"Escape\" && !pending) {\n setInteracting(false);\n dragging.current = false;\n setFrame(dragStartFrame.current);\n presenter.setFrame(dragStartFrame.current);\n }\n }}\n />\n \n );\n}\n\nfunction EffortBindingRow({ scope, t }: EffortBindingRowProps) {\n const snapshot = useSyncExternalStore(\n (listener) => scope.subscribe(listener),\n () => scope.getSnapshot(),\n );\n const [pending, setPending] = useState(false);\n if (!snapshot.enabled) return null;\n\n return (\n
\n {t(\"effort.binding\")}\n {\n if (pending) return;\n setPending(true);\n void scope.setBindEffort(next).catch(() => undefined).finally(() => setPending(false));\n }}\n />\n
\n );\n}\n\nexport const inject = [\n \"slots\",\n \"sessions\",\n \"modelDirectories\",\n \"locale\",\n \"theme\",\n];\n\nfunction createPreferenceStore(): PreferenceStore {\n // The market's active skin is the source of truth for whether this client\n // should be visible. The appearance switch is therefore scoped to this\n // client activation and must not survive switching away and back.\n try {\n localStorage.removeItem(\"dsh-liang-intensity-skin.enabled\");\n } catch {\n // Storage may be unavailable; the in-memory default still enables Liang.\n }\n let snapshot: SkinSettings = {\n enabled: true,\n bindEffort: localStorage.getItem(BIND_EFFORT_KEY) !== \"0\",\n };\n const listeners = new Set<() => void>();\n const onStorage = (event: StorageEvent) => {\n if (event.key !== BIND_EFFORT_KEY) return;\n const next = {\n enabled: snapshot.enabled,\n bindEffort: event.newValue !== \"0\",\n };\n if (next.enabled === snapshot.enabled && next.bindEffort === snapshot.bindEffort) return;\n snapshot = next;\n for (const listener of listeners) listener();\n };\n window.addEventListener(\"storage\", onStorage);\n return {\n getSnapshot: () => snapshot,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async set(enabled) {\n if (enabled === snapshot.enabled) return;\n snapshot = { ...snapshot, enabled };\n for (const listener of listeners) listener();\n },\n async setBindEffort(bindEffort) {\n if (bindEffort === snapshot.bindEffort) return;\n localStorage.setItem(BIND_EFFORT_KEY, bindEffort ? \"1\" : \"0\");\n snapshot = { ...snapshot, bindEffort };\n for (const listener of listeners) listener();\n },\n dispose() {\n window.removeEventListener(\"storage\", onStorage);\n listeners.clear();\n },\n };\n}\n\nexport function apply(ctx: ClientContext) {\n const style = document.createElement(\"style\");\n style.dataset.plugin = PACKAGE_ID;\n style.textContent = styles;\n document.head.append(style);\n ctx.effect(() => () => style.remove(), \"liang-intensity-skin: scoped styles\");\n\n const scope = createPreferenceStore();\n ctx.effect(() => () => scope.dispose(), \"liang-intensity-skin: appearance preference\");\n const theme = ctx.get(\"theme\") as ThemeService;\n const presenter = new SkinPresenter(scope, theme);\n ctx.effect(() => () => presenter.dispose(), \"liang-intensity-skin: backdrop presenter\");\n ctx.effect(\n () => installLiangAppearanceButton(scope, presenter, PACKAGE_ID),\n \"liang-intensity-skin: native appearance extension\",\n );\n ctx.effect(\n () => ctx.locale.register(LOCALE_NAMESPACE, {\n zh: { \"effort.binding\": \"\u6ED1\u52A8\u53D8\u7956\u7ED1\u5B9A\u601D\u8003\u7B49\u7EA7\" },\n en: { \"effort.binding\": \"Bind slider to reasoning level\" },\n }), \"liang-intensity-skin: settings locale\");\n\n ctx.slots.inject(\"settings.general.item\", () => ctx.slots.register({\n name: \"settings.general.item\",\n id: \"liang-effort-binding\",\n order: 10.5,\n locale: LOCALE_NAMESPACE,\n inject: () => ({ scope }),\n }, EffortBindingRow));\n\n ctx.slots.inject(\"conversation.input.right\", () => ctx.slots.register({\n name: \"conversation.input.right\",\n id: \"liang-intensity-control\",\n order: 10,\n inject: (sessionId: string) => {\n const available = ctx.sessions.subagentAddress(sessionId) === undefined;\n const directory = ctx.modelDirectories.directoryFor(sessionId) as ModelDirectory;\n return {\n directory: directory.store,\n presenter,\n scope,\n load: () => {\n if (available) void directory.load().catch(() => undefined);\n },\n select: (selection: ModelSelection) => available\n ? directory.select(selection).then((result) => result.ok, () => false)\n : Promise.resolve(false),\n };\n },\n }, LiangEffortSlider));\n}\n", ".liang-skin-backdrop {\n position: fixed;\n z-index: -1;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n background: var(--liang-page, transparent);\n opacity: 0;\n transition: opacity 220ms ease, background-color 180ms linear;\n}\n\nbody[data-liang-skin=\"on\"] .liang-skin-backdrop {\n opacity: 1;\n}\n\n.liang-skin-backdrop img {\n position: absolute;\n top: 50%;\n right: 0;\n display: block;\n width: min(42vw, 720px);\n height: 100%;\n object-fit: cover;\n object-position: 50% 42%;\n opacity: var(--liang-portrait-opacity, 0.45);\n transform: translateY(-50%);\n filter: none;\n mask-image: linear-gradient(90deg, transparent 0%, rgb(0 0 0 / 20%) 10%, black 30%, black 100%);\n transition: filter 180ms linear;\n}\n\n.liang-skin-backdrop img {\n display: none;\n}\n\n.liang-skin-backdrop[data-media=\"sequence\"] .liang-skin-sequence-frame {\n display: block;\n opacity: var(--liang-portrait-opacity, 0.82);\n}\n\n.liang-skin-backdrop[data-media=\"color\"] img {\n display: none;\n}\n\n.liang-skin-backdrop::after {\n position: absolute;\n inset: 0;\n content: \"\";\n background:\n linear-gradient(90deg, var(--liang-page, transparent) 0 28%, transparent 57% 100%),\n linear-gradient(var(--liang-bg-base, transparent), var(--liang-bg-base, transparent));\n}\n\nbody[data-liang-skin=\"on\"] {\n /* Keep the backdrop above the canvas but below content and native portals.\n The host owns root positioning, dimensions, and its own stacking order. */\n isolation: isolate;\n --dsw-alias-bg-base: transparent !important;\n --dsw-alias-bg-layer-1: var(--liang-layer-1) !important;\n --dsw-alias-bg-layer-2: var(--liang-layer-2) !important;\n --dsw-alias-bg-layer-3: var(--liang-layer-3) !important;\n --dsw-specific-sidebar-fill: var(--liang-sidebar) !important;\n --dsw-alias-label-primary: var(--liang-ink) !important;\n --dsw-alias-label-primary-dimmed: var(--liang-ink) !important;\n --dsw-alias-label-secondary: var(--liang-secondary) !important;\n --dsw-alias-label-tertiary: var(--liang-tertiary) !important;\n --dsw-alias-label-caption: var(--liang-tertiary) !important;\n --dsw-alias-border-l1: var(--liang-border) !important;\n --dsw-alias-border-l2-darkmode-thin: var(--liang-border) !important;\n --dsw-alias-border-l2: var(--liang-border) !important;\n --dsw-alias-border-l3: var(--liang-border) !important;\n --dsw-alias-button-primary-fill: var(--liang-accent) !important;\n --dsw-alias-button-primary-hover: var(--liang-accent-hover) !important;\n --dsw-alias-button-info-fill: var(--liang-accent) !important;\n --dsw-alias-button-info-hover: var(--liang-accent-hover) !important;\n --dsw-alias-state-business-primary: var(--liang-accent) !important;\n --dsw-alias-interactive-bg-hover: var(--liang-hover) !important;\n --dsw-alias-interactive-bg-active: var(--liang-hover) !important;\n --dsw-alias-button-elevated-fill: var(--liang-layer-1) !important;\n --dsw-alias-button-floating-fill: var(--liang-layer-2) !important;\n --dsw-alias-button-floating-hover: var(--liang-layer-3) !important;\n --dsw-alias-interactive-bg-hover-solid: var(--liang-layer-2) !important;\n --dsw-specific-bubble: var(--liang-layer-2) !important;\n --dsw-specific-input-major: var(--liang-layer-1) !important;\n --dsw-specific-menu: var(--liang-layer-3) !important;\n --dsw-specific-selector: var(--liang-layer-2) !important;\n --dsw-alias-markdown-citation: var(--liang-layer-2) !important;\n --dsw-alias-markdown-code-block-banner: var(--liang-layer-1) !important;\n --dsw-alias-markdown-code-block: var(--liang-layer-1) !important;\n --dsw-alias-markdown-code-segment-selected: var(--liang-layer-2) !important;\n --dsw-alias-markdown-code-segment-unselected: var(--liang-layer-1) !important;\n --dsw-alias-markdown-inline-code: var(--liang-layer-2) !important;\n --dsw-alias-markdown-placeholder: var(--liang-layer-1) !important;\n --dsw-alias-markdown-tag: var(--liang-layer-2) !important;\n background: var(--liang-page) !important;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) {\n --dsw-alias-brand-primary-new-colorprimary-new-color: rgb(65 118 230) !important;\n --dsw-alias-brand-primary: rgb(15 17 21) !important;\n --dsw-alias-brand-text: rgb(15 17 21) !important;\n --dsw-alias-button-primary-fill: rgb(15 17 21) !important;\n --dsw-alias-button-primary-hover: rgb(53 54 56) !important;\n --dsw-alias-button-info-fill: rgb(65 118 230) !important;\n --dsw-alias-button-info-hover: rgb(103 158 254) !important;\n --dsw-alias-state-business-primary: rgb(65 118 230) !important;\n --dsw-alias-label-primary-inverted: rgb(249 250 251) !important;\n --dsw-alias-button-elevated-fill: rgb(255 255 255) !important;\n --dsw-alias-button-floating-fill: rgb(255 255 255) !important;\n --dsw-alias-button-floating-hover: rgb(241 243 245) !important;\n --dsw-alias-interactive-bg-hover-solid: rgb(241 243 245) !important;\n --dsw-specific-bubble: rgb(237 243 254) !important;\n --dsw-specific-input-major: rgb(255 255 255) !important;\n --dsw-specific-menu: rgb(255 255 255) !important;\n --dsw-specific-selector: rgb(245 246 247) !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] {\n --dsw-alias-brand-primary-new-colorprimary-new-color: #c19a49 !important;\n --dsw-alias-brand-primary: #c19a49 !important;\n --dsw-alias-brand-text: #d5b56e !important;\n --dsw-alias-button-info-fill: #c19a49 !important;\n --dsw-alias-button-info-hover: #d5b56e !important;\n --dsw-alias-state-business-primary: #c19a49 !important;\n --dsw-alias-bg-module-platform: var(--liang-layer-3) !important;\n --dsw-specific-sidebar-nav-item-active: var(--liang-layer-3) !important;\n --dsw-specific-sidebar-nav-item-hover: var(--liang-hover) !important;\n}\n\n/* The host renders the running-turn label as a clipped blue text gradient.\n Retarget only that gradient's color tokens in the custom dark skin; keep\n the host's text clipping and shimmer behavior, and leave light mode alone. */\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] [class*=\"_turnStatus\"] {\n --dsw-static-deepseek-500: #c19a49 !important;\n --dsw-static-deepseek-200: #d5b56e !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] [class*=\"_turnStatusClock\"] {\n color: #c19a49 !important;\n -webkit-text-fill-color: #c19a49 !important;\n}\n\n/* Preserve the wordmark badge contrast in the low-intensity shell. */\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) button[aria-label=\"\u65B0\u5EFA\u4F1A\u8BDD\"] svg[viewBox=\"0 0 182 24\"] > rect[width=\"52\"] {\n fill: rgb(15 17 21) !important;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) button[aria-label=\"\u65B0\u5EFA\u4F1A\u8BDD\"] svg[viewBox=\"0 0 182 24\"] > g[clip-path*=\"badge\"] path {\n fill: rgb(249 250 251) !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] button[aria-label=\"\u65B0\u5EFA\u4F1A\u8BDD\"] svg[viewBox=\"0 0 182 24\"] > rect[width=\"52\"] {\n fill: #c19a49 !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] button[aria-label=\"\u65B0\u5EFA\u4F1A\u8BDD\"] svg[viewBox=\"0 0 182 24\"] > g[clip-path*=\"badge\"] path {\n fill: #171816 !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] [aria-label^=\"\u9009\u62E9\u6A21\u578B\"] > span:nth-of-type(2) {\n color: #d5b56e !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] [class*=\"heroGlow\"] ellipse {\n fill: #c19a49 !important;\n fill-opacity: 0.16 !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] span[class*=\"_previewBadge\"] {\n color: #171816 !important;\n background: #c19a49 !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] button[aria-label=\"\u53D1\u9001\u6D88\u606F\"] {\n color: #171816 !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] button[aria-label=\"\u53D1\u9001\u6D88\u606F\"]:disabled {\n opacity: 0.62;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) button[aria-label=\"\u53D1\u9001\u6D88\u606F\"] {\n color: #fff !important;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) button[aria-label=\"\u53D1\u9001\u6D88\u606F\"]:disabled {\n color: rgb(255 255 255 / 92%) !important;\n background: rgb(65 118 230 / 42%) !important;\n opacity: 1;\n}\n\nbody[data-liang-skin=\"on\"] > #root {\n background: transparent !important;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-dsh-desktop-mode=\"compatibility\"],\n [data-dsh-desktop-mode=\"extended\"]\n) .liang-skin-backdrop {\n top: var(--dsh-desktop-frame-height, 0px);\n}\n\n/* Windows paints the sidebar fill across the host frame, covering the portrait.\n Match macOS's transparent frame while keeping the sidebar, caption, and\n conversation surfaces painted by their own host rules. */\nhtml[data-windows-titlebar] body[data-liang-skin=\"on\"] #root [class*=\"_frame\"]:has(> [class*=\"_centerCol\"]) {\n background: transparent !important;\n}\n\n/* macOS adds different translucent sidebar materials for native light/dark.\n While Liang is active, the effort palette owns the sidebar fill. */\nhtml[data-platform=\"darwin\"] body[data-liang-skin=\"on\"] #root [class*=\"_frame\"]:has(> [class*=\"_centerCol\"]) > [class*=\"_sidebarCol\"] {\n background: var(--liang-sidebar) !important;\n}\n\n/* Keep the host's expanded new-session surface on the same palette. The\n collapsed icon keeps its native transparent surface and hover behavior. */\nhtml[data-platform=\"darwin\"] body[data-liang-skin=\"on\"] #root [class*=\"_sidebarCol\"] [class*=\"_root\"]:has(> [class*=\"_logoRow\"]):not([class*=\"_collapsed\"]) button[class*=\"_newSession\"] {\n background: var(--dsw-alias-button-elevated-fill) !important;\n}\n\nhtml[data-platform=\"darwin\"] body[data-liang-skin=\"on\"] #root [class*=\"_sidebarCol\"] [class*=\"_root\"]:has(> [class*=\"_logoRow\"]):not([class*=\"_collapsed\"]) button[class*=\"_newSession\"]:hover {\n background: var(--dsw-alias-button-floating-hover) !important;\n}\n\n/* The native dark rule also outranks the collapsed icon's transparent fill. */\nhtml[data-platform=\"darwin\"] body[data-liang-skin=\"on\"] #root [class*=\"_sidebarCol\"] [class*=\"_root\"][class*=\"_collapsed\"]:has(> [class*=\"_logoRow\"]) button[class*=\"_newSession\"] {\n background: transparent !important;\n}\n\nhtml[data-platform=\"darwin\"] body[data-liang-skin=\"on\"] #root [class*=\"_sidebarCol\"] [class*=\"_root\"][class*=\"_collapsed\"]:has(> [class*=\"_logoRow\"]) button[class*=\"_newSession\"]:hover {\n background: var(--dsw-alias-interactive-bg-hover) !important;\n}\n\nbody[data-liang-skin=\"on\"] button,\nbody[data-liang-skin=\"on\"] input,\nbody[data-liang-skin=\"on\"] textarea {\n transition: color 160ms linear, background-color 160ms linear, border-color 160ms linear;\n}\n\n/* The composer card has a native theme-mode fill, so keep its surface and\n readable ink aligned with the active Liang palette. */\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) [data-composer-card=\"true\"] {\n border-color: var(--liang-border) !important;\n color: var(--liang-ink) !important;\n background: var(--liang-layer-1) !important;\n box-shadow: 0 8px 28px rgb(20 22 20 / 8%);\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) [data-composer-card=\"true\"] :is(textarea, button, [data-input-mirror=\"true\"]) {\n color: var(--liang-ink) !important;\n caret-color: var(--liang-accent) !important;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) [data-composer-card=\"true\"] textarea::placeholder {\n color: var(--liang-tertiary) !important;\n opacity: 1;\n}\n\n.liang-effort-control {\n --liang-control-accent: var(--dsw-alias-brand-primary-new-colorprimary-new-color, #4176e6);\n --liang-control-rail: var(--dsw-alias-border-l3, rgb(0 0 0 / 16%));\n position: relative;\n display: flex;\n width: 124px;\n height: 28px;\n align-items: center;\n margin: 0 3px;\n overflow: visible;\n}\n\nbody[data-liang-skin=\"on\"] .liang-effort-control {\n --liang-control-accent: var(--liang-secondary);\n --liang-control-rail: color-mix(in srgb, var(--liang-secondary) 28%, transparent);\n}\n\n.liang-effort-control__ticks {\n position: absolute;\n z-index: 0;\n inset: 0 10px;\n pointer-events: none;\n}\n\n.liang-effort-control__ticks::before {\n position: absolute;\n top: 50%;\n right: 0;\n left: 0;\n height: 1px;\n content: \"\";\n background: var(--liang-control-rail);\n transform: translateY(-50%);\n}\n\n.liang-effort-control__tooltip {\n position: absolute;\n z-index: 4;\n bottom: calc(100% + 7px);\n left: calc(10px + (100% - 20px) * var(--liang-slider-ratio));\n min-width: max-content;\n padding: 5px 8px;\n border: 1px solid var(--liang-border, rgb(0 0 0 / 12%));\n border-radius: 7px;\n color: var(--liang-ink, #171816);\n background: var(--liang-layer-1, #fff);\n box-shadow: 0 5px 16px rgb(0 0 0 / 16%);\n font-size: 11px;\n font-weight: 600;\n line-height: 16px;\n letter-spacing: 0.01em;\n pointer-events: none;\n transform: translateX(-50%);\n white-space: nowrap;\n}\n\n.liang-effort-control__tooltip::after {\n position: absolute;\n top: 100%;\n left: 50%;\n width: 7px;\n height: 7px;\n content: \"\";\n background: inherit;\n transform: translate(-50%, -4px) rotate(45deg);\n}\n\n.liang-effort-control__tick {\n position: absolute;\n top: 50%;\n width: 1px;\n height: 7px;\n background: var(--liang-control-accent);\n opacity: 0.42;\n transform: translate(-50%, -50%);\n}\n\n.liang-effort-control__range {\n position: relative;\n z-index: 1;\n width: calc(100% - 7px);\n height: 28px;\n margin: 0 3.5px;\n cursor: ew-resize;\n appearance: none;\n background: transparent;\n touch-action: pan-y;\n}\n\n.liang-effort-control__range:disabled {\n cursor: progress;\n opacity: 0.58;\n}\n\n.liang-effort-control__range::-webkit-slider-runnable-track {\n height: 1px;\n border-radius: 1px;\n background: transparent;\n}\n\n.liang-effort-control__range::-webkit-slider-thumb {\n width: 13px;\n height: 13px;\n margin-top: -6px;\n border: 1.5px solid color-mix(in srgb, var(--liang-control-accent) 72%, transparent);\n border-radius: 50%;\n appearance: none;\n background: radial-gradient(circle, var(--liang-control-accent) 0 2px, color-mix(in srgb, var(--liang-control-accent) 22%, var(--dsw-alias-bg-layer-1, #fff)) 2.5px);\n box-shadow: 0 1px 4px rgb(0 0 0 / 14%);\n}\n\nbody[data-liang-skin=\"on\"] .liang-effort-control__range::-webkit-slider-thumb {\n width: 13px;\n height: 13px;\n margin-top: -6px;\n border-color: color-mix(in srgb, var(--liang-control-accent) 72%, transparent);\n background: radial-gradient(circle, var(--liang-control-accent) 0 2px, color-mix(in srgb, var(--liang-control-accent) 22%, var(--liang-layer-1)) 2.5px);\n}\n\n.liang-effort-control__range::-moz-range-track {\n height: 1px;\n background: transparent;\n}\n\n.liang-effort-control__range::-moz-range-progress {\n height: 1px;\n background: transparent;\n}\n\n.liang-effort-control__range::-moz-range-thumb {\n width: 11px;\n height: 11px;\n border: 1.5px solid color-mix(in srgb, var(--liang-control-accent) 72%, transparent);\n border-radius: 50%;\n background: color-mix(in srgb, var(--liang-control-accent) 24%, var(--dsw-alias-bg-layer-1, #fff));\n}\n\n.liang-effort-control__range:focus-visible {\n outline: none;\n}\n\n.liang-effort-control[data-state=\"error\"] {\n --liang-control-accent: var(--dsw-alias-state-error-primary, #e43c3c);\n}\n\n.liang-appearance-choice {\n box-sizing: border-box;\n border: 1px solid var(--dsw-alias-border-l2);\n flex: 180px;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n gap: 4px;\n padding: 20px 32px;\n border-radius: 16px;\n font: inherit;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n background: transparent;\n font-size: 14px;\n line-height: 22px;\n display: flex;\n}\n\n[class*=\"_cubeRow\"]:has(.liang-appearance-choice) {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n\n.liang-appearance-choice:hover:not([aria-pressed=\"true\"]) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.liang-appearance-choice[aria-pressed=\"true\"] {\n border-color: var(--dsw-static-neutral-bluish-400);\n background: var(--dsw-alias-bg-module-platform);\n}\n\n.liang-appearance-choice__icon {\n display: block;\n flex: 0 0 16px;\n width: 16px;\n height: 16px;\n color: currentColor;\n font-size: 16px;\n line-height: 16px;\n}\n\n.liang-appearance-choice__label {\n display: block;\n}\n\n/* Keep the host's three theme cubes untouched. These rules only style the\n fourth button that is appended to the host cube row. */\n.liang-appearance-choice:disabled {\n cursor: progress;\n opacity: 0.7;\n}\n\n.liang-appearance-binding {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 16px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n font-size: 14px;\n line-height: 20px;\n}\n\n@media (max-width: 760px) {\n .liang-effort-control {\n width: 92px;\n }\n\n .liang-skin-backdrop img {\n right: -18vw;\n width: 92vw;\n opacity: calc(var(--liang-portrait-opacity, 0.45) * 0.72);\n }\n\n}\n\n@media (prefers-reduced-motion: reduce) {\n .liang-skin-backdrop,\n .liang-skin-backdrop img,\n body[data-liang-skin=\"on\"] button,\n body[data-liang-skin=\"on\"] input,\n body[data-liang-skin=\"on\"] textarea {\n transition: none;\n }\n}\n", "export interface AppearanceSnapshot {\n enabled: boolean;\n}\n\nexport interface AppearanceScope {\n getSnapshot(): AppearanceSnapshot;\n subscribe(listener: () => void): () => void;\n}\n\nexport interface AppearancePresenter {\n choose(enabled: boolean): Promise;\n}\n\nconst NATIVE_APPEARANCE_ROW = '[class*=\"_cubeRow\"]';\nconst LIANG_APPEARANCE_BUTTON = \"liang-appearance-choice\";\n\nfunction hasClassSuffix(node: Element, suffix: string) {\n return [...node.classList].some((name) => name.endsWith(suffix));\n}\n\nfunction findAppearanceRow() {\n for (const row of document.querySelectorAll(NATIVE_APPEARANCE_ROW)) {\n if (!hasClassSuffix(row, \"_cubeRow\")) continue;\n // All three native appearance choices are direct button children. Validate\n // the structure as well as the suffix so unrelated cube rows stay alone.\n const nativeButtons = [...row.children].filter(\n (node): node is HTMLButtonElement => node instanceof HTMLButtonElement\n && hasClassSuffix(node, \"_themeCube\"),\n );\n if (nativeButtons.length < 3) continue;\n\n let group = row.parentElement;\n while (group !== null && !hasClassSuffix(group, \"_group\")) {\n group = group.parentElement;\n }\n group ??= row.parentElement;\n if (group !== null) return { row, group, nativeButtons };\n }\n return null;\n}\n\ninterface MountedAppearance {\n row: HTMLElement;\n group: HTMLElement;\n button: HTMLButtonElement;\n handleCustomClick: () => void;\n}\n\nexport function installLiangAppearanceButton(\n scope: AppearanceScope,\n presenter: AppearancePresenter,\n packageId: string,\n): () => void {\n let pending = false;\n let disposed = false;\n let mounted: MountedAppearance | null = null;\n const nativeClickHandlers = new Map void>();\n\n const unmount = () => {\n for (const [button, handler] of nativeClickHandlers) {\n button.removeEventListener(\"click\", handler, { capture: true });\n }\n nativeClickHandlers.clear();\n if (mounted === null) return;\n mounted.button.removeEventListener(\"click\", mounted.handleCustomClick);\n mounted.button.remove();\n mounted = null;\n };\n\n const choose = async (enabled: boolean) => {\n if (disposed || pending || scope.getSnapshot().enabled === enabled) return;\n pending = true;\n sync();\n try {\n await presenter.choose(enabled);\n } catch {\n // The preference store remains the source of truth after a failed choice.\n } finally {\n pending = false;\n if (!disposed) sync();\n }\n };\n\n const createMount = (row: HTMLElement, group: HTMLElement): MountedAppearance => {\n const english = document.documentElement.lang.toLowerCase().startsWith(\"en\");\n const button = document.createElement(\"button\");\n button.className = LIANG_APPEARANCE_BUTTON;\n button.type = \"button\";\n button.dataset.plugin = packageId;\n button.setAttribute(\"aria-label\", \"\u6ED1\u52A8\u53D8\u7956\");\n\n const icon = document.createElement(\"span\");\n icon.className = \"liang-appearance-choice__icon\";\n icon.setAttribute(\"aria-hidden\", \"true\");\n icon.textContent = \"\u25C8\";\n\n const label = document.createElement(\"span\");\n label.className = \"liang-appearance-choice__label\";\n label.textContent = english ? \"Slider\" : \"\u6ED1\u52A8\u53D8\u7956\";\n button.append(icon, label);\n const handleCustomClick = () => { void choose(true); };\n button.addEventListener(\"click\", handleCustomClick);\n\n return { row, group, button, handleCustomClick };\n };\n\n const sync = () => {\n if (disposed) return;\n const target = findAppearanceRow();\n if (target === null) {\n unmount();\n return;\n }\n if (mounted !== null && (mounted.row !== target.row || mounted.group !== target.group)) {\n unmount();\n }\n mounted ??= createMount(target.row, target.group);\n const { row, button } = mounted;\n if (button.parentElement !== row) row.append(button);\n\n const snapshot = scope.getSnapshot();\n button.disabled = pending;\n button.setAttribute(\"aria-pressed\", String(snapshot.enabled));\n\n for (const [nativeButton, handler] of nativeClickHandlers) {\n if (target.nativeButtons.includes(nativeButton)) continue;\n nativeButton.removeEventListener(\"click\", handler, { capture: true });\n nativeClickHandlers.delete(nativeButton);\n }\n for (const nativeButton of target.nativeButtons) {\n if (nativeClickHandlers.has(nativeButton)) continue;\n const handleNativeClick = () => { void choose(false); };\n nativeClickHandlers.set(nativeButton, handleNativeClick);\n nativeButton.addEventListener(\"click\", handleNativeClick, { capture: true });\n }\n };\n\n const observer = new MutationObserver(sync);\n observer.observe(document.body, { childList: true, subtree: true });\n const unsubscribe = scope.subscribe(sync);\n sync();\n\n return () => {\n if (disposed) return;\n // Mark disposal first: outstanding choices and queued observer callbacks\n // must never attach new controls after the client has been deactivated.\n disposed = true;\n observer.disconnect();\n unsubscribe();\n unmount();\n };\n}\n", "export interface PortraitSource {\n level: number;\n url: string;\n}\n\nexport interface PortraitState {\n index: number | null;\n url: string | null;\n status: \"loading\" | \"ready\" | \"failed\";\n image: PortraitImage | null;\n}\n\nexport interface PortraitImage {\n src: string;\n loading: \"eager\" | \"lazy\";\n decoding: \"async\" | \"sync\" | \"auto\";\n decode(): Promise;\n addEventListener(type: \"load\" | \"error\", listener: () => void): void;\n removeEventListener(type: \"load\" | \"error\", listener: () => void): void;\n removeAttribute(name: string): void;\n}\n\nexport interface PortraitLoaderOptions {\n sources: readonly PortraitSource[];\n onChange: (state: PortraitState) => void;\n createImage?: () => PortraitImage;\n}\n\ninterface Asset {\n url: string;\n status: \"idle\" | \"loading\" | \"ready\" | \"failed\";\n loadedUrl: string | null;\n retries: number;\n image: PortraitImage | null;\n timer: ReturnType | null;\n detach: (() => void) | null;\n}\n\nconst RETRY_DELAYS = [500, 1500, 3000] as const;\n\nfunction retryUrl(source: string, retry: number) {\n if (retry === 0) return source;\n const hashIndex = source.indexOf(\"#\");\n const hash = hashIndex < 0 ? \"\" : source.slice(hashIndex);\n const base = hashIndex < 0 ? source : source.slice(0, hashIndex);\n const queryIndex = base.indexOf(\"?\");\n const path = queryIndex < 0 ? base : base.slice(0, queryIndex);\n const query = new URLSearchParams(queryIndex < 0 ? \"\" : base.slice(queryIndex + 1));\n query.set(\"retry\", String(retry));\n return `${path}?${query}${hash}`;\n}\n\n/**\n * Decode frames independently and return the decoded image itself for display.\n * start() resumes after stop(); stop() silently releases every request/image.\n * dispose() is permanent. select() also accepts a target before start().\n */\nexport function createPortraitLoader(options: PortraitLoaderOptions) {\n const sources = options.sources.map((source) => ({ ...source }));\n const assets = new Map();\n for (const { url } of sources) {\n if (!assets.has(url)) {\n assets.set(url, {\n url, status: \"idle\", loadedUrl: null, retries: 0,\n image: null, timer: null, detach: null,\n });\n }\n }\n const createImage = options.createImage ?? (() => new Image());\n let onChange: PortraitLoaderOptions[\"onChange\"] | null = options.onChange;\n let active = false;\n let disposed = false;\n let generation = 0;\n let targetLevel = sources[0]?.level ?? 0;\n let displayedIndex: number | null = null;\n let lastState: PortraitState | null = null;\n\n const isCurrent = (run: number) => active && !disposed && generation === run;\n const nearestIndex = (available: (index: number) => boolean) => {\n let nearest: number | null = null;\n for (let index = 0; index < sources.length; index += 1) {\n if (!available(index)) continue;\n const distance = Math.abs(sources[index].level - targetLevel);\n const previous = nearest === null ? Infinity : Math.abs(sources[nearest].level - targetLevel);\n if (nearest === null || distance < previous || (distance === previous && sources[index].level > sources[nearest].level)) {\n nearest = index;\n }\n }\n return nearest;\n };\n const isReady = (index: number) => assets.get(sources[index].url)?.status === \"ready\";\n const emit = (state: PortraitState) => {\n if (lastState?.index === state.index && lastState.url === state.url && lastState.status === state.status) return;\n lastState = state;\n onChange?.(state);\n };\n const reconcile = () => {\n if (!active || disposed) return;\n const target = nearestIndex(() => true);\n if (target !== null && isReady(target)) displayedIndex = target;\n else if (displayedIndex === null) displayedIndex = nearestIndex(isReady);\n if (displayedIndex !== null) {\n const asset = assets.get(sources[displayedIndex].url)!;\n emit({ index: displayedIndex, url: asset.loadedUrl, image: asset.image, status: \"ready\" });\n } else {\n const exhausted = [...assets.values()].every((asset) => asset.status === \"failed\");\n emit({ index: null, url: null, image: null, status: exhausted ? \"failed\" : \"loading\" });\n }\n };\n\n const request = (asset: Asset, run: number) => {\n if (!isCurrent(run)) return;\n asset.status = \"loading\";\n const image = createImage();\n asset.image = image;\n image.loading = \"eager\";\n image.decoding = \"async\";\n const url = retryUrl(asset.url, asset.retries);\n let settled = false;\n let decoding = false;\n const current = () => !settled && isCurrent(run) && asset.image === image;\n const detach = () => {\n image.removeEventListener(\"load\", handleLoad);\n image.removeEventListener(\"error\", handleError);\n asset.detach = null;\n };\n const handleError = () => {\n if (!current()) return;\n settled = true;\n detach();\n image.removeAttribute(\"src\");\n asset.image = null;\n if (asset.retries < RETRY_DELAYS.length) {\n const delay = RETRY_DELAYS[asset.retries];\n asset.retries += 1;\n asset.timer = setTimeout(() => {\n if (!isCurrent(run)) return;\n asset.timer = null;\n request(asset, run);\n }, delay);\n } else {\n asset.status = \"failed\";\n reconcile();\n }\n };\n const handleLoad = () => {\n if (!current() || decoding) return;\n decoding = true;\n void image.decode().then(() => {\n if (!current()) return;\n settled = true;\n detach();\n asset.loadedUrl = url;\n asset.status = \"ready\";\n reconcile();\n }, handleError);\n };\n asset.detach = detach;\n image.addEventListener(\"load\", handleLoad);\n image.addEventListener(\"error\", handleError);\n image.src = url;\n };\n\n const stop = () => {\n active = false;\n generation += 1;\n for (const asset of assets.values()) {\n if (asset.timer !== null) clearTimeout(asset.timer);\n asset.timer = null;\n asset.detach?.();\n asset.image?.removeAttribute(\"src\");\n asset.image = null;\n asset.status = \"idle\";\n asset.loadedUrl = null;\n asset.retries = 0;\n }\n displayedIndex = null;\n lastState = null;\n };\n\n return {\n start() {\n if (disposed || active) return;\n active = true;\n const run = ++generation;\n reconcile();\n for (const asset of assets.values()) request(asset, run);\n },\n select(level: number) {\n if (disposed || !Number.isFinite(level)) return;\n targetLevel = level;\n reconcile();\n },\n stop,\n dispose() {\n stop();\n disposed = true;\n onChange = null;\n },\n };\n}\n", "export const PREVIEW_MAX_FRAME = 240;\nexport const MAX_LEVEL = 30;\n\nconst LIANG_RANKS = [\"\u5C0F\u96BE\u6881\", \"\u7262\u6881\", \"\u6881\u5B50\", \"\u6881\u5723\", \"\u6881\u795E\", \"\u6881\u7956\"] as const;\n\nexport interface EffortLike {\n id: string;\n name: string;\n description?: string;\n}\n\nexport interface Palette {\n level: number;\n stage: number;\n strength: number;\n page: string;\n base: string;\n layer1: string;\n layer2: string;\n layer3: string;\n sidebar: string;\n ink: string;\n secondary: string;\n tertiary: string;\n border: string;\n accent: string;\n accentHover: string;\n hover: string;\n portraitOpacity: string;\n}\n\ntype Rgb = readonly [number, number, number];\n\ninterface PaletteStop {\n at: number;\n page: Rgb;\n surface: Rgb;\n surface2: Rgb;\n ink: Rgb;\n secondary: Rgb;\n accent: Rgb;\n portraitOpacity: number;\n}\n\n// These are the original calibrator's six stage anchors: pale neutral at the\n// low end, progressively smoked surfaces, then charcoal and antique gold.\nconst STOPS: readonly PaletteStop[] = [\n { at: 0, page: [232, 233, 229], surface: [248, 248, 245], surface2: [238, 239, 235], ink: [23, 24, 22], secondary: [112, 116, 111], accent: [181, 43, 36], portraitOpacity: 0.92 },\n { at: 6, page: [211, 211, 206], surface: [239, 239, 234], surface2: [224, 224, 218], ink: [26, 26, 24], secondary: [101, 101, 96], accent: [181, 43, 36], portraitOpacity: 0.93 },\n { at: 12, page: [171, 168, 162], surface: [211, 208, 201], surface2: [190, 186, 179], ink: [27, 26, 24], secondary: [83, 79, 74], accent: [166, 54, 42], portraitOpacity: 0.94 },\n { at: 18, page: [117, 112, 106], surface: [154, 148, 140], surface2: [128, 122, 115], ink: [24, 22, 20], secondary: [65, 61, 56], accent: [154, 56, 42], portraitOpacity: 0.94 },\n { at: 24, page: [43, 39, 37], surface: [48, 43, 40], surface2: [58, 51, 46], ink: [244, 241, 232], secondary: [184, 180, 169], accent: [181, 92, 54], portraitOpacity: 0.94 },\n { at: 30, page: [17, 17, 17], surface: [30, 27, 25], surface2: [41, 35, 30], ink: [244, 241, 232], secondary: [184, 180, 169], accent: [193, 154, 73], portraitOpacity: 0.94 },\n];\n\nexport function clampFrame(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.min(PREVIEW_MAX_FRAME, Math.max(0, Math.round(value)));\n}\n\nexport function frameForEffort(index: number, count: number): number {\n if (count <= 1 || !Number.isFinite(index)) return 0;\n const safe = Math.min(count - 1, Math.max(0, Math.round(index)));\n return Math.round((safe / (count - 1)) * PREVIEW_MAX_FRAME);\n}\n\nexport function nearestEffortIndex(frame: number, efforts: readonly EffortLike[]): number {\n if (efforts.length === 0) return -1;\n const safe = clampFrame(frame);\n let best = 0;\n let distance = Math.abs(safe - frameForEffort(0, efforts.length));\n for (let index = 1; index < efforts.length; index += 1) {\n const next = Math.abs(safe - frameForEffort(index, efforts.length));\n if (next < distance) {\n best = index;\n distance = next;\n }\n }\n return best;\n}\n\nexport function selectedEffortIndex(\n efforts: readonly EffortLike[],\n selectedId?: string,\n defaultId?: string,\n): number {\n const id = selectedId ?? defaultId;\n return id === undefined ? -1 : efforts.findIndex((effort) => effort.id === id);\n}\n\nexport function portraitBlendForLevel(level: number, anchors: readonly number[]) {\n if (anchors.length === 0) return { lowerIndex: -1, upperIndex: -1, mix: 0 };\n const safeLevel = Number.isFinite(level)\n ? Math.min(anchors[anchors.length - 1], Math.max(anchors[0], level))\n : anchors[0];\n let upperIndex = anchors.findIndex((anchor) => anchor >= safeLevel);\n if (upperIndex < 0) upperIndex = anchors.length - 1;\n const lowerIndex = Math.max(0, upperIndex - (anchors[upperIndex] > safeLevel ? 1 : 0));\n const span = anchors[upperIndex] - anchors[lowerIndex];\n return {\n lowerIndex,\n upperIndex,\n mix: span === 0 ? 0 : (safeLevel - anchors[lowerIndex]) / span,\n };\n}\n\nexport function liangRankForFrame(rawFrame: number): string {\n const level = (clampFrame(rawFrame) / PREVIEW_MAX_FRAME) * MAX_LEVEL;\n const index = level >= MAX_LEVEL ? LIANG_RANKS.length - 1 : Math.floor(level / 6);\n return LIANG_RANKS[Math.min(LIANG_RANKS.length - 1, Math.max(0, index))];\n}\n\nexport function indicatorLabel(rawFrame: number, efforts: readonly EffortLike[]): string {\n const effort = efforts[nearestEffortIndex(rawFrame, efforts)];\n return effort === undefined\n ? liangRankForFrame(rawFrame)\n : `${liangRankForFrame(rawFrame)} \u00B7 ${effort.name}`;\n}\n\nfunction lerp(a: number, b: number, amount: number): number {\n return a + (b - a) * amount;\n}\n\nfunction mix(a: Rgb, b: Rgb, amount: number): Rgb {\n return [\n Math.round(lerp(a[0], b[0], amount)),\n Math.round(lerp(a[1], b[1], amount)),\n Math.round(lerp(a[2], b[2], amount)),\n ];\n}\n\nfunction rgb(value: Rgb, alpha = 1): string {\n return alpha === 1\n ? `rgb(${value[0]} ${value[1]} ${value[2]})`\n : `rgb(${value[0]} ${value[1]} ${value[2]} / ${alpha})`;\n}\n\nexport function paletteForFrame(rawFrame: number): Palette {\n const frame = clampFrame(rawFrame);\n const level = (frame / PREVIEW_MAX_FRAME) * MAX_LEVEL;\n const portraitStage = Math.min(5, Math.floor(level / 6));\n const portraitFrom = STOPS[portraitStage];\n const portraitTo = STOPS[Math.min(5, portraitStage + 1)];\n const portraitAmount = portraitFrom === portraitTo\n ? 0\n : (level - portraitFrom.at) / (portraitTo.at - portraitFrom.at);\n\n // The UI itself is intentionally binary: levels 0\u201323 keep the native light\n // shell; entering the original \u6881\u795E/\u6881\u7956 region at level 24 switches black/gold.\n const dark = level >= 24;\n const stage = dark ? 5 : 0;\n const ui = dark ? STOPS[5] : STOPS[0];\n const page = ui.page;\n const surface = ui.surface;\n const surface2 = ui.surface2;\n const sidebar = mix(page, surface2, 0.25);\n const ink = ui.ink;\n const secondary = ui.secondary;\n // Keep Harness' native blue in the light shell. The original calibrator's\n // antique gold only becomes the product accent once the dark region begins.\n const accent: Rgb = dark ? [193, 154, 73] : [65, 118, 230];\n const accentHover = mix(accent, ink, 0.13);\n\n return {\n level,\n stage,\n strength: level / MAX_LEVEL,\n page: rgb(page),\n // Keep the shell readable while allowing the right-side portrait to remain\n // visibly present. Dense controls use the opaque layer tokens below.\n base: rgb(page, dark ? 0.42 : 0.28),\n layer1: rgb(surface, 0.94),\n layer2: rgb(surface2, 0.96),\n layer3: rgb(surface2, 0.99),\n sidebar: rgb(sidebar, 0.96),\n ink: rgb(ink),\n secondary: rgb(secondary),\n tertiary: rgb(mix(secondary, page, 0.28)),\n border: rgb(ink, dark ? 0.15 : 0.12),\n accent: rgb(accent),\n accentHover: rgb(accentHover),\n hover: rgb(ink, dark ? 0.09 : 0.07),\n portraitOpacity: String(lerp(\n portraitFrom.portraitOpacity,\n portraitTo.portraitOpacity,\n portraitAmount,\n )),\n };\n}\n"], - "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAMO;AACP,sCAAuB;;;ACPvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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", + "sourcesContent": ["import {\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { Switch } from \"@deepseek-ai/dsh-client-ui-primitives\";\nimport type { Context as ClientContext } from \"@deepseek-ai/cordis\";\nimport type { SnapshotStore } from \"@deepseek-ai/dsh-client-store\";\nimport type {} from \"@deepseek-ai/dsh-client-ui-conversation/client\";\nimport type {} from \"@deepseek-ai/dsh-client-ui-settings/client\";\nimport type {} from \"@deepseek-ai/dsh-client-ui-model-selection/client\";\nimport styles from \"./skin.css\";\nimport { installLiangAppearanceButton } from \"./appearance\";\nimport { createPortraitLoader } from \"./portrait-loader\";\nimport {\n PREVIEW_MAX_FRAME,\n frameForEffort,\n indicatorLabel,\n nearestEffortIndex,\n paletteForFrame,\n selectedEffortIndex,\n type EffortLike,\n} from \"./logic\";\n\nconst PACKAGE_ID = \"dsh-client-liang-intensity-skin\";\nconst LOCALE_NAMESPACE = \"liang.skin\";\nconst ASSET_PREFIX = `/plugins/${PACKAGE_ID}/assets`;\nconst BIND_EFFORT_KEY = \"dsh-liang-intensity-skin.bind-effort\";\n\nconst PORTRAIT_ANCHORS = [\n { level: 0, file: \"stage-00.webp\" },\n { level: 1, file: \"level-01.webp\" },\n { level: 3, file: \"level-03.webp\" },\n { level: 4, file: \"level-04.webp\" },\n { level: 6, file: \"stage-06.webp\" },\n { level: 7, file: \"level-07.webp\" },\n { level: 9, file: \"level-09.webp\" },\n { level: 10, file: \"level-10.webp\" },\n { level: 12, file: \"stage-12.webp\" },\n { level: 13, file: \"level-13.webp\" },\n { level: 14, file: \"level-14.webp\" },\n { level: 15, file: \"bridge-15.webp\" },\n { level: 16, file: \"level-16.webp\" },\n { level: 17, file: \"level-17.webp\" },\n { level: 18, file: \"stage-18.webp\" },\n { level: 19, file: \"level-19.webp\" },\n { level: 21, file: \"level-21.webp\" },\n { level: 22, file: \"level-22.webp\" },\n { level: 24, file: \"stage-24.webp\" },\n { level: 25, file: \"level-25.webp\" },\n { level: 27, file: \"bridge-27.webp\" },\n { level: 28, file: \"level-28.webp\" },\n { level: 29, file: \"level-29.webp\" },\n { level: 30, file: \"stage-30.webp\" },\n] as const;\n\ninterface SkinSettings {\n enabled: boolean;\n bindEffort: boolean;\n}\n\ninterface PreferenceStore {\n getSnapshot(): SkinSettings;\n subscribe(listener: () => void): () => void;\n set(enabled: boolean): Promise;\n setBindEffort(enabled: boolean): Promise;\n dispose(): void;\n}\n\ntype NativeThemeId = \"light\" | \"dark\";\n\ninterface ThemeService {\n getTheme(): { preference: string };\n setTheme(id: NativeThemeId | \"system\"): void;\n}\n\ninterface ModelSelection {\n provider: string;\n model: string;\n reasoningEffort?: string;\n}\n\ninterface CatalogModel {\n id: string;\n reasoning?: {\n efforts: EffortLike[];\n defaultEffort?: string;\n };\n}\n\ninterface ModelDirectoryState {\n current: ModelSelection | null;\n groups: readonly { id: string; models: CatalogModel[] }[];\n status: \"idle\" | \"loading\" | \"ready\" | \"selecting\" | \"error\";\n error: string | null;\n}\n\ninterface ModelDirectory {\n store: SnapshotStore;\n load(): Promise;\n select(selection: ModelSelection): Promise<{ ok: boolean }>;\n}\n\ninterface SliderProps {\n directory: SnapshotStore;\n load: () => void;\n select: (selection: ModelSelection) => Promise;\n presenter: SkinPresenter;\n scope: PreferenceStore;\n}\n\ninterface EffortBindingRowProps {\n scope: PreferenceStore;\n t: (key: string) => string;\n}\n\nconst cssVariables = [\n \"--liang-strength\",\n \"--liang-page\",\n \"--liang-bg-base\",\n \"--liang-layer-1\",\n \"--liang-layer-2\",\n \"--liang-layer-3\",\n \"--liang-sidebar\",\n \"--liang-ink\",\n \"--liang-secondary\",\n \"--liang-tertiary\",\n \"--liang-border\",\n \"--liang-accent\",\n \"--liang-accent-hover\",\n \"--liang-hover\",\n \"--liang-portrait-opacity\",\n] as const;\n\nclass SkinPresenter {\n private readonly scope: PreferenceStore;\n private readonly theme: ThemeService;\n private readonly root: HTMLDivElement;\n private readonly portraits: ReturnType;\n private enabled = true;\n // Default to the max frame so the first paint after load is the dark shell;\n // starting at 0 flashed the light palette before the directory resolved.\n private frame = PREVIEW_MAX_FRAME;\n private pendingFrame = PREVIEW_MAX_FRAME;\n private raf = 0;\n private disposed = false;\n private unsubscribe: () => void;\n\n constructor(scope: PreferenceStore, theme: ThemeService) {\n this.scope = scope;\n this.theme = theme;\n this.root = document.createElement(\"div\");\n this.root.className = \"liang-skin-backdrop\";\n this.root.dataset.plugin = PACKAGE_ID;\n this.root.dataset.media = \"color\";\n this.root.setAttribute(\"aria-hidden\", \"true\");\n this.portraits = createPortraitLoader({\n sources: PORTRAIT_ANCHORS.map(({ level, file }) => ({\n level,\n url: `${ASSET_PREFIX}/portrait-source-v2/${file}`,\n })),\n onChange: ({ image }) => {\n if (this.disposed || !this.enabled) return;\n if (image === null) {\n this.root.dataset.media = \"color\";\n this.root.replaceChildren();\n return;\n }\n // Reuse the decoded node so a frame swap cannot start a second fetch.\n const portrait = image as HTMLImageElement;\n portrait.className = \"liang-skin-sequence-frame\";\n portrait.alt = \"\";\n portrait.draggable = false;\n if (this.root.firstChild !== portrait) this.root.replaceChildren(portrait);\n this.root.dataset.media = \"sequence\";\n },\n });\n\n this.unsubscribe = scope.subscribe(() => this.syncSettings());\n this.syncSettings();\n }\n\n private syncSettings() {\n this.setEnabled(this.scope.getSnapshot().enabled);\n }\n\n isEnabled() {\n return this.enabled;\n }\n\n getFrame() {\n return this.frame;\n }\n\n setEnabled(enabled: boolean) {\n if (this.disposed) return;\n this.enabled = enabled;\n if (enabled) {\n if (!this.root.isConnected) document.body.prepend(this.root);\n document.body.dataset.liangSkin = \"on\";\n this.applyFrame();\n this.portraits.start();\n } else {\n if (this.raf !== 0) cancelAnimationFrame(this.raf);\n this.raf = 0;\n this.root.remove();\n this.portraits.stop();\n this.root.replaceChildren();\n this.root.dataset.media = \"color\";\n delete document.body.dataset.liangSkin;\n delete document.body.dataset.liangStage;\n for (const name of cssVariables) document.body.style.removeProperty(name);\n }\n }\n\n setFrame(frame: number) {\n if (this.disposed) return;\n this.pendingFrame = Math.min(PREVIEW_MAX_FRAME, Math.max(0, Math.round(frame)));\n this.frame = this.pendingFrame;\n if (!this.enabled) return;\n if (this.raf !== 0) return;\n this.raf = requestAnimationFrame(() => {\n this.raf = 0;\n this.frame = this.pendingFrame;\n this.applyFrame();\n });\n }\n\n private applyFrame() {\n if (this.disposed || !this.enabled) return;\n const palette = paletteForFrame(this.frame);\n const body = document.body;\n this.syncNativeTheme(palette.stage === 5 ? \"dark\" : \"light\");\n body.dataset.liangStage = String(palette.stage);\n body.style.setProperty(\"--liang-strength\", String(palette.strength));\n body.style.setProperty(\"--liang-page\", palette.page);\n body.style.setProperty(\"--liang-bg-base\", palette.base);\n body.style.setProperty(\"--liang-layer-1\", palette.layer1);\n body.style.setProperty(\"--liang-layer-2\", palette.layer2);\n body.style.setProperty(\"--liang-layer-3\", palette.layer3);\n body.style.setProperty(\"--liang-sidebar\", palette.sidebar);\n body.style.setProperty(\"--liang-ink\", palette.ink);\n body.style.setProperty(\"--liang-secondary\", palette.secondary);\n body.style.setProperty(\"--liang-tertiary\", palette.tertiary);\n body.style.setProperty(\"--liang-border\", palette.border);\n body.style.setProperty(\"--liang-accent\", palette.accent);\n body.style.setProperty(\"--liang-accent-hover\", palette.accentHover);\n body.style.setProperty(\"--liang-hover\", palette.hover);\n body.style.setProperty(\"--liang-portrait-opacity\", palette.portraitOpacity);\n this.updatePortrait(palette.level);\n }\n\n syncNativeTheme(theme: NativeThemeId = paletteForFrame(this.frame).stage === 5 ? \"dark\" : \"light\") {\n if (!this.enabled || this.theme.getTheme().preference === theme) return;\n this.theme.setTheme(theme);\n }\n\n private updatePortrait(level: number) {\n this.portraits.select(level);\n }\n\n async choose(enabled: boolean) {\n this.setEnabled(enabled);\n try {\n await this.scope.set(enabled);\n } catch (error) {\n this.syncSettings();\n throw error;\n }\n }\n\n dispose() {\n if (this.disposed) return;\n this.disposed = true;\n this.unsubscribe();\n if (this.raf !== 0) cancelAnimationFrame(this.raf);\n this.portraits.dispose();\n this.root.remove();\n document.body.removeAttribute(\"data-liang-skin\");\n delete document.body.dataset.liangStage;\n for (const name of cssVariables) document.body.style.removeProperty(name);\n }\n}\n\nfunction modelReasoning(state: ModelDirectoryState) {\n const current = state.current;\n if (current === null) return null;\n const group = state.groups.find((item) => item.id === current.provider);\n const model = group?.models.find((item) => item.id === current.model);\n if (model?.reasoning === undefined) return null;\n return {\n selection: current,\n efforts: model.reasoning.efforts,\n defaultEffort: model.reasoning.defaultEffort,\n };\n}\n\nfunction LiangEffortSlider({ directory, load, select, presenter, scope }: SliderProps) {\n const state = useSyncExternalStore(\n (listener) => directory.subscribe(listener),\n () => directory.getSnapshot(),\n );\n const skin = useSyncExternalStore(\n (listener) => scope.subscribe(listener),\n () => scope.getSnapshot(),\n );\n const reasoning = useMemo(() => modelReasoning(state), [state]);\n const efforts = reasoning?.efforts ?? [];\n const committedIndex = selectedEffortIndex(\n efforts,\n reasoning?.selection.reasoningEffort,\n reasoning?.defaultEffort,\n );\n // An unknown committed effort defaults to the max frame: a fresh\n // conversation paints the dark shell immediately instead of flashing the\n // light palette until the directory load resolves the real effort.\n const committedFrame = committedIndex < 0\n ? PREVIEW_MAX_FRAME\n : frameForEffort(committedIndex, efforts.length);\n const bindEffort = skin.bindEffort;\n const [frame, setFrame] = useState(() => bindEffort ? committedFrame : presenter.getFrame());\n const [pending, setPending] = useState(false);\n const [failed, setFailed] = useState(false);\n const [interacting, setInteracting] = useState(false);\n const dragging = useRef(false);\n const dragStartFrame = useRef(frame);\n const enabled = skin.enabled;\n\n useEffect(() => {\n if (enabled) load();\n }, [enabled, load]);\n\n useEffect(() => {\n if (!bindEffort || dragging.current || pending) return;\n setFrame(committedFrame);\n presenter.setFrame(committedFrame);\n }, [bindEffort, committedFrame, pending, presenter]);\n\n if (!enabled) return null;\n if (bindEffort && (reasoning === null || efforts.length < 2)) return null;\n\n const previewIndex = nearestEffortIndex(frame, efforts);\n const previewEffort = efforts[previewIndex];\n const progressRatio = frame / PREVIEW_MAX_FRAME;\n const tooltipLabel = indicatorLabel(frame, bindEffort ? efforts : []);\n\n const commit = async (rawFrame: number) => {\n if (!bindEffort) {\n dragging.current = false;\n setFrame(rawFrame);\n presenter.setFrame(rawFrame);\n return;\n }\n const targetIndex = nearestEffortIndex(rawFrame, efforts);\n const target = efforts[targetIndex];\n if (target === undefined) return;\n const targetFrame = frameForEffort(targetIndex, efforts.length);\n dragging.current = false;\n setFrame(targetFrame);\n presenter.setFrame(targetFrame);\n if (targetIndex === committedIndex || pending) return;\n setPending(true);\n setFailed(false);\n const accepted = await select({\n provider: reasoning.selection.provider,\n model: reasoning.selection.model,\n reasoningEffort: target.id,\n });\n setPending(false);\n if (!accepted) {\n setFailed(true);\n setFrame(committedFrame);\n presenter.setFrame(committedFrame);\n }\n };\n\n return (\n \n {interacting && (\n \n {tooltipLabel}\n \n )}\n
\n {efforts.map((effort, index) => (\n \n ))}\n
\n {\n dragging.current = true;\n dragStartFrame.current = frame;\n setInteracting(true);\n }}\n onInput={(event) => {\n dragging.current = true;\n const next = Number(event.currentTarget.value);\n setFrame(next);\n presenter.setFrame(next);\n }}\n onPointerUp={(event) => {\n setInteracting(false);\n void commit(Number(event.currentTarget.value));\n }}\n onPointerCancel={() => {\n setInteracting(false);\n dragging.current = false;\n setFrame(dragStartFrame.current);\n presenter.setFrame(dragStartFrame.current);\n }}\n onKeyUp={(event) => {\n setInteracting(false);\n if (event.key !== \"Escape\") void commit(Number(event.currentTarget.value));\n }}\n onBlur={(event) => {\n setInteracting(false);\n if (dragging.current) void commit(Number(event.currentTarget.value));\n }}\n onKeyDown={(event) => {\n setInteracting(true);\n if (event.key === \"Escape\" && !pending) {\n setInteracting(false);\n dragging.current = false;\n setFrame(dragStartFrame.current);\n presenter.setFrame(dragStartFrame.current);\n }\n }}\n />\n \n );\n}\n\nfunction EffortBindingRow({ scope, t }: EffortBindingRowProps) {\n const snapshot = useSyncExternalStore(\n (listener) => scope.subscribe(listener),\n () => scope.getSnapshot(),\n );\n const [pending, setPending] = useState(false);\n if (!snapshot.enabled) return null;\n\n return (\n
\n {t(\"effort.binding\")}\n {\n if (pending) return;\n setPending(true);\n void scope.setBindEffort(next).catch(() => undefined).finally(() => setPending(false));\n }}\n />\n
\n );\n}\n\nexport const inject = [\n \"slots\",\n \"sessions\",\n \"modelDirectories\",\n \"locale\",\n \"theme\",\n];\n\nfunction createPreferenceStore(): PreferenceStore {\n // The market's active skin is the source of truth for whether this client\n // should be visible. The appearance switch is therefore scoped to this\n // client activation and must not survive switching away and back.\n try {\n localStorage.removeItem(\"dsh-liang-intensity-skin.enabled\");\n } catch {\n // Storage may be unavailable; the in-memory default still enables Liang.\n }\n let snapshot: SkinSettings = {\n enabled: true,\n bindEffort: localStorage.getItem(BIND_EFFORT_KEY) !== \"0\",\n };\n const listeners = new Set<() => void>();\n const onStorage = (event: StorageEvent) => {\n if (event.key !== BIND_EFFORT_KEY) return;\n const next = {\n enabled: snapshot.enabled,\n bindEffort: event.newValue !== \"0\",\n };\n if (next.enabled === snapshot.enabled && next.bindEffort === snapshot.bindEffort) return;\n snapshot = next;\n for (const listener of listeners) listener();\n };\n window.addEventListener(\"storage\", onStorage);\n return {\n getSnapshot: () => snapshot,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async set(enabled) {\n if (enabled === snapshot.enabled) return;\n snapshot = { ...snapshot, enabled };\n for (const listener of listeners) listener();\n },\n async setBindEffort(bindEffort) {\n if (bindEffort === snapshot.bindEffort) return;\n localStorage.setItem(BIND_EFFORT_KEY, bindEffort ? \"1\" : \"0\");\n snapshot = { ...snapshot, bindEffort };\n for (const listener of listeners) listener();\n },\n dispose() {\n window.removeEventListener(\"storage\", onStorage);\n listeners.clear();\n },\n };\n}\n\nexport function apply(ctx: ClientContext) {\n const style = document.createElement(\"style\");\n style.dataset.plugin = PACKAGE_ID;\n style.textContent = styles;\n document.head.append(style);\n ctx.effect(() => () => style.remove(), \"liang-intensity-skin: scoped styles\");\n\n const scope = createPreferenceStore();\n ctx.effect(() => () => scope.dispose(), \"liang-intensity-skin: appearance preference\");\n const theme = ctx.get(\"theme\") as ThemeService;\n const presenter = new SkinPresenter(scope, theme);\n ctx.effect(() => () => presenter.dispose(), \"liang-intensity-skin: backdrop presenter\");\n ctx.effect(\n () => installLiangAppearanceButton(scope, presenter, PACKAGE_ID),\n \"liang-intensity-skin: native appearance extension\",\n );\n ctx.effect(\n () => ctx.locale.register(LOCALE_NAMESPACE, {\n zh: { \"effort.binding\": \"\u6ED1\u52A8\u53D8\u7956\u7ED1\u5B9A\u601D\u8003\u7B49\u7EA7\" },\n en: { \"effort.binding\": \"Bind slider to reasoning level\" },\n }), \"liang-intensity-skin: settings locale\");\n\n ctx.slots.inject(\"settings.general.item\", () => ctx.slots.register({\n name: \"settings.general.item\",\n id: \"liang-effort-binding\",\n order: 10.5,\n locale: LOCALE_NAMESPACE,\n inject: () => ({ scope }),\n }, EffortBindingRow));\n\n ctx.slots.inject(\"conversation.input.right\", () => ctx.slots.register({\n name: \"conversation.input.right\",\n id: \"liang-intensity-control\",\n order: 10,\n inject: (sessionId: string) => {\n const available = ctx.sessions.subagentAddress(sessionId) === undefined;\n const directory = ctx.modelDirectories.directoryFor(sessionId) as ModelDirectory;\n return {\n directory: directory.store,\n presenter,\n scope,\n load: () => {\n if (available) void directory.load().catch(() => undefined);\n },\n select: (selection: ModelSelection) => available\n ? directory.select(selection).then((result) => result.ok, () => false)\n : Promise.resolve(false),\n };\n },\n }, LiangEffortSlider));\n}\n", ".liang-skin-backdrop {\n position: fixed;\n z-index: -1;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n background: var(--liang-page, transparent);\n opacity: 0;\n transition: opacity 220ms ease, background-color 180ms linear;\n}\n\nbody[data-liang-skin=\"on\"] .liang-skin-backdrop {\n opacity: 1;\n}\n\n.liang-skin-backdrop img {\n position: absolute;\n top: 50%;\n right: 0;\n display: block;\n width: min(42vw, 720px);\n height: 100%;\n object-fit: cover;\n object-position: 50% 42%;\n opacity: var(--liang-portrait-opacity, 0.45);\n transform: translateY(-50%);\n filter: none;\n mask-image: linear-gradient(90deg, transparent 0%, rgb(0 0 0 / 20%) 10%, black 30%, black 100%);\n transition: filter 180ms linear;\n}\n\n.liang-skin-backdrop img {\n display: none;\n}\n\n.liang-skin-backdrop[data-media=\"sequence\"] .liang-skin-sequence-frame {\n display: block;\n opacity: var(--liang-portrait-opacity, 0.82);\n}\n\n.liang-skin-backdrop[data-media=\"color\"] img {\n display: none;\n}\n\n.liang-skin-backdrop::after {\n position: absolute;\n inset: 0;\n content: \"\";\n background:\n linear-gradient(90deg, var(--liang-page, transparent) 0 28%, transparent 57% 100%),\n linear-gradient(var(--liang-bg-base, transparent), var(--liang-bg-base, transparent));\n}\n\nbody[data-liang-skin=\"on\"] {\n /* Keep the backdrop above the canvas but below content and native portals.\n The host owns root positioning, dimensions, and its own stacking order. */\n isolation: isolate;\n --dsw-alias-bg-base: transparent !important;\n --dsw-alias-bg-layer-1: var(--liang-layer-1) !important;\n --dsw-alias-bg-layer-2: var(--liang-layer-2) !important;\n --dsw-alias-bg-layer-3: var(--liang-layer-3) !important;\n --dsw-specific-sidebar-fill: var(--liang-sidebar) !important;\n --dsw-alias-label-primary: var(--liang-ink) !important;\n --dsw-alias-label-primary-dimmed: var(--liang-ink) !important;\n --dsw-alias-label-secondary: var(--liang-secondary) !important;\n --dsw-alias-label-tertiary: var(--liang-tertiary) !important;\n --dsw-alias-label-caption: var(--liang-tertiary) !important;\n --dsw-alias-border-l1: var(--liang-border) !important;\n --dsw-alias-border-l2-darkmode-thin: var(--liang-border) !important;\n --dsw-alias-border-l2: var(--liang-border) !important;\n --dsw-alias-border-l3: var(--liang-border) !important;\n --dsw-alias-button-primary-fill: var(--liang-accent) !important;\n --dsw-alias-button-primary-hover: var(--liang-accent-hover) !important;\n --dsw-alias-button-info-fill: var(--liang-accent) !important;\n --dsw-alias-button-info-hover: var(--liang-accent-hover) !important;\n --dsw-alias-state-business-primary: var(--liang-accent) !important;\n --dsw-alias-interactive-bg-hover: var(--liang-hover) !important;\n --dsw-alias-interactive-bg-active: var(--liang-hover) !important;\n --dsw-alias-button-elevated-fill: var(--liang-layer-1) !important;\n --dsw-alias-button-floating-fill: var(--liang-layer-2) !important;\n --dsw-alias-button-floating-hover: var(--liang-layer-3) !important;\n --dsw-alias-interactive-bg-hover-solid: var(--liang-layer-2) !important;\n --dsw-specific-bubble: var(--liang-layer-2) !important;\n --dsw-specific-input-major: var(--liang-layer-1) !important;\n --dsw-specific-menu: var(--liang-layer-3) !important;\n --dsw-specific-selector: var(--liang-layer-2) !important;\n --dsw-alias-markdown-citation: var(--liang-layer-2) !important;\n --dsw-alias-markdown-code-block-banner: var(--liang-layer-1) !important;\n --dsw-alias-markdown-code-block: var(--liang-layer-1) !important;\n --dsw-alias-markdown-code-segment-selected: var(--liang-layer-2) !important;\n --dsw-alias-markdown-code-segment-unselected: var(--liang-layer-1) !important;\n --dsw-alias-markdown-inline-code: var(--liang-layer-2) !important;\n --dsw-alias-markdown-placeholder: var(--liang-layer-1) !important;\n --dsw-alias-markdown-tag: var(--liang-layer-2) !important;\n background: var(--liang-page) !important;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) {\n --dsw-alias-brand-primary-new-colorprimary-new-color: rgb(65 118 230) !important;\n --dsw-alias-brand-primary: rgb(15 17 21) !important;\n --dsw-alias-brand-text: rgb(15 17 21) !important;\n --dsw-alias-button-primary-fill: rgb(15 17 21) !important;\n --dsw-alias-button-primary-hover: rgb(53 54 56) !important;\n --dsw-alias-button-info-fill: rgb(65 118 230) !important;\n --dsw-alias-button-info-hover: rgb(103 158 254) !important;\n --dsw-alias-state-business-primary: rgb(65 118 230) !important;\n --dsw-alias-label-primary-inverted: rgb(249 250 251) !important;\n --dsw-alias-button-elevated-fill: rgb(255 255 255) !important;\n --dsw-alias-button-floating-fill: rgb(255 255 255) !important;\n --dsw-alias-button-floating-hover: rgb(241 243 245) !important;\n --dsw-alias-interactive-bg-hover-solid: rgb(241 243 245) !important;\n --dsw-specific-bubble: rgb(237 243 254) !important;\n --dsw-specific-input-major: rgb(255 255 255) !important;\n --dsw-specific-menu: rgb(255 255 255) !important;\n --dsw-specific-selector: rgb(245 246 247) !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] {\n --dsw-alias-brand-primary-new-colorprimary-new-color: #c19a49 !important;\n --dsw-alias-brand-primary: #c19a49 !important;\n --dsw-alias-brand-text: #d5b56e !important;\n --dsw-alias-button-info-fill: #c19a49 !important;\n --dsw-alias-button-info-hover: #d5b56e !important;\n --dsw-alias-state-business-primary: #c19a49 !important;\n --dsw-alias-bg-module-platform: var(--liang-layer-3) !important;\n --dsw-specific-sidebar-nav-item-active: var(--liang-layer-3) !important;\n --dsw-specific-sidebar-nav-item-hover: var(--liang-hover) !important;\n}\n\n/* The host renders the running-turn label as a clipped blue text gradient.\n Retarget only that gradient's color tokens in the custom dark skin; keep\n the host's text clipping and shimmer behavior, and leave light mode alone. */\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] [class*=\"_turnStatus\"] {\n --dsw-static-deepseek-500: #c19a49 !important;\n --dsw-static-deepseek-200: #d5b56e !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] [class*=\"_turnStatusClock\"] {\n color: #c19a49 !important;\n -webkit-text-fill-color: #c19a49 !important;\n}\n\n/* Preserve the wordmark badge contrast in the low-intensity shell. */\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) button[aria-label=\"\u65B0\u5EFA\u4F1A\u8BDD\"] svg[viewBox=\"0 0 182 24\"] > rect[width=\"52\"] {\n fill: rgb(15 17 21) !important;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) button[aria-label=\"\u65B0\u5EFA\u4F1A\u8BDD\"] svg[viewBox=\"0 0 182 24\"] > g[clip-path*=\"badge\"] path {\n fill: rgb(249 250 251) !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] button[aria-label=\"\u65B0\u5EFA\u4F1A\u8BDD\"] svg[viewBox=\"0 0 182 24\"] > rect[width=\"52\"] {\n fill: #c19a49 !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] button[aria-label=\"\u65B0\u5EFA\u4F1A\u8BDD\"] svg[viewBox=\"0 0 182 24\"] > g[clip-path*=\"badge\"] path {\n fill: #171816 !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] [aria-label^=\"\u9009\u62E9\u6A21\u578B\"] > span:nth-of-type(2) {\n color: #d5b56e !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] [class*=\"heroGlow\"] ellipse {\n fill: #c19a49 !important;\n fill-opacity: 0.16 !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] span[class*=\"_previewBadge\"] {\n color: #171816 !important;\n background: #c19a49 !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] button[aria-label=\"\u53D1\u9001\u6D88\u606F\"] {\n color: #171816 !important;\n}\n\nbody[data-liang-skin=\"on\"][data-liang-stage=\"5\"] button[aria-label=\"\u53D1\u9001\u6D88\u606F\"]:disabled {\n opacity: 0.62;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) button[aria-label=\"\u53D1\u9001\u6D88\u606F\"] {\n color: #fff !important;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) button[aria-label=\"\u53D1\u9001\u6D88\u606F\"]:disabled {\n color: rgb(255 255 255 / 92%) !important;\n background: rgb(65 118 230 / 42%) !important;\n opacity: 1;\n}\n\nbody[data-liang-skin=\"on\"] > #root {\n background: transparent !important;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-dsh-desktop-mode=\"compatibility\"],\n [data-dsh-desktop-mode=\"extended\"]\n) .liang-skin-backdrop {\n top: var(--dsh-desktop-frame-height, 0px);\n}\n\n/* Native account pages are full-screen body portals. Their header and loading\n surface must stay opaque when the chat background token is transparent. */\nbody[data-liang-skin=\"on\"] > [role=\"dialog\"][aria-modal=\"true\"][class*=\"_overlay\"]:has(> header[data-window-drag] button[class*=\"_back\"]):has(> [class*=\"_viewport\"]) {\n --dsw-alias-bg-base: var(--liang-page, #fff) !important;\n background: var(--liang-page, #fff) !important;\n}\n\n/* Windows paints the sidebar fill across the host frame, covering the portrait.\n Match macOS's transparent frame while keeping the sidebar, caption, and\n conversation surfaces painted by their own host rules. */\nhtml[data-windows-titlebar] body[data-liang-skin=\"on\"] #root [class*=\"_frame\"]:has(> [class*=\"_centerCol\"]) {\n background: transparent !important;\n}\n\n/* macOS adds different translucent sidebar materials for native light/dark.\n While Liang is active, the effort palette owns the sidebar fill. */\nhtml[data-platform=\"darwin\"] body[data-liang-skin=\"on\"] #root [class*=\"_frame\"]:has(> [class*=\"_centerCol\"]) > [class*=\"_sidebarCol\"] {\n background: var(--liang-sidebar) !important;\n}\n\n/* Keep the host's expanded new-session surface on the same palette. The\n collapsed icon keeps its native transparent surface and hover behavior. */\nhtml[data-platform=\"darwin\"] body[data-liang-skin=\"on\"] #root [class*=\"_sidebarCol\"] [class*=\"_root\"]:has(> [class*=\"_logoRow\"]):not([class*=\"_collapsed\"]) button[class*=\"_newSession\"] {\n background: var(--dsw-alias-button-elevated-fill) !important;\n}\n\nhtml[data-platform=\"darwin\"] body[data-liang-skin=\"on\"] #root [class*=\"_sidebarCol\"] [class*=\"_root\"]:has(> [class*=\"_logoRow\"]):not([class*=\"_collapsed\"]) button[class*=\"_newSession\"]:hover {\n background: var(--dsw-alias-button-floating-hover) !important;\n}\n\n/* The native dark rule also outranks the collapsed icon's transparent fill. */\nhtml[data-platform=\"darwin\"] body[data-liang-skin=\"on\"] #root [class*=\"_sidebarCol\"] [class*=\"_root\"][class*=\"_collapsed\"]:has(> [class*=\"_logoRow\"]) button[class*=\"_newSession\"] {\n background: transparent !important;\n}\n\nhtml[data-platform=\"darwin\"] body[data-liang-skin=\"on\"] #root [class*=\"_sidebarCol\"] [class*=\"_root\"][class*=\"_collapsed\"]:has(> [class*=\"_logoRow\"]) button[class*=\"_newSession\"]:hover {\n background: var(--dsw-alias-interactive-bg-hover) !important;\n}\n\nbody[data-liang-skin=\"on\"] button,\nbody[data-liang-skin=\"on\"] input,\nbody[data-liang-skin=\"on\"] textarea {\n transition: color 160ms linear, background-color 160ms linear, border-color 160ms linear;\n}\n\n/* The composer card has a native theme-mode fill, so keep its surface and\n readable ink aligned with the active Liang palette. */\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) [data-composer-card=\"true\"] {\n border-color: var(--liang-border) !important;\n color: var(--liang-ink) !important;\n background: var(--liang-layer-1) !important;\n box-shadow: 0 8px 28px rgb(20 22 20 / 8%);\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) [data-composer-card=\"true\"] :is(textarea, button, [data-input-mirror=\"true\"]) {\n color: var(--liang-ink) !important;\n caret-color: var(--liang-accent) !important;\n}\n\nbody[data-liang-skin=\"on\"]:is(\n [data-liang-stage=\"0\"],\n [data-liang-stage=\"1\"],\n [data-liang-stage=\"2\"],\n [data-liang-stage=\"3\"]\n) [data-composer-card=\"true\"] textarea::placeholder {\n color: var(--liang-tertiary) !important;\n opacity: 1;\n}\n\n.liang-effort-control {\n --liang-control-accent: var(--dsw-alias-brand-primary-new-colorprimary-new-color, #4176e6);\n --liang-control-rail: var(--dsw-alias-border-l3, rgb(0 0 0 / 16%));\n position: relative;\n display: flex;\n width: 124px;\n height: 28px;\n align-items: center;\n margin: 0 3px;\n overflow: visible;\n}\n\nbody[data-liang-skin=\"on\"] .liang-effort-control {\n --liang-control-accent: var(--liang-secondary);\n --liang-control-rail: color-mix(in srgb, var(--liang-secondary) 28%, transparent);\n}\n\n.liang-effort-control__ticks {\n position: absolute;\n z-index: 0;\n inset: 0 10px;\n pointer-events: none;\n}\n\n.liang-effort-control__ticks::before {\n position: absolute;\n top: 50%;\n right: 0;\n left: 0;\n height: 1px;\n content: \"\";\n background: var(--liang-control-rail);\n transform: translateY(-50%);\n}\n\n.liang-effort-control__tooltip {\n position: absolute;\n z-index: 4;\n bottom: calc(100% + 7px);\n left: calc(10px + (100% - 20px) * var(--liang-slider-ratio));\n min-width: max-content;\n padding: 5px 8px;\n border: 1px solid var(--liang-border, rgb(0 0 0 / 12%));\n border-radius: 7px;\n color: var(--liang-ink, #171816);\n background: var(--liang-layer-1, #fff);\n box-shadow: 0 5px 16px rgb(0 0 0 / 16%);\n font-size: 11px;\n font-weight: 600;\n line-height: 16px;\n letter-spacing: 0.01em;\n pointer-events: none;\n transform: translateX(-50%);\n white-space: nowrap;\n}\n\n.liang-effort-control__tooltip::after {\n position: absolute;\n top: 100%;\n left: 50%;\n width: 7px;\n height: 7px;\n content: \"\";\n background: inherit;\n transform: translate(-50%, -4px) rotate(45deg);\n}\n\n.liang-effort-control__tick {\n position: absolute;\n top: 50%;\n width: 1px;\n height: 7px;\n background: var(--liang-control-accent);\n opacity: 0.42;\n transform: translate(-50%, -50%);\n}\n\n.liang-effort-control__range {\n position: relative;\n z-index: 1;\n width: calc(100% - 7px);\n height: 28px;\n margin: 0 3.5px;\n cursor: ew-resize;\n appearance: none;\n background: transparent;\n touch-action: pan-y;\n}\n\n.liang-effort-control__range:disabled {\n cursor: progress;\n opacity: 0.58;\n}\n\n.liang-effort-control__range::-webkit-slider-runnable-track {\n height: 1px;\n border-radius: 1px;\n background: transparent;\n}\n\n.liang-effort-control__range::-webkit-slider-thumb {\n width: 13px;\n height: 13px;\n margin-top: -6px;\n border: 1.5px solid color-mix(in srgb, var(--liang-control-accent) 72%, transparent);\n border-radius: 50%;\n appearance: none;\n background: radial-gradient(circle, var(--liang-control-accent) 0 2px, color-mix(in srgb, var(--liang-control-accent) 22%, var(--dsw-alias-bg-layer-1, #fff)) 2.5px);\n box-shadow: 0 1px 4px rgb(0 0 0 / 14%);\n}\n\nbody[data-liang-skin=\"on\"] .liang-effort-control__range::-webkit-slider-thumb {\n width: 13px;\n height: 13px;\n margin-top: -6px;\n border-color: color-mix(in srgb, var(--liang-control-accent) 72%, transparent);\n background: radial-gradient(circle, var(--liang-control-accent) 0 2px, color-mix(in srgb, var(--liang-control-accent) 22%, var(--liang-layer-1)) 2.5px);\n}\n\n.liang-effort-control__range::-moz-range-track {\n height: 1px;\n background: transparent;\n}\n\n.liang-effort-control__range::-moz-range-progress {\n height: 1px;\n background: transparent;\n}\n\n.liang-effort-control__range::-moz-range-thumb {\n width: 11px;\n height: 11px;\n border: 1.5px solid color-mix(in srgb, var(--liang-control-accent) 72%, transparent);\n border-radius: 50%;\n background: color-mix(in srgb, var(--liang-control-accent) 24%, var(--dsw-alias-bg-layer-1, #fff));\n}\n\n.liang-effort-control__range:focus-visible {\n outline: none;\n}\n\n.liang-effort-control[data-state=\"error\"] {\n --liang-control-accent: var(--dsw-alias-state-error-primary, #e43c3c);\n}\n\n.liang-appearance-choice {\n box-sizing: border-box;\n border: 1px solid var(--dsw-alias-border-l2);\n flex: 180px;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n gap: 4px;\n padding: 20px 32px;\n border-radius: 16px;\n font: inherit;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n background: transparent;\n font-size: 14px;\n line-height: 22px;\n display: flex;\n}\n\n[class*=\"_cubeRow\"]:has(.liang-appearance-choice) {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n\n.liang-appearance-choice:hover:not([aria-pressed=\"true\"]) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.liang-appearance-choice[aria-pressed=\"true\"] {\n border-color: var(--dsw-static-neutral-bluish-400);\n background: var(--dsw-alias-bg-module-platform);\n}\n\n.liang-appearance-choice__icon {\n display: block;\n flex: 0 0 16px;\n width: 16px;\n height: 16px;\n color: currentColor;\n font-size: 16px;\n line-height: 16px;\n}\n\n.liang-appearance-choice__label {\n display: block;\n}\n\n/* Keep the host's three theme cubes untouched. These rules only style the\n fourth button that is appended to the host cube row. */\n.liang-appearance-choice:disabled {\n cursor: progress;\n opacity: 0.7;\n}\n\n.liang-appearance-binding {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 16px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n font-size: 14px;\n line-height: 20px;\n}\n\n@media (max-width: 760px) {\n .liang-effort-control {\n width: 92px;\n }\n\n .liang-skin-backdrop img {\n right: -18vw;\n width: 92vw;\n opacity: calc(var(--liang-portrait-opacity, 0.45) * 0.72);\n }\n\n}\n\n@media (prefers-reduced-motion: reduce) {\n .liang-skin-backdrop,\n .liang-skin-backdrop img,\n body[data-liang-skin=\"on\"] button,\n body[data-liang-skin=\"on\"] input,\n body[data-liang-skin=\"on\"] textarea {\n transition: none;\n }\n}\n", "export interface AppearanceSnapshot {\n enabled: boolean;\n}\n\nexport interface AppearanceScope {\n getSnapshot(): AppearanceSnapshot;\n subscribe(listener: () => void): () => void;\n}\n\nexport interface AppearancePresenter {\n choose(enabled: boolean): Promise;\n}\n\nconst NATIVE_APPEARANCE_ROW = '[class*=\"_cubeRow\"]';\nconst LIANG_APPEARANCE_BUTTON = \"liang-appearance-choice\";\n\nfunction hasClassSuffix(node: Element, suffix: string) {\n return [...node.classList].some((name) => name.endsWith(suffix));\n}\n\nfunction findAppearanceRow() {\n for (const row of document.querySelectorAll(NATIVE_APPEARANCE_ROW)) {\n if (!hasClassSuffix(row, \"_cubeRow\")) continue;\n // All three native appearance choices are direct button children. Validate\n // the structure as well as the suffix so unrelated cube rows stay alone.\n const nativeButtons = [...row.children].filter(\n (node): node is HTMLButtonElement => node instanceof HTMLButtonElement\n && hasClassSuffix(node, \"_themeCube\"),\n );\n if (nativeButtons.length < 3) continue;\n\n let group = row.parentElement;\n while (group !== null && !hasClassSuffix(group, \"_group\")) {\n group = group.parentElement;\n }\n group ??= row.parentElement;\n if (group !== null) return { row, group, nativeButtons };\n }\n return null;\n}\n\ninterface MountedAppearance {\n row: HTMLElement;\n group: HTMLElement;\n button: HTMLButtonElement;\n handleCustomClick: () => void;\n}\n\nexport function installLiangAppearanceButton(\n scope: AppearanceScope,\n presenter: AppearancePresenter,\n packageId: string,\n): () => void {\n let pending = false;\n let disposed = false;\n let mounted: MountedAppearance | null = null;\n const nativeClickHandlers = new Map void>();\n\n const unmount = () => {\n for (const [button, handler] of nativeClickHandlers) {\n button.removeEventListener(\"click\", handler, { capture: true });\n }\n nativeClickHandlers.clear();\n if (mounted === null) return;\n mounted.button.removeEventListener(\"click\", mounted.handleCustomClick);\n mounted.button.remove();\n mounted = null;\n };\n\n const choose = async (enabled: boolean) => {\n if (disposed || pending || scope.getSnapshot().enabled === enabled) return;\n pending = true;\n sync();\n try {\n await presenter.choose(enabled);\n } catch {\n // The preference store remains the source of truth after a failed choice.\n } finally {\n pending = false;\n if (!disposed) sync();\n }\n };\n\n const createMount = (row: HTMLElement, group: HTMLElement): MountedAppearance => {\n const english = document.documentElement.lang.toLowerCase().startsWith(\"en\");\n const button = document.createElement(\"button\");\n button.className = LIANG_APPEARANCE_BUTTON;\n button.type = \"button\";\n button.dataset.plugin = packageId;\n button.setAttribute(\"aria-label\", \"\u6ED1\u52A8\u53D8\u7956\");\n\n const icon = document.createElement(\"span\");\n icon.className = \"liang-appearance-choice__icon\";\n icon.setAttribute(\"aria-hidden\", \"true\");\n icon.textContent = \"\u25C8\";\n\n const label = document.createElement(\"span\");\n label.className = \"liang-appearance-choice__label\";\n label.textContent = english ? \"Slider\" : \"\u6ED1\u52A8\u53D8\u7956\";\n button.append(icon, label);\n const handleCustomClick = () => { void choose(true); };\n button.addEventListener(\"click\", handleCustomClick);\n\n return { row, group, button, handleCustomClick };\n };\n\n const sync = () => {\n if (disposed) return;\n const target = findAppearanceRow();\n if (target === null) {\n unmount();\n return;\n }\n if (mounted !== null && (mounted.row !== target.row || mounted.group !== target.group)) {\n unmount();\n }\n mounted ??= createMount(target.row, target.group);\n const { row, button } = mounted;\n if (button.parentElement !== row) row.append(button);\n\n const snapshot = scope.getSnapshot();\n button.disabled = pending;\n button.setAttribute(\"aria-pressed\", String(snapshot.enabled));\n\n for (const [nativeButton, handler] of nativeClickHandlers) {\n if (target.nativeButtons.includes(nativeButton)) continue;\n nativeButton.removeEventListener(\"click\", handler, { capture: true });\n nativeClickHandlers.delete(nativeButton);\n }\n for (const nativeButton of target.nativeButtons) {\n if (nativeClickHandlers.has(nativeButton)) continue;\n const handleNativeClick = () => { void choose(false); };\n nativeClickHandlers.set(nativeButton, handleNativeClick);\n nativeButton.addEventListener(\"click\", handleNativeClick, { capture: true });\n }\n };\n\n const observer = new MutationObserver(sync);\n observer.observe(document.body, { childList: true, subtree: true });\n const unsubscribe = scope.subscribe(sync);\n sync();\n\n return () => {\n if (disposed) return;\n // Mark disposal first: outstanding choices and queued observer callbacks\n // must never attach new controls after the client has been deactivated.\n disposed = true;\n observer.disconnect();\n unsubscribe();\n unmount();\n };\n}\n", "export interface PortraitSource {\n level: number;\n url: string;\n}\n\nexport interface PortraitState {\n index: number | null;\n url: string | null;\n status: \"loading\" | \"ready\" | \"failed\";\n image: PortraitImage | null;\n}\n\nexport interface PortraitImage {\n src: string;\n loading: \"eager\" | \"lazy\";\n decoding: \"async\" | \"sync\" | \"auto\";\n decode(): Promise;\n addEventListener(type: \"load\" | \"error\", listener: () => void): void;\n removeEventListener(type: \"load\" | \"error\", listener: () => void): void;\n removeAttribute(name: string): void;\n}\n\nexport interface PortraitLoaderOptions {\n sources: readonly PortraitSource[];\n onChange: (state: PortraitState) => void;\n createImage?: () => PortraitImage;\n}\n\ninterface Asset {\n url: string;\n status: \"idle\" | \"loading\" | \"ready\" | \"failed\";\n loadedUrl: string | null;\n retries: number;\n image: PortraitImage | null;\n timer: ReturnType | null;\n detach: (() => void) | null;\n}\n\nconst RETRY_DELAYS = [500, 1500, 3000] as const;\n\nfunction retryUrl(source: string, retry: number) {\n if (retry === 0) return source;\n const hashIndex = source.indexOf(\"#\");\n const hash = hashIndex < 0 ? \"\" : source.slice(hashIndex);\n const base = hashIndex < 0 ? source : source.slice(0, hashIndex);\n const queryIndex = base.indexOf(\"?\");\n const path = queryIndex < 0 ? base : base.slice(0, queryIndex);\n const query = new URLSearchParams(queryIndex < 0 ? \"\" : base.slice(queryIndex + 1));\n query.set(\"retry\", String(retry));\n return `${path}?${query}${hash}`;\n}\n\n/**\n * Decode frames independently and return the decoded image itself for display.\n * start() resumes after stop(); stop() silently releases every request/image.\n * dispose() is permanent. select() also accepts a target before start().\n */\nexport function createPortraitLoader(options: PortraitLoaderOptions) {\n const sources = options.sources.map((source) => ({ ...source }));\n const assets = new Map();\n for (const { url } of sources) {\n if (!assets.has(url)) {\n assets.set(url, {\n url, status: \"idle\", loadedUrl: null, retries: 0,\n image: null, timer: null, detach: null,\n });\n }\n }\n const createImage = options.createImage ?? (() => new Image());\n let onChange: PortraitLoaderOptions[\"onChange\"] | null = options.onChange;\n let active = false;\n let disposed = false;\n let generation = 0;\n let targetLevel = sources[0]?.level ?? 0;\n let displayedIndex: number | null = null;\n let lastState: PortraitState | null = null;\n\n const isCurrent = (run: number) => active && !disposed && generation === run;\n const nearestIndex = (available: (index: number) => boolean) => {\n let nearest: number | null = null;\n for (let index = 0; index < sources.length; index += 1) {\n if (!available(index)) continue;\n const distance = Math.abs(sources[index].level - targetLevel);\n const previous = nearest === null ? Infinity : Math.abs(sources[nearest].level - targetLevel);\n if (nearest === null || distance < previous || (distance === previous && sources[index].level > sources[nearest].level)) {\n nearest = index;\n }\n }\n return nearest;\n };\n const isReady = (index: number) => assets.get(sources[index].url)?.status === \"ready\";\n const emit = (state: PortraitState) => {\n if (lastState?.index === state.index && lastState.url === state.url && lastState.status === state.status) return;\n lastState = state;\n onChange?.(state);\n };\n const reconcile = () => {\n if (!active || disposed) return;\n const target = nearestIndex(() => true);\n if (target !== null && isReady(target)) displayedIndex = target;\n else if (displayedIndex === null) displayedIndex = nearestIndex(isReady);\n if (displayedIndex !== null) {\n const asset = assets.get(sources[displayedIndex].url)!;\n emit({ index: displayedIndex, url: asset.loadedUrl, image: asset.image, status: \"ready\" });\n } else {\n const exhausted = [...assets.values()].every((asset) => asset.status === \"failed\");\n emit({ index: null, url: null, image: null, status: exhausted ? \"failed\" : \"loading\" });\n }\n };\n\n const request = (asset: Asset, run: number) => {\n if (!isCurrent(run)) return;\n asset.status = \"loading\";\n const image = createImage();\n asset.image = image;\n image.loading = \"eager\";\n image.decoding = \"async\";\n const url = retryUrl(asset.url, asset.retries);\n let settled = false;\n let decoding = false;\n const current = () => !settled && isCurrent(run) && asset.image === image;\n const detach = () => {\n image.removeEventListener(\"load\", handleLoad);\n image.removeEventListener(\"error\", handleError);\n asset.detach = null;\n };\n const handleError = () => {\n if (!current()) return;\n settled = true;\n detach();\n image.removeAttribute(\"src\");\n asset.image = null;\n if (asset.retries < RETRY_DELAYS.length) {\n const delay = RETRY_DELAYS[asset.retries];\n asset.retries += 1;\n asset.timer = setTimeout(() => {\n if (!isCurrent(run)) return;\n asset.timer = null;\n request(asset, run);\n }, delay);\n } else {\n asset.status = \"failed\";\n reconcile();\n }\n };\n const handleLoad = () => {\n if (!current() || decoding) return;\n decoding = true;\n void image.decode().then(() => {\n if (!current()) return;\n settled = true;\n detach();\n asset.loadedUrl = url;\n asset.status = \"ready\";\n reconcile();\n }, handleError);\n };\n asset.detach = detach;\n image.addEventListener(\"load\", handleLoad);\n image.addEventListener(\"error\", handleError);\n image.src = url;\n };\n\n const stop = () => {\n active = false;\n generation += 1;\n for (const asset of assets.values()) {\n if (asset.timer !== null) clearTimeout(asset.timer);\n asset.timer = null;\n asset.detach?.();\n asset.image?.removeAttribute(\"src\");\n asset.image = null;\n asset.status = \"idle\";\n asset.loadedUrl = null;\n asset.retries = 0;\n }\n displayedIndex = null;\n lastState = null;\n };\n\n return {\n start() {\n if (disposed || active) return;\n active = true;\n const run = ++generation;\n reconcile();\n for (const asset of assets.values()) request(asset, run);\n },\n select(level: number) {\n if (disposed || !Number.isFinite(level)) return;\n targetLevel = level;\n reconcile();\n },\n stop,\n dispose() {\n stop();\n disposed = true;\n onChange = null;\n },\n };\n}\n", "export const PREVIEW_MAX_FRAME = 240;\nexport const MAX_LEVEL = 30;\n\nconst LIANG_RANKS = [\"\u5C0F\u96BE\u6881\", \"\u7262\u6881\", \"\u6881\u5B50\", \"\u6881\u5723\", \"\u6881\u795E\", \"\u6881\u7956\"] as const;\n\nexport interface EffortLike {\n id: string;\n name: string;\n description?: string;\n}\n\nexport interface Palette {\n level: number;\n stage: number;\n strength: number;\n page: string;\n base: string;\n layer1: string;\n layer2: string;\n layer3: string;\n sidebar: string;\n ink: string;\n secondary: string;\n tertiary: string;\n border: string;\n accent: string;\n accentHover: string;\n hover: string;\n portraitOpacity: string;\n}\n\ntype Rgb = readonly [number, number, number];\n\ninterface PaletteStop {\n at: number;\n page: Rgb;\n surface: Rgb;\n surface2: Rgb;\n ink: Rgb;\n secondary: Rgb;\n accent: Rgb;\n portraitOpacity: number;\n}\n\n// These are the original calibrator's six stage anchors: pale neutral at the\n// low end, progressively smoked surfaces, then charcoal and antique gold.\nconst STOPS: readonly PaletteStop[] = [\n { at: 0, page: [232, 233, 229], surface: [248, 248, 245], surface2: [238, 239, 235], ink: [23, 24, 22], secondary: [112, 116, 111], accent: [181, 43, 36], portraitOpacity: 0.92 },\n { at: 6, page: [211, 211, 206], surface: [239, 239, 234], surface2: [224, 224, 218], ink: [26, 26, 24], secondary: [101, 101, 96], accent: [181, 43, 36], portraitOpacity: 0.93 },\n { at: 12, page: [171, 168, 162], surface: [211, 208, 201], surface2: [190, 186, 179], ink: [27, 26, 24], secondary: [83, 79, 74], accent: [166, 54, 42], portraitOpacity: 0.94 },\n { at: 18, page: [117, 112, 106], surface: [154, 148, 140], surface2: [128, 122, 115], ink: [24, 22, 20], secondary: [65, 61, 56], accent: [154, 56, 42], portraitOpacity: 0.94 },\n { at: 24, page: [43, 39, 37], surface: [48, 43, 40], surface2: [58, 51, 46], ink: [244, 241, 232], secondary: [184, 180, 169], accent: [181, 92, 54], portraitOpacity: 0.94 },\n { at: 30, page: [17, 17, 17], surface: [30, 27, 25], surface2: [41, 35, 30], ink: [244, 241, 232], secondary: [184, 180, 169], accent: [193, 154, 73], portraitOpacity: 0.94 },\n];\n\nexport function clampFrame(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.min(PREVIEW_MAX_FRAME, Math.max(0, Math.round(value)));\n}\n\nexport function frameForEffort(index: number, count: number): number {\n if (count <= 1 || !Number.isFinite(index)) return 0;\n const safe = Math.min(count - 1, Math.max(0, Math.round(index)));\n return Math.round((safe / (count - 1)) * PREVIEW_MAX_FRAME);\n}\n\nexport function nearestEffortIndex(frame: number, efforts: readonly EffortLike[]): number {\n if (efforts.length === 0) return -1;\n const safe = clampFrame(frame);\n let best = 0;\n let distance = Math.abs(safe - frameForEffort(0, efforts.length));\n for (let index = 1; index < efforts.length; index += 1) {\n const next = Math.abs(safe - frameForEffort(index, efforts.length));\n if (next < distance) {\n best = index;\n distance = next;\n }\n }\n return best;\n}\n\nexport function selectedEffortIndex(\n efforts: readonly EffortLike[],\n selectedId?: string,\n defaultId?: string,\n): number {\n const id = selectedId ?? defaultId;\n return id === undefined ? -1 : efforts.findIndex((effort) => effort.id === id);\n}\n\nexport function portraitBlendForLevel(level: number, anchors: readonly number[]) {\n if (anchors.length === 0) return { lowerIndex: -1, upperIndex: -1, mix: 0 };\n const safeLevel = Number.isFinite(level)\n ? Math.min(anchors[anchors.length - 1], Math.max(anchors[0], level))\n : anchors[0];\n let upperIndex = anchors.findIndex((anchor) => anchor >= safeLevel);\n if (upperIndex < 0) upperIndex = anchors.length - 1;\n const lowerIndex = Math.max(0, upperIndex - (anchors[upperIndex] > safeLevel ? 1 : 0));\n const span = anchors[upperIndex] - anchors[lowerIndex];\n return {\n lowerIndex,\n upperIndex,\n mix: span === 0 ? 0 : (safeLevel - anchors[lowerIndex]) / span,\n };\n}\n\nexport function liangRankForFrame(rawFrame: number): string {\n const level = (clampFrame(rawFrame) / PREVIEW_MAX_FRAME) * MAX_LEVEL;\n const index = level >= MAX_LEVEL ? LIANG_RANKS.length - 1 : Math.floor(level / 6);\n return LIANG_RANKS[Math.min(LIANG_RANKS.length - 1, Math.max(0, index))];\n}\n\nexport function indicatorLabel(rawFrame: number, efforts: readonly EffortLike[]): string {\n const effort = efforts[nearestEffortIndex(rawFrame, efforts)];\n return effort === undefined\n ? liangRankForFrame(rawFrame)\n : `${liangRankForFrame(rawFrame)} \u00B7 ${effort.name}`;\n}\n\nfunction lerp(a: number, b: number, amount: number): number {\n return a + (b - a) * amount;\n}\n\nfunction mix(a: Rgb, b: Rgb, amount: number): Rgb {\n return [\n Math.round(lerp(a[0], b[0], amount)),\n Math.round(lerp(a[1], b[1], amount)),\n Math.round(lerp(a[2], b[2], amount)),\n ];\n}\n\nfunction rgb(value: Rgb, alpha = 1): string {\n return alpha === 1\n ? `rgb(${value[0]} ${value[1]} ${value[2]})`\n : `rgb(${value[0]} ${value[1]} ${value[2]} / ${alpha})`;\n}\n\nexport function paletteForFrame(rawFrame: number): Palette {\n const frame = clampFrame(rawFrame);\n const level = (frame / PREVIEW_MAX_FRAME) * MAX_LEVEL;\n const portraitStage = Math.min(5, Math.floor(level / 6));\n const portraitFrom = STOPS[portraitStage];\n const portraitTo = STOPS[Math.min(5, portraitStage + 1)];\n const portraitAmount = portraitFrom === portraitTo\n ? 0\n : (level - portraitFrom.at) / (portraitTo.at - portraitFrom.at);\n\n // The UI itself is intentionally binary: levels 0\u201323 keep the native light\n // shell; entering the original \u6881\u795E/\u6881\u7956 region at level 24 switches black/gold.\n const dark = level >= 24;\n const stage = dark ? 5 : 0;\n const ui = dark ? STOPS[5] : STOPS[0];\n const page = ui.page;\n const surface = ui.surface;\n const surface2 = ui.surface2;\n const sidebar = mix(page, surface2, 0.25);\n const ink = ui.ink;\n const secondary = ui.secondary;\n // Keep Harness' native blue in the light shell. The original calibrator's\n // antique gold only becomes the product accent once the dark region begins.\n const accent: Rgb = dark ? [193, 154, 73] : [65, 118, 230];\n const accentHover = mix(accent, ink, 0.13);\n\n return {\n level,\n stage,\n strength: level / MAX_LEVEL,\n page: rgb(page),\n // Keep the shell readable while allowing the right-side portrait to remain\n // visibly present. Dense controls use the opaque layer tokens below.\n base: rgb(page, dark ? 0.42 : 0.28),\n layer1: rgb(surface, 0.94),\n layer2: rgb(surface2, 0.96),\n layer3: rgb(surface2, 0.99),\n sidebar: rgb(sidebar, 0.96),\n ink: rgb(ink),\n secondary: rgb(secondary),\n tertiary: rgb(mix(secondary, page, 0.28)),\n border: rgb(ink, dark ? 0.15 : 0.12),\n accent: rgb(accent),\n accentHover: rgb(accentHover),\n hover: rgb(ink, dark ? 0.09 : 0.07),\n portraitOpacity: String(lerp(\n portraitFrom.portraitOpacity,\n portraitTo.portraitOpacity,\n portraitAmount,\n )),\n };\n}\n"], + "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAMO;AACP,sCAAuB;;;ACPvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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", "names": [] } diff --git a/scripts/test-client-compatibility.mjs b/scripts/test-client-compatibility.mjs index 2a13816..fa29e96 100644 --- a/scripts/test-client-compatibility.mjs +++ b/scripts/test-client-compatibility.mjs @@ -11,7 +11,11 @@ const projectRoot = fileURLToPath(new URL("../", import.meta.url)); const packageId = "@leon___/dsh-client-liang-intensity-skin"; const viewport = { width: 1100, height: 700 }; const css = await readFile(new URL("../src/client/skin.css", import.meta.url), "utf8"); -const compiled = await build({ absWorkingDir: projectRoot, entryPoints: ["src/client/appearance.ts"], +const compiled = await build({ absWorkingDir: projectRoot, + stdin: { resolveDir: projectRoot, contents: ` + export { installLiangAppearanceButton } from './src/client/appearance.ts'; + export { paletteForFrame, PREVIEW_MAX_FRAME } from './src/client/logic.ts'; + ` }, bundle: true, write: false, format: "iife", globalName: "LiangAppearance", platform: "browser", target: "es2022" }); async function chromeExecutable() { @@ -232,6 +236,131 @@ async function appearanceChecks(page, hash) { assert.equal(await page.locator(".liang-appearance-choice, .liang-appearance-binding").count(), 0); } +// Account settings are a body portal, not a generic modal. Match the audited +// Host structure while using different build hashes and multiple class names. +async function overlayChecks(context) { + let scenarios = 0; + for (const hash of ["oldAccount42", "_44HXVa"]) { + const { page, errors } = await prepare(context, { enabled: false }); + await page.evaluate(hash => { + const style = document.createElement("style"); + style.textContent = ` + body { --dsw-alias-label-primary: rgb(34 38 42); --dsw-alias-label-secondary: rgb(75 81 87); + --dsw-alias-border-l1: rgb(180 184 188); --dsw-radius-sm: 4px; } + [data-account-host-fixture] { position: fixed; inset: 0; z-index: 1001; + display: flex; flex-direction: column; background: var(--dsw-alias-bg-base); } + html[data-windows-titlebar] [data-account-host-fixture] { padding-top: var(--dsh-windows-titlebar-height); } + [data-account-host-fixture] > header { -webkit-app-region: initial; box-sizing: border-box; + border-bottom: .5px solid var(--dsw-alias-border-l1); flex: none; height: 48px; padding: 6px 16px; } + [data-account-host-fixture] .controls { display: flex; align-items: center; gap: 24px; padding-top: 5px; } + [data-account-host-fixture] button { box-sizing: border-box; border-radius: var(--dsw-radius-sm); + height: 28px; color: var(--dsw-alias-label-secondary); font: inherit; white-space: nowrap; + cursor: pointer; background: transparent; border: none; justify-content: center; align-items: center; + gap: 8px; padding: 0 6px; font-size: 14px; line-height: 22px; display: inline-flex; } + [data-account-host-fixture] > [class*="_viewport"] { min-height: 0; + color: var(--dsw-alias-label-secondary); flex: 1; position: relative; } + [data-native-overlay-sentinel] { position: fixed; left: -2000px; width: 100px; + height: 60px; --dsw-alias-bg-base: rgb(201 183 165); + background: rgb(201 183 165); color: rgb(35 47 59); } + [data-native-overlay-sentinel] > header { background: rgb(190 170 150); } + #nested-account { inset: auto auto 0 -2000px; width: 100px; height: 60px; + --dsw-alias-bg-base: rgb(210 190 170); --dsw-alias-label-secondary: rgb(35 47 59); } + `; + document.querySelector("#liang-styles").before(style); + const accountMarkup = (id, nested = false) => ` + `; + document.querySelector("#root").insertAdjacentHTML("beforeend", accountMarkup("nested-account", true)); + document.body.insertAdjacentHTML("beforeend", ` + + + ${accountMarkup("account-overlay")}`); + window.accountBackClicks = 0; + document.querySelector("#account-back").addEventListener("click", () => window.accountBackClicks++); + window.overlayFixture = { + applyPalette(frame) { + const palette = window.LiangAppearance.paletteForFrame(frame); + const mapping = { page: "page", base: "bg-base", layer1: "layer-1", layer2: "layer-2", + layer3: "layer-3", sidebar: "sidebar", ink: "ink", secondary: "secondary", + tertiary: "tertiary", border: "border", accent: "accent", accentHover: "accent-hover", + hover: "hover", portraitOpacity: "portrait-opacity" }; + for (const [key, variable] of Object.entries(mapping)) { + document.body.style.setProperty(`--liang-${variable}`, palette[key]); + } + document.body.dataset.liangStage = String(palette.stage); + return palette; + }, + snapshot() { + const read = element => { + const computed = getComputedStyle(element); + return { background: computed.backgroundColor, color: computed.color, + base: computed.getPropertyValue("--dsw-alias-bg-base").trim() }; + }; + const overlay = document.querySelector("#account-overlay"); + return { overlay: read(overlay), header: read(overlay.querySelector("header")), + back: read(overlay.querySelector("button")), viewport: read(overlay.querySelector('[class*="_viewport"]')), + sentinels: [...document.querySelectorAll('#ordinary-modal, #ordinary-modal > header, #ordinary-menu, #nested-account, #nested-account > header, #nested-back, #nested-account > [class*="_viewport"]')].map(read) }; + }, + }; + }, hash); + const maxFrame = await page.evaluate(() => window.LiangAppearance.PREVIEW_MAX_FRAME); + for (const { name, frame, stage } of [ + { name: "light", frame: 0, stage: 0 }, + { name: "black-gold", frame: maxFrame, stage: 5 }, + ]) { + await page.evaluate(() => window.fixture.setEnabled(false)); + const palette = await page.evaluate(frame => window.overlayFixture.applyPalette(frame), frame); + assert.equal(palette.stage, stage, "Use the actual internal maximum frame for black-gold"); + const native = await page.evaluate(() => window.overlayFixture.snapshot()); + await page.evaluate(() => window.fixture.setEnabled(true)); + const active = await page.evaluate(() => window.overlayFixture.snapshot()); + const expectedPage = `rgb(${palette.page.match(/\d+/g).join(", ")})`; + const expectedSecondary = `rgb(${palette.secondary.match(/\d+/g).join(", ")})`; + assert.equal(active.overlay.background, expectedPage, `${hash}/${name}: account overlay restores an opaque page surface`); + assert.equal(active.overlay.base, palette.page, `${hash}/${name}: background token is local to the account overlay`); + assert.equal(active.viewport.background, "rgba(0, 0, 0, 0)", "Keep the native transparent viewport"); + assert.equal(active.viewport.base, palette.page, `${hash}/${name}: viewport inherits the locally restored base token`); + assert.equal(active.viewport.color, expectedSecondary, "Keep the native viewport secondary text color"); + assert.equal(active.header.background, "rgba(0, 0, 0, 0)", "Keep the native transparent header"); + assert.equal(active.back.background, "rgba(0, 0, 0, 0)", "Keep the native transparent back button"); + assert.equal(active.back.color, expectedSecondary, `${hash}/${name}: back icon and label use the native secondary text token`); + assert.equal(await page.locator("#account-overlay > header").evaluate(element => getComputedStyle(element).height), "48px"); + assert.equal(await page.locator("#account-back").evaluate(element => getComputedStyle(element).height), "28px"); + const pageRgb = palette.page.match(/\d+/g).map(Number); + closeColor(await pixelAt(page, 500, 20), pageRgb, `${hash}/${name}: header paints over the opaque account surface`); + closeColor(await pixelAt(page, 1070, 430), pageRgb, `${hash}/${name}: portrait does not bleed through the account viewport`); + assert.deepEqual(active.sentinels, native.sentinels, "Account styles must not affect ordinary modals, menus, or non-body portals"); + const clicks = await page.evaluate(() => window.accountBackClicks); + await page.locator("#account-back").click(); + assert.equal(await page.evaluate(() => window.accountBackClicks), clicks + 1, "Native back button remains clickable"); + await page.evaluate(() => window.fixture.setEnabled(false)); + assert.deepEqual(await page.evaluate(() => window.overlayFixture.snapshot()), native, "Disabling the skin restores every native overlay style"); + scenarios++; + console.log(`PASS account overlay ${hash}/${name}: opaque portal, native header/back, ordinary modal/menu boundaries, skin-off restore`); + } + assert.deepEqual(errors, [], `${hash}: overlay browser errors`); + await page.close(); + } + return scenarios; +} + const browser = await chromium.launch({ executablePath: await chromeExecutable(), headless: true }); try { const context = await browser.newContext({ viewport, reducedMotion: "reduce", deviceScaleFactor: 1 }); @@ -262,7 +391,9 @@ try { assert.equal(await second.page.evaluate(() => window.fixture.snapshot().enabled), true); closeColor(await pixelAt(first.page, 1070, 430), [240, 244, 248], "First window skin off"); closeColor(await pixelAt(second.page, 1070, 430), [24, 156, 72], "Second window remains visible"); - assert.deepEqual([...first.errors, ...second.errors], []); await context.close(); + assert.deepEqual([...first.errors, ...second.errors], []); + const overlayScenarios = await overlayChecks(context); + await context.close(); console.log("PASS independent windows: host geometry and appearance state stay isolated"); - console.log("Client compatibility browser checks passed (11 scenarios). Native Desktop validation remains manual."); + console.log(`Client compatibility browser checks passed (${cases.length + 3 + overlayScenarios} scenarios). Native Desktop validation remains manual.`); } finally { await browser.close(); } diff --git a/src/client/skin.css b/src/client/skin.css index 2414cf7..db29648 100644 --- a/src/client/skin.css +++ b/src/client/skin.css @@ -225,6 +225,13 @@ body[data-liang-skin="on"]:is( top: var(--dsh-desktop-frame-height, 0px); } +/* Native account pages are full-screen body portals. Their header and loading + surface must stay opaque when the chat background token is transparent. */ +body[data-liang-skin="on"] > [role="dialog"][aria-modal="true"][class*="_overlay"]:has(> header[data-window-drag] button[class*="_back"]):has(> [class*="_viewport"]) { + --dsw-alias-bg-base: var(--liang-page, #fff) !important; + background: var(--liang-page, #fff) !important; +} + /* Windows paints the sidebar fill across the host frame, covering the portrait. Match macOS's transparent frame while keeping the sidebar, caption, and conversation surfaces painted by their own host rules. */