upgrade modal, improve UI/UX significantly
16
.claude/commands/dev.md
Normal 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).
|
||||
16
.claude/commands/rebuild.md
Normal 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
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
BIN
icons/black-and-white-worldographer.wxx
Normal file
252
icons/black_white.properties
Normal 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
|
After Width: | Height: | Size: 7.9 KiB |
BIN
icons/bw-brokenlands.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
icons/bw-cactus-heavy.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
icons/bw-cactus.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
icons/bw-desert-rocky.png
Normal file
|
After Width: | Height: | Size: 907 B |
BIN
icons/bw-desert.png
Normal file
|
After Width: | Height: | Size: 748 B |
BIN
icons/bw-dunes.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
icons/bw-evergreen-heavy.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
icons/bw-evergreen-hills.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
icons/bw-evergreen-mountain.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
icons/bw-evergreen-mountains.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
icons/bw-evergreen.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/bw-forest-heavy.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
icons/bw-forest-mixed-heavy.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
icons/bw-forest-mixed-hills.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
icons/bw-forest-mixed-mountain.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
icons/bw-forest-mixed-mountains.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
icons/bw-forest-mixed.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
icons/bw-forest.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
icons/bw-forested-hills.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/bw-forested-mountain.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
icons/bw-forested-mountains.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
icons/bw-grassland.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
icons/bw-hills.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
icons/bw-jungle-heavy.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
icons/bw-jungle-hills.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
icons/bw-jungle-mountain.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
icons/bw-jungle-mountains.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
icons/bw-jungle.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
icons/bw-marsh.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
icons/bw-mountain-snow.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
icons/bw-mountain.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
icons/bw-mountains-snow.png
Normal file
|
After Width: | Height: | Size: 7 KiB |
BIN
icons/bw-mountains.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
icons/bw-swamp.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
icons/bw-volcano-dormant.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
icons/bw-volcano.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
253
styles.css
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||