From 373eb12a6d4ba5d10d784553f2c3370fca07d715 Mon Sep 17 00:00:00 2001 From: cptn_cosmo Date: Mon, 4 May 2026 01:18:01 +0200 Subject: [PATCH] implement custom hotbar card view for Daggerheart items with dynamic tooltips --- README.md | 24 ++++++ module.json | 38 +++++++++ scripts/main.js | 84 +++++++++++++++++++ styles/cardview.css | 179 +++++++++++++++++++++++++++++++++++++++++ templates/cardview.hbs | 34 ++++++++ 5 files changed, 359 insertions(+) create mode 100644 README.md create mode 100644 module.json create mode 100644 scripts/main.js create mode 100644 styles/cardview.css create mode 100644 templates/cardview.hbs diff --git a/README.md b/README.md new file mode 100644 index 0000000..d5d46e3 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Daggerheart Hotbar Card View + +A Foundry VTT module for the Daggerheart system that enhances the hotbar experience. This module replaces the standard, simple hover-tooltips on the hotbar icons with a full, visually appealing playing card view of the item, making it much easier to read abilities, spells, and items at a glance. + +## Features + +- Fully stylized playing card view for hotbar items. +- Dynamic color support: seamlessly integrates with both light and dark modes within the Foundry VTT UI. +- Properly displays the item's level, cost, domains, and full descriptions without clipping. + +## Installation + +You can install this module by pasting the following link into the **Manifest URL** field in the Foundry VTT Module installation menu: + +```text +https://git.geeks.gay/cosmo/dh-hotbar-cardview/raw/branch/main/module.json +``` + +Alternatively, you can download the [zip archive directly](https://git.geeks.gay/cosmo/dh-hotbar-cardview/releases/latest/download/dh-hotbar-cardview.zip). + +## Compatibility + +- **Foundry VTT**: V14+ +- **System**: Daggerheart (v2.1.0+) diff --git a/module.json b/module.json new file mode 100644 index 0000000..37d0c02 --- /dev/null +++ b/module.json @@ -0,0 +1,38 @@ +{ + "id": "dh-hotbar-cardview", + "title": "Daggerheart Hotbar Card View", + "description": "Replaces the hover-tooltips on the hotbar icons with a full card view of the item for Daggerheart.", + "version": "1.0.0", + "compatibility": { + "minimum": "14", + "verified": "14" + }, + "authors": [ + { + "name": "Cosmo", + "email": "cptncosmo@gmail.com", + "url": "https://git.geeks.gay/cosmo/", + "discord": "@cptn_cosmo" + } + ], + "relationships": { + "systems": [ + { + "id": "daggerheart", + "type": "system", + "compatibility": { + "minimum": "2.1.0" + } + } + ] + }, + "esmodules": [ + "scripts/main.js" + ], + "styles": [ + "styles/cardview.css" + ], + "url": "https://git.geeks.gay/cosmo/dh-hotbar-cardview", + "manifest": "https://git.geeks.gay/cosmo/dh-hotbar-cardview/raw/branch/main/module.json", + "download": "https://git.geeks.gay/cosmo/dh-hotbar-cardview/releases/latest/download/dh-hotbar-cardview.zip" +} \ No newline at end of file diff --git a/scripts/main.js b/scripts/main.js new file mode 100644 index 0000000..02339db --- /dev/null +++ b/scripts/main.js @@ -0,0 +1,84 @@ +Hooks.once('init', () => { + const originalActivate = CONFIG.ux.TooltipManager.prototype.activate; + + CONFIG.ux.TooltipManager.prototype.activate = async function(element, options = {}) { + if (element.dataset.tooltip?.startsWith('#dhcard#')) { + const uuid = element.dataset.tooltip.slice(8); + const item = await fromUuid(uuid); + if (item) { + // Use Daggerheart's enrichText to prepare the item's descriptions + if (typeof this.enrichText === 'function') { + await this.enrichText(item); + } + + options.html = await renderTemplate('modules/dh-hotbar-cardview/templates/cardview.hbs', { + item: item, + description: item.system?.enrichedDescription ?? item.enrichedDescription ?? item.system?.description, + config: CONFIG.DH, + allDomains: CONFIG.DH.DOMAIN?.allDomains ? CONFIG.DH.DOMAIN.allDomains() : {} + }); + + options.direction = this.constructor.TOOLTIP_DIRECTIONS.UP; + options.cssClass = 'dh-hotbar-card-tooltip'; + } + } + return originalActivate.call(this, element, options); + }; +}); + +// Use a MutationObserver to aggressively update the hotbar tooltips as soon as the DOM changes. +// This ensures that the dataset is ready BEFORE the user hovers for the first time. +Hooks.once('ready', () => { + function processHotbar() { + const hotbarEls = document.querySelectorAll('#hotbar, .hotbar, [id^="hotbar"]'); + for (const hotbarEl of hotbarEls) { + const slots = hotbarEl.querySelectorAll('[data-slot]'); + for (const slotEl of slots) { + const slot = slotEl.dataset.slot; + if (!slot) continue; + + const macroId = slotEl.dataset.macroId || game.user.hotbar[slot]; + if (!macroId) continue; + + const macro = game.macros.get(macroId); + if (macro && macro.command) { + const match = macro.command.match(/useItem\s*\(\s*["']([^"']+)["']\s*\)/); + if (match) { + const uuid = match[1]; + const tooltipText = `#dhcard#${uuid}`; + + // Overwrite the tooltip attribute on the slot and all inner elements + slotEl.dataset.tooltip = tooltipText; + const innerTooltips = slotEl.querySelectorAll('[data-tooltip]'); + for (const inner of innerTooltips) { + inner.dataset.tooltip = tooltipText; + } + } + } + } + } + } + + // Process immediately + processHotbar(); + + // Re-process on hotbar changes + Hooks.on('updateUser', processHotbar); + Hooks.on('updateMacro', processHotbar); + Hooks.on('renderDhHotbar', processHotbar); + + // Watch for any dynamic DOM changes inside the hotbar container + const observer = new MutationObserver((mutations) => { + let shouldProcess = false; + for (const mutation of mutations) { + if (mutation.addedNodes.length > 0 || mutation.type === 'attributes') { + shouldProcess = true; + break; + } + } + if (shouldProcess) processHotbar(); + }); + + const hotbarWrapper = document.getElementById('ui-bottom') || document.body; + observer.observe(hotbarWrapper, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-slot', 'data-macro-id'] }); +}); diff --git a/styles/cardview.css b/styles/cardview.css new file mode 100644 index 0000000..c021ba9 --- /dev/null +++ b/styles/cardview.css @@ -0,0 +1,179 @@ +:root { + /* Use standard Foundry/System tooltip background, falling back to dark */ + --dh-card-bg: var(--color-background-tooltip, rgba(0, 0, 0, 0.9)); + --dh-card-text: var(--color-text-light-primary, #eeeeee); + /* Daggerheart gold */ + --dh-card-title: var(--color-primary, #f2cf5b); + --dh-card-border: #444; + --dh-card-banner-border: #222; +} + +.theme-dark, +.dark-mode { + --dh-card-bg: var(--color-background-tooltip, #1a1a1a); + --dh-card-text: var(--color-text-light-primary, #dddddd); + --dh-card-title: var(--color-primary, #f2cf5b); + --dh-card-border: #444; + --dh-card-banner-border: #222; +} + +/* Base Tooltip Override */ +.tooltip.dh-hotbar-card-tooltip, +#tooltip.dh-hotbar-card-tooltip, +.dh-hotbar-card-tooltip { + background: transparent !important; + border: none !important; + box-shadow: none !important; + padding: 0 !important; + margin-bottom: 12px !important; /* Slightly increase distance to the hotbar */ + max-width: none !important; + white-space: normal !important; +} + +.dh-hotbar-card { + width: 360px; + min-height: 504px; /* ~1:1.4 aspect ratio for playing card */ + background: var(--dh-card-bg); + border: 2px solid var(--dh-card-border); + border-radius: 12px; + overflow: hidden; + color: var(--dh-card-text); + font-family: 'Signika', 'Helvetica Neue', sans-serif; + box-shadow: 0 8px 24px rgba(0,0,0,0.8); + text-align: left; + display: flex; + flex-direction: column; + white-space: normal; +} + +.dh-hotbar-card .card-art-container { + position: relative; + width: 100%; + height: 180px; + border-bottom: 4px solid #f2cf5b; /* Gold line */ + background: #333; + display: flex; + justify-content: center; + align-items: center; + overflow: visible; /* To allow the banner to overlap */ +} + +.dh-hotbar-card .card-art { + width: 100%; + height: 100%; + object-fit: cover; + object-position: top; +} + +.dh-hotbar-card .card-ribbon { + position: absolute; + top: -2px; + left: 20px; + width: 44px; + height: 80px; + background: #1a1a1a; + border: 2px solid #f2cf5b; + border-top: none; + clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 85%, 0 100%); + display: flex; + flex-direction: column; + align-items: center; + padding-top: 5px; + z-index: 10; +} + +.dh-hotbar-card .card-ribbon .level { + font-size: 22px; + font-weight: 800; + color: white; + margin-bottom: 2px; + line-height: 1; +} + +.dh-hotbar-card .card-ribbon img { + width: 28px; + height: 28px; + /* brightness(0) invert(1) makes the icon white */ + filter: brightness(0) invert(1) drop-shadow(0 0 2px rgba(255,255,255,0.3)); + border: none; +} + +.dh-hotbar-card .card-cost { + position: absolute; + top: 15px; + right: 15px; + width: 40px; + height: 40px; + background: #1a1a1a; + border: 2px solid #f2cf5b; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + color: white; + font-weight: 800; + font-size: 18px; + z-index: 10; +} + +.dh-hotbar-card .card-cost i { + font-size: 12px; + margin-left: 2px; +} + +.dh-hotbar-card .card-type-banner { + position: absolute; + bottom: -14px; + left: 50%; + transform: translateX(-50%); + background: #1a1a1a; + color: white; + font-weight: 800; + font-size: 14px; + letter-spacing: 1px; + text-transform: uppercase; + padding: 4px 24px; + clip-path: polygon(10px 0, calc(100% - 10px) 0, 100% 50%, calc(100% - 10px) 100%, 10px 100%, 0 50%); + z-index: 10; +} + +.dh-hotbar-card .card-type-banner.ancestry { + left: auto; + right: 20px; + transform: none; + background: #f2cf5b; + color: #000; + border: 2px solid var(--dh-card-banner-border); +} + +/* Adjust layout so content doesn't collide with the banner */ +.dh-hotbar-card .card-content { + padding: 24px 20px 20px 20px; + background: var(--dh-card-bg); + flex-grow: 1; +} + +.dh-hotbar-card .card-title { + font-family: 'Cinzel', serif; + font-size: 28px; + font-weight: 900; + margin: 0 0 10px 0; + color: var(--dh-card-title); + text-transform: uppercase; + border: none; + line-height: 1.1; +} + +.dh-hotbar-card .card-description { + font-size: 14px; + line-height: 1.4; + color: var(--dh-card-text); +} + +.dh-hotbar-card .card-description p { + margin: 0 0 8px 0; +} + +.dh-hotbar-card .card-description strong { + font-weight: bold; +} diff --git a/templates/cardview.hbs b/templates/cardview.hbs new file mode 100644 index 0000000..2243e75 --- /dev/null +++ b/templates/cardview.hbs @@ -0,0 +1,34 @@ +
+
+ {{item.name}} + + {{#if (eq item.type "domainCard")}} +
+ {{item.system.level}} + {{#with (lookup allDomains item.system.domain) as | domain |}} + {{domain.label}} + {{/with}} +
+ +
+ {{item.system.recallCost}} + +
+ {{/if}} + +
+ {{#if item.system.type}} + {{#with (lookup config.DOMAIN.cardTypes item.system.type) as | cardType |}} + {{localize cardType.label}} + {{/with}} + {{else}} + {{localize (concat 'TYPES.Item.' item.type)}} + {{/if}} +
+
+ +
+

{{item.name}}

+
{{{description}}}
+
+