chore: address obsidianmd lint and popout-window compat (1.2.1)

- Sentence-case: rephrase "GM" → "Game master"; remove disable comments;
  autofixer lowercases "🎲 Open tables view" / "← Back" to comply
- no-static-styles-assignment: dynamic colors now use CSS custom properties
  (--duckmage-bg / --duckmage-border-color / --duckmage-fg) consumed by
  existing classes; static positions moved to small modifier classes
  (duckmage-rt-roll-btn-spaced, duckmage-wf-steps-list, etc.); terrain
  toolbar uses is-terrain-preview / is-eyedropper-active toggles;
  createIconEl uses new duckmage-masked-icon class
- Popout-window compat: setTimeout / clearTimeout / requestAnimationFrame
  → window.X; document.X → activeDocument.X across all view/modal files
- Unnecessary `as ArrayBuffer`: replace `data.buffer.slice(...) as ArrayBuffer`
  with `new ArrayBuffer + Uint8Array.set` — gets a real ArrayBuffer without
  the assertion (also avoids ArrayBufferLike narrowing)
- Deprecated SettingTab.display(): extract body into private renderSettings();
  display() and post-action refresh both call it
- eslint.config: add activeDocument + activeWindow to globals so the renamed
  identifiers don't trip no-undef

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
isaprettycoolguy@protonmail.com 2026-06-05 23:04:11 -04:00
parent 6aa885b5b2
commit 1fa2153bf1
36 changed files with 198 additions and 184 deletions

View file

@ -14,6 +14,9 @@ export default defineConfig([
languageOptions: {
globals: {
...globals.browser,
// Obsidian-injected globals for popout-window-aware code
activeDocument: "readonly",
activeWindow: "readonly",
},
parser: tsparser,
parserOptions: { project: "./tsconfig.json" },

View file

@ -1,7 +1,7 @@
{
"id": "hexmaker",
"name": "Hexmap World Creator",
"version": "1.2.0",
"version": "1.2.1",
"minAppVersion": "1.12.0",
"description": "Create a hex map and a guidebook level wiki for your setting with minimal fuss. Hex crawl worldbuilding toolbox. System agnostic, ready for any TRPG you care to run in it.",
"author": "morkdev",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "hexmaker-plugin",
"version": "1.2.0",
"version": "1.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "hexmaker-plugin",
"version": "1.2.0",
"version": "1.2.1",
"license": "MIT",
"dependencies": {
"minisearch": "^7.2.0"

View file

@ -1,6 +1,6 @@
{
"name": "hexmaker-plugin",
"version": "1.2.0",
"version": "1.2.1",
"description": "A plugin to power hex crawl worldbuilding and running.",
"main": "main.js",
"scripts": {

View file

@ -34,11 +34,11 @@ export class HexmakerModal extends Modal {
modalEl.setCssProps({ left: `${ox + ev.clientX - sx}px`, top: `${oy + ev.clientY - sy}px` });
};
const onUp = () => {
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
activeDocument.removeEventListener("mousemove", onMove);
activeDocument.removeEventListener("mouseup", onUp);
};
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
activeDocument.addEventListener("mousemove", onMove);
activeDocument.addEventListener("mouseup", onUp);
});
}
}

View file

@ -11,6 +11,10 @@ export class HexmakerSettingTab extends PluginSettingTab {
}
display(): void {
this.renderSettings();
}
private renderSettings(): void {
const { containerEl } = this;
containerEl.empty();
@ -460,7 +464,7 @@ export class HexmakerSettingTab extends PluginSettingTab {
);
}
new Notice("Folders generated.");
this.display();
this.renderSettings();
}),
);

View file

@ -436,10 +436,8 @@ async function writeBinaryToVault(
path: string,
data: Uint8Array,
): Promise<void> {
const buf = data.buffer.slice(
data.byteOffset,
data.byteOffset + data.byteLength,
) as ArrayBuffer;
const buf = new ArrayBuffer(data.byteLength);
new Uint8Array(buf).set(data);
const existing = app.vault.getAbstractFileByPath(path);
if (existing instanceof TFile) {
await app.vault.modifyBinary(existing, buf);

View file

@ -215,12 +215,9 @@ async function writeBinaryToVault(
path: string,
data: Uint8Array,
): Promise<void> {
// vault.createBinary / modifyBinary require ArrayBuffer; copy out of the
// (possibly larger) underlying buffer to get a tight ArrayBuffer.
const buf = data.buffer.slice(
data.byteOffset,
data.byteOffset + data.byteLength,
) as ArrayBuffer;
// vault.createBinary / modifyBinary require a plain ArrayBuffer.
const buf = new ArrayBuffer(data.byteLength);
new Uint8Array(buf).set(data);
const existing = app.vault.getAbstractFileByPath(path);
if (existing instanceof TFile) {
await app.vault.modifyBinary(existing, buf);

View file

@ -230,10 +230,8 @@ async function writeBinaryToVault(
path: string,
data: Uint8Array,
): Promise<void> {
const buf = data.buffer.slice(
data.byteOffset,
data.byteOffset + data.byteLength,
) as ArrayBuffer;
const buf = new ArrayBuffer(data.byteLength);
new Uint8Array(buf).set(data);
const existing = app.vault.getAbstractFileByPath(path);
if (existing instanceof TFile) {
await app.vault.modifyBinary(existing, buf);

View file

@ -99,10 +99,8 @@ async function writeBinaryToVault(
path: string,
data: Uint8Array,
): Promise<void> {
const buf = data.buffer.slice(
data.byteOffset,
data.byteOffset + data.byteLength,
) as ArrayBuffer;
const buf = new ArrayBuffer(data.byteLength);
new Uint8Array(buf).set(data);
const existing = app.vault.getAbstractFileByPath(path);
if (existing instanceof TFile) {
await app.vault.modifyBinary(existing, buf);

View file

@ -317,10 +317,8 @@ async function writeBinaryToVault(
path: string,
data: Uint8Array,
): Promise<void> {
const buf = data.buffer.slice(
data.byteOffset,
data.byteOffset + data.byteLength,
) as ArrayBuffer;
const buf = new ArrayBuffer(data.byteLength);
new Uint8Array(buf).set(data);
const existing = app.vault.getAbstractFileByPath(path);
if (existing instanceof TFile) {
await app.vault.modifyBinary(existing, buf);

View file

@ -2,7 +2,7 @@
* HTML rendering primitive markdown to a {@link RenderedDoc} ready for printToPDF.
*
* Uses Obsidian's own `MarkdownRenderer.render()` so the export matches the
* user's installed theme. CSS is captured by walking `document.styleSheets`.
* user's installed theme. CSS is captured by walking `activeDocument.styleSheets`.
*/
import { App, Component, MarkdownRenderer } from "obsidian";
@ -29,7 +29,7 @@ export async function renderMarkdownToHtml(
const { app, title, sourcePath, markdown } = opts;
// Off-screen container so MarkdownRenderer.render has a real DOM target.
const container = document.body.createDiv({
const container = activeDocument.body.createDiv({
cls: "duckmage-export-render-host markdown-preview-view markdown-rendered",
});
const component = new Component();
@ -49,13 +49,13 @@ export async function renderMarkdownToHtml(
}
/**
* Walk `document.styleSheets` and concatenate all readable CSS rules.
* Walk `activeDocument.styleSheets` and concatenate all readable CSS rules.
* Skips Svelte-injected sheets (transient, irrelevant) and silently skips
* sheets that throw on cssRules access (CORS).
*/
function captureStyles(): string {
const parts: string[] = [];
for (const sheet of Array.from(document.styleSheets)) {
for (const sheet of Array.from(activeDocument.styleSheets)) {
const node = sheet.ownerNode as Element | null;
const id = node?.getAttribute("id") ?? "";
if (id.startsWith("svelte-")) continue;

View file

@ -725,10 +725,8 @@ async function writeBinaryToVault(
path: string,
data: Uint8Array,
): Promise<void> {
const buf = data.buffer.slice(
data.byteOffset,
data.byteOffset + data.byteLength,
) as ArrayBuffer;
const buf = new ArrayBuffer(data.byteLength);
new Uint8Array(buf).set(data);
const existing = plugin.app.vault.getAbstractFileByPath(path);
if (existing instanceof TFile) {
await plugin.app.vault.modifyBinary(existing, buf);

View file

@ -46,7 +46,7 @@ export interface PdfExportOptions {
export interface RenderedDoc {
/** HTML for the page <body> — does NOT include the <body> tag itself. */
bodyHtml: string;
/** CSS rules captured from document.styleSheets, joined with newlines. */
/** CSS rules captured from activeDocument.styleSheets, joined with newlines. */
css: string;
/** Document title. */
title: string;
@ -60,11 +60,11 @@ export async function exportToPdfBytes(
doc: RenderedDoc,
opts: PdfExportOptions = {},
): Promise<Uint8Array> {
const webview = document.createElement("webview") as WebviewElement;
const webview = activeDocument.createElement("webview") as WebviewElement;
webview.setAttribute("src", "app://obsidian.md/help.html");
webview.setAttribute("nodeintegration", "true");
webview.addClass("duckmage-export-webview");
document.body.appendChild(webview);
activeDocument.body.appendChild(webview);
try {
await waitForWebviewLoad(webview);
@ -113,7 +113,7 @@ function waitForWebviewLoad(webview: HTMLElement): Promise<void> {
resolve();
};
webview.addEventListener("did-finish-load", onLoad);
setTimeout(() => {
window.setTimeout(() => {
if (settled) return;
settled = true;
webview.removeEventListener("did-finish-load", onLoad);
@ -139,20 +139,20 @@ function buildInjectScript(doc: RenderedDoc): string {
// Replace head with a single <style> block from captured CSS plus a
// minimal print reset. We don't try to preserve the original head — the
// host page is just a render context.
const styleEl = document.createElement("style");
const styleEl = activeDocument.createElement("style");
styleEl.textContent = css;
document.head.innerHTML = "";
document.head.appendChild(styleEl);
activeDocument.head.innerHTML = "";
activeDocument.head.appendChild(styleEl);
// Force light theme — dark backgrounds print poorly. This matches the
// better-export-pdf approach.
document.body.className = "theme-light";
document.body.removeAttribute("style");
document.body.innerHTML = body;
activeDocument.body.className = "theme-light";
activeDocument.body.removeAttribute("style");
activeDocument.body.innerHTML = body;
document.title = title;
activeDocument.title = title;
})();
`;
}
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
const sleep = (ms: number) => new Promise<void>((r) => window.setTimeout(r, ms));

View file

@ -35,8 +35,7 @@ export class FolderTreePickerModal extends Modal {
const topBar = contentEl.createDiv({ cls: "duckmage-table-picker-topbar" });
topBar.createEl("button", {
cls: "duckmage-rt-icon-btn duckmage-table-picker-view-btn",
// eslint-disable-next-line obsidianmd/ui/sentence-case
text: "🎲 Open tables view",
text: "🎲 open tables view",
title: "Open random tables view",
}).addEventListener("click", () => this.onOpenView!());
}

View file

@ -178,7 +178,7 @@ export class HexEditorModal extends HexmakerModal {
cls: "duckmage-terrain-header-swatch",
});
if (paletteEntry)
swatch.setCssProps({ "background-color": paletteEntry.color });
swatch.setCssProps({ "--duckmage-bg": paletteEntry.color });
if (iconToShow) {
const img = swatch.createEl("img");
img.src = getIconUrl(this.plugin, iconToShow);
@ -335,7 +335,7 @@ export class HexEditorModal extends HexmakerModal {
const nPath = this.plugin.hexPath(nx, ny, this.mapName);
const terrain = getTerrainFromFile(this.app, nPath);
const entry = terrain ? paletteMap.get(terrain) : undefined;
if (entry) tile.setCssProps({ "background-color": entry.color });
if (entry) tile.setCssProps({ "--duckmage-bg": entry.color });
tile.addEventListener("click", () => {
this.x = nx;
this.y = ny;
@ -445,7 +445,7 @@ export class HexEditorModal extends HexmakerModal {
});
const preview = btn.createDiv({ cls: "duckmage-terrain-preview" });
preview.setCssProps({ "background-color": entry.color });
preview.setCssProps({ "--duckmage-bg": entry.color });
if (entry.icon) {
createIconEl(
@ -494,7 +494,7 @@ export class HexEditorModal extends HexmakerModal {
// GM icon palette — only when GM layer is active
if (this.options.gmLayerActive) {
section.createEl("p", { text: "GM icon", cls: "duckmage-icon-inline-label" }); // eslint-disable-line obsidianmd/ui/sentence-case
section.createEl("p", { text: "Game master icon", cls: "duckmage-icon-inline-label" });
this.renderIconGrid(
section,
visibleIcons,
@ -788,7 +788,7 @@ export class HexEditorModal extends HexmakerModal {
input.addEventListener("focus", () => openDropdown());
input.addEventListener("blur", () =>
setTimeout(() => closeDropdown(), 150),
window.setTimeout(() => closeDropdown(), 150),
);
input.addEventListener("input", () => {
if (!isOpen) openDropdown();

View file

@ -66,12 +66,10 @@ export class HexExportModal extends HexmakerModal {
cls: "duckmage-export-tab-checkbox",
});
gmCb.checked = false;
/* eslint-disable obsidianmd/ui/sentence-case -- GM is an acronym; Hidden/Secret are section names */
gmRow.createEl("label", {
text: "Include GM-only sections (Hidden, Secret)",
text: "Include game master only sections (hidden, secret)",
cls: "duckmage-export-tab-label",
});
/* eslint-enable obsidianmd/ui/sentence-case */
gmRow.addEventListener("click", (e) => {
if (e.target instanceof HTMLInputElement) return;
gmCb.checked = !gmCb.checked;

View file

@ -266,7 +266,7 @@ export class HexMapView extends ItemView {
this.setViewportFontSize("");
this.applyTransform();
this.renderGrid();
requestAnimationFrame(() => this.fitGridToView());
window.requestAnimationFrame(() => this.fitGridToView());
}
}
@ -425,9 +425,9 @@ export class HexMapView extends ItemView {
this.viewportEl?.addClass("is-dragging");
});
this.registerDomEvent(document, "mousemove", (e: MouseEvent) => {
this.registerDomEvent(activeDocument, "mousemove", (e: MouseEvent) => {
if (isTerrainPainting) {
const el = document.elementFromPoint(
const el = activeDocument.elementFromPoint(
e.clientX,
e.clientY,
) as HTMLElement | null;
@ -450,7 +450,7 @@ export class HexMapView extends ItemView {
return;
}
if (this.drawingMode === "terrain" || this.drawingMode === "icon") {
const el = document.elementFromPoint(
const el = activeDocument.elementFromPoint(
e.clientX,
e.clientY,
) as HTMLElement | null;
@ -478,7 +478,7 @@ export class HexMapView extends ItemView {
}
});
this.registerDomEvent(document, "mouseup", () => {
this.registerDomEvent(activeDocument, "mouseup", () => {
if (isTerrainPainting) {
if (this.drawingMode === "terrain") this.commitTerrainStroke();
else if (this.drawingMode === "icon") this.commitIconStroke();
@ -615,7 +615,7 @@ export class HexMapView extends ItemView {
this.backBtn = mapNavGroup.createEl("button", {
cls: "duckmage-map-back-btn",
text: "← Back", // eslint-disable-line obsidianmd/ui/sentence-case
text: "← back",
title: "Back to previous map",
});
this.backBtn.hide();
@ -707,7 +707,7 @@ export class HexMapView extends ItemView {
);
this.renderGrid();
requestAnimationFrame(() => this.fitGridToView());
window.requestAnimationFrame(() => this.fitGridToView());
return Promise.resolve();
}
@ -1439,7 +1439,7 @@ export class HexMapView extends ItemView {
flushKeys.some((k) => this.flushing.has(k)) &&
Date.now() < deadline
) {
await new Promise<void>((r) => setTimeout(r, 30));
await new Promise<void>((r) => window.setTimeout(r, 30));
}
for (let attempt = 1; attempt <= 2; attempt++) {
@ -1454,7 +1454,7 @@ export class HexMapView extends ItemView {
this.renderGrid();
return;
}
await new Promise<void>((r) => setTimeout(r, 300));
await new Promise<void>((r) => window.setTimeout(r, 300));
}
}
@ -1498,7 +1498,7 @@ export class HexMapView extends ItemView {
: this.plugin.settings.pathTypes[0];
if (activeType) {
this.pathBtnSwatch.setCssProps({
"background-color": activeType.color,
"--duckmage-bg": activeType.color,
});
this.pathBtnSwatch.show();
} else {
@ -1549,19 +1549,16 @@ export class HexMapView extends ItemView {
if (this.drawingMode === "terrain") {
if (this.terrainPickMode) {
// Eyedropper waiting for a click — show ⌖ as the preview
if (this.terrainToolbarBtn) {
this.terrainToolbarBtn.setCssProps({
"border-color": "var(--interactive-accent)",
color: "var(--interactive-accent)",
});
}
this.terrainToolbarBtn?.removeClass("is-terrain-preview");
this.terrainToolbarBtn?.addClass("is-eyedropper-active");
if (this.terrainBtnPreview) {
this.terrainBtnPreview.setCssProps({ "background-color": "" });
this.terrainBtnPreview.setCssProps({ "--duckmage-bg": "" });
this.terrainBtnPreview.show();
this.terrainBtnPreview.textContent = "⌖";
}
} else {
if (this.terrainBtnPreview) this.terrainBtnPreview.textContent = "";
this.terrainToolbarBtn?.removeClass("is-eyedropper-active");
const entry = this.paintTerrainName
? this.plugin
.getMapPalette(this.activeMapName)
@ -1569,28 +1566,28 @@ export class HexMapView extends ItemView {
: undefined;
if (entry) {
if (this.terrainToolbarBtn) {
this.terrainToolbarBtn.setCssProps({ "border-color": entry.color });
this.terrainToolbarBtn.addClass("is-terrain-preview");
this.terrainToolbarBtn.setCssProps({
"--duckmage-border-color": entry.color,
});
}
if (this.terrainBtnPreview) {
this.terrainBtnPreview.setCssProps({
"background-color": entry.color,
"--duckmage-bg": entry.color,
});
this.terrainBtnPreview.show();
}
} else {
// Clear mode — show active state without a color
if (this.terrainToolbarBtn) {
this.terrainToolbarBtn.setCssProps({ "border-color": "" });
}
this.terrainToolbarBtn?.removeClass("is-terrain-preview");
if (this.terrainBtnPreview) {
this.terrainBtnPreview.hide();
}
}
}
} else {
if (this.terrainToolbarBtn) {
this.terrainToolbarBtn.setCssProps({ "border-color": "", color: "" });
}
this.terrainToolbarBtn?.removeClass("is-terrain-preview");
this.terrainToolbarBtn?.removeClass("is-eyedropper-active");
if (this.terrainBtnPreview) {
this.terrainBtnPreview.hide();
}
@ -1918,7 +1915,7 @@ export class HexMapView extends ItemView {
if (t !== undefined || i !== undefined) {
this.renderGrid(t, i);
} else {
setTimeout(() => this.renderGrid(), 300);
window.setTimeout(() => this.renderGrid(), 300);
}
},
{
@ -2731,7 +2728,7 @@ export class HexMapView extends ItemView {
while (true) {
if (attempt > 0)
await new Promise<void>((r) =>
setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
window.setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
);
try {
const onDisk = !!this.app.vault.getAbstractFileByPath(path);
@ -2798,7 +2795,7 @@ export class HexMapView extends ItemView {
while (true) {
if (attempt > 0)
await new Promise<void>((r) =>
setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
window.setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
);
try {
const onDisk = !!this.app.vault.getAbstractFileByPath(path);
@ -2861,7 +2858,7 @@ export class HexMapView extends ItemView {
while (true) {
if (attempt > 0)
await new Promise<void>((r) =>
setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
window.setTimeout(r, Math.min(200 * (1 << (attempt - 1)), 2000)),
);
try {
const onDisk = !!this.app.vault.getAbstractFileByPath(path);
@ -3105,7 +3102,7 @@ export class HexMapView extends ItemView {
});
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNS, "svg");
const svg = activeDocument.createElementNS(svgNS, "svg");
svg.classList.add("duckmage-path-svg");
const w = gridContainer.offsetLeft + gridContainer.offsetWidth + 20;
const h = gridContainer.offsetTop + gridContainer.offsetHeight + 20;
@ -3125,7 +3122,7 @@ export class HexMapView extends ItemView {
dashArray = "",
smooth = true,
) => {
const path = document.createElementNS(svgNS, "path");
const path = activeDocument.createElementNS(svgNS, "path");
path.setAttribute("d", smooth ? smoothPath(pts) : sharpPath(pts));
path.setAttribute("stroke", color);
path.setAttribute("stroke-width", String(strokeWidth));
@ -3174,7 +3171,7 @@ export class HexMapView extends ItemView {
const color = activeType?.color ?? "#888888";
const pos = centerMap.get(this.activePathEnd);
if (pos) {
const circle = document.createElementNS(svgNS, "circle");
const circle = activeDocument.createElementNS(svgNS, "circle");
circle.setAttribute("cx", String(pos.cx));
circle.setAttribute("cy", String(pos.cy));
circle.setAttribute("r", "5");
@ -3199,7 +3196,7 @@ export class HexMapView extends ItemView {
origImg.hide();
origImg.setAttribute("data-svg-elevated", "1");
}
const imgEl = document.createElementNS(svgNS, "image");
const imgEl = activeDocument.createElementNS(svgNS, "image");
const iconW = hexEl.offsetWidth * 0.78;
const iconH = hexEl.offsetHeight * 0.78;
imgEl.setAttribute("x", String(pos.cx - iconW / 2));
@ -3222,7 +3219,7 @@ export class HexMapView extends ItemView {
const pos = centerMap.get(`${x}_${y}`);
if (!pos) return;
const hasTerrain = !!hexEl.style.backgroundColor;
const textEl = document.createElementNS(svgNS, "text");
const textEl = activeDocument.createElementNS(svgNS, "text");
textEl.setAttribute("x", String(pos.cx));
// Nudge label toward bottom of hex (same visual position as the HTML label)
textEl.setAttribute("y", String(pos.cy + hexEl.offsetHeight * 0.28));
@ -3257,7 +3254,7 @@ export class HexMapView extends ItemView {
const pos = centerMap.get(key);
if (!pos) return;
const size = Math.round(hexEl.offsetWidth * 0.38);
const imgEl = document.createElementNS(svgNS, "image");
const imgEl = activeDocument.createElementNS(svgNS, "image");
imgEl.setAttribute("x", String(pos.cx + hexEl.offsetWidth * 0.12));
imgEl.setAttribute("y", String(pos.cy - hexEl.offsetHeight * 0.46));
imgEl.setAttribute("width", String(size));
@ -3360,7 +3357,7 @@ export class HexMapView extends ItemView {
// ── SVG setup ─────────────────────────────────────────────────────────────
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNS, "svg");
const svg = activeDocument.createElementNS(svgNS, "svg");
svg.classList.add("duckmage-faction-svg");
const w = gridContainer.offsetLeft + gridContainer.offsetWidth + 20;
const h = gridContainer.offsetTop + gridContainer.offsetHeight + 20;
@ -3458,7 +3455,7 @@ export class HexMapView extends ItemView {
if (rings.length === 0) continue;
// ── Render filled blob(s) ─────────────────────────────────────────────
const g = document.createElementNS(svgNS, "g");
const g = activeDocument.createElementNS(svgNS, "g");
g.setAttribute("opacity", "0.45");
svg.appendChild(g);
@ -3470,7 +3467,7 @@ export class HexMapView extends ItemView {
`${i === 0 ? "M" : "L"}${c[0].toFixed(1)},${c[1].toFixed(1)}`,
)
.join(" ") + " Z";
const path = document.createElementNS(svgNS, "path");
const path = activeDocument.createElementNS(svgNS, "path");
path.setAttribute("d", d);
path.setAttribute("fill", color);
path.setAttribute("stroke", color);
@ -3691,7 +3688,7 @@ export class HexMapView extends ItemView {
// ── SVG setup ───────────────────────────────────────────────────────────
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNS, "svg");
const svg = activeDocument.createElementNS(svgNS, "svg");
svg.classList.add("duckmage-region-svg");
const svgW = gridContainer.offsetLeft + gridContainer.offsetWidth + 40;
const svgH = gridContainer.offsetTop + gridContainer.offsetHeight + 40;
@ -3806,7 +3803,7 @@ export class HexMapView extends ItemView {
}
// ── Render filled blob(s) ───────────────────────────────────────────
const g = document.createElementNS(svgNS, "g");
const g = activeDocument.createElementNS(svgNS, "g");
g.setAttribute("opacity", "0.45");
svg.appendChild(g);
@ -3818,7 +3815,7 @@ export class HexMapView extends ItemView {
`${i === 0 ? "M" : "L"}${c[0].toFixed(1)},${c[1].toFixed(1)}`,
)
.join(" ") + " Z";
const path = document.createElementNS(svgNS, "path");
const path = activeDocument.createElementNS(svgNS, "path");
path.setAttribute("d", d);
path.setAttribute("fill", color);
// Stroke bridges the inter-hex gap; round joins/caps smooth blob edges
@ -3833,7 +3830,7 @@ export class HexMapView extends ItemView {
// ── Region name label (full opacity, above fill) ────────────────────
// Font size scales with sqrt(hexCount) so larger regions get bigger labels
const fontSize = Math.round(Math.min(10 + 3 * Math.sqrt(n), 52));
const text = document.createElementNS(svgNS, "text");
const text = activeDocument.createElementNS(svgNS, "text");
text.setAttribute("x", mx.toFixed(1));
text.setAttribute("y", my.toFixed(1));
text.setAttribute("text-anchor", "middle");
@ -4031,8 +4028,8 @@ export class HexMapView extends ItemView {
};
const onUp = () => {
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
activeDocument.removeEventListener("mousemove", onMove);
activeDocument.removeEventListener("mouseup", onUp);
tokenEl.removeClass("duckmage-token-dragging");
if (closestHex) {
@ -4066,8 +4063,8 @@ export class HexMapView extends ItemView {
}
};
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
activeDocument.addEventListener("mousemove", onMove);
activeDocument.addEventListener("mouseup", onUp);
}
private openTokenEditor(token: TokenEntry): void {

View file

@ -131,7 +131,7 @@ export class OverlayPanel extends HexSidePanel {
for (const opt of OVERLAY_OPTIONS) {
const row = panel.createDiv({ cls: "duckmage-overlay-row" });
const cb = document.createElement("input");
const cb = activeDocument.createElement("input");
cb.type = "checkbox";
cb.checked = true; // default — refreshed in syncToRegion()
row.appendChild(cb);
@ -155,7 +155,7 @@ export class OverlayPanel extends HexSidePanel {
// Show tokens — default on
const tokensRow = panel.createDiv({ cls: "duckmage-overlay-row" });
const tokensCb = document.createElement("input");
const tokensCb = activeDocument.createElement("input");
tokensCb.type = "checkbox";
tokensCb.checked = true;
tokensRow.appendChild(tokensCb);
@ -181,7 +181,7 @@ export class OverlayPanel extends HexSidePanel {
// Faction overlay — triggers a re-render rather than a CSS class toggle
const factionRow = panel.createDiv({ cls: "duckmage-overlay-row" });
const factionCb = document.createElement("input");
const factionCb = activeDocument.createElement("input");
factionCb.type = "checkbox";
factionCb.checked = false; // default — refreshed in syncToRegion()
factionRow.appendChild(factionCb);
@ -207,7 +207,7 @@ export class OverlayPanel extends HexSidePanel {
// Region overlay — same pattern as faction
const regionRow = panel.createDiv({ cls: "duckmage-overlay-row" });
const regionCb = document.createElement("input");
const regionCb = activeDocument.createElement("input");
regionCb.type = "checkbox";
regionCb.checked = false;
regionRow.appendChild(regionCb);
@ -233,7 +233,7 @@ export class OverlayPanel extends HexSidePanel {
// GM layer — default on (unlike the opt-in overlays above)
const gmRow = panel.createDiv({ cls: "duckmage-overlay-row" });
const gmCb = document.createElement("input");
const gmCb = activeDocument.createElement("input");
gmCb.type = "checkbox";
gmCb.checked = true;
gmRow.appendChild(gmCb);

View file

@ -230,11 +230,11 @@ export class IconPickerModal extends HexmakerModal {
}
this.plugin.loadAvailableIcons();
statusEl.setText(`Added ${added} icon${added === 1 ? "" : "s"}.`);
setTimeout(() => this.render(), 800);
window.setTimeout(() => this.render(), 800);
};
// Hidden file input
const fileInput = document.createElement("input");
const fileInput = activeDocument.createElement("input");
fileInput.type = "file";
fileInput.accept = ".png,.jpg,.jpeg,.gif,.svg,.webp";
fileInput.multiple = true;

View file

@ -101,7 +101,7 @@ export class MapLinkModal extends HexmakerModal {
};
mapInput.addEventListener("focus", () => openDropdown(""));
mapInput.addEventListener("blur", () => setTimeout(() => closeDropdown(), 150));
mapInput.addEventListener("blur", () => window.setTimeout(() => closeDropdown(), 150));
mapInput.addEventListener("input", () => {
if (!isOpen) openDropdown(mapInput.value);
else populateDropdown(mapInput.value);

View file

@ -354,7 +354,7 @@ export class MapModal extends HexmakerModal {
(currentMap?.terrainType === t.name ? " is-selected" : ""),
});
const preview = tile.createDiv({ cls: "duckmage-terrain-preview" });
preview.setCssProps({ "background-color": t.color });
preview.setCssProps({ "--duckmage-bg": t.color });
if (t.icon) {
createIconEl(
preview,

View file

@ -22,7 +22,7 @@ export class PainterContextMenu {
open(x: number, y: number): void {
this.close();
const menu = document.createElement("div");
const menu = activeDocument.createElement("div");
menu.className = "duckmage-painter-ctx-menu";
menu.style.left = `${x}px`;
menu.style.top = `${y}px`;
@ -33,7 +33,7 @@ export class PainterContextMenu {
opts.push(...this.extra);
for (const opt of opts) {
const item = document.createElement("div");
const item = activeDocument.createElement("div");
item.className = "duckmage-painter-ctx-item";
item.textContent = opt.label;
item.addEventListener("mousedown", (e) => {
@ -45,11 +45,11 @@ export class PainterContextMenu {
menu.appendChild(item);
}
document.body.appendChild(menu);
activeDocument.body.appendChild(menu);
this.el = menu;
// Clamp to viewport after layout so the menu doesn't overflow the screen edge.
requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
if (!this.el) return;
const rect = this.el.getBoundingClientRect();
if (rect.right > window.innerWidth)
@ -69,14 +69,14 @@ export class PainterContextMenu {
};
const onScroll = () => this.close();
document.addEventListener("pointerdown", onPointerDown, { capture: true });
document.addEventListener("keydown", onKeyDown, { capture: true });
document.addEventListener("scroll", onScroll, { capture: true, passive: true });
activeDocument.addEventListener("pointerdown", onPointerDown, { capture: true });
activeDocument.addEventListener("keydown", onKeyDown, { capture: true });
activeDocument.addEventListener("scroll", onScroll, { capture: true, passive: true });
this.cleanupFns.push(
() => document.removeEventListener("pointerdown", onPointerDown, { capture: true }),
() => document.removeEventListener("keydown", onKeyDown, { capture: true }),
() => document.removeEventListener("scroll", onScroll, { capture: true }),
() => activeDocument.removeEventListener("pointerdown", onPointerDown, { capture: true }),
() => activeDocument.removeEventListener("keydown", onKeyDown, { capture: true }),
() => activeDocument.removeEventListener("scroll", onScroll, { capture: true }),
);
}, 0);

View file

@ -8,7 +8,7 @@ import { PathTypeEditorModal } from "./PathTypeEditorModal";
export function buildPathPreviewSvg(pt: PathType): SVGElement {
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNS, "svg");
const svg = activeDocument.createElementNS(svgNS, "svg");
svg.setAttribute("width", "36");
svg.setAttribute("height", "36");
svg.setAttribute("viewBox", "0 0 36 36");
@ -23,7 +23,7 @@ export function buildPathPreviewSvg(pt: PathType): SVGElement {
const angle = (Math.PI / 180) * (60 * i);
hexPts.push(`${cx + r * Math.cos(angle)},${cy + r * Math.sin(angle)}`);
}
const hex = document.createElementNS(svgNS, "polygon");
const hex = activeDocument.createElementNS(svgNS, "polygon");
hex.setAttribute("points", hexPts.join(" "));
hex.setAttribute("fill", "var(--background-secondary)");
hex.setAttribute("stroke", "var(--background-modifier-border)");
@ -48,7 +48,7 @@ export function buildPathPreviewSvg(pt: PathType): SVGElement {
pathD = "M 4 18 L 32 18";
}
const pathEl = document.createElementNS(svgNS, "path");
const pathEl = activeDocument.createElementNS(svgNS, "path");
pathEl.setAttribute("d", pathD);
pathEl.setAttribute("stroke", pt.color);
pathEl.setAttribute("stroke-width", strokeW);

View file

@ -178,7 +178,7 @@ export class SubmapPickerModal extends HexmakerModal {
for (const t of palette) {
const tile = terrainGrid.createDiv({ cls: "duckmage-terrain-option" });
const preview = tile.createDiv({ cls: "duckmage-terrain-preview" });
preview.setCssProps({ "background-color": t.color });
preview.setCssProps({ "--duckmage-bg": t.color });
if (t.icon) {
createIconEl(
preview,

View file

@ -123,7 +123,7 @@ export class TerrainPickerModal extends HexmakerModal {
for (const entry of this.palette) {
const btn = grid.createDiv({ cls: "duckmage-terrain-option" });
const preview = btn.createDiv({ cls: "duckmage-terrain-preview" });
preview.setCssProps({ 'background-color': entry.color });
preview.setCssProps({ '--duckmage-bg': entry.color });
if (entry.icon) {
createIconEl(preview, getIconUrl(this.plugin, entry.icon), entry.name, entry.iconColor, "duckmage-terrain-preview-icon");
}
@ -175,7 +175,7 @@ export class TerrainPickerModal extends HexmakerModal {
// Standard colored preview + icon (same as pick mode)
const preview = tile.createDiv({ cls: "duckmage-terrain-preview" });
preview.setCssProps({ 'background-color': entry.color });
preview.setCssProps({ '--duckmage-bg': entry.color });
if (entry.icon) {
createIconEl(preview, getIconUrl(this.plugin, entry.icon), entry.name, entry.iconColor, "duckmage-terrain-preview-icon");
}

View file

@ -326,10 +326,10 @@ export class HexTableView extends ItemView {
if (!HEX_PATTERN.test(file.path)) return;
const existing = this.updateTimers.get(file.path);
if (existing) clearTimeout(existing);
if (existing) window.clearTimeout(existing);
this.updateTimers.set(
file.path,
setTimeout(() => {
window.setTimeout(() => {
this.updateTimers.delete(file.path);
void this.updateRow(file.path);
}, 300),
@ -352,7 +352,7 @@ export class HexTableView extends ItemView {
}
onClose(): Promise<void> {
for (const timer of this.updateTimers.values()) clearTimeout(timer);
for (const timer of this.updateTimers.values()) window.clearTimeout(timer);
this.updateTimers.clear();
this.contentEl.empty();
return Promise.resolve();
@ -440,7 +440,7 @@ export class HexTableView extends ItemView {
if (this.filterYMaxInput) this.filterYMaxInput.placeholder = String(yMax);
// ── Phase 1: skeleton render (sync — coords + terrain from metadata cache) ──
const table = document.createElement("table");
const table = activeDocument.createElement("table");
table.className = "duckmage-hex-table";
const thead = table.createEl("thead");
@ -494,7 +494,7 @@ export class HexTableView extends ItemView {
const ok = await fillBatch(i, REST_BATCH);
if (!ok) break;
// Yield to the browser between batches to keep the UI responsive
await new Promise<void>((r) => setTimeout(r, 0));
await new Promise<void>((r) => window.setTimeout(r, 0));
}
}
@ -667,7 +667,7 @@ export class HexTableView extends ItemView {
const leaf = this.app.workspace.getLeaf("tab");
await leaf.setViewState({ type: VIEW_TYPE_HEX_MAP });
// Wait one frame for the view to render before centering
setTimeout(
window.setTimeout(
() => (leaf.view as unknown as WithCenterOnHex).centerOnHex(x, y),
100,
);
@ -898,12 +898,12 @@ export class HexTableView extends ItemView {
// <col> elements + explicit table width is the only reliable way to drive
// table-layout:fixed column widths across browsers.
const colgroup = document.createElement("colgroup");
const colgroup = activeDocument.createElement("colgroup");
const cols: HTMLTableColElement[] = [];
let totalWidth = 0;
for (let i = 0; i < ths.length; i++) {
const w = defaultWidths[i] ?? 160;
const col = document.createElement("col");
const col = activeDocument.createElement("col");
col.style.width = `${w}px`;
colgroup.appendChild(col);
cols.push(col);
@ -922,7 +922,7 @@ export class HexTableView extends ItemView {
const startX = e.clientX;
const startW = parseInt(col.style.width, 10);
const startTW = parseInt(table.style.width, 10);
document.body.setCssProps({ cursor: "col-resize" });
activeDocument.body.setCssProps({ cursor: "col-resize" });
const onMove = (me: MouseEvent) => {
const newW = Math.max(20, startW + me.clientX - startX);
@ -930,12 +930,12 @@ export class HexTableView extends ItemView {
table.style.width = `${startTW + (newW - startW)}px`;
};
const onUp = () => {
document.body.setCssProps({ cursor: "" });
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
activeDocument.body.setCssProps({ cursor: "" });
activeDocument.removeEventListener("mousemove", onMove);
activeDocument.removeEventListener("mouseup", onUp);
};
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
activeDocument.addEventListener("mousemove", onMove);
activeDocument.addEventListener("mouseup", onUp);
});
}
}

View file

@ -243,7 +243,7 @@ export class RandomTableEditorModal extends HexmakerModal {
resultInput.placeholder = "Result…";
resultInput.rows = 1;
// Size to content immediately, then keep in sync as the user types
requestAnimationFrame(() => autoResize(resultInput));
window.requestAnimationFrame(() => autoResize(resultInput));
resultInput.addEventListener("input", () => {
const val = resultInput.value;
const m = /^\[\[(.+?)(?:\|[^\]]+)?\]\]$/.exec(val.trim());

View file

@ -269,7 +269,7 @@ export class RandomTableModal extends HexmakerModal {
e.stopPropagation();
void navigator.clipboard.writeText(entry.result);
copyBtn.setText("✓");
setTimeout(() => copyBtn.setText("⎘"), 1200);
window.setTimeout(() => copyBtn.setText("⎘"), 1200);
});
});

View file

@ -314,7 +314,6 @@ export class RandomTableView extends ItemView {
attr: { placeholder: "Generate from folder or file (optional)…" },
});
fromFolderInput.setAttribute("list", fromFolderDatalistId);
fromFolderInput.setCssProps({ "margin-top": "6px" });
const createTable = async () => {
const name = newInput.value.trim();
@ -598,7 +597,7 @@ export class RandomTableView extends ItemView {
}
private scheduleListRefresh(): void {
if (this.listRefreshTimer !== null) clearTimeout(this.listRefreshTimer);
if (this.listRefreshTimer !== null) window.clearTimeout(this.listRefreshTimer);
this.listRefreshTimer = window.setTimeout(() => {
this.listRefreshTimer = null;
void this.loadList();
@ -606,7 +605,7 @@ export class RandomTableView extends ItemView {
}
private scheduleDetailRefresh(): void {
if (this.detailRefreshTimer !== null) clearTimeout(this.detailRefreshTimer);
if (this.detailRefreshTimer !== null) window.clearTimeout(this.detailRefreshTimer);
this.detailRefreshTimer = window.setTimeout(() => {
this.detailRefreshTimer = null;
if (this.viewMode === "workflows") {
@ -940,26 +939,24 @@ export class RandomTableView extends ItemView {
const runBtn = this.detailEl.createEl("button", {
text: "Roll workflow",
cls: "duckmage-rt-roll-btn mod-cta",
cls: "duckmage-rt-roll-btn duckmage-rt-roll-btn-spaced mod-cta",
});
runBtn.addEventListener("click", () => {
new WorkflowWizardModal(this.app, this.plugin, file).open();
});
runBtn.setCssProps({ "margin-right": "8px" });
const copyWfLinkBtn = this.detailEl.createEl("button", {
text: "🔗 copy link",
cls: "duckmage-rt-copy-link-btn",
cls: "duckmage-rt-copy-link-btn duckmage-rt-copy-link-btn-spaced",
});
copyWfLinkBtn.title = "Copy a Markdown link to open this workflow";
copyWfLinkBtn.setCssProps({ "margin-bottom": "12px" });
copyWfLinkBtn.addEventListener("click", () => {
const vault = encodeURIComponent(this.app.vault.getName());
const path = encodeURIComponent(file.path);
const link = `[🔗 ${file.basename}](obsidian://duckmage-workflow?vault=${vault}&file=${path})`;
void navigator.clipboard.writeText(link).then(() => {
copyWfLinkBtn.setText("Copied!");
setTimeout(() => copyWfLinkBtn.setText("🔗 copy link"), 1500);
window.setTimeout(() => copyWfLinkBtn.setText("🔗 copy link"), 1500);
});
});
@ -985,8 +982,7 @@ export class RandomTableView extends ItemView {
text: "Steps",
cls: "duckmage-rt-history-label",
});
const list = stepsEl.createEl("ul");
list.setCssProps({ margin: "0", "padding-left": "18px" });
const list = stepsEl.createEl("ul", { cls: "duckmage-wf-steps-list" });
for (const step of workflow.steps) {
const li = list.createEl("li");
let primaryName: string;
@ -1600,7 +1596,7 @@ export class RandomTableView extends ItemView {
e.stopPropagation();
void navigator.clipboard.writeText(entry.result);
copyBtn.setText("✓");
setTimeout(() => copyBtn.setText("⎘"), 1200);
window.setTimeout(() => copyBtn.setText("⎘"), 1200);
});
});
}
@ -1619,7 +1615,7 @@ export class RandomTableView extends ItemView {
const link = `[🎲 ${file.basename}](obsidian://duckmage-roll?vault=${vault}&file=${path})`;
void navigator.clipboard.writeText(link).then(() => {
copyLinkBtn.setText("Copied!");
setTimeout(() => copyLinkBtn.setText("🎲 copy link"), 1500);
window.setTimeout(() => copyLinkBtn.setText("🎲 copy link"), 1500);
});
});
@ -1643,7 +1639,7 @@ export class RandomTableView extends ItemView {
copyBtn.addEventListener("click", () => {
void navigator.clipboard.writeText(resultTextarea.value);
copyBtn.setText("Copied!");
setTimeout(() => copyBtn.setText("Copy"), 1500);
window.setTimeout(() => copyBtn.setText("Copy"), 1500);
});
// "Open note" button — shown only when the table has a linked folder
const openNoteBtn = resultBtns.createEl("button", {
@ -1684,7 +1680,7 @@ export class RandomTableView extends ItemView {
link.addEventListener("click", () => {
this.setViewMode("workflows");
// After mode switch, select the workflow
setTimeout(() => {
window.setTimeout(() => {
if (wfFile instanceof TFile) void this.loadWorkflow(wfFile);
}, 50);
});
@ -1692,9 +1688,8 @@ export class RandomTableView extends ItemView {
const newWfLink = linksEl.createEl("a", {
text: "+ new workflow with this table",
cls: "duckmage-rt-entry-link",
cls: "duckmage-rt-entry-link duckmage-rt-new-wf-link",
});
newWfLink.setCssProps({ "font-style": "italic" });
newWfLink.addEventListener("click", () => {
void (async () => {
this.setViewMode("workflows");

View file

@ -79,7 +79,7 @@ export function registerRollerBlock(plugin: HexmakerPlugin): void {
copyBtn.addEventListener("click", () => {
void navigator.clipboard.writeText(resultTextarea.value);
copyBtn.setText("Copied!");
setTimeout(() => copyBtn.setText("Copy"), 1200);
window.setTimeout(() => copyBtn.setText("Copy"), 1200);
});
// History: scrollable list, each item individually copyable
@ -99,7 +99,7 @@ export function registerRollerBlock(plugin: HexmakerPlugin): void {
hCopyBtn.addEventListener("click", () => {
void navigator.clipboard.writeText(item);
hCopyBtn.setText("✓");
setTimeout(() => hCopyBtn.setText("⎘"), 1000);
window.setTimeout(() => hCopyBtn.setText("⎘"), 1000);
});
}
};

View file

@ -148,8 +148,7 @@ export class WorkflowEditorModal extends HexmakerModal {
updateValidation();
});
const validationEl = templateSectionWrap.createDiv();
validationEl.setCssProps({ "margin-top": "4px", "font-size": "0.85em" });
const validationEl = templateSectionWrap.createDiv({ cls: "duckmage-wf-template-validation" });
// ── Description ───────────────────────────────────────────────────
const descRow = contentEl.createDiv({
@ -171,9 +170,8 @@ export class WorkflowEditorModal extends HexmakerModal {
// ── Results folder row ────────────────────────────────────────────
const rfRow = contentEl.createDiv({
cls: "duckmage-table-editor-name-row",
cls: "duckmage-table-editor-name-row duckmage-wf-editor-results-row",
});
rfRow.setCssProps({ "margin-top": "8px" });
rfRow.createEl("label", {
text: "Results folder",
cls: "duckmage-table-editor-name-label",

View file

@ -118,7 +118,6 @@ export class WorkflowWizardModal extends HexmakerModal {
this.resultTextarea = contentEl.createEl("textarea", {
cls: "duckmage-wf-template-area",
});
this.resultTextarea.setCssProps({ "min-height": "120px" });
this.resultTextarea.readOnly = true;
this.resultTextarea.value = this.assembleResult();
@ -128,7 +127,7 @@ export class WorkflowWizardModal extends HexmakerModal {
.writeText(this.resultTextarea?.value ?? "")
.then(() => {
copyResultBtn.setText("Copied!");
setTimeout(() => copyResultBtn.setText("Copy result"), 1500);
window.setTimeout(() => copyResultBtn.setText("Copy result"), 1500);
});
});

View file

@ -37,17 +37,10 @@ export function createIconEl(
cls: string,
): HTMLElement {
if (iconColor) {
const div = parent.createEl("div", { cls, title: alt });
const div = parent.createEl("div", { cls: `${cls} duckmage-masked-icon`, title: alt });
div.setCssProps({
'mask-image': `url("${src}")`,
'-webkit-mask-image': `url("${src}")`,
'mask-size': 'contain',
'-webkit-mask-size': 'contain',
'mask-repeat': 'no-repeat',
'-webkit-mask-repeat': 'no-repeat',
'mask-position': 'center',
'-webkit-mask-position': 'center',
'background-color': iconColor,
'--duckmage-mask-url': `url("${src}")`,
'--duckmage-bg': iconColor,
});
return div;
}

View file

@ -392,7 +392,7 @@ div.duckmage-hex-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: var(--background-modifier-border);
background-color: var(--duckmage-bg, var(--background-modifier-border));
cursor: pointer;
border: 1px solid
color-mix(in srgb, var(--background-modifier-border) 60%, transparent);
@ -3051,6 +3051,7 @@ tr:hover .duckmage-rt-entry-copy-btn:hover {
.duckmage-rt-from-folder-input {
width: 100%;
font-size: 0.85em;
margin-top: 6px;
}
/* Confirm delete modal */
@ -4665,3 +4666,42 @@ tr:hover .duckmage-rt-entry-copy-btn:hover {
visibility: hidden;
pointer-events: none;
}
/* JS-driven custom-property consumers
Dynamic colors (terrain swatches, path swatches, etc.) are set as CSS
custom properties via setCssProps; these rules consume them. Fallbacks
keep the default appearance when the variable is unset.
*/
.duckmage-terrain-header-swatch { background-color: var(--duckmage-bg, transparent); }
.duckmage-terrain-preview { background-color: var(--duckmage-bg, transparent); }
.duckmage-terrain-btn-preview { background-color: var(--duckmage-bg, transparent); }
.duckmage-path-btn-swatch { background-color: var(--duckmage-bg, transparent); }
.duckmage-draw-btn-terrain.is-terrain-preview {
border-color: var(--duckmage-border-color, currentcolor);
}
.duckmage-draw-btn-terrain.is-eyedropper-active {
border-color: var(--interactive-accent);
color: var(--interactive-accent);
}
/* Tinted icon helper utils.ts createIconEl applies this class and sets
--duckmage-mask-url + --duckmage-bg. */
.duckmage-masked-icon {
-webkit-mask-image: var(--duckmage-mask-url);
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-image: var(--duckmage-mask-url);
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
background-color: var(--duckmage-bg);
}
/* Static-layout helpers replacing inline setCssProps calls in workflows view */
.duckmage-rt-roll-btn-spaced { margin-right: 8px; }
.duckmage-rt-copy-link-btn-spaced { margin-bottom: 12px; }
.duckmage-wf-steps-list { margin: 0; padding-left: 18px; }
.duckmage-rt-new-wf-link { font-style: italic; }
.duckmage-wf-template-validation { margin-top: 4px; font-size: 0.85em; }
.duckmage-wf-editor-results-row { margin-top: 8px; }

View file

@ -35,5 +35,6 @@
"1.1.13": "1.12.0",
"1.1.14": "1.12.0",
"1.1.15": "1.12.0",
"1.2.0": "1.12.0"
"1.2.0": "1.12.0",
"1.2.1": "1.12.0"
}