upgrade modal, improve UI/UX significantly

This commit is contained in:
isaprettycoolguy@protonmail.com 2026-03-08 12:18:55 -04:00
parent 1680e4d4a8
commit 9ae1c407e2
45 changed files with 1490 additions and 530 deletions

16
.claude/commands/dev.md Normal file
View file

@ -0,0 +1,16 @@
---
description: Start the duckmage plugin dev watcher (esbuild watch mode)
allowed-tools: Bash(npm:*)
---
Start esbuild in watch mode for the duckmage plugin. This rebuilds `main.js` automatically on every TypeScript file save.
```
cd /mnt/c/Users/markr/Documents/KB/journal/.obsidian/plugins/duckmage-plugin && npm run dev
```
Run this in the background — it is a long-running process. Tell the user the watcher has started, then remind them:
- `main.js` rebuilds automatically on every save to `main.ts`
- To reload the plugin in Obsidian after a rebuild: open the developer console (Ctrl+Shift+I) and run:
`app.plugins.disablePlugin('duckmage-plugin'); app.plugins.enablePlugin('duckmage-plugin');`
- Or use `/rebuild` instead for a one-off production build (includes type-checking).

View file

@ -0,0 +1,16 @@
---
description: Build the duckmage plugin (type-check + bundle)
allowed-tools: Bash(npm:*)
---
Current directory: !`pwd`
Run the production build for the duckmage Obsidian plugin:
```
cd /mnt/c/Users/markr/Documents/KB/journal/.obsidian/plugins/duckmage-plugin && npm run build
```
Report the result clearly:
- If it succeeded, confirm that `main.js` was updated.
- If it failed, show the full error output and identify the likely cause (TypeScript type error, missing import, syntax error, etc.).

64
CLAUDE.md Normal file
View file

@ -0,0 +1,64 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
# Development (watch mode with inline sourcemaps) — use with Hot Reload (see below)
npm run dev
# Production build (type-checks then bundles)
npm run build
# Bump version (updates manifest.json and versions.json, stages both)
npm run version
```
Slash commands (invoke inside Claude Code):
- `/dev` — starts esbuild in watch mode (long-running; pairs with Hot Reload)
- `/rebuild` — one-off production build; reports errors if the build fails
There are no tests. The built output is `main.js` in the repo root, which Obsidian loads directly from this plugin folder.
## Dev loop
Each coding session:
1. Run `npm run dev` in a terminal (or `/dev` from Claude Code) — esbuild watches for changes and rebuilds `main.js` on every save
2. After a rebuild, reload the plugin in Obsidian via the developer console (Ctrl+Shift+I):
```js
app.plugins.disablePlugin('duckmage-plugin'); app.plugins.enablePlugin('duckmage-plugin');
```
3. Use `/rebuild` for a final production build before committing (runs the TypeScript type-check too)
## Architecture
This is a single-file Obsidian plugin (`main.ts`) compiled by esbuild into `main.js`. All source lives in `main.ts`; there are no other source files.
### Plugin purpose
Renders an interactive hex-grid map for tabletop RPG world-building inside Obsidian. Each hex cell corresponds to a Markdown note on disk.
### Key classes
- **`DuckmagePlugin`** — Main plugin entry point. Registers the view, ribbon icon, command, and settings tab. Exposes `hexPath(x, y)`, `createHexNote(x, y)`, and `loadAvailableIcons()`. Stores `availableIcons: string[]` (PNGs from the `icons/` folder, loaded at startup).
- **`HexMapView`** (extends `ItemView`) — Renders the hex grid. `renderGrid(terrainOverrides?)` does a full DOM re-render; the optional `terrainOverrides` map allows immediate visual updates before the metadata cache catches up. Left-click opens/creates a note; right-click opens `HexEditorModal`.
- **`HexEditorModal`** (extends `Modal`) — The right-click editor. Has a terrain picker (color swatch + icon grid) and link sections for Towns, Dungeons, and Features. Uses `ensureHexNote()` to create the note on demand before writing to it.
- **`FileLinkSuggestModal`** (extends `SuggestModal<TFile>`) — Reusable file-search modal scoped to `worldFolder`. Takes an `onChoose` callback.
- **`DuckmageSettingTab`** — Settings UI for folder paths, grid dimensions, hex gap, and terrain palette (name + color + icon dropdown).
### Data model
- **Hex notes**: stored at `{hexFolder}/{x}_{y}.md` (e.g. `RPG/duckmage/hexes/3_7.md`). Created via `DuckmagePlugin.createHexNote(x, y)` using the configured template or the built-in `DEFAULT_HEX_TEMPLATE`.
- **Template placeholders**: `{{x}}`, `{{y}}`, `{{title}}`. The default template and any custom template should include `## Towns`, `## Dungeons`, and `## Features` headings so that links added via the editor land in the right section.
- **Terrain**: stored in a hex note's YAML frontmatter as `terrain: <name>`. Read via `getTerrainFromFile` (uses metadata cache); written via `setTerrainInFile` (parses/patches raw file content).
- **Terrain icons**: PNG files in the plugin's `icons/` folder. Loaded at startup into `plugin.availableIcons`. Each `TerrainColor` entry has an optional `icon` filename. URLs are resolved via `getIconUrl(plugin, filename)``app.vault.adapter.getResourcePath(manifest.dir + '/icons/' + filename)`.
- **Section links**: `addLinkToSection(app, filePath, section, linkText)` inserts a wiki-link under the named `##` heading (appending the section if absent). `getLinksInSection` reads them back for display.
- **Settings** (`data.json`): `worldFolder`, `hexFolder`, `templatePath`, `hexGap`, `terrainPalette` (array of `{name, color, icon?}`), `gridSize` ({cols, rows}), `zoomLevel`.
### Key conventions
- Folder paths are normalized (no leading/trailing slashes) via `normalizeFolder()`.
- Links use vault-relative paths via `metadataCache.fileToLinktext(file, sourcePath)`.
- `obsidian` is an esbuild external — never bundled; provided by Obsidian at runtime.
- CSS classes use the `duckmage-` prefix (styles in `styles.css`).

188
data.json
View file

@ -1,13 +1,14 @@
{
"mySetting": "default",
"worldFolder": "RPG/duckmage/world",
"hexFolder": "RPG/duckmage/world/hexes",
"hexFolder": "RPG/duckmage/hexes",
"templatePath": "RPG/duckmage/world/hextemplate.md",
"hexGap": "0.15",
"terrainPalette": [
{
"name": "mountain",
"color": "#6e7b91"
"color": "#6e7b91",
"icon": "bw-mountain.png"
},
{
"name": "water",
@ -15,19 +16,23 @@
},
{
"name": "grass",
"color": "#229b66"
"color": "#638c7b",
"icon": "bw-grassland.png"
},
{
"name": "forest",
"color": "#0a431f"
"color": "#0a431f",
"icon": "bw-forest-heavy.png"
},
{
"name": "cliffs",
"color": "#a86f1f"
"color": "#a86f1f",
"icon": "bw-brokenlands.png"
},
{
"name": "desert",
"color": "#c6b47b"
"color": "#c6b47b",
"icon": "bw-cactus.png"
},
{
"name": "snow",
@ -35,12 +40,177 @@
},
{
"name": "aaaaa",
"color": "#600aff"
"color": "#432e6b",
"icon": "bw-swamp.png"
},
{
"name": "hills",
"color": "#a8a29e",
"icon": "bw-hills.png"
},
{
"name": "desert rocky",
"color": "#d97706",
"icon": "bw-desert-rocky.png"
},
{
"name": "dunes",
"color": "#fbbf24",
"icon": "bw-dunes.png"
},
{
"name": "cactus",
"color": "#ca8a04",
"icon": "bw-cactus.png"
},
{
"name": "cactus heavy",
"color": "#b45309",
"icon": "bw-cactus-heavy.png"
},
{
"name": "badlands",
"color": "#c2410c",
"icon": "bw-badlands.png"
},
{
"name": "brokenlands",
"color": "#92400e",
"icon": "bw-brokenlands.png"
},
{
"name": "forest heavy",
"color": "#15803d",
"icon": "bw-forest-heavy.png"
},
{
"name": "forested hills",
"color": "#22c55e",
"icon": "bw-forested-hills.png"
},
{
"name": "mixed forest",
"color": "#16a34a",
"icon": "bw-forest-mixed.png"
},
{
"name": "mixed forest heavy",
"color": "#15803d",
"icon": "bw-forest-mixed-heavy.png"
},
{
"name": "mixed forest hills",
"color": "#22c55e",
"icon": "bw-forest-mixed-hills.png"
},
{
"name": "evergreen",
"color": "#166534",
"icon": "bw-evergreen.png"
},
{
"name": "evergreen heavy",
"color": "#14532d",
"icon": "bw-evergreen-heavy.png"
},
{
"name": "evergreen hills",
"color": "#4ade80",
"icon": "bw-evergreen-hills.png"
},
{
"name": "jungle",
"color": "#15803d",
"icon": "bw-jungle.png"
},
{
"name": "jungle heavy",
"color": "#14532d",
"icon": "bw-jungle-heavy.png"
},
{
"name": "jungle hills",
"color": "#4ade80",
"icon": "bw-jungle-hills.png"
},
{
"name": "mountain peak",
"color": "#78716c",
"icon": "bw-mountain.png"
},
{
"name": "mountains snow",
"color": "#bfdbfe",
"icon": "bw-mountains-snow.png"
},
{
"name": "forested mountain",
"color": "#6b9e7c",
"icon": "bw-forested-mountain.png"
},
{
"name": "forested mountains",
"color": "#5e8c6a",
"icon": "bw-forested-mountains.png"
},
{
"name": "mixed forest mountain",
"color": "#6b9e7c",
"icon": "bw-forest-mixed-mountain.png"
},
{
"name": "mixed forest mountains",
"color": "#5e8c6a",
"icon": "bw-forest-mixed-mountains.png"
},
{
"name": "evergreen mountain",
"color": "#6b7280",
"icon": "bw-evergreen-mountain.png"
},
{
"name": "evergreen mountains",
"color": "#4b5563",
"icon": "bw-evergreen-mountains.png"
},
{
"name": "jungle mountain",
"color": "#4d7c0f",
"icon": "bw-jungle-mountain.png"
},
{
"name": "jungle mountains",
"color": "#3f6212",
"icon": "bw-jungle-mountains.png"
},
{
"name": "volcano",
"color": "#b91c1c",
"icon": "bw-volcano.png"
},
{
"name": "volcano dormant",
"color": "#78350f",
"icon": "bw-volcano-dormant.png"
},
{
"name": "marsh",
"color": "#4d7c0f",
"icon": "bw-marsh.png"
},
{
"name": "swamp",
"color": "#365314",
"icon": "bw-swamp.png"
}
],
"gridSize": {
"cols": 25,
"rows": 16
"cols": 26,
"rows": 17
},
"gridOffset": {
"x": -1,
"y": -1
},
"zoomLevel": 1
}

Binary file not shown.

View file

@ -0,0 +1,252 @@
symbolnames=Heavy Forest Forested Hills Mountains Marsh Farmland Swamp Light Forest Grassland Cactus Evergreen Mountains Forested Mountains Heavy Cactus Jungle Heavy Evergreen Sandy Desert Sand Dunes Rocky Desert Evergreen Hills Ocean Light Evergreen Badlands Sea Jungle Hills Volcano Hills
Heavy_Forest.category=Forests
Heavy_Forest.elevation=0
Heavy_Forest.iconfilename=bw-forest-heavy.png
Heavy_Forest.scalefactor=0.82
Heavy_Forest.iscustom=false
Heavy_Forest.isdrawborder=false
Heavy_Forest.isfeature=false
Heavy_Forest.isfill=true
Heavy_Forest.isuseicon=true
Heavy_Forest.backgroundrgb=-1
Forested_Hills.category=Forests
Forested_Hills.elevation=2
Forested_Hills.iconfilename=bw-forested-hills.png
Forested_Hills.scalefactor=0.82
Forested_Hills.iscustom=false
Forested_Hills.isdrawborder=false
Forested_Hills.isfeature=false
Forested_Hills.isfill=true
Forested_Hills.isuseicon=true
Forested_Hills.backgroundrgb=-1
Mountains.category=Other Land
Mountains.elevation=4
Mountains.iconfilename=bw-mountains.png
Mountains.scalefactor=0.82
Mountains.iscustom=false
Mountains.isdrawborder=false
Mountains.isfeature=false
Mountains.isfill=true
Mountains.isuseicon=true
Mountains.backgroundrgb=-1
Marsh.category=Other Land
Marsh.elevation=-1
Marsh.iconfilename=bw-marsh.png
Marsh.scalefactor=0.82
Marsh.iscustom=false
Marsh.isdrawborder=false
Marsh.isfeature=false
Marsh.isfill=true
Marsh.isuseicon=true
Marsh.backgroundrgb=-1
Farmland.category=Other Land
Farmland.elevation=0
Farmland.iconfilename=
Farmland.scalefactor=0.82
Farmland.iscustom=false
Farmland.isdrawborder=false
Farmland.isfeature=false
Farmland.isfill=true
Farmland.isuseicon=true
Farmland.backgroundrgb=-1
Swamp.category=Other Land
Swamp.elevation=-1
Swamp.iconfilename=bw-swamp.png
Swamp.scalefactor=0.82
Swamp.iscustom=false
Swamp.isdrawborder=false
Swamp.isfeature=false
Swamp.isfill=true
Swamp.isuseicon=true
Swamp.backgroundrgb=-1
Light_Forest.category=Forests
Light_Forest.elevation=0
Light_Forest.iconfilename=bw-forest.png
Light_Forest.scalefactor=0.82
Light_Forest.iscustom=false
Light_Forest.isdrawborder=false
Light_Forest.isfeature=false
Light_Forest.isfill=true
Light_Forest.isuseicon=true
Light_Forest.backgroundrgb=-1
Grassland.category=Other Land
Grassland.elevation=0
Grassland.iconfilename=bw-grassland.png
Grassland.scalefactor=0.82
Grassland.iscustom=false
Grassland.isdrawborder=false
Grassland.isfeature=false
Grassland.isfill=true
Grassland.isuseicon=true
Grassland.backgroundrgb=-1
Cactus.category=Rough Land
Cactus.elevation=0
Cactus.iconfilename=bw-cactus.png
Cactus.scalefactor=0.82
Cactus.iscustom=false
Cactus.isdrawborder=false
Cactus.isfeature=false
Cactus.isfill=true
Cactus.isuseicon=true
Cactus.backgroundrgb=-1
Evergreen_Mountains.category=Forests
Evergreen_Mountains.elevation=4
Evergreen_Mountains.iconfilename=bw-evergreen-mountains.png
Evergreen_Mountains.scalefactor=0.82
Evergreen_Mountains.iscustom=false
Evergreen_Mountains.isdrawborder=false
Evergreen_Mountains.isfeature=false
Evergreen_Mountains.isfill=true
Evergreen_Mountains.isuseicon=true
Evergreen_Mountains.backgroundrgb=-1
Forested_Mountains.category=Forests
Forested_Mountains.elevation=4
Forested_Mountains.iconfilename=bw-forested-mountains.png
Forested_Mountains.scalefactor=0.82
Forested_Mountains.iscustom=false
Forested_Mountains.isdrawborder=false
Forested_Mountains.isfeature=false
Forested_Mountains.isfill=true
Forested_Mountains.isuseicon=true
Forested_Mountains.backgroundrgb=-1
Heavy_Cactus.category=Rough Land
Heavy_Cactus.elevation=0
Heavy_Cactus.iconfilename=bw-cactus-heavy.png
Heavy_Cactus.scalefactor=0.82
Heavy_Cactus.iscustom=false
Heavy_Cactus.isdrawborder=false
Heavy_Cactus.isfeature=false
Heavy_Cactus.isfill=true
Heavy_Cactus.isuseicon=true
Heavy_Cactus.backgroundrgb=-1
Jungle.category=Forests
Jungle.elevation=0
Jungle.iconfilename=bw-jungle.png
Jungle.scalefactor=0.82
Jungle.iscustom=false
Jungle.isdrawborder=false
Jungle.isfeature=false
Jungle.isfill=true
Jungle.isuseicon=true
Jungle.backgroundrgb=-1
Heavy_Evergreen.category=Forests
Heavy_Evergreen.elevation=0
Heavy_Evergreen.iconfilename=bw-evergreen-heavy.png
Heavy_Evergreen.scalefactor=0.82
Heavy_Evergreen.iscustom=false
Heavy_Evergreen.isdrawborder=false
Heavy_Evergreen.isfeature=false
Heavy_Evergreen.isfill=true
Heavy_Evergreen.isuseicon=true
Heavy_Evergreen.backgroundrgb=-1
Sandy_Desert.category=Rough Land
Sandy_Desert.elevation=0
Sandy_Desert.iconfilename=bw-desert.png
Sandy_Desert.scalefactor=0.82
Sandy_Desert.iscustom=false
Sandy_Desert.isdrawborder=false
Sandy_Desert.isfeature=false
Sandy_Desert.isfill=true
Sandy_Desert.isuseicon=true
Sandy_Desert.backgroundrgb=-1
Sand_Dunes.category=Rough Land
Sand_Dunes.elevation=2
Sand_Dunes.iconfilename=bw-dunes.png
Sand_Dunes.scalefactor=0.82
Sand_Dunes.iscustom=false
Sand_Dunes.isdrawborder=false
Sand_Dunes.isfeature=false
Sand_Dunes.isfill=true
Sand_Dunes.isuseicon=true
Sand_Dunes.backgroundrgb=-1
Rocky_Desert.category=Rough Land
Rocky_Desert.elevation=0
Rocky_Desert.iconfilename=bw-desert-rocky.png
Rocky_Desert.scalefactor=0.82
Rocky_Desert.iscustom=false
Rocky_Desert.isdrawborder=false
Rocky_Desert.isfeature=false
Rocky_Desert.isfill=true
Rocky_Desert.isuseicon=true
Rocky_Desert.backgroundrgb=-1
Evergreen_Hills.category=Forests
Evergreen_Hills.elevation=2
Evergreen_Hills.iconfilename=bw-evergreen-hills.png
Evergreen_Hills.scalefactor=0.82
Evergreen_Hills.iscustom=false
Evergreen_Hills.isdrawborder=false
Evergreen_Hills.isfeature=false
Evergreen_Hills.isfill=true
Evergreen_Hills.isuseicon=true
Evergreen_Hills.backgroundrgb=-1
Ocean.category=Water
Ocean.elevation=-3
Ocean.iconfilename=
Ocean.scalefactor=0.82
Ocean.iscustom=false
Ocean.isdrawborder=false
Ocean.isfeature=false
Ocean.isfill=true
Ocean.isuseicon=true
Ocean.backgroundrgb=-6710887
Light_Evergreen.category=Forests
Light_Evergreen.elevation=0
Light_Evergreen.iconfilename=bw-evergreen.png
Light_Evergreen.scalefactor=0.82
Light_Evergreen.iscustom=false
Light_Evergreen.isdrawborder=false
Light_Evergreen.isfeature=false
Light_Evergreen.isfill=true
Light_Evergreen.isuseicon=true
Light_Evergreen.backgroundrgb=-1
Badlands.category=Rough Land
Badlands.elevation=0
Badlands.iconfilename=bw-brokenlands.png
Badlands.scalefactor=0.82
Badlands.iscustom=false
Badlands.isdrawborder=false
Badlands.isfeature=false
Badlands.isfill=true
Badlands.isuseicon=true
Badlands.backgroundrgb=-1
Sea.category=Water
Sea.elevation=-1
Sea.iconfilename=
Sea.scalefactor=0.82
Sea.iscustom=false
Sea.isdrawborder=false
Sea.isfeature=false
Sea.isfill=true
Sea.isuseicon=true
Sea.backgroundrgb=-3355444
Jungle_Hills.category=Forests
Jungle_Hills.elevation=2
Jungle_Hills.iconfilename=bw-jungle-hills.png
Jungle_Hills.scalefactor=0.82
Jungle_Hills.iscustom=false
Jungle_Hills.isdrawborder=false
Jungle_Hills.isfeature=false
Jungle_Hills.isfill=true
Jungle_Hills.isuseicon=true
Jungle_Hills.backgroundrgb=-1
Volcano.category=Other Land
Volcano.elevation=4
Volcano.iconfilename=bw-volcano.png
Volcano.scalefactor=0.82
Volcano.iscustom=false
Volcano.isdrawborder=false
Volcano.isfeature=false
Volcano.isfill=true
Volcano.isuseicon=true
Volcano.backgroundrgb=-1
Hills.category=Other Land
Hills.elevation=2
Hills.iconfilename=bw-hills.png
Hills.scalefactor=0.82
Hills.iscustom=false
Hills.isdrawborder=false
Hills.isfeature=false
Hills.isfill=true
Hills.isuseicon=true
Hills.backgroundrgb=-1

BIN
icons/bw-badlands.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
icons/bw-brokenlands.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/bw-cactus-heavy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
icons/bw-cactus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
icons/bw-desert-rocky.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 B

BIN
icons/bw-desert.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

BIN
icons/bw-dunes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
icons/bw-evergreen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/bw-forest-heavy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
icons/bw-forest-mixed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
icons/bw-forest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/bw-forested-hills.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
icons/bw-grassland.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/bw-hills.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
icons/bw-jungle-heavy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
icons/bw-jungle-hills.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
icons/bw-jungle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
icons/bw-marsh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
icons/bw-mountain-snow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/bw-mountain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
icons/bw-mountains-snow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

BIN
icons/bw-mountains.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

BIN
icons/bw-swamp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
icons/bw-volcano.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

1231
main.ts

File diff suppressed because it is too large Load diff

View file

@ -1,31 +1,41 @@
/* Duckmage plugin styles */
/* Hex map view */
/* ── Hex map view ─────────────────────────────────────────────────────────── */
.duckmage-hex-map-container {
overflow: hidden;
position: relative;
width: 100%;
height: 100%;
}
.duckmage-hex-map-viewport {
position: absolute;
top: 0;
left: 0;
transform-origin: 0 0;
will-change: transform;
cursor: grab;
padding: 1em;
overflow: auto;
}
.duckmage-hex-map-viewport.is-dragging {
cursor: grabbing;
}
.duckmage-hex-row {
display: flex;
flex-wrap: nowrap;
justify-content: flex-start;
/* Flat-top hex: vertical step is 3/4 of hex height, so pull next row up by 1/4 of full height = half of --hex-size */
margin-bottom: calc(var(--hex-size, 2.2em) * -0.5);
}
.duckmage-hex-row:nth-child(even) {
/* Offset even rows by exactly half hex width so they tessellate */
.duckmage-hex-row-offset {
margin-left: calc(var(--hex-size, 2.2em) * 1.732 / 2);
}
.duckmage-hex:focus {
outline: none;
}
.duckmage-hex {
--hex-size: 2.2em;
/* Flat-top regular hex: width = sqrt(3)*size, height = 2*size */
width: calc(var(--hex-size) * 1.732);
height: calc(var(--hex-size) * 2);
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
@ -41,14 +51,15 @@
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.duckmage-hex:focus { outline: none; }
.duckmage-hex:hover {
background: var(--background-modifier-hover);
border-color: var(--interactive-accent);
}
/* Has-note state: accent border and dot only (no purple fill so terrain/clear stays clear) */
.duckmage-hex-exists {
border-color: var(--interactive-accent);
border-color: var(--background-modifier-border-hover);
}
.duckmage-hex-exists:hover {
@ -56,12 +67,29 @@
background: var(--background-modifier-hover);
}
/* Terrain icon fills the hex, sits behind the label */
.duckmage-hex-icon {
position: absolute;
width: 78%;
height: 78%;
object-fit: contain;
pointer-events: none;
opacity: 0.75;
}
.duckmage-hex-label {
font-size: 0.65em;
position: relative; /* above icon */
font-size: 0.6em;
font-weight: 600;
color: var(--text-muted);
pointer-events: none;
user-select: none;
z-index: 1;
}
/* Keep label readable over terrain color */
.duckmage-hex[style*="background-color"] .duckmage-hex-label {
text-shadow: 0 0 3px var(--background-primary), 0 0 6px var(--background-primary);
}
.duckmage-hex-dot {
@ -72,14 +100,205 @@
border-radius: 50%;
background: var(--interactive-accent);
pointer-events: none;
z-index: 1;
}
/* Terrain-colored hex: keep label readable (text shadow if needed) */
.duckmage-hex[style*="background-color"] .duckmage-hex-label {
text-shadow: 0 0 2px var(--background-primary), 0 0 4px var(--background-primary);
/* ── Hex editor modal ─────────────────────────────────────────────────────── */
.duckmage-hex-editor h2 {
margin-bottom: 0.75em;
}
/* Settings: terrain palette list */
.duckmage-hex-editor h3 {
margin: 0.25em 0 0.5em;
}
.duckmage-hex-editor h4 {
margin: 0;
font-size: 0.9em;
font-weight: 600;
}
.duckmage-editor-divider {
margin: 1em 0;
border: none;
border-top: 1px solid var(--background-modifier-border);
}
.duckmage-editor-section {
margin-bottom: 1em;
}
/* Terrain picker grid */
.duckmage-terrain-picker {
display: flex;
flex-wrap: wrap;
gap: 0.4em;
margin-bottom: 0.75em;
}
.duckmage-terrain-option {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25em;
padding: 0.35em;
border-radius: 6px;
cursor: pointer;
border: 2px solid transparent;
background: var(--background-secondary);
transition: border-color 0.1s ease, background 0.1s ease;
min-width: 52px;
}
.duckmage-terrain-option:hover {
background: var(--background-modifier-hover);
border-color: var(--interactive-accent);
}
.duckmage-terrain-option.is-selected {
border-color: var(--interactive-accent);
background: var(--background-modifier-hover);
}
.duckmage-terrain-preview {
width: 36px;
height: 36px;
border-radius: 4px;
position: relative;
overflow: hidden;
flex-shrink: 0;
}
.duckmage-terrain-preview-icon {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain;
}
.duckmage-terrain-option-name {
font-size: 0.7em;
text-align: center;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 54px;
}
.duckmage-clear-btn {
margin-top: 0.5em;
}
/* Link sections */
.duckmage-editor-link-section {
margin-bottom: 0.85em;
}
.duckmage-link-section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.3em;
}
.duckmage-add-btn {
font-size: 0.8em;
padding: 0.15em 0.5em;
}
.duckmage-link-list {
display: flex;
flex-direction: column;
gap: 0.2em;
padding-left: 0.5em;
}
.duckmage-link-item {
font-size: 0.85em;
color: var(--text-accent);
}
.duckmage-link-empty {
font-size: 0.85em;
color: var(--text-faint);
}
.duckmage-suggestion-path {
color: var(--text-muted);
}
/* ── Expand buttons ───────────────────────────────────────────────────────── */
.duckmage-expand-btn {
position: absolute;
z-index: 10;
width: 26px;
height: 26px;
padding: 0;
border-radius: 50%;
background: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
color: var(--text-muted);
font-size: 1.1em;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.5;
transition: opacity 0.15s ease, color 0.15s ease;
}
.duckmage-expand-btn:hover {
opacity: 1;
color: var(--text-normal);
}
.duckmage-expand-top { top: 4px; left: 50%; transform: translateX(-50%); }
.duckmage-expand-bottom { bottom: 4px; left: 50%; transform: translateX(-50%); }
.duckmage-expand-left { left: 4px; top: 50%; transform: translateY(-50%); }
.duckmage-expand-right { right: 4px; top: 50%; transform: translateY(-50%); }
/* Text note sections */
.duckmage-editor-text-section {
display: flex;
flex-direction: column;
gap: 0.25em;
margin-bottom: 0.85em;
}
.duckmage-text-section-label {
font-size: 0.85em;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.duckmage-text-section-textarea {
width: 100%;
resize: vertical;
font-size: 0.9em;
font-family: var(--font-text);
background: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
padding: 0.4em 0.5em;
color: var(--text-normal);
line-height: 1.5;
box-sizing: border-box;
}
.duckmage-text-section-textarea:focus {
outline: none;
border-color: var(--interactive-accent);
}
/* ── Settings: terrain palette ────────────────────────────────────────────── */
.duckmage-palette-list .duckmage-palette-item {
margin-bottom: 0.5em;
}