diff --git a/README.md b/README.md index 4ba921eb..c2eddb45 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ Some helper elements for the MCDM Draw Steel TTRPG Please use this [form to report bugs](https://docs.google.com/forms/d/e/1FAIpQLSc6m-pZ0NLt2EArE-Tcxr-XbAPMyhu40ANHJKtyRvvwBd2LSw/viewform?usp=sharing&ouid=105036387964900154878) if you find them! -You can see [Element Documentation Here](https://steelcompendium.github.io/draw-steel-elements/power-roll/) or see below. - ![sample](./docs/Media/sample.png) ## Elements diff --git a/main.ts b/main.ts index 9ebad7ae..5aa120c3 100644 --- a/main.ts +++ b/main.ts @@ -4,9 +4,10 @@ import {DEFAULT_SETTINGS, DSESettings} from "@model/Settings"; import {CompendiumDownloader} from "@utils/CompendiumDownloader"; import { registerElements } from '@/utils/RegisterElements'; import { initializeSchemaRegistry, resetSchemaRegistry } from '@utils/JsonSchemaValidator'; -import componentWrapperSchemaYaml from '@model/schemas/ComponentWrapperSchema.yaml'; import "./styles-source.css"; +import commonElementFieldsSchema from '@model/schemas/CommonElementFieldsSchema.yaml'; + export default class DrawSteelAdmonitionPlugin extends Plugin { settings: DSESettings; @@ -39,9 +40,9 @@ export default class DrawSteelAdmonitionPlugin extends Plugin { private initializeSchemas() { const dependencySchemas = [ { - id: "https://steelcompendium.io/schemas/component-wrapper-1.0.0", - schema: componentWrapperSchemaYaml - } + id: commonElementFieldsSchema.$id ?? "", + schema: commonElementFieldsSchema + }, // Add more dependency schemas here as needed // Note: Don't register main schemas that are being validated directly ]; diff --git a/package.json b/package.json index d8b13fd8..044ca839 100644 --- a/package.json +++ b/package.json @@ -21,30 +21,30 @@ "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-keywords": "^5.1.0", - "vue": "^3.5.20" + "vue": "^3.5.27" }, "devDependencies": { - "@codemirror/language": "^6.11.3", + "@codemirror/language": "^6.12.1", "@types/css": "^0.0.38", "@types/jest": "^30.0.0", - "@types/node": "^24.3.0", - "@types/uuid": "^10.0.0", - "@typescript-eslint/eslint-plugin": "8.41.0", - "@typescript-eslint/parser": "8.41.0", - "@vue/compiler-sfc": "3.5.20", + "@types/node": "^25.2.2", + "@types/uuid": "^11.0.0", + "@typescript-eslint/eslint-plugin": "8.54.0", + "@typescript-eslint/parser": "8.54.0", + "@vue/compiler-sfc": "3.5.27", "builtin-modules": "5.0.0", "css": "^3.0.0", - "esbuild": "0.25.9", + "esbuild": "0.27.3", "identity-obj-proxy": "^3.0.0", - "jest": "^30.0.5", + "jest": "^30.2.0", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", - "obsidian": "1.8.7", + "obsidian": "1.11.4", "steel-compendium-sdk": "2.1.5", - "ts-jest": "^29.4.1", + "ts-jest": "^29.4.6", "tslib": "2.8.1", - "typescript": "5.9.2", - "unplugin-vue": "^7.0.1", - "vue-tsc": "^3.0.6" + "typescript": "5.9.3", + "unplugin-vue": "^7.1.1", + "vue-tsc": "^3.2.4" } } diff --git a/src/drawSteelAdmonition/Common/AbstractElementView.ts b/src/drawSteelAdmonition/Common/AbstractElementView.ts new file mode 100644 index 00000000..8243e569 --- /dev/null +++ b/src/drawSteelAdmonition/Common/AbstractElementView.ts @@ -0,0 +1,17 @@ +import { MarkdownPostProcessorContext, Plugin } from "obsidian"; + +export abstract class AbstractElementView { + protected plugin: Plugin; + protected data: any; + protected ctx: MarkdownPostProcessorContext; + protected options?: any + + constructor(plugin: Plugin, data: any, ctx: MarkdownPostProcessorContext, options?: any) { + this.plugin = plugin; + this.data = data; + this.ctx = ctx; + this.options = options; + } + + public abstract build(parent: HTMLElement, children?: Array, options?: any): HTMLElement | Promise; +} diff --git a/src/drawSteelAdmonition/Common/CommonElementWrapperView.ts b/src/drawSteelAdmonition/Common/CommonElementWrapperView.ts new file mode 100644 index 00000000..f058e539 --- /dev/null +++ b/src/drawSteelAdmonition/Common/CommonElementWrapperView.ts @@ -0,0 +1,59 @@ +import { MarkdownPostProcessorContext, Plugin } from "obsidian"; +import { CommonElementWrapper } from "@model/CommonElementWrapper"; +import { AbstractElementView } from "@drawSteelAdmonition/Common/AbstractElementView"; + +export class CommonElementWrapperView extends AbstractElementView { + private state: { isCollapsed: boolean }; + + constructor( + plugin: Plugin, + data: CommonElementWrapper, + ctx: MarkdownPostProcessorContext, + options: { elementName: string }, + ) { + super(plugin, data, ctx, options); + this.state = { isCollapsed: this.data?.collapse_default ?? false }; + } + + public build( + parent: HTMLElement, + children: Array, + ): HTMLElement { + const container = parent.createEl("div", { + cls: "ds-common-element-wrapper", + }); + container.toggleClass( + "ds-common-element-wrapper-collapsible", + this.data.collapsible, + ); + + // Hide Indicator + // TODO: implement + + if (!this.state.isCollapsed) { + if (children) { + container.appendChild(children[0]) + } + } else { + container.createEl("div", { text: this.options.elementName }); + } + + // Container click handler - only triggers on container itself + container.addEventListener("click", (event: MouseEvent) => { + if (this.data.collapsible == false) return; + if (event.target === container) { + this.state.isCollapsed = !this.state.isCollapsed; + container.empty(); + if (!this.state.isCollapsed) { + if (children) { + container.appendChild(children[0]) + } + } else { + container.createEl("div", { text: this.options.elementName }); + } + } + }); + + return container; + } +} diff --git a/src/drawSteelAdmonition/Counter/CounterHorizontalView.ts b/src/drawSteelAdmonition/Counter/CounterHorizontalView.ts new file mode 100644 index 00000000..75b6a8ac --- /dev/null +++ b/src/drawSteelAdmonition/Counter/CounterHorizontalView.ts @@ -0,0 +1,112 @@ +import { + MarkdownPostProcessorContext, + Plugin, + setIcon, +} from "obsidian"; +import { Counter } from "@model/Counter"; +import { CounterView } from "@drawSteelAdmonition/Counter/CounterView"; +import { CodeBlocks } from "@utils/CodeBlocks"; + +export class CounterHorizontalView extends CounterView { + constructor( + plugin: Plugin, + data: Counter, + ctx: MarkdownPostProcessorContext, + ) { + super(plugin, data, ctx); + } + + public async build(parent: HTMLElement): Promise { + const container = super.buildBase(parent); + + // TODO: Remove + // Not implemented warning + container.createEl('div', { + text: "Horizontal Counter is not implemented yet" + }) + + // // LAYOUT + // const displayContainer = container.createEl("div", { + // cls: "ds-counter-display-container", + // }); + + // const nameTopDisplay = displayContainer.createEl("div", { + // cls: "ds-counter-name", + // text: this.data?.name_top, + // }); + + // const valueDisplay = displayContainer.createEl("input", { + // cls: "ds-counter-value", + // value: this.data?.current_value.toString(), + // placeholder: this.data?.current_value.toString(), + // type: "number", + // }); + // valueDisplay.readOnly = false; + + // const nameBottomDisplay = displayContainer.createEl("div", { + // cls: "ds-counter-name", + // text: this.data?.name_bottom, + // }); + + // const controlsContainer = container.createEl("div", { + // cls: "ds-counter-controls", + // }); + + // const incrementButton = controlsContainer.createEl("button", { + // cls: "ds-counter-button", + // }); + // const decrementButton = controlsContainer.createEl("button", { + // cls: "ds-counter-button", + // }); + + // // EVENTS + // valueDisplay.addEventListener("click", () => { + // valueDisplay.focus(); + // valueDisplay.select(); + // }); + // valueDisplay.addEventListener("change", () => { + // let newValue = parseInt(valueDisplay.value); + // if (!isNaN(newValue)) { + // newValue = Math.min(newValue, this.data?.max_value ?? Infinity); + // newValue = Math.max(newValue, this.data.min_value ?? 0); + // this.data.current_value = newValue; + // valueDisplay.value = newValue.toString(); + // super.updateButtons(incrementButton, decrementButton); + // if (this.data.auto_save) { + // CodeBlocks.updateCounter( + // this.plugin.app, + // this.data, + // this.ctx, + // ); + // } + // } + // }); + + // incrementButton.addEventListener("click", () => { + // this.incrementValue(); + // valueDisplay.value = this.data.current_value.toString(); + // this.updateButtons(incrementButton, decrementButton); + // CodeBlocks.updateCounter(this.plugin.app, this.data, this.ctx); + // }); + + // decrementButton.addEventListener("click", () => { + // this.decrementValue(); + // valueDisplay.value = this.data.current_value.toString(); + // this.updateButtons(incrementButton, decrementButton); + // CodeBlocks.updateCounter(this.plugin.app, this.data, this.ctx); + // }); + + // // STYLING + // valueDisplay.style.fontSize = `${this.data.value_height}em`; + // setIcon(incrementButton, "chevron-up"); + // setIcon(decrementButton, "chevron-down"); + + // nameTopDisplay.style.fontSize = `${this.data.name_top_height}em`; + // nameBottomDisplay.style.fontSize = `${this.data.name_bottom_height}em`; + + // // ADDITIONAL + // this.updateButtons(incrementButton, decrementButton); + + return container; + } +} diff --git a/src/drawSteelAdmonition/Counter/CounterVerticalView.ts b/src/drawSteelAdmonition/Counter/CounterVerticalView.ts new file mode 100644 index 00000000..13ddc29c --- /dev/null +++ b/src/drawSteelAdmonition/Counter/CounterVerticalView.ts @@ -0,0 +1,69 @@ +import { MarkdownPostProcessorContext, Plugin, setIcon } from "obsidian"; +import { Counter } from "@model/Counter"; +import { CounterView } from "@drawSteelAdmonition/Counter/CounterView"; +import { CodeBlocks } from "@utils/CodeBlocks"; + +export class CounterVerticalView extends CounterView { + constructor( + plugin: Plugin, + data: Counter, + ctx: MarkdownPostProcessorContext, + ) { + super(plugin, data, ctx); + } + + public async build(parent: HTMLElement): Promise { + const container = super.buildBase(parent); + + // LAYOUT + const displayContainer = container.createEl("div", { + cls: "ds-counter-display-container", + }); + + const nameTopDisplay = displayContainer.createEl("div", { + cls: "ds-counter-name", + text: this.data?.name_top, + }); + + const valueDisplay = displayContainer.createEl("input", { + cls: "ds-counter-value", + value: this.clampedCurrentValue().toString(), + placeholder: this.clampedCurrentValue().toString(), + type: "number", + }); + valueDisplay.readOnly = false; + + const nameBottomDisplay = displayContainer.createEl("div", { + cls: "ds-counter-name", + text: this.data?.name_bottom, + }); + + const controlsContainer = container.createEl("div", { + cls: "ds-counter-controls", + }); + + const incrementButton = controlsContainer.createEl("button", { + cls: "ds-counter-button", + }); + const decrementButton = controlsContainer.createEl("button", { + cls: "ds-counter-button", + }); + + // EVENTS + this.setEventListeners(valueDisplay, incrementButton, decrementButton); + + // STYLING + valueDisplay.style.fontSize = `${this.data.value_height}em`; + setIcon(incrementButton, "chevron-up"); + setIcon(decrementButton, "chevron-down"); + + nameTopDisplay.style.fontSize = `${this.data.name_top_height}em`; + nameBottomDisplay.style.fontSize = `${this.data.name_bottom_height}em`; + + // ADDITIONAL + this.applyValue(valueDisplay, incrementButton, decrementButton); + + + return container; + } +} diff --git a/src/drawSteelAdmonition/Counter/CounterView.ts b/src/drawSteelAdmonition/Counter/CounterView.ts index 915bdbd5..c6bf9c58 100644 --- a/src/drawSteelAdmonition/Counter/CounterView.ts +++ b/src/drawSteelAdmonition/Counter/CounterView.ts @@ -1,175 +1,121 @@ -import { Component, MarkdownPostProcessorContext, Plugin, setIcon } from "obsidian"; +import { MarkdownPostProcessorContext, Plugin, setIcon } from "obsidian"; +import { CommonElementWrapperView } from "@drawSteelAdmonition/Common/CommonElementWrapperView"; import { Counter } from "@model/Counter"; import { CodeBlocks } from "@utils/CodeBlocks"; - -export class CounterView { - private plugin: Plugin; - private data: Counter; - private ctx: MarkdownPostProcessorContext; - - constructor(plugin: Plugin, data: Counter, ctx: MarkdownPostProcessorContext) { - this.plugin = plugin; - this.data = data; - this.ctx = ctx; - } - - public build(parent: HTMLElement) { - const container = parent.createEl("div", { cls: "ds-counter-container" }); - - // Create display area for value and name - const displayContainer = container.createEl("div", { cls: "ds-counter-display-container" }); - - // Value display - const valueDisplay = displayContainer.createEl("div", { - cls: "ds-counter-value", - text: this.data.current_value.toString(), - }); - - // Set the font size based on value_height - valueDisplay.style.fontSize = `${this.data.value_height}em`; - - // Add click event to make it editable - valueDisplay.addEventListener("click", () => { - this.makeValueEditable(valueDisplay, incrementButton, decrementButton); - }); - - // Name display - const nameDisplay = displayContainer.createEl("div", { - cls: "ds-counter-name", - text: this.data.name, - }); - - // Set the font size based on name_height - nameDisplay.style.fontSize = `${this.data.name_height}em`; - - // Controls container - const controlsContainer = container.createEl("div", { cls: "ds-counter-controls" }); - - // Increment button - const incrementButton = controlsContainer.createEl("button", { cls: "ds-counter-button" }); - setIcon(incrementButton, "chevron-up"); - incrementButton.addEventListener("click", () => { - this.incrementValue(); - valueDisplay.textContent = this.data.current_value.toString(); - this.updateButtons(incrementButton, decrementButton); - CodeBlocks.updateCounter(this.plugin.app, this.data, this.ctx); - }); - - // Decrement button - const decrementButton = controlsContainer.createEl("button", { cls: "ds-counter-button" }); - setIcon(decrementButton, "chevron-down"); - decrementButton.addEventListener("click", () => { - this.decrementValue(); - valueDisplay.textContent = this.data.current_value.toString(); - this.updateButtons(incrementButton, decrementButton); - CodeBlocks.updateCounter(this.plugin.app, this.data, this.ctx); - }); - - // Initial button state - this.updateButtons(incrementButton, decrementButton); - - // Style the container to position controls on the right - container.addClass("ds-counter-flex"); - } - - private incrementValue() { - const { current_value, max_value } = this.data; - if (max_value !== undefined && current_value >= max_value) { - return; - } - this.data.current_value += 1; - } - - private decrementValue() { - const { current_value, min_value } = this.data; - if (current_value <= min_value) { - return; - } - this.data.current_value -= 1; - } - - private updateButtons(incrementButton: HTMLElement, decrementButton: HTMLElement) { - const { current_value, max_value, min_value } = this.data; - if (max_value !== undefined && current_value >= max_value) { - incrementButton.setAttribute('disabled', 'true'); - } else { - incrementButton.removeAttribute('disabled'); - } - if (current_value <= min_value) { - decrementButton.setAttribute('disabled', 'true'); - } else { - decrementButton.removeAttribute('disabled'); - } - } - - private makeValueEditable(valueDisplay: HTMLElement, incrementButton: HTMLElement, decrementButton: HTMLElement) { - // Create input field - const inputField = createEl('input', { - type: 'number', - value: this.data.current_value.toString(), - cls: 'ds-counter-input', - }); - - // Set the font size - inputField.style.fontSize = `${this.data.value_height}em`; - inputField.style.height = `1em`; - - // Replace valueDisplay with inputField - valueDisplay.replaceWith(inputField); - inputField.focus(); - inputField.select(); - - // Disable increment and decrement buttons while editing - incrementButton.setAttribute('disabled', 'true'); - decrementButton.setAttribute('disabled', 'true'); - - const finishEditing = () => { - let newValue = parseInt(inputField.value); - if (isNaN(newValue)) { - newValue = this.data.current_value; // Revert if invalid - } else { - if (this.data.max_value !== undefined) { - newValue = Math.min(newValue, this.data.max_value); - } - newValue = Math.max(newValue, this.data.min_value); - } - - this.data.current_value = newValue; - - // Create new valueDisplay - const newValueDisplay = createEl('div', { - cls: 'ds-counter-value', - text: newValue.toString(), - }); - - // Set font size - newValueDisplay.style.fontSize = `${this.data.value_height}em`; - - // Add click event - newValueDisplay.addEventListener("click", () => { - this.makeValueEditable(newValueDisplay, incrementButton, decrementButton); - }); - - // Replace inputField with newValueDisplay - inputField.replaceWith(newValueDisplay); - - // Update buttons - this.updateButtons(incrementButton, decrementButton); - - // Update code block - CodeBlocks.updateCounter(this.plugin.app, this.data, this.ctx); - }; - - inputField.addEventListener('blur', finishEditing); - - inputField.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - finishEditing(); - } else if (e.key === 'Escape') { - // Cancel editing - inputField.value = this.data.current_value.toString(); - finishEditing(); - } - }); - } +import { AbstractElementView } from "@drawSteelAdmonition/Common/AbstractElementView"; + +export abstract class CounterView extends AbstractElementView { + constructor( + plugin: Plugin, + data: Counter, + ctx: MarkdownPostProcessorContext, + ) { + super(plugin, data, ctx); + } + + public async build(parent: HTMLElement): Promise { + let view: CounterView; + if (this.data?.style === "horizontal") { + const { CounterHorizontalView } = + await import("@drawSteelAdmonition/Counter/CounterHorizontalView"); + view = new CounterHorizontalView(this.plugin, this.data, this.ctx); + } else { + const { CounterVerticalView } = + await import("@drawSteelAdmonition/Counter/CounterVerticalView"); + view = new CounterVerticalView(this.plugin, this.data, this.ctx); + } + return view.build(parent); + } + + protected buildBase(parent: HTMLElement): HTMLElement { + const container = parent.createEl("div", { + cls: "ds-counter-container", + }); + + const elementWrapper = new CommonElementWrapperView( + this.plugin, + this.data, + this.ctx, + { elementName: "Counter" }, + ); + + elementWrapper.build(parent, [container]); + return container; + } + + protected clampedCurrentValue() { + const { current_value, max_value, min_value } = this.data; + return Math.clamp( + current_value, + min_value ?? -Infinity, + max_value ?? Infinity, + ); + } + + protected incrementValue() { + this.data.current_value += 1; + this.data.current_value = this.clampedCurrentValue(); + } + + protected decrementValue() { + this.data.current_value -= 1; + this.data.current_value = this.clampedCurrentValue(); + } + + protected updateButtons( + incrementButton: HTMLElement, + decrementButton: HTMLElement, + ) { + const { current_value, max_value, min_value } = this.data; + incrementButton.toggleAttribute( + "disabled", + max_value !== undefined && current_value >= max_value, + ); + decrementButton.toggleAttribute("disabled", current_value <= min_value); + } + + protected applyValue( + valueDisplay: HTMLInputElement, + incrementButton: HTMLButtonElement, + decrementButton: HTMLButtonElement, + ) { + this.data.current_value = this.clampedCurrentValue(); + valueDisplay.value = this.data.current_value.toString(); + this.updateButtons(incrementButton, decrementButton); + if (this.data.auto_save) { + CodeBlocks.updateCounter(this.plugin.app, this.data, this.ctx); + } + } + + protected setEventListeners( + valueDisplay: HTMLInputElement, + incrementButton: HTMLButtonElement, + decrementButton: HTMLButtonElement, + ) { + valueDisplay.addEventListener("click", () => { + if (document.activeElement !== valueDisplay) { + valueDisplay.select(); + } + valueDisplay.focus(); + }); + + valueDisplay.addEventListener("change", () => { + let newValue = parseInt(valueDisplay.value); + if (!isNaN(newValue)) { + this.data.current_value = newValue; + this.data.current_value = this.clampedCurrentValue(); + this.applyValue(valueDisplay, incrementButton, decrementButton); + } + }); + + incrementButton.addEventListener("click", () => { + this.incrementValue(); + this.applyValue(valueDisplay, incrementButton, decrementButton); + }); + + decrementButton.addEventListener("click", () => { + this.decrementValue(); + this.applyValue(valueDisplay, incrementButton, decrementButton); + }); + } } diff --git a/src/drawSteelAdmonition/GenericElementProcessor.ts b/src/drawSteelAdmonition/GenericElementProcessor.ts new file mode 100644 index 00000000..686e8eeb --- /dev/null +++ b/src/drawSteelAdmonition/GenericElementProcessor.ts @@ -0,0 +1,105 @@ +import { Plugin, MarkdownPostProcessorContext } from "obsidian"; +import { AbstractElementView } from "@drawSteelAdmonition/Common/AbstractElementView"; + +type ElementViewClass = new (plugin: Plugin, model: any, ctx: MarkdownPostProcessorContext) => T + +export class GenericElementProcessor { + plugin: Plugin; + view: ElementViewClass; + model: any; + element_name: string; + edit_mode_safe: boolean; + + readonly handler = ( + source: string, + el: HTMLElement, + ctx: MarkdownPostProcessorContext, + ) => this.postProcess(source, el, ctx); + + constructor( + plugin: Plugin, + view: ElementViewClass, + model: any, + element_name: string, + edit_mode_safe: boolean = false, + ) { + this.plugin = plugin; + this.view = view; + this.model = model; + this.element_name = element_name; + this.edit_mode_safe = edit_mode_safe; + } + + public postProcess( + source: string, + el: HTMLElement, + ctx: MarkdownPostProcessorContext, + ): void | Promise { + genericElementPostProcess( + this.plugin, + source, + el, + ctx, + this.view, + this.model, + this.element_name, + this.edit_mode_safe, + ); + } +} + +export function genericElementPostProcess( + plugin: Plugin, + source: string, + el: HTMLElement, + ctx: MarkdownPostProcessorContext, + view: ElementViewClass, + model: any, + user_message_name: string, + edit_mode_safe: boolean, +): void | Promise { + // Edit mode safe elements don't have any interactive or otherwise + // important elements outside the preview codeblock and thus don't need to + // be indented + let wrapper_class: string = edit_mode_safe + ? "ds-element-wrapper-edit-safe" + : "ds-element-wrapper"; + + // Create a wrapper for the element + const elementWrapper = el.createEl("div", { cls: wrapper_class }); + try { + // Parse the YAML and create the view + let parsedData: any; + if (model) { + parsedData = model.parseYaml(source); + } + + // Create the view instance + const viewInstance = new view(plugin, parsedData, ctx); + + // Render the element (using build method for consistency with existing views) + viewInstance.build(elementWrapper); + + // Avoid clicks in reading mode from opening edit view + const stop = (e: Event) => { + e.preventDefault(); + e.stopPropagation(); + }; + // elementWrapper.addEventListener("dblclick", stop, { capture: true }); + elementWrapper.addEventListener("mousedown", stop, { capture: true }); + elementWrapper.addEventListener("pointerdown", stop, { capture: true }); + + // Store view instance for cleanup if needed + (elementWrapper as any)._elementView = viewInstance; + } catch (error) { + // Display error message to the user + let userMessage = + `The Draw Steel Elements plugin loaded the ${user_message_name} properly, but ` + + `failed to process the input config. Please correct the following error:\n\n`; + userMessage += error.message; + elementWrapper.createEl("div", { + text: userMessage, + cls: "error-message", + }); + } +} diff --git a/src/drawSteelAdmonition/statblock/StatblockProcessor.ts b/src/drawSteelAdmonition/statblock/StatblockProcessor.ts index bdd01833..546dab9a 100644 --- a/src/drawSteelAdmonition/statblock/StatblockProcessor.ts +++ b/src/drawSteelAdmonition/statblock/StatblockProcessor.ts @@ -24,7 +24,6 @@ export class StatblockProcessor { e.preventDefault(); e.stopPropagation(); }; - // container.addEventListener("dblclick", stop, { capture: true }); container.addEventListener("mousedown", stop, {capture: true}); container.addEventListener("pointerdown", stop, {capture: true}); } catch (error) { diff --git a/src/model/AbstractModel.ts b/src/model/AbstractModel.ts new file mode 100644 index 00000000..532104ef --- /dev/null +++ b/src/model/AbstractModel.ts @@ -0,0 +1,17 @@ +import {parseYaml} from "obsidian"; + +export abstract class AbstractModel { + public static parseYaml(source: string) { + let data: any; + try { + data = parseYaml(source); + } catch (error: any) { + throw new Error("Invalid YAML format: " + error.message); + } + return this.parse(data); + } + + public static parse(data: any): AbstractModel { + throw new Error("Subclasses of AbstractModel must implement parse()"); + } +} diff --git a/src/model/CommonElementWrapper.ts b/src/model/CommonElementWrapper.ts new file mode 100644 index 00000000..dae42b3c --- /dev/null +++ b/src/model/CommonElementWrapper.ts @@ -0,0 +1,27 @@ +import {parseYaml} from "obsidian"; + +export class CommonElementWrapper { + public collapsible: boolean; + public collapse_default: boolean; + + public static parseYaml(source: string) { + let data: any; + try { + data = parseYaml(source); + } catch (error: any) { + throw new Error("Invalid YAML format: " + error.message); + } + return CommonElementWrapper.parse(data); + } + + public static parse(data: any): CommonElementWrapper { + return new CommonElementWrapper( + data.collapsible, + data.collapse_default); + } + + constructor(collapsible: boolean, collapse_default: boolean) { + this.collapsible = collapsible ?? true; + this.collapse_default = collapse_default ?? false; + } +} diff --git a/src/model/ComponentWrapper.ts b/src/model/ComponentWrapper.ts index c7fd356b..eab7af45 100644 --- a/src/model/ComponentWrapper.ts +++ b/src/model/ComponentWrapper.ts @@ -1,19 +1,9 @@ -import {parseYaml} from "obsidian"; -import {Creature, CreatureInstance, Hero} from "@drawSteelAdmonition/EncounterData"; +import { AbstractModel } from "@model/AbstractModel"; -export class ComponentWrapper { - collapsible: boolean; - collapse_default: boolean; - - public static parseYaml(source: string) { - let data: any; - try { - data = parseYaml(source); - } catch (error: any) { - throw new Error("Invalid YAML format: " + error.message); - } - return ComponentWrapper.parse(data); - } +export class ComponentWrapper extends AbstractModel{ + public collapsible: boolean; + public collapse_default: boolean; + public element_name: string; public static parse(data: any): ComponentWrapper { return new ComponentWrapper( @@ -22,6 +12,7 @@ export class ComponentWrapper { } constructor(collapsible: boolean, collapse_default: boolean) { + super(); this.collapsible = collapsible ?? true; this.collapse_default = collapse_default ?? false; } diff --git a/src/model/Counter.ts b/src/model/Counter.ts index 6d542c32..d0eaad2f 100644 --- a/src/model/Counter.ts +++ b/src/model/Counter.ts @@ -1,39 +1,99 @@ -import {parseYaml} from "obsidian"; +import { parseYaml } from "obsidian"; +import { + validateDataWithSchema, + ValidationError, +} from "@utils/JsonSchemaValidator"; +import { ComponentWrapper } from "@model/ComponentWrapper"; +import counterSchemaYaml from "@model/schemas/Counter.yaml"; -export class Counter { - max_value?: number; - current_value: number; - min_value: number; - name: string; - value_height: number; - name_height: number; +type HideButtonsType = "both" | "neither" | "plus" | "minus" | undefined; +type StyleType = "default" | "horizontal" | "vertical" | undefined; +export class Counter extends ComponentWrapper { + name_top: string; + name_bottom: string; + current_value: number; + max_value?: number; + min_value: number; + auto_save: boolean; + name_top_height: number; + name_bottom_height: number; + value_height: number; + max_value_height: number; + hide_buttons: HideButtonsType; + style: StyleType; - public static parseYaml(source: string) { - let data: any; - try { - data = parseYaml(source); - } catch (error: any) { - throw new Error("Invalid YAML format: " + error.message); - } - return Counter.parse(data); - } + public static parseYaml(source: string) { + try { + // Validate YAML content against YAML schema (all dependencies pre-registered) + const validation = validateDataWithSchema( + source, + counterSchemaYaml, + ); + if (!validation.valid) { + const errorMessages = validation.errors + .map( + (error: ValidationError) => + `${error.path}: ${error.message}`, + ) + .join(", "); + throw new Error("Schema validation failed: " + errorMessages); + } - public static parse(data: any): Counter { - return new Counter( - data.max_value, - data.current_value ? data.current_value : 0, - data.min_value ? data.min_value : 0, - data.name, - data.value_height ? data.value_height : 3, - data.name_height ? data.name_height : 1); - } + // Parse the YAML after validation + const data = parseYaml(source); + return Counter.parse(data); + } catch (error: any) { + throw new Error("Invalid YAML format: " + error.message); + } + } - constructor(max_value: number| undefined, current_value: number, min_value: number, name: string, value_height: number, name_height: number) { - this.max_value = max_value; - this.current_value = current_value; - this.min_value = min_value; - this.name = name; - this.value_height = value_height; - this.name_height = name_height; - } + public static parse(data: any): Counter { + return { + collapsible: data.collapsible, + collapse_default: data.collapse_default, + name_top: data.name_top, + name_bottom: data.name_bottom ?? data.name, + current_value: data.current_value, + max_value: data.max_value, + min_value: data.min_value, + auto_save: data.auto_save, + name_top_height: data.name_top_height ?? data.name_height ?? 1, + name_bottom_height: + data.name_bottom_height ?? data.name_height ?? 1, + value_height: data.value_height ?? 3, + max_value_height: data.max_value_height ?? data.value_height, + hide_buttons: data.hide_buttons, + style: data.style, + } as Counter; + } + + constructor( + collapsible: boolean, + collapse_default: boolean, + max_value: number | undefined, + current_value: number, + min_value: number, + auto_save: boolean, + name_top: string, + name_bottom: string, + value_height: number, + name_top_height: number, + name_bottom_height: number, + hide_buttons: HideButtonsType, + style: StyleType, + ) { + super(collapsible, collapse_default); + this.current_value = current_value ?? 0; + this.min_value = min_value ?? undefined; + this.max_value = max_value ?? undefined; + this.auto_save = auto_save == false; + this.name_top = name_top; + this.name_bottom = name_bottom; + this.value_height = value_height ?? 3; + this.max_value_height = this.max_value_height ?? this.value_height; + this.name_top_height = name_top_height ?? 1; + this.name_bottom_height = name_bottom_height ?? 1; + this.hide_buttons = hide_buttons ?? "neither"; + this.style = style ?? "default"; + } } diff --git a/src/model/schemas/CommonElementFieldsSchema.yaml b/src/model/schemas/CommonElementFieldsSchema.yaml new file mode 100644 index 00000000..b7628e9a --- /dev/null +++ b/src/model/schemas/CommonElementFieldsSchema.yaml @@ -0,0 +1,18 @@ +# JSON Schema in YAML format for Draw Steel Character Skills +$schema: "https://json-schema.org/draft/2019-09/schema" +$id: dse.common-component-fields.schema.json-1.0.0 +title: "Draw Steel Common Component Fields" +description: "Schema for validating Draw Steel common component fields data" + +type: object +properties: + collapsible: + type: boolean + description: Whether the component can be collapsed or not + default: true + errorMessage: "collapsible must be true or false" + collapse_default: + type: boolean + description: Whether the component is collapsed or not at first render + default: false + errorMessage: "collapse_default must be true or false" diff --git a/src/model/schemas/ComponentWrapperSchema.yaml b/src/model/schemas/ComponentWrapperSchema.yaml deleted file mode 100644 index a365048f..00000000 --- a/src/model/schemas/ComponentWrapperSchema.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# JSON Schema in YAML format for Draw Steel Character Skills -$schema: "https://json-schema.org/draft/2019-09/schema" -$id: "https://steelcompendium.io/schemas/component-wrapper-1.0.0" -title: "Draw Steel Common Component Fields" -description: "Schema for validating Draw Steel base component wrapper data" - -type: object -properties: - collapsible: - type: boolean - description: Whether the component can be collapsed or not - default: true - errorMessage: "collapsible must be true or false" - collapse_default: - type: boolean - description: Whether the component is collapsed or not at first render - default: false - errorMessage: "collapse_default must be true or false" -... diff --git a/src/model/schemas/Counter.yaml b/src/model/schemas/Counter.yaml new file mode 100644 index 00000000..12aca74b --- /dev/null +++ b/src/model/schemas/Counter.yaml @@ -0,0 +1,77 @@ +# JSON Schema in YAML format for Draw Steel Character Skills +$schema: https://json-schema.org/draft/2019-09/schema +$id: dse.counter.schema.json-1.0.0 +title: Draw Steel Character Skills +description: Schema for validating Draw Steel character skills data +type: object +allOf: + - $ref: dse.common-component-fields.schema.json-1.0.0 + - type: object + properties: + name_top: + type: string + description: The name shown above the counter (e.g., "Stamina", "Heroic Resource"). + name_bottom: + type: string + description: The name shown below the counter (e.g., "Focus", "Drama). + name: + type: string + description: Alias for name_bottom for backwards compatibility. If name_bottom exists name will be ignored. + current_value: + type: number + description: The current value of the counter. + max_value: + type: number + description: The maximum value the counter can reach. + min_value: + type: number + description: The minimum value the counter can reach. + auto_save: + type: boolean + description: Whether the value should be saved back to yaml. + value_height: + type: number + description: Adjusts the size of the counter value text in the rendered output as a multiplier. + max_value_height: + type: number + description: Adjusts the size of the counter max_value text in the rendered output as a multiplier. + name_top_height: + type: number + description: Adjusts the size of the counter name_top text in the rendered output as a multiplier. + name_bottom_height: + type: number + description: Adjusts the size of the counter name_bottom text in the rendered output as a multiplier. + name_height: + type: number + description: Alias for name_bottom_height for backwards compatibility. If name_bottom_height exists name_height will be ignored. + hide_buttons: + type: string + description: \"both\" will hide both buttons, \"plus\" and \"minus\" will hide the corresponding button, \"neither\" will hide neither. + transform: + - trim + - toLowerCase + enum: + - both + - neither + - plus + - minus + default: neither + errorMessage: hide_buttons must be either \"both\", \"neither\", \"plus\" or \minus\" + style: + type: string + description: The style the counter should use. + transform: + - trim + - toLowerCase + enum: + - default # Alias for vertical + - horizontal + - vertical + default: vertical + errorMessage: style must be either "default", "horizontal" or "vertical" + +unevaluatedProperties: + const: false + errorMessage: | + Counter object contains invalid property '${0#}'. + Allowed properties: name_top, name_bottom, current_value, max_value, min_value, value_height, name_height, style. diff --git a/src/model/schemas/SkillsSchema.yaml b/src/model/schemas/SkillsSchema.yaml index 58c51b81..7eebdf5e 100644 --- a/src/model/schemas/SkillsSchema.yaml +++ b/src/model/schemas/SkillsSchema.yaml @@ -1,12 +1,12 @@ # JSON Schema in YAML format for Draw Steel Character Skills $schema: https://json-schema.org/draft/2019-09/schema -$id: https://steelcompendium.io/schemas/skills-1.0.0 +$id: dse.skills.schema.json-1.0.0 title: Draw Steel Character Skills description: Schema for validating Draw Steel character skills data type: object allOf: - - $ref: https://steelcompendium.io/schemas/component-wrapper-1.0.0 + - "$ref": dse.common-component-fields.schema.json-1.0.0 - properties: only_show_selected: type: boolean diff --git a/src/model/schemas/StaminaBarSchema.yaml b/src/model/schemas/StaminaBarSchema.yaml index 8538ca08..8657a03d 100644 --- a/src/model/schemas/StaminaBarSchema.yaml +++ b/src/model/schemas/StaminaBarSchema.yaml @@ -1,12 +1,12 @@ # JSON Schema in YAML format for Draw Steel Character Skills $schema: https://json-schema.org/draft/2019-09/schema -$id: https://steelcompendium.io/schemas/stamina-bar-1.0.0 +$id: dse.stamina-bar.schema.json-1.0.0 title: Draw Steel Stamina Bar description: Schema for validating Draw Steel stamina bar data type: object allOf: - - $ref: https://steelcompendium.io/schemas/component-wrapper-1.0.0 + - "$ref": dse.common-component-fields.schema.json-1.0.0 - type: object required: [max_stamina] errorMessage: diff --git a/src/types/yaml.d.ts b/src/types/yaml.d.ts index 72e4aa30..f8ede9fa 100644 --- a/src/types/yaml.d.ts +++ b/src/types/yaml.d.ts @@ -1,10 +1,45 @@ -// Type declarations for YAML files loaded as text +// JSON Schema interface for better type safety +interface JSONSchema { + $schema?: string; + $id?: string; + title?: string; + description?: string; + type?: string | string[]; + properties?: Record; + required?: string[]; + allOf?: JSONSchema[]; + anyOf?: JSONSchema[]; + oneOf?: JSONSchema[]; + $ref?: string; + items?: JSONSchema; + additionalProperties?: boolean | JSONSchema; + unevaluatedProperties?: boolean | JSONSchema; + enum?: any[]; + const?: any; + default?: any; + errorMessage?: string | Record; + definitions?: Record; + [key: string]: any; // Allow additional schema properties +} + +// More specific typing for schema files +declare module '*Schema.yaml' { + const content: JSONSchema; + export default content; +} + +declare module '*schema.yaml' { + const content: JSONSchema; + export default content; +} + +// General YAML files as JSON objects declare module '*.yaml' { - const content: string; + const content: Record; export default content; } declare module '*.yml' { - const content: string; + const content: Record; export default content; } diff --git a/src/utils/JsonSchemaValidator.ts b/src/utils/JsonSchemaValidator.ts index 0b7a03d4..3ba20afe 100644 --- a/src/utils/JsonSchemaValidator.ts +++ b/src/utils/JsonSchemaValidator.ts @@ -204,7 +204,22 @@ export function validateDataWithSchema(data: string | object, schema: string | o // Parse schema if it's a string let parsedSchema: object; if (typeof schema === 'string') { - parsedSchema = isJsonString(schema) ? JSON.parse(schema) : parseYaml(schema); + // Try JSON first + if (isJsonString(schema)) { + parsedSchema = JSON.parse(schema); + } else { + // Assume YAML and parse it + try { + parsedSchema = parseYaml(schema); + } catch (yamlError: any) { + throw new Error(`Failed to parse schema as YAML: ${yamlError.message}`); + } + } + + // Validate that we got an object + if (typeof parsedSchema !== 'object' || parsedSchema === null) { + throw new Error(`Schema must be an object, got ${typeof parsedSchema}`); + } } else { parsedSchema = schema; } @@ -212,7 +227,15 @@ export function validateDataWithSchema(data: string | object, schema: string | o // Parse data if it's a string let parsedData: any; if (typeof data === 'string') { - parsedData = isJsonString(data) ? JSON.parse(data) : parseYaml(data); + if (isJsonString(data)) { + parsedData = JSON.parse(data); + } else { + try { + parsedData = parseYaml(data); + } catch (yamlError: any) { + throw new Error(`Failed to parse data as YAML: ${yamlError.message}`); + } + } } else { parsedData = data; } diff --git a/src/utils/RegisterElements.ts b/src/utils/RegisterElements.ts index 80c2ed23..65f928c0 100644 --- a/src/utils/RegisterElements.ts +++ b/src/utils/RegisterElements.ts @@ -6,10 +6,11 @@ import {StatblockProcessor} from "@drawSteelAdmonition/statblock/StatblockProces import { FeatureProcessor } from "@drawSteelAdmonition/Features/FeatureProcessor"; import { FeatureblockProcessor } from "@drawSteelAdmonition/featureblock/FeatureblockProcessor"; import { StaminaBarProcessor } from "@drawSteelAdmonition/StaminaBar/StaminaBarProcessor"; -import {CounterProcessor} from "@drawSteelAdmonition/Counter/CounterProcessor"; import {CharacteristicsProcessor} from "@drawSteelAdmonition/Characteristics/CharacteristicsProcessor"; import {ValuesRowProcessor} from "@drawSteelAdmonition/ValuesRow/ValuesRowProcessor"; import { genericComponentProcessor } from "./ComponentProcessor"; +import { GenericElementProcessor } from "@drawSteelAdmonition/GenericElementProcessor"; +import {CounterView} from "@drawSteelAdmonition/Counter/CounterView"; import HorizontalRule from "@drawSteelComponents/HorizontalRule.vue" import SkillList from "@drawSteelComponents/SkillList/SkillList.vue"; @@ -17,6 +18,7 @@ import StaminaBar from "@drawSteelComponents/StaminaBar/StaminaBar.vue"; import { Skills as SkillsModel } from "@model/Skills"; import { StaminaBar as StaminaBarModel } from '@/model/StaminaBar'; +import { Counter as CounterModel } from '@model/Counter'; export function registerElements (plugin: Plugin) { @@ -53,7 +55,7 @@ export function registerElements (plugin: Plugin) { plugin.registerMarkdownCodeBlockProcessor("ds-stamina", staminaBarProcessor.handler); plugin.registerMarkdownCodeBlockProcessor("ds-stamina-bar", staminaBarProcessor.handler); - let counterProcessor = new CounterProcessor(plugin); + let counterProcessor = new GenericElementProcessor(plugin, CounterView, CounterModel, "Counter Element"); plugin.registerMarkdownCodeBlockProcessor("ds-ct", counterProcessor.handler); plugin.registerMarkdownCodeBlockProcessor("ds-counter", counterProcessor.handler); diff --git a/styles-source.css b/styles-source.css index 30796709..d3622fa0 100644 --- a/styles-source.css +++ b/styles-source.css @@ -3,68 +3,79 @@ /* ==================== */ .ds-multiline { - white-space: pre-line; + white-space: pre-line; } .ds-labeled-icon { - display: flex; - align-items: center; - justify-content: center; + display: flex; + align-items: center; + justify-content: center; - .icon { - display: flex; - align-items: center; - justify-content: space-between; - margin-right: .5em; - } - - .text { + .icon { + display: flex; + align-items: center; + justify-content: space-between; + margin-right: 0.5em; + } - } + .text { + } } /* Top-left border fade for containers */ .ds-container { - position: relative; -} - -.ds-container::before, -.ds-container::after { - content: ''; - position: absolute; - background: linear-gradient(to right, transparent, var(--icon-color)); - height: 1px; /* Border thickness */ - width: 100%; -} + position: relative; + + &::before, + &::after { + content: ""; + position: absolute; + background: linear-gradient(to right, transparent, var(--icon-color)); + height: 1px; /* Border thickness */ + width: 100%; + } -.ds-container::before { - top: 0; - left: 0; - background: linear-gradient(to right, var(--icon-color), transparent); - width: 12em; -} + &::before { + top: 0; + left: 0; + background: linear-gradient(to right, var(--icon-color), transparent); + width: 12em; + } -.ds-container::after { - top: 0; - left: 0; - height: 100%; - max-height: 12em; - background: linear-gradient(to bottom, var(--icon-color), transparent); - width: 1px; /* Border thickness */ + &::after { + top: 0; + left: 0; + height: 100%; + max-height: 12em; + background: linear-gradient(to bottom, var(--icon-color), transparent); + width: 1px; /* Border thickness */ + } } /* ==================== */ /* Settings */ /* ==================== */ - button.settings-action-button { - padding: 10px 20px; - font-size: 16px; - background-color: var(--interactive-accent); - float: right; + padding: 10px 20px; + font-size: 16px; + background-color: var(--interactive-accent); + float: right; } +/* ========================== */ +/* Common Element Wrapper */ +/* ========================== */ + +.ds-common-element-wrapper { + display: flex; + justify-content: center; +} + +.ds-common-element-wrapper-collapsible { + cursor: help; +} + /* ==================== */ /* Features List */ /* ==================== */ @@ -73,12 +84,11 @@ button.settings-action-button { .ds-feature-container { padding-top: 0.5rem; padding-bottom: 0.5rem; - } - /* Hover effect on each "container" */ - - .ds-feature-container:hover { - background-color: var(--color-base-25); + /* Hover effect on each "container" */ + &:hover { + background-color: var(--color-base-25); + } } /* @@ -104,65 +114,65 @@ button.settings-action-button { justify-content: space-between; } - .ds-feature-name-value, - .ds-feature-cost-value { - font-weight: bold; - } + .ds-feature-name-value, + .ds-feature-cost-value { + font-weight: bold; + } .ds-feature-ability-type { font-weight: bold; } - .ds-feature-roll-value, - .ds-feature-tier-key { - font-weight: bold; - } + .ds-feature-roll-value, + .ds-feature-tier-key { + font-weight: bold; + } - .ds-feature-flavor-value, - .ds-feature-note-list, - .ds-feature-note-line { - font-style: italic; - } + .ds-feature-flavor-value, + .ds-feature-note-list, + .ds-feature-note-line { + font-style: italic; + } - .ds-feature-note-list { - margin-bottom: 0; - } + .ds-feature-note-list { + margin-bottom: 0; + } - .ds-feature-detail-line, - .ds-feature-field-line { - margin-top: 0.5em; - margin-bottom: 0.75em; - } + .ds-feature-detail-line, + .ds-feature-field-line { + margin-top: 0.5em; + margin-bottom: 0.75em; + } - .ds-feature-detail-table-row { - display: flex; + .ds-feature-detail-table-row { + display: flex; justify-content: space-between; - } + } - .ds-feature-detail-table-cell { - /*this will float the text to the left*/ + .ds-feature-detail-table-cell { + /*this will float the text to the left*/ /*width: 50%;*/ - } + } - .ds-feature-detail-key, - .ds-feature-field-key { - font-weight: bold; - } + .ds-feature-detail-key, + .ds-feature-field-key { + font-weight: bold; + } - .ds-feature-inline-p, - .ds-feature-inline-p p { - display: inline; - } + .ds-feature-inline-p, + .ds-feature-inline-p p { + display: inline; + } - .ds-feature-inline-p p, - .ds-feature-inline-p ul { - margin-block-start: 0; - margin-block-end: 0; - } + .ds-feature-inline-p p, + .ds-feature-inline-p ul { + margin-block-start: 0; + margin-block-end: 0; + } - .ds-feature-inline-p ul { - display: grid; - } + .ds-feature-inline-p ul { + display: grid; + } } /* ========================== */ @@ -170,53 +180,52 @@ button.settings-action-button { /* ========================== */ .ds-feature-ele-container { - position: relative; - background: var(--code-background); - margin-top: 0.5em; - margin-bottom: 0.5em; - padding: 1em; - letter-spacing: 0.03em; -} - -/* Indentation levels for power roll container */ - -.ds-feature-ele-container:has(> .indent-1) { - margin-left: calc(var(--list-indent) * 1); -} + position: relative; + background: var(--code-background); + margin-top: 0.5em; + margin-bottom: 0.5em; + padding: 1em; + letter-spacing: 0.03em; + + /* Indentation levels for power roll container */ + &:has(> .indent-1) { + margin-left: calc(var(--list-indent) * 1); + } -.ds-feature-ele-container:has(> .indent-2) { - margin-left: calc(var(--list-indent) * 2); -} + &:has(> .indent-2) { + margin-left: calc(var(--list-indent) * 2); + } -.ds-feature-ele-container:has(> .indent-3) { - margin-left: calc(var(--list-indent) * 3); -} + &:has(> .indent-3) { + margin-left: calc(var(--list-indent) * 3); + } -.ds-feature-ele-container:has(> .indent-4) { - margin-left: calc(var(--list-indent) * 4); -} + &:has(> .indent-4) { + margin-left: calc(var(--list-indent) * 4); + } -.ds-feature-ele-container:has(> .indent-5) { - margin-left: calc(var(--list-indent) * 5); -} + &:has(> .indent-5) { + margin-left: calc(var(--list-indent) * 5); + } -.ds-feature-ele-container:has(> .indent-6) { - margin-left: calc(var(--list-indent) * 6); + &:has(> .indent-6) { + margin-left: calc(var(--list-indent) * 6); + } } /* DSA tag styling (what is this for?) */ span.dsa, code.dsa { - background-color: var(--tag-background); - border: var(--tag-border-width) solid var(--tag-border-color); - border-radius: var(--tag-radius); - color: var(--tag-color); - font-size: var(--tag-size); - font-weight: var(--tag-weight); - text-decoration: var(--tag-decoration); - padding: var(--tag-padding-y) var(--tag-padding-x); - line-height: 1; + background-color: var(--tag-background); + border: var(--tag-border-width) solid var(--tag-border-color); + border-radius: var(--tag-radius); + color: var(--tag-color); + font-size: var(--tag-size); + font-weight: var(--tag-weight); + text-decoration: var(--tag-decoration); + padding: var(--tag-padding-y) var(--tag-padding-x); + line-height: 1; } /* =========================== */ @@ -224,196 +233,196 @@ code.dsa { /* =========================== */ .ds-pr-tier-line { - display: flex; - line-height: 1em; + display: flex; + line-height: 1em; } .tier-key-container { - display: flex; - font-size: 1em; - margin-right: 0.75em; - max-height: 1em; + display: flex; + font-size: 1em; + margin-right: 0.75em; + max-height: 1em; } /* Tier 1 */ .t1-key-body { - position: relative; - width: 3em; - text-align: center; - font-size: 0.8em; - font-weight: bold; -} - -.t1-key-body:before { - content: ""; - position: absolute; - inset: 0; - background: var(--text-normal); - - clip-path: polygon( - 0.2em 0.2em, - 0.4em 0, - calc(100% - 0.2em) 0, - 100% 0.2em, - 100% calc(100% - 0.2em), - calc(100% - 0.2em) 100%, - 0.4em 100%, - 0.2em calc(100% - 0.2em), - 0.2em calc(50% + 0.2em), - 0 50%, - 0.2em calc(50% - 0.2em), - 0.2em 0.2em, - calc(0.2em + 1px) calc(0.2em + 0.41px), - calc(0.2em + 1px) calc(50% - 0.2em + 0.41px), - 1.41px 50%, - calc(0.2em + 1px) calc(50% + 0.2em - 0.41px), - calc(0.2em + 1px) calc(100% - 0.2em - 0.41px), - calc(0.4em + 0.41px) calc(100% - 1px), - calc(100% - 0.2em - 0.41px) calc(100% - 1px), - calc(100% - 1px) calc(100% - 0.2em - 0.41px), - calc(100% - 1px) calc(0.2em + 0.41px), - calc(100% - 0.2em - 0.41px) 1px, - calc(0.4em + 0.41px) 1px, - calc(0.2em + 1px) calc(0.2em + 0.41px) - ); + position: relative; + width: 3em; + text-align: center; + font-size: 0.8em; + font-weight: bold; + + &:before { + content: ""; + position: absolute; + inset: 0; + background: var(--text-normal); + /* clip-path ... */ + clip-path: polygon( + 0.2em 0.2em, + 0.4em 0, + calc(100% - 0.2em) 0, + 100% 0.2em, + 100% calc(100% - 0.2em), + calc(100% - 0.2em) 100%, + 0.4em 100%, + 0.2em calc(100% - 0.2em), + 0.2em calc(50% + 0.2em), + 0 50%, + 0.2em calc(50% - 0.2em), + 0.2em 0.2em, + calc(0.2em + 1px) calc(0.2em + 0.41px), + calc(0.2em + 1px) calc(50% - 0.2em + 0.41px), + 1.41px 50%, + calc(0.2em + 1px) calc(50% + 0.2em - 0.41px), + calc(0.2em + 1px) calc(100% - 0.2em - 0.41px), + calc(0.4em + 0.41px) calc(100% - 1px), + calc(100% - 0.2em - 0.41px) calc(100% - 1px), + calc(100% - 1px) calc(100% - 0.2em - 0.41px), + calc(100% - 1px) calc(0.2em + 0.41px), + calc(100% - 0.2em - 0.41px) 1px, + calc(0.4em + 0.41px) 1px, + calc(0.2em + 1px) calc(0.2em + 0.41px) + ); + } } .t1-key-body-text { - font-size: 0.9em; + font-size: 0.9em; } /* Tier 2 */ .t2-key-body { - position: relative; - width: 3em; - text-align: center; - font-size: 0.8em; - font-weight: bold; -} - -.t2-key-body:before { - content: ""; - position: absolute; - inset: 0; - background: var(--text-normal); - clip-path: polygon( - 0 0.2em, - 0.2em 0, - calc(100% - 0.2em) 0, - 100% 0.2em, - 100% calc(100% - 0.2em), - calc(100% - 0.2em) 100%, - 0.2em 100%, - 0 calc(100% - 0.2em), - 0 0.2em, - 1px calc(0.2em + 0.41px), - 1px calc(100% - 0.2em - 0.41px), - calc(0.2em + 0.41px) calc(100% - 1px), - calc(100% - 0.2em - 0.41px) calc(100% - 1px), - calc(100% - 1px) calc(100% - 0.2em - 0.41px), - calc(100% - 1px) calc(0.2em + 0.41px), - calc(100% - 0.2em - 0.41px) 1px, - calc(0.2em + 0.41px) 1px, - 1px calc(0.2em + 0.41px) - ); + position: relative; + width: 3em; + text-align: center; + font-size: 0.8em; + font-weight: bold; + + &:before { + content: ""; + position: absolute; + inset: 0; + background: var(--text-normal); + clip-path: polygon( + 0 0.2em, + 0.2em 0, + calc(100% - 0.2em) 0, + 100% 0.2em, + 100% calc(100% - 0.2em), + calc(100% - 0.2em) 100%, + 0.2em 100%, + 0 calc(100% - 0.2em), + 0 0.2em, + 1px calc(0.2em + 0.41px), + 1px calc(100% - 0.2em - 0.41px), + calc(0.2em + 0.41px) calc(100% - 1px), + calc(100% - 0.2em - 0.41px) calc(100% - 1px), + calc(100% - 1px) calc(100% - 0.2em - 0.41px), + calc(100% - 1px) calc(0.2em + 0.41px), + calc(100% - 0.2em - 0.41px) 1px, + calc(0.2em + 0.41px) 1px, + 1px calc(0.2em + 0.41px) + ); + } } .t2-key-body-text { - font-size: 0.9em; + font-size: 0.9em; } /* Tier 3 */ .t3-key-body { - position: relative; - width: 3em; - text-align: center; - font-size: 0.8em; - font-weight: bold; -} - -.t3-key-body:before { - content: ""; - position: absolute; - inset: 0; - background: var(--text-normal); - clip-path: polygon( - 0 0.2em, - 0.2em 0, - calc(100% - 0.4em) 0, - calc(100% - 0.2em) 0.2em, - calc(100% - 0.2em) calc(50% - 0.2em), - 100% 50%, - calc(100% - 0.2em) calc(50% + 0.2em), - calc(100% - 0.2em) calc(100% - 0.2em), - calc(100% - 0.4em) 100%, - 0.2em 100%, - 0 calc(100% - 0.2em), - 0 0.2em, - 1px calc(0.2em + 0.41px), - 1px calc(100% - 0.2em - 0.41px), - calc(0.2em + 0.41px) calc(100% - 1px), - calc(100% - 0.4em - 0.41px) calc(100% - 1px), - calc(100% - 0.2em - 1px) calc(100% - 0.2em - 0.41px), - calc(100% - 0.2em - 1px) calc(50% + 0.2em - 0.41px), - calc(100% - 1.41px) 50%, - calc(100% - 0.2em - 1px) calc(50% - 0.2em + 0.41px), - calc(100% - 0.2em - 1px) calc(0.2em + 0.41px), - calc(100% - 0.4em - 0.41px) 1px, - calc(0.2em + 0.41px) 1px, - 1px calc(0.2em + 0.41px) - ); + position: relative; + width: 3em; + text-align: center; + font-size: 0.8em; + font-weight: bold; + + &:before { + content: ""; + position: absolute; + inset: 0; + background: var(--text-normal); + clip-path: polygon( + 0 0.2em, + 0.2em 0, + calc(100% - 0.4em) 0, + calc(100% - 0.2em) 0.2em, + calc(100% - 0.2em) calc(50% - 0.2em), + 100% 50%, + calc(100% - 0.2em) calc(50% + 0.2em), + calc(100% - 0.2em) calc(100% - 0.2em), + calc(100% - 0.4em) 100%, + 0.2em 100%, + 0 calc(100% - 0.2em), + 0 0.2em, + 1px calc(0.2em + 0.41px), + 1px calc(100% - 0.2em - 0.41px), + calc(0.2em + 0.41px) calc(100% - 1px), + calc(100% - 0.4em - 0.41px) calc(100% - 1px), + calc(100% - 0.2em - 1px) calc(100% - 0.2em - 0.41px), + calc(100% - 0.2em - 1px) calc(50% + 0.2em - 0.41px), + calc(100% - 1.41px) 50%, + calc(100% - 0.2em - 1px) calc(50% - 0.2em + 0.41px), + calc(100% - 0.2em - 1px) calc(0.2em + 0.41px), + calc(100% - 0.4em - 0.41px) 1px, + calc(0.2em + 0.41px) 1px, + 1px calc(0.2em + 0.41px) + ); + } } .t3-key-body-text { - font-size: 0.9em; + font-size: 0.9em; } /* Critical */ .crit-key-body { - position: relative; - width: 3em; - text-align: center; - font-size: 0.8em; - font-weight: bold; -} - -.crit-key-body:before { - content: ""; - position: absolute; - inset: 0; - background: var(--text-normal); - clip-path: polygon( - 0 0.2em, - 0.2em 0, - calc(100% - 0.4em) 0, - calc(100% - 0.2em) 0.2em, - calc(100% - 0.2em) calc(50% - 0.2em), - 100% 50%, - calc(100% - 0.2em) calc(50% + 0.2em), - calc(100% - 0.2em) calc(100% - 0.2em), - calc(100% - 0.4em) 100%, - 0.2em 100%, - 0 calc(100% - 0.2em), - 0 0.2em, - 1px calc(0.2em + 0.41px), - 1px calc(100% - 0.2em - 0.41px), - calc(0.2em + 0.41px) calc(100% - 1px), - calc(100% - 0.4em - 0.41px) calc(100% - 1px), - calc(100% - 0.2em - 3px) calc(100% - 0.2em - 0.41px), - calc(100% - 0.2em - 3px) calc(0.2em + 0.41px), - calc(100% - 0.4em - 0.41px) 1px, - calc(0.2em + 0.41px) 1px, - 1px calc(0.2em + 0.41px) - ); + position: relative; + width: 3em; + text-align: center; + font-size: 0.8em; + font-weight: bold; + + &:before { + content: ""; + position: absolute; + inset: 0; + background: var(--text-normal); + clip-path: polygon( + 0 0.2em, + 0.2em 0, + calc(100% - 0.4em) 0, + calc(100% - 0.2em) 0.2em, + calc(100% - 0.2em) calc(50% - 0.2em), + 100% 50%, + calc(100% - 0.2em) calc(50% + 0.2em), + calc(100% - 0.2em) calc(100% - 0.2em), + calc(100% - 0.4em) 100%, + 0.2em 100%, + 0 calc(100% - 0.2em), + 0 0.2em, + 1px calc(0.2em + 0.41px), + 1px calc(100% - 0.2em - 0.41px), + calc(0.2em + 0.41px) calc(100% - 1px), + calc(100% - 0.4em - 0.41px) calc(100% - 1px), + calc(100% - 0.2em - 3px) calc(100% - 0.2em - 0.41px), + calc(100% - 0.2em - 3px) calc(0.2em + 0.41px), + calc(100% - 0.4em - 0.41px) 1px, + calc(0.2em + 0.41px) 1px, + 1px calc(0.2em + 0.41px) + ); + } } .crit-key-body-text { - font-size: 0.9em; - padding-right: 3px; + font-size: 0.9em; + padding-right: 3px; } /* ==================== */ @@ -421,41 +430,49 @@ code.dsa { /* ==================== */ .ds-hr-container { - display: flex; - align-items: center; - position: relative; - margin-top: 6px; -} - -.ds-hr-left-line, -.ds-hr-right-line { - flex-grow: 1; - height: 2px; - background-color: var(--icon-color); - top: 50%; /* Center it vertically */ - left: 0; - right: 0; -} + display: flex; + align-items: center; + position: relative; + margin-top: 6px; + + .ds-hr-left-line, + .ds-hr-right-line { + flex-grow: 1; + height: 2px; + background-color: var(--icon-color); + top: 50%; /* Center it vertically */ + left: 0; + right: 0; + } -.ds-hr-left-line { - background: linear-gradient(to left, var(--icon-color) 0%, transparent 100%); -} + .ds-hr-left-line { + background: linear-gradient( + to left, + var(--icon-color) 0%, + transparent 100% + ); + } -.ds-hr-right-line { - background: linear-gradient(to right, var(--icon-color) 0%, transparent 100%); -} + .ds-hr-right-line { + background: linear-gradient( + to right, + var(--icon-color) 0%, + transparent 100% + ); + } -.ds-hr-center { - width: 14px; - height: 14px; - background-color: var(--icon-color); - transform: translateZ(1px) translateY(1px) rotate(45deg); /* Rotate to make a diamond */ - position: relative; - z-index: 1; /* Ensure the diamond is above the lines */ - margin: 0 auto; /* Center the diamond */ - border-bottom: 2px solid var(--icon-color); - border-right: 2px solid var(--icon-color); - box-shadow: inset 0 0 0 3px var(--background-primary); + .ds-hr-center { + width: 14px; + height: 14px; + background-color: var(--icon-color); + transform: translateZ(1px) translateY(1px) rotate(45deg); /* Rotate to make a diamond */ + position: relative; + z-index: 1; /* Ensure the diamond is above the lines */ + margin: 0 auto; /* Center the diamond */ + border-bottom: 2px solid var(--icon-color); + border-right: 2px solid var(--icon-color); + box-shadow: inset 0 0 0 3px var(--background-primary); + } } /* ==================== */ @@ -463,32 +480,31 @@ code.dsa { /* ==================== */ .dsa-setting-row { - display: flex; - justify-content: flex-end; -} + display: flex; + justify-content: flex-end; -.dsa-setting-row * { - margin-left: 0.5em; -} + * { + margin-left: 0.5em; + } -.dsa-setting-row-title { - margin-right: auto; - text-transform: capitalize; - align-items: center; - display: flex; + .dsa-setting-row-title { + margin-right: auto; + text-transform: capitalize; + align-items: center; + display: flex; + } } .dsa-sample-editor { - margin: 0.5em; - display: inline-block; + margin: 0.5em; + display: inline-block; } .dsa-hidden { - display: none; -} - -.cm-active .dsa-hidden { - display: inline; + display: none; + .cm-active & { + display: inline; + } } /* ==================== */ @@ -498,164 +514,159 @@ code.dsa { /* Error Message Styling */ .error-message { - font-family: monospace; - white-space: pre; - text-wrap: wrap; - background-color: var(--code-background); - padding: 1em; - position: relative; + font-family: monospace; + white-space: pre; + text-wrap: wrap; + background-color: var(--code-background); + padding: 1em; + position: relative; } /* Turn Indicator Styling */ .turn-indicator { - cursor: pointer; -} - -.turn-indicator svg { - margin: 1px; - height: calc(100% - 2px); - width: calc(100% - 2px); - color: var(--text-normal); - background-color: var(--code-background); - border-radius: 4px 0 0 4px; -} + cursor: pointer; + + svg { + margin: 1px; + height: calc(100% - 2px); + width: calc(100% - 2px); + color: var(--text-normal); + background-color: var(--code-background); + border-radius: 4px 0 0 4px; + } -.turn-indicator.taken-turn svg { - background-color: limegreen; - color: var(--code-background); + &.taken-turn svg { + background-color: limegreen; + color: var(--code-background); + } } /* Top action bar */ .top-action-bar { - margin-bottom: 10px; - display: flex; - justify-content: flex-start; - align-items: center; -} + margin-bottom: 10px; + display: flex; + justify-content: flex-start; + align-items: center; -.top-action-bar button { - margin-right: 1em; + button { + margin-right: 1em; + } } /* Character Rows */ .heroes-container .character-row, .enemy-group { - position: relative; - padding-top: 1px; - padding-right: 1em; - margin-bottom: 0.75em; - background-color: var(--code-background); -} - -/* Top-left border fade for character rows */ - -.heroes-container .character-row::before, -.enemy-group::before, -.heroes-container .character-row::after, -.enemy-group::after { - content: ''; - position: absolute; - background: linear-gradient(to right, transparent, var(--icon-color)); - height: 1px; /* Border thickness */ - width: 100%; -} + position: relative; + padding-top: 1px; + padding-right: 1em; + margin-bottom: 0.75em; + background-color: var(--code-background); + + /* Top-left border fade for character rows */ + &::before, + &::after { + content: ""; + position: absolute; + background: linear-gradient(to right, transparent, var(--icon-color)); + height: 1px; /* Border thickness */ + width: 100%; + } -.heroes-container .character-row::before, -.enemy-group::before { - top: 0; - left: 0; - background: linear-gradient(to right, var(--icon-color), transparent); - width: 12em; -} + &::before { + top: 0; + left: 0; + background: linear-gradient(to right, var(--icon-color), transparent); + width: 12em; + } -.heroes-container .character-row::after, -.enemy-group::after { - top: 0; - left: 0; - height: 100%; - max-height: 12em; - background: linear-gradient(to bottom, var(--icon-color), transparent); - width: 1px; /* Border thickness */ + &::after { + top: 0; + left: 0; + height: 100%; + max-height: 12em; + background: linear-gradient(to bottom, var(--icon-color), transparent); + width: 1px; /* Border thickness */ + } } /* Enemies Header */ .enemies-header { - display: flex; - justify-content: space-between; - align-items: center; + display: flex; + justify-content: space-between; + align-items: center; } /* Villain Power Styling */ .malice-container { - font-weight: bold; - color: red; - display: flex; - align-items: center; -} - -.malice-container .malice-modifiers { - visibility: hidden; - margin-right: 0.5em; -} + font-weight: bold; + color: red; + display: flex; + align-items: center; + + .malice-modifiers { + visibility: hidden; + margin-right: 0.5em; + } -.malice-container:hover .malice-modifiers { - visibility: visible; + &:hover .malice-modifiers { + visibility: visible; + } } .malice-modifiers svg { - height: 100%; - width: 100%; + height: 100%; + width: 100%; } .malice-modifier { - cursor: pointer; - height: 24px; -} + cursor: pointer; + height: 24px; -.malice-modifier:hover { - background-color: var(--code-background); + &:hover { + background-color: var(--code-background); + } } .malice-text { - user-select: none; + user-select: none; } /* Enemy Group Container */ .enemy-group-container { - display: flex; + display: flex; } .enemy-group { - padding: 1em; - width: 100%; + padding: 1em; + width: 100%; } .group-header { - display: flex; - align-items: center; - justify-content: space-between; -} + display: flex; + align-items: center; + justify-content: space-between; -.group-header h4 { - margin-top: 0; + h4 { + margin-top: 0; + } } /* Creature Group */ .creature-group { - margin-bottom: 20px; + margin-bottom: 20px; } /* Detailed Creature Row */ .creature-detail-row { - margin-bottom: 10px; + margin-bottom: 10px; } /* ============================= */ @@ -663,69 +674,63 @@ code.dsa { /* ============================= */ .creature-instances-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); - gap: 10px; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); + gap: 10px; + + @media (min-width: 600px) { + grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); + } + + @media (max-width: 599px) { + grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); + } } .creature-instance-cell { - text-align: center; - padding: 5px; - background-color: var(--color-base-30); - border: 1px solid transparent; - border-radius: 4px; - cursor: pointer; - display: flex; - flex-direction: column; - align-items: center; - min-width: 80px; -} + text-align: center; + padding: 5px; + background-color: var(--color-base-30); + border: 1px solid transparent; + border-radius: 4px; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + min-width: 80px; + + &.selected { + border-color: #d50000; + box-shadow: 0 0 5px #d50000; + } -.creature-instance-cell.selected { - border-color: #D50000; - box-shadow: 0 0 5px #D50000; + @media (max-width: 599px) { + min-width: 60px; + } } .instance-image { - width: 50px; - height: 50px; - margin-bottom: 5px; -} - -.instance-image img { - width: 100%; - height: 100%; - object-fit: cover; - pointer-events: none; + width: 50px; + height: 50px; + margin-bottom: 5px; + + img { + width: 100%; + height: 100%; + object-fit: cover; + pointer-events: none; + } } .instance-stamina { - font-size: 14px; - word-wrap: break-word; - text-align: center; - max-width: 100%; -} - -/* Responsive Adjustments for Grid */ - -@media (min-width: 600px) { - .creature-instances-grid { - grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); - } -} - -@media (max-width: 599px) { - .creature-instances-grid { - grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); - } - - .creature-instance-cell { - min-width: 60px; - } + font-size: 14px; + word-wrap: break-word; + text-align: center; + max-width: 100%; - .instance-stamina { - font-size: 12px; - } + @media (max-width: 599px) { + font-size: 12px; + } } /* =========================== */ @@ -733,80 +738,80 @@ code.dsa { /* =========================== */ .hero-container { - display: flex; + display: flex; } .character-icon, .enemy-group-icon { - height: 1.5em; - width: 1.5em; - border: 1px solid var(--icon-color); - border-right: 0; - border-radius: 5px 0 0 5px; + height: 1.5em; + width: 1.5em; + border: 1px solid var(--icon-color); + border-right: 0; + border-radius: 5px 0 0 5px; } .character-row { - display: flex; - align-items: center; - margin-bottom: 10px; - width: 100%; + display: flex; + align-items: center; + margin-bottom: 10px; + width: 100%; } .character-image { - display: flex; -} + display: flex; -.character-image img { - width: 60px; - height: 60px; - object-fit: cover; + img { + width: 60px; + height: 60px; + object-fit: cover; + } } .character-info { - flex: 1; - margin-left: 10px; + flex: 1; + margin-left: 10px; } .character-name { - font-weight: bold; + font-weight: bold; } .character-conditions { - margin-top: 5px; - height: 24px; - display: flex; - align-items: center; + margin-top: 5px; + height: 24px; + display: flex; + align-items: center; + + svg { + display: block; + } } .condition-icon, .add-condition-icon { - width: 24px; - height: 24px; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; -} - -.character-conditions svg { - display: block; + width: 24px; + height: 24px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; } .add-condition-icon { - color: var(--color-base-50); -} + color: var(--color-base-50); -.add-condition-icon:hover { - color: var(--color-base-100); + &:hover { + color: var(--color-base-100); + } } .character-health { - margin-left: 10px; + margin-left: 10px; } .character-stamina { - cursor: pointer; - font-weight: bold; + cursor: pointer; + font-weight: bold; } /* ==================== */ @@ -814,457 +819,462 @@ code.dsa { /* ==================== */ .stamina-header { - text-align: center; + text-align: center; } /* Stamina Bar */ .stamina-bar-container { - width: 100%; - background-color: var(--code-background); - border: 1px solid var(--text-normal); - border-radius: 5px; - margin-bottom: 10px; - position: relative; - height: 20px; + width: 100%; + background-color: var(--code-background); + border: 1px solid var(--text-normal); + border-radius: 5px; + margin-bottom: 10px; + position: relative; + height: 20px; } .stamina-bar { - width: 100%; - height: 100%; - position: relative; - display: flex; + width: 100%; + height: 100%; + position: relative; + display: flex; } .stamina-bar-overlay { - position: absolute; - height: calc(100% - 2px); - width: 33.33%; - background-color: rgba(0, 0, 0, 0.2); - left: 1px; - top: 1px; - border: 1.5px dashed var(--code-background); - z-index: 1; - border-radius: 3px 0 0 3px; - color: var(--code-background); - text-align: center; - line-height: 100%; - font-size: 0.7em; - padding-top: 1px; - text-shadow: 0.5px 0.5px 4px var(--text-normal); - font-weight: bold; - box-shadow: 1px 0px 0px 0px var(--text-normal); - display: inline-flex; - align-items: center; - justify-content: center; + position: absolute; + height: calc(100% - 2px); + width: 33.33%; + background-color: rgba(0, 0, 0, 0.2); + left: 1px; + top: 1px; + border: 1.5px dashed var(--code-background); + z-index: 1; + border-radius: 3px 0 0 3px; + color: var(--code-background); + text-align: center; + line-height: 100%; + font-size: 0.7em; + padding-top: 1px; + text-shadow: 0.5px 0.5px 4px var(--text-normal); + font-weight: bold; + box-shadow: 1px 0px 0px 0px var(--text-normal); + display: inline-flex; + align-items: center; + justify-content: center; } .stamina-bar-fill-left { - position: relative; - height: calc(100% - 2px); - top: 1px; - left: 1px; - background-color: limegreen; /* Default color */ - width: 50%; /* Will be updated dynamically */ - border-radius: 3px 3px 3px 3px; + position: relative; + height: calc(100% - 2px); + top: 1px; + left: 1px; + background-color: limegreen; /* Default color */ + width: 50%; /* Will be updated dynamically */ + border-radius: 3px 3px 3px 3px; } .stamina-bar-fill-right { - height: calc(100% - 2px); - top: 1px; - position: relative; - background-color: deepskyblue; /* Default color */ - width: 50%; /* Will be updated dynamically */ - border-radius: 0 3px 3px 0; - border-left: 1px solid var(--code-background); + height: calc(100% - 2px); + top: 1px; + position: relative; + background-color: deepskyblue; /* Default color */ + width: 50%; /* Will be updated dynamically */ + border-radius: 0 3px 3px 0; + border-left: 1px solid var(--code-background); } /* Numeric Stamina Display */ .stamina-numeric-container { - display: -webkit-inline-box; - margin-bottom: 10px; + display: -webkit-inline-box; + margin-bottom: 10px; } .stamina-adjust-btn { - width: 30px; - height: 30px; - display: flex; - justify-content: center; - cursor: pointer; -} - -.stamina-adjust-btn svg { - height: 100%; + width: 30px; + height: 30px; + display: flex; + justify-content: center; + cursor: pointer; + + svg { + height: 100%; + } } .stamina-value-display { - width: 3em; - text-align: center; - margin: 0 5px; + width: 3em; + text-align: center; + margin: 0 5px; } .max-stamina-display { - margin-left: 5px; - margin-right: 5px; + margin-left: 5px; + margin-right: 5px; } /* Apply Damage/Healing */ .modifier-container { - display: flex; - align-items: center; - margin-bottom: 10px; - justify-content: space-between; -} - -.modifier-container > div { - width: 100%; - padding: 1em; + display: flex; + align-items: center; + margin-bottom: 10px; + justify-content: space-between; + + > div { + width: 100%; + padding: 1em; + } } .vertical-divider { - padding: 0 !important; - padding-top: 8em !important; - width: 1px !important; - border-left: 1px solid var(--color-base-40); + padding: 0 !important; + padding-top: 8em !important; + width: 1px !important; + border-left: 1px solid var(--color-base-40); } .apply-row { - display: flex; - justify-content: space-around; - align-items: center; - margin-top: 0.5em; + display: flex; + justify-content: space-around; + align-items: center; + margin-top: 0.5em; } .apply-input { - width: 60px; + width: 60px; } .apply-btn, .quick-mod-btn { - display: flex; - width: 100%; - margin-top: 0.5em; - justify-content: start; + display: flex; + width: 100%; + margin-top: 0.5em; + justify-content: start; + + .btn-text { + height: 100%; + margin-left: 0.5em; + } } -.apply-btn .btn-text, -.quick-mod-btn .btn-text, .reset-button .btn-text { - height: 100%; - margin-left: 0.5em; + height: 100%; + margin-left: 0.5em; } /* Temporary Stamina Container */ .temp-stamina-container { - background-color: var(--code-background); - margin-top: 0.5em; - border-radius: 5px; - padding: 0.5em 0; + background-color: var(--code-background); + margin-top: 0.5em; + border-radius: 5px; + padding: 0.5em 0; } .temp-stamina-title { - text-align: center; - font-size: var(--font-ui-small); + text-align: center; + font-size: var(--font-ui-small); } .temp-stamina-body { - display: flex; - justify-content: space-evenly; - align-items: center; - margin-top: 0.5em; + display: flex; + justify-content: space-evenly; + align-items: center; + margin-top: 0.5em; } .temp-stamina-input { - width: 3em; + width: 3em; } /* Action Button */ .action-button-container { - text-align: center; - margin-top: 20px; - display: flex; - justify-content: space-between; + text-align: center; + margin-top: 20px; + display: flex; + justify-content: space-between; } button.action-button { - padding: 10px 20px; - font-size: 16px; - background-color: var(--interactive-accent); -} - -.action-button.disabled { - opacity: 0.5; - pointer-events: none; - background-color: var(--interactive-normal); + padding: 10px 20px; + font-size: 16px; + background-color: var(--interactive-accent); + + &.disabled { + opacity: 0.5; + pointer-events: none; + background-color: var(--interactive-normal); + } } /* ============================ */ /* Minion Stamina Pool Modal */ /* ============================ */ -.minion-stamina-modal .stamina-header { - text-align: center; -} - -/* Stamina Pool Bar */ - -.minion-stamina-modal .stamina-bar-container { - width: 100%; - background-color: var(--code-background); - border: 1px solid var(--text-normal); - border-radius: 5px; - margin-bottom: 10px; - position: relative; - height: 20px; -} +.minion-stamina-modal { + .stamina-header { + text-align: center; + } -.minion-stamina-modal .stamina-bar { - width: 100%; - height: 100%; - position: relative; - overflow: hidden; -} + /* Stamina Pool Bar */ -.minion-stamina-modal .stamina-bar-fill-left { - position: relative; - height: calc(100% - 2px); - top: 1px; - left: 1px; - background-color: limegreen; - width: 0%; /* Will be updated dynamically */ - border-radius: 3px 0 0 3px; - z-index: 1; -} + .stamina-bar-container { + width: 100%; + background-color: var(--code-background); + border: 1px solid var(--text-normal); + border-radius: 5px; + margin-bottom: 10px; + position: relative; + height: 20px; + } -.minion-stamina-modal .stamina-bar-fill-right { - position: relative; - height: calc(100% - 2px); - top: 1px; - background-color: deepskyblue; - width: 0%; /* Will be updated dynamically */ - border-radius: 0 3px 3px 0; - z-index: 2; -} + .stamina-bar { + width: 100%; + height: 100%; + position: relative; + overflow: hidden; + } -.minion-stamina-modal .stamina-bar-tick { - position: absolute; - bottom: 0; - height: 50%; - width: 2px; - background-color: var(--code-background); - border-right: 1px solid var(--text-normal); - z-index: 3; -} + .stamina-bar-fill-left { + position: relative; + height: calc(100% - 2px); + top: 1px; + left: 1px; + background-color: limegreen; + width: 0%; /* Will be updated dynamically */ + border-radius: 3px 0 0 3px; + z-index: 1; + } -.minion-stamina-modal .stamina-bar-tick::after { - display: none; -} + .stamina-bar-fill-right { + position: relative; + height: calc(100% - 2px); + top: 1px; + background-color: deepskyblue; + width: 0%; /* Will be updated dynamically */ + border-radius: 0 3px 3px 0; + z-index: 2; + } -/* Stamina Modifiers */ + .stamina-bar-tick { + position: absolute; + bottom: 0; + height: 50%; + width: 2px; + background-color: var(--code-background); + border-right: 1px solid var(--text-normal); + z-index: 3; + + &::after { + display: none; + } + } -.minion-stamina-modal .stamina-mod-container { - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 10px; -} + /* Stamina Modifiers */ -.minion-stamina-modal .stamina-mod-container .stamina-adjust-btn { - width: 30px; - height: 30px; - display: flex; - justify-content: center; - cursor: pointer; -} + .stamina-mod-container { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 10px; -.minion-stamina-modal .stamina-mod-container .stamina-adjust-btn svg { - height: 100%; -} + .stamina-adjust-btn { + width: 30px; + height: 30px; + display: flex; + justify-content: center; + cursor: pointer; -.minion-stamina-modal .stamina-mod-container .stamina-value-display { - width: 4em; - text-align: center; - margin: 0 5px; -} + svg { + height: 100%; + } + } -.minion-stamina-modal .stamina-mod-container .max-stamina-display { - margin-left: 5px; - margin-right: 5px; -} + .stamina-value-display { + width: 4em; + text-align: center; + margin: 0 5px; + } -/* Apply Damage Section */ + .max-stamina-display { + margin-left: 5px; + margin-right: 5px; + } + } -.minion-stamina-modal .apply-container { - margin-bottom: 10px; -} + /* Apply Damage Section */ -.minion-stamina-modal .apply-row { - display: flex; - justify-content: center; - align-items: center; - margin-top: 0.5em; -} + .apply-container { + margin-bottom: 10px; + } -.minion-stamina-modal .apply-row span { - margin: 0 5px; - text-align: center; -} + .apply-row { + display: flex; + justify-content: center; + align-items: center; + margin-top: 0.5em; -.minion-stamina-modal .apply-row input { - width: 3em; -} + span { + margin: 0 5px; + text-align: center; + } -.minion-stamina-modal .apply-input { - width: 60px; -} + input { + width: 3em; + } + } -/* Apply Damage Button */ + .apply-input { + width: 60px; + } -.minion-stamina-modal .apply-btn { - display: flex; - width: 100%; - margin-top: 0.5em; - justify-content: center; - max-width: fit-content; -} + /* Apply Damage Button */ -.minion-stamina-modal .apply-btn .btn-icon { - margin-right: 0.5em; - width: 1em; - height: 1em; -} + .apply-btn { + display: flex; + width: 100%; + margin-top: 0.5em; + justify-content: center; + max-width: fit-content; + + .btn-icon { + margin-right: 0.5em; + width: 1em; + height: 1em; + } -.minion-stamina-modal .apply-btn .btn-text { - line-height: 24px; -} + .btn-text { + line-height: 24px; + } + } -/* Info Text */ + /* Info Text */ -.minion-stamina-modal .info-text { - text-align: center; - font-size: var(--font-ui-large); - margin: 1em 0; - white-space: pre-line; -} + .info-text { + text-align: center; + font-size: var(--font-ui-large); + margin: 1em 0; + white-space: pre-line; + } -/* Minion List */ + /* Minion List */ -.minion-stamina-modal .minion-list-container { - max-height: 200px; - overflow-y: auto; - margin-bottom: 10px; - border-radius: 5px; -} + .minion-list-container { + max-height: 200px; + overflow-y: auto; + margin-bottom: 10px; + border-radius: 5px; + } -.minion-stamina-modal .minion-row { - display: inline-flex; - align-items: center; - padding: 0.25em 0; - justify-items: center; - justify-content: start; - width: 50%; -} + .minion-row { + display: inline-flex; + align-items: center; + padding: 0.25em 0; + justify-items: center; + justify-content: start; + width: 50%; -.minion-stamina-modal .minion-row:hover { - background-color: var(--code-background); -} + &:hover { + background-color: var(--code-background); + } + } -.minion-stamina-modal .minion-checkbox { - margin-right: 10px; -} + .minion-checkbox { + margin-right: 10px; -.minion-stamina-modal .minion-checkbox[disabled] + span { - color: var(--text-faint); -} + &[disabled] + span { + color: var(--text-faint); + } -.minion-stamina-modal .minion-checkbox:checked + span { - color: crimson; -} + &:checked + span { + color: crimson; + } + } -.minion-stamina-modal .minion-name { - margin-right: 10px; - flex-grow: 1; -} + .minion-name { + margin-right: 10px; + flex-grow: 1; + } -.minion-stamina-modal .minion-conditions { - display: flex; - align-items: center; -} + .minion-conditions { + display: flex; + align-items: center; + } -/* Condition Icons */ + /* Condition Icons */ -.minion-stamina-modal .condition-icon { - width: 20px; - height: 20px; - margin-right: 5px; - cursor: pointer; -} + .condition-icon { + width: 20px; + height: 20px; + margin-right: 5px; + cursor: pointer; -.minion-stamina-modal .condition-icon svg { - width: 100%; - height: 100%; -} + svg { + width: 100%; + height: 100%; + } + } -/* Add Condition Icon */ + /* Add Condition Icon */ -.minion-stamina-modal .add-condition-icon { - width: 20px; - height: 20px; - cursor: pointer; -} + .add-condition-icon { + width: 20px; + height: 20px; + cursor: pointer; -.minion-stamina-modal .add-condition-icon svg { - width: 100%; - height: 100%; -} + svg { + width: 100%; + height: 100%; + } + } -/* Action Button */ + /* Action Button */ -.minion-stamina-modal .action-button-container { - text-align: center; - margin-top: 20px; - display: flex; - justify-content: space-between; -} + .action-button-container { + text-align: center; + margin-top: 20px; + display: flex; + justify-content: space-between; + } -.minion-stamina-modal button.action-button { - padding: 10px 20px; - font-size: 16px; - background-color: var(--interactive-accent); -} + button.action-button { + padding: 10px 20px; + font-size: 16px; + background-color: var(--interactive-accent); + } -.minion-stamina-modal .action-button.disabled { - opacity: 0.5; - pointer-events: none; - background-color: var(--interactive-normal); -} + .action-button.disabled { + opacity: 0.5; + pointer-events: none; + background-color: var(--interactive-normal); + } -.minion-stamina-modal .warning-icon { - flex-grow: 1; - display: flex; - justify-content: flex-end; - align-items: center; - color: orange; - margin-right: 1em; -} + .warning-icon { + flex-grow: 1; + display: flex; + justify-content: flex-end; + align-items: center; + color: orange; + margin-right: 1em; + } -/* Reset Button */ + /* Reset Button */ -.minion-stamina-modal .reset-button { - display: flex; - align-items: center; -} + .reset-button { + display: flex; + align-items: center; -.minion-stamina-modal .reset-button .btn-icon { - margin-right: 0.5em; -} + .btn-icon { + margin-right: 0.5em; + } -.minion-stamina-modal .reset-button .btn-text { - line-height: 24px; + .btn-text { + line-height: 24px; + } + } } /* ===================== */ @@ -1273,196 +1283,201 @@ button.action-button { /* Conditions List */ .conditions-list { - display: flex; - flex-wrap: wrap; + display: flex; + flex-wrap: wrap; } /* Condition Item */ .condition-item { - position: relative; - display: flex; - align-items: center; - width: calc(50% - 2em); - padding: 4px 1em; - /* padding-left: 1em; */ - cursor: pointer; - box-sizing: border-box; - transition: background-color 0.2s; - margin: 0 1em 0.25em 1em; - border: 2px solid transparent; - border-radius: 4px; -} - -.condition-item:hover { - background-color: var(--code-background); -} + position: relative; + display: flex; + align-items: center; + width: calc(50% - 2em); + padding: 4px 1em; + /* padding-left: 1em; */ + cursor: pointer; + box-sizing: border-box; + transition: background-color 0.2s; + margin: 0 1em 0.25em 1em; + border: 2px solid transparent; + border-radius: 4px; + + &:hover { + background-color: var(--code-background); + + .condition-customize-icon { + display: inline-block; /* Show on hover */ + } + } -/* Selected Condition */ -.condition-item.selected { - border: 2px solid var(--interactive-accent); - border-radius: 4px; + /* Selected Condition */ + &.selected { + border: 2px solid var(--interactive-accent); + border-radius: 4px; + } } /* Condition Icon Preview */ .condition-icon-preview { - font-size: 1.5em; + font-size: 1.5em; } /* Condition Label */ .condition-label { - margin-left: 8px; - flex-grow: 1; + margin-left: 8px; + flex-grow: 1; } /* Customize Icon (Cog) */ .condition-customize-icon { - margin-left: 8px; - cursor: pointer; - display: none; /* Hidden by default */ -} - -.condition-item:hover .condition-customize-icon { - display: inline-block; /* Show on hover */ -} - -.condition-customize-icon { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); -} - -/* Customize Icon Size */ -.condition-customize-icon svg { - width: 1em; - height: 1em; + margin-left: 8px; + cursor: pointer; + display: none; /* Hidden by default */ + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + + /* Customize Icon Size */ + svg { + width: 1em; + height: 1em; + } } /* Horizontal Divider */ .horizontal-divider { - width: 100%; - margin: 1em 0; - height: 1px; - background-color: var(--color-base-40); + width: 100%; + margin: 1em 0; + height: 1px; + background-color: var(--color-base-40); } /* Modal Buttons */ -.add-condition-modal .modal-buttons button, -.customize-condition-modal .modal-buttons button { - margin: 1em 0 0 1em; -} +.add-condition-modal, +.customize-condition-modal { + .modal-buttons { + display: flex; + justify-content: flex-end; -.add-condition-modal .modal-buttons, -.customize-condition-modal .modal-buttons { - display: flex; - justify-content: flex-end; + button { + margin: 1em 0 0 1em; + } + } } .customize-condition-body { - display: flex; - justify-content: space-between; - align-items: center; + display: flex; + justify-content: space-between; + align-items: center; } -.customize-condition-modal .color-picker-container, -.customize-condition-modal .effect-container { - margin-top: 1em; - display: flex; - align-items: center; - justify-content: flex-start; -} +.customize-condition-modal { + .color-picker-container, + .effect-container { + margin-top: 1em; + display: flex; + align-items: center; + justify-content: flex-start; + } -.customize-condition-modal label { - width: 4em; - margin-right: 1em; + label { + width: 4em; + margin-right: 1em; + } } .customize-condition-preview { - height: 2em; - width: 100%; - display: flex; - justify-content: center; - align-items: center; + height: 2em; + width: 100%; + display: flex; + justify-content: center; + align-items: center; } /* ===================== */ /* Effects */ /* ===================== */ - /* Blink effect */ .condition-effect-blink { - animation: blink-animation 1s steps(2, start) infinite; + animation: blink-animation 1s steps(2, start) infinite; } @keyframes blink-animation { - to { - opacity: 0; - } + to { + opacity: 0; + } } /* For blur-blink effect */ -.condition-effect-blur-pulse path, -.condition-effect-blur-pulse svg { - animation: blur-pulse-animation 2s infinite; - overflow: visible; +.condition-effect-blur-pulse { + path, + svg { + animation: blur-pulse-animation 2s infinite; + overflow: visible; + } } @keyframes blur-pulse-animation { - 0% { - filter: blur(0px); - } - 50% { - filter: blur(2px); - } - 100% { - filter: blur(0px); - } + 0% { + filter: blur(0px); + } + 50% { + filter: blur(2px); + } + 100% { + filter: blur(0px); + } } /* Glow Effect */ -.condition-effect-glow path, -.condition-effect-glow svg { - filter: drop-shadow(0 0 4px currentColor); - overflow: visible; +.condition-effect-glow { + path, + svg { + filter: drop-shadow(0 0 4px currentColor); + overflow: visible; + } } /* Glow Pulse Effect */ -.condition-effect-glow-pulse path, -.condition-effect-glow-pulse svg { - animation: glow-pulse-animation 2s infinite; - overflow: visible; +.condition-effect-glow-pulse { + path, + svg { + animation: glow-pulse-animation 2s infinite; + overflow: visible; + } } @keyframes glow-pulse-animation { - 0% { - filter: drop-shadow(0 0 1px currentColor); - } - 50% { - filter: drop-shadow(0 0 4px currentColor); - } - 100% { - filter: drop-shadow(0 0 1px currentColor); - } + 0% { + filter: drop-shadow(0 0 1px currentColor); + } + 50% { + filter: drop-shadow(0 0 4px currentColor); + } + 100% { + filter: drop-shadow(0 0 1px currentColor); + } } /* Breathing effect */ .condition-effect-breathing { - animation: breathing-animation 3s ease-in-out infinite; + animation: breathing-animation 3s ease-in-out infinite; } @keyframes breathing-animation { - 0% { - opacity: 0.5; - } - 50% { - opacity: 1; - } - 100% { - opacity: 0.5; - } + 0% { + opacity: 0.5; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.5; + } } /* ==================== */ @@ -1470,281 +1485,277 @@ button.action-button { /* ==================== */ .ds-nt-container { - position: relative; - background: var(--code-background); - margin-top: .5em; - margin-bottom: .5em; - padding: 1em; - letter-spacing: 0.03em; -} + position: relative; + background: var(--code-background); + margin-top: 0.5em; + margin-bottom: 0.5em; + padding: 1em; + letter-spacing: 0.03em; + + .ds-nt-name-line, + .ds-nt-name-line div { + display: flex; + align-items: center; + justify-content: space-between; + } -.ds-nt-container { - .ds-nt-name-line, .ds-nt-name-line div { - display: flex; - align-items: center; - justify-content: space-between; - } - - .ds-nt-name-value { - font-weight: bold; - } - - .ds-nt-sidebar { - - } - - .ds-nt-trackers { - - } - - /*.ds-nt-button-selected {*/ - /* background-color: var(--background-modifier-border-focus);*/ - /*}*/ - - .ds-nt-patience-container { - display: flex; - /*justify-content: space-between;*/ - margin-top: .5em; - margin-bottom: .5em; - } - - .ds-nt-patience-bubble-container { - display: flex; - justify-content: space-between; - width: 100%; - position: relative; - margin-left: 1em; - margin-right: 1em; - } - - .ds-nt-patience-bubble-container:before { - content: ""; - position: absolute; - width: 100%; - transform: translateY(-50%); - height: 5px; - background-color: var(--color-base-35); - top: 50%; - } - - .ds-nt-patience-label { - font-weight: bold; - display: flex; - align-items: center; - } - - .ds-nt-patience-bubble { - background-color: var(--code-background); - border: 1px solid var(--text-normal); - border-radius: 50%; - height: 2em; - width: 2em; - text-align: center; - line-height: 2em; - z-index: 1; - cursor: pointer; - } - - .ds-nt-interest-offer-container { - position: relative; - } - - .ds-nt-interest-offer-container:before { - content: ""; - position: absolute; - height: calc(100% - 2em); - margin-top: 1em; - transform: translateX(-50%); - width: 5px; - background-color: var(--color-base-35); - left: 1.5em; - } - - .ds-nt-interest-header { - font-weight: bold; - } - - .ds-nt-interest-line { - display: flex; - position: relative; - z-index: 1; - } - - .ds-nt-interest-line.ds-nt-interest-selected .ds-nt-interest-label, - .ds-nt-patience-container .ds-nt-patience-selected { - background-color: var(--background-modifier-border-focus); - } - - .ds-nt-interest-line .ds-nt-interest-faded { - color: var(--text-faint); - } - - .ds-nt-interest-line.ds-nt-interest-current .ds-nt-interest-label { - box-shadow: 0 0 20px var(--color-accent); - } - - .ds-nt-interest-label { - background-color: var(--code-background); - border: 1px solid var(--text-normal); - margin: 0.5em; - border-radius: 50%; - width: 2em; - min-width: 2em; - height: 2em; - line-height: 2em; - text-align: center; - cursor: pointer; - } - - .ds-nt-interest-offer { - display: flex; - align-items: center; - justify-content: center; - } - - .ds-nt-actions-container { - margin-top: 1em; - margin-bottom: 1em; - } - - .ds-nt-action-tabs { - display: flex; - justify-content: space-around; - } - - .ds-nt-action-tab { - border: 1px solid var(--text-normal); - border-radius: .25em .25em 0 0; - padding: .5em; - width: 40%; - text-align: center; - cursor: pointer - } - - .ds-nt-action-tab.active { - border-bottom: 1px solid var(--code-background); - } - - .ds-nt-action-container { - border: 1px solid var(--text-normal); - border-radius: .25em; - margin-top: -1px; - padding: 1em; - display: none; - } - - .ds-nt-action-container.active { - display: block; - } - - - .ds-nt-argument-body { - } - - .ds-nt-argument-modifiers { - display: flex; - } - - .ds-nt-argument-modifiers > div { - width: 50%; - } - - .ds-nt-argument-modifier-motivation-header, - .ds-nt-argument-modifier-pitfall-header { - font-weight: bold; - } - - .ds-nt-argument-modifier-motivation-line, - .ds-nt-argument-modifier-pitfall-line { - margin-left: 2em; - } - - .ds-nt-argument-modifier-other { - margin-top: 0.3em; - } - - /* disabled checkboxes */ - - .ds-nt-argument-body input[type=checkbox]:disabled, + .ds-nt-name-value { + font-weight: bold; + } + + .ds-nt-sidebar { + } + + .ds-nt-trackers { + } + + /*.ds-nt-button-selected {*/ + /* background-color: var(--background-modifier-border-focus);*/ + /*}*/ + + .ds-nt-patience-container { + display: flex; + /*justify-content: space-between;*/ + margin-top: 0.5em; + margin-bottom: 0.5em; + } + + .ds-nt-patience-bubble-container { + display: flex; + justify-content: space-between; + width: 100%; + position: relative; + margin-left: 1em; + margin-right: 1em; + + &:before { + content: ""; + position: absolute; + width: 100%; + transform: translateY(-50%); + height: 5px; + background-color: var(--color-base-35); + top: 50%; + } + } + + .ds-nt-patience-label { + font-weight: bold; + display: flex; + align-items: center; + } + + .ds-nt-patience-bubble { + background-color: var(--code-background); + border: 1px solid var(--text-normal); + border-radius: 50%; + height: 2em; + width: 2em; + text-align: center; + line-height: 2em; + z-index: 1; + cursor: pointer; + } + + .ds-nt-interest-offer-container { + position: relative; + + &:before { + content: ""; + position: absolute; + height: calc(100% - 2em); + margin-top: 1em; + transform: translateX(-50%); + width: 5px; + background-color: var(--color-base-35); + left: 1.5em; + } + } + + .ds-nt-interest-header { + font-weight: bold; + } + + .ds-nt-interest-line { + display: flex; + position: relative; + z-index: 1; + + &.ds-nt-interest-selected .ds-nt-interest-label, + .ds-nt-patience-container .ds-nt-patience-selected { + background-color: var(--background-modifier-border-focus); + } + + .ds-nt-interest-faded { + color: var(--text-faint); + } + + &.ds-nt-interest-current .ds-nt-interest-label { + box-shadow: 0 0 20px var(--color-accent); + } + } + + .ds-nt-interest-label { + background-color: var(--code-background); + border: 1px solid var(--text-normal); + margin: 0.5em; + border-radius: 50%; + width: 2em; + min-width: 2em; + height: 2em; + line-height: 2em; + text-align: center; + cursor: pointer; + } + + .ds-nt-interest-offer { + display: flex; + align-items: center; + justify-content: center; + } + + .ds-nt-actions-container { + margin-top: 1em; + margin-bottom: 1em; + } + + .ds-nt-action-tabs { + display: flex; + justify-content: space-around; + } + + .ds-nt-action-tab { + border: 1px solid var(--text-normal); + border-radius: 0.25em 0.25em 0 0; + padding: 0.5em; + width: 40%; + text-align: center; + cursor: pointer; + + &.active { + border-bottom: 1px solid var(--code-background); + } + } + + .ds-nt-action-container { + border: 1px solid var(--text-normal); + border-radius: 0.25em; + margin-top: -1px; + padding: 1em; + display: none; + + &.active { + display: block; + } + } + + .ds-nt-argument-body { + } + + .ds-nt-argument-modifiers { + display: flex; + + > div { + width: 50%; + } + } + + .ds-nt-argument-modifier-motivation-header, + .ds-nt-argument-modifier-pitfall-header { + font-weight: bold; + } + + .ds-nt-argument-modifier-motivation-line, + .ds-nt-argument-modifier-pitfall-line { + margin-left: 2em; + } + + .ds-nt-argument-modifier-other { + margin-top: 0.3em; + } + + /* disabled checkboxes */ + + .ds-nt-argument-body input[type=checkbox]:disabled, /* labels with disabled inputs */ .ds-nt-argument-body label:has(input:disabled) { - color: var(--text-faint); - } - - /* Make sure hover effects on disabled checkboxes dont show */ - - .ds-nt-argument-body input[type=checkbox]:disabled { - border: 1px solid var(--checkbox-border-color); - } - - .ds-nt-argument-body input[type=checkbox]:disabled:checked { - background-color: var(--checkbox-border-color); - } - - .ds-nt-arg-motivation-used { - text-decoration: line-through; - } - - .ds-nt-argument-power-roll { - margin-top: .3em; - } - - .ds-nt-argument-power-roll .ds-pr-roll-line { - font-weight: bold; - } - - .ds-nt-argument-power-roll .ds-feature-detail-line { - margin: 0; - padding: 0.3em; - user-select: none; - } - - .ds-nt-argument-power-roll .ds-pr-tier-line { - margin-left: 1em; - border: 1px solid transparent; - border-radius: 5px; - cursor: pointer; - } - - .ds-nt-argument-power-roll .ds-pr-tier-line.active { - background-color: var(--color-base-25); - border: 1px solid var(--color-base-30); - box-shadow: 0 0 5px var(--color-accent); - } - - .ds-nt-argument-footer { - display: flex; - align-items: center; - justify-content: flex-end; - } - - .ds-nt-details-label { - display: block; - margin-left: 2em; - } - - .ds-nt-details-header { - font-weight: bold; - margin-top: 1em; - margin-bottom: 1em; - } - - .ds-nt-details-name { - font-weight: bold; - } - - /* labels with disabled inputs */ - - .ds-nt-motivation-label:has(input:checked) { - text-decoration: line-through; - } - - .btn-icon { - display: flex; - align-items: center; - justify-content: space-between; - margin-right: 1em; - } + color: var(--text-faint); + } + + /* Make sure hover effects on disabled checkboxes dont show */ + + .ds-nt-argument-body input[type="checkbox"]:disabled { + border: 1px solid var(--checkbox-border-color); + + &:checked { + background-color: var(--checkbox-border-color); + } + } + + .ds-nt-arg-motivation-used { + text-decoration: line-through; + } + + .ds-nt-argument-power-roll { + margin-top: 0.3em; + + .ds-pr-roll-line { + font-weight: bold; + } + + .ds-feature-detail-line { + margin: 0; + padding: 0.3em; + user-select: none; + } + + .ds-pr-tier-line { + margin-left: 1em; + border: 1px solid transparent; + border-radius: 5px; + cursor: pointer; + + &.active { + background-color: var(--color-base-25); + border: 1px solid var(--color-base-30); + box-shadow: 0 0 5px var(--color-accent); + } + } + } + + .ds-nt-argument-footer { + display: flex; + align-items: center; + justify-content: flex-end; + } + + .ds-nt-details-label { + display: block; + margin-left: 2em; + } + + .ds-nt-details-header { + font-weight: bold; + margin-top: 1em; + margin-bottom: 1em; + } + + .ds-nt-details-name { + font-weight: bold; + } + + /* labels with disabled inputs */ + + .ds-nt-motivation-label:has(input:checked) { + text-decoration: line-through; + } + + .btn-icon { + display: flex; + align-items: center; + justify-content: space-between; + margin-right: 1em; + } } /* ==================== */ @@ -1790,101 +1801,103 @@ button.action-button { /* ==================== */ .ds-sb-container { - background-color: var(--code-background); - padding-bottom: 1rem; + background-color: var(--code-background); + padding-bottom: 1rem; - /* General space-between (left-right) line */ + /* General space-between (left-right) line */ - .ds-sb-title-line, - .ds-sb-subtitle-line, - .ds-sb-info-line, - .ds-sb-stats-line { - display: flex; - justify-content: space-between; - align-items: center; - } + .ds-sb-title-line, + .ds-sb-subtitle-line, + .ds-sb-info-line, + .ds-sb-stats-line { + display: flex; + justify-content: space-between; + align-items: center; + } - /* General purpose separator */ + /* General purpose separator */ - .ds-sb-characteristics-line { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - } + .ds-sb-characteristics-line { + padding-top: 0.5rem; + padding-bottom: 0.5rem; - /* Hover effect on each "container" */ + /* Hover effect on each "container" */ + &:hover { + background-color: var(--color-base-25); + } + } - .ds-sb-characteristics-line:hover, - .ds-sb-info-line:hover, - .ds-sb-stats-line:hover { - background-color: var(--color-base-25); - } + .ds-sb-info-line:hover, + .ds-sb-stats-line:hover { + background-color: var(--color-base-25); + } - /* + /* Each line needs to have padding. Ideally this would be at the root container, but line backgrounds need 100% width */ - .ds-sb-title-line, - .ds-sb-subtitle-line, - .ds-sb-info-line, - .ds-sb-stats-line, - .ds-feature-container, - .ds-hr-container { - padding-left: 1rem; - padding-right: 1rem; - } - - .ds-hr-container { - margin: 0.5rem 0 0.5em 0; - } - - .ds-sb-title-line { - font-size: var(--font-ui-large); - font-weight: bold; - background-color: var(--color-base-25); - padding-top: 0.5rem; - } - .ds-sb-subtitle-line { - background-color: var(--color-base-25); - padding-bottom: 0.5rem; - } - - .ds-sb-stats-row { - display: flex; - justify-content: space-around; - align-items: center; - padding-bottom: 0.5rem; - } - - .ds-sb-stats-item { - text-align: center; - width: -webkit-fill-available; - } - .ds-sb-stats-item-top { - font-size: calc(var(--font-ui-large) * 1.5); - font-weight: bold; - } - .ds-sb-stats-item-bottom { - } - - .ds-sb-stats-line { - margin-top: 0.25rem; - } - - .ds-sb-characteristics-line { - font-weight: bold; - text-align: center; - margin-top: 0.5rem; - } - .ds-sb-characteristics-pair { - width: -webkit-fill-available; - } - - /*.ds-feature-detail-line:hover,*/ - /*.ds-feature-detail-table-cell:hover,*/ - /*.ds-sb-trait-effect:hover {*/ - /* background-color: var(--color-base-30);*/ - /*}*/ + .ds-sb-title-line, + .ds-sb-subtitle-line, + .ds-sb-info-line, + .ds-sb-stats-line, + .ds-feature-container, + .ds-hr-container { + padding-left: 1rem; + padding-right: 1rem; + } + + .ds-hr-container { + margin: 0.5rem 0 0.5em 0; + } + + .ds-sb-title-line { + font-size: var(--font-ui-large); + font-weight: bold; + background-color: var(--color-base-25); + padding-top: 0.5rem; + } + .ds-sb-subtitle-line { + background-color: var(--color-base-25); + padding-bottom: 0.5rem; + } + + .ds-sb-stats-row { + display: flex; + justify-content: space-around; + align-items: center; + padding-bottom: 0.5rem; + } + + .ds-sb-stats-item { + text-align: center; + width: -webkit-fill-available; + } + .ds-sb-stats-item-top { + font-size: calc(var(--font-ui-large) * 1.5); + font-weight: bold; + } + .ds-sb-stats-item-bottom { + } + + .ds-sb-stats-line { + margin-top: 0.25rem; + } + + .ds-sb-characteristics-line { + font-weight: bold; + text-align: center; + margin-top: 0.5rem; + } + .ds-sb-characteristics-pair { + width: -webkit-fill-available; + } + + /*.ds-feature-detail-line:hover,*/ + /*.ds-feature-detail-table-cell:hover,*/ + /*.ds-sb-trait-effect:hover {*/ + /* background-color: var(--color-base-30);*/ + /*}*/ } /* ==================== */ @@ -1892,24 +1905,26 @@ button.action-button { /* ==================== */ .ds-fb-container { - background-color: var(--code-background); - padding-bottom: 1rem; - - .ds-fb-flavor, .ds-fb-stats, .ds-hr-container, .ds-feature-container { - padding-left: 1rem; - padding-right: 1rem; - } + background-color: var(--code-background); + padding-bottom: 1rem; + .ds-fb-flavor, + .ds-fb-stats, + .ds-hr-container, + .ds-feature-container { + padding-left: 1rem; + padding-right: 1rem; + } - .ds-fb-flavor { - font-style: italic; - margin-bottom: 0.5rem; - } + .ds-fb-flavor { + font-style: italic; + margin-bottom: 0.5rem; + } - .ds-fb-stats { - list-style: none; - margin: 0; - margin-bottom: 0.5rem; + .ds-fb-stats { + list-style: none; + margin: 0; + margin-bottom: 0.5rem; .ds-fb-stats-row { display: flex; @@ -1932,7 +1947,7 @@ button.action-button { .ds-fb-stats-line { margin-top: 0.25rem; } - } + } } /* ==================== */ @@ -1940,57 +1955,57 @@ button.action-button { /* ==================== */ .ds-counter-container { - display: flex; - align-items: center; - justify-content: space-evenly; - width: 100%; - - .ds-counter-display-container { - text-align: center; - } - - .ds-counter-value { - font-size: 2em; - font-weight: bold; - line-height: 1em; - min-width: 1.5em; - } - - .ds-counter-name { - font-size: 1em; - color: var(--text-muted); - } - - .ds-counter-controls { - display: flex; - flex-direction: column; - padding: 1em; - } - - .ds-counter-button { - background: none; - border: none; - cursor: pointer; - padding: 0.5em; - } - - .ds-counter-button:hover { - background-color: var(--background-modifier-hover); - } - - .ds-counter-input { - font-size: inherit; - text-align: center; - width: 100%; - border: none; - background: transparent; - outline: none; - } - - .ds-counter-button[disabled] { - opacity: 0.5; - cursor: not-allowed; - } + display: flex; + align-items: center; + justify-content: center; + width: fit-content; + cursor: default; + + .ds-counter-display-container { + text-align: center; + } + + .ds-counter-value { + font-weight: bold; + line-height: 1em; + min-width: 1.5em; + width: 4.5ch; /* Wide enough to fit 9999 which should be plenty*/ + background-color: transparent; + border: transparent; + text-align: center; + padding: 0; + padding-bottom: 0.13em; + height: 1em; + cursor: text; + pointer-events: auto; + } + + .ds-counter-name { + font-size: 1em; + color: var(--text-muted); + } + + .ds-counter-controls { + display: flex; + flex-direction: column; + padding: 1em 0.2em; + } + + .ds-counter-button { + background: none; + border: none; + cursor: pointer; + padding: 0.5em; + + &:hover { + background-color: var(--background-modifier-hover); + } + + &[disabled] { + opacity: 0.5; + cursor: not-allowed; + } + } } /* ==================== */ @@ -1999,46 +2014,46 @@ button.action-button { .ds-characteristics-container, .ds-values-row-container { - width: 100%; - overflow-x: auto; - - .ds-characteristics-row, - .ds-values-row-row { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: flex-start; - } - - .ds-characteristics-cell, - .ds-values-row-cell { - flex: 1; - text-align: center; - margin: 0 5px; - } - - .ds-characteristics-value, - .ds-values-row-value { - font-weight: bold; - } - - .ds-characteristics-name, - .ds-values-row-name { - color: var(--text-muted); - } - - @media (max-width: 600px) { - .ds-characteristics-row, - .ds-values-row-row { - flex-direction: column; - align-items: center; - } - - .ds-characteristics-cell, - .ds-values-row-cell { - margin: 10px 0; - } - } + width: 100%; + overflow-x: auto; + + .ds-characteristics-row, + .ds-values-row-row { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + } + + .ds-characteristics-cell, + .ds-values-row-cell { + flex: 1; + text-align: center; + margin: 0 5px; + } + + .ds-characteristics-value, + .ds-values-row-value { + font-weight: bold; + } + + .ds-characteristics-name, + .ds-values-row-name { + color: var(--text-muted); + } + + @media (max-width: 600px) { + .ds-characteristics-row, + .ds-values-row-row { + flex-direction: column; + align-items: center; + } + + .ds-characteristics-cell, + .ds-values-row-cell { + margin: 10px 0; + } + } } /* ==================== */ @@ -2046,77 +2061,78 @@ button.action-button { /* ==================== */ .ds-skills-container { - width: 100%; - - /* Skill group */ - .ds-skill-group { - /*margin-bottom: 1em;*/ - } - - /* Skill group title */ - .ds-skill-group-title { - font-size: 1.2em; - margin-bottom: 0.5em; - margin-top: 0; - text-transform: lowercase; - } - .ds-skill-group-title:first-letter{ - text-transform: uppercase; - } - - /* Skills list */ - .ds-skill-list { - list-style: none; - padding: 0; - } - - /* Skill item */ - .ds-skill-item { - display: flex; - align-items: center; - margin-bottom: 0.3em; - } - - /* Skill indicator */ - .ds-skill-indicator { - width: 1em; - height: 1em; - margin-right: 0.5em; - border: 1px solid var(--text-muted); - display: inline-block; - border-radius: 0.2em; - } - - .ds-skill-indicator.enabled { - background-color: var(--interactive-accent); - } - - .ds-skill-indicator.disabled { - background-color: transparent; - } - - /* Skill name */ - .ds-skill-name { - cursor: default; - text-transform: lowercase; - } - .ds-skill-name:first-letter{ - text-transform: uppercase; - } -} + width: 100%; + + /* Skill group */ + .ds-skill-group { + /*margin-bottom: 1em;*/ + } + + /* Skill group title */ + .ds-skill-group-title { + font-size: 1.2em; + margin-bottom: 0.5em; + margin-top: 0; + text-transform: lowercase; + + &:first-letter { + text-transform: uppercase; + } + } + /* Skills list */ + .ds-skill-list { + list-style: none; + padding: 0; + } + + /* Skill item */ + .ds-skill-item { + display: flex; + align-items: center; + margin-bottom: 0.3em; + } + + /* Skill indicator */ + .ds-skill-indicator { + width: 1em; + height: 1em; + margin-right: 0.5em; + border: 1px solid var(--text-muted); + display: inline-block; + border-radius: 0.2em; + + &.enabled { + background-color: var(--interactive-accent); + } + + &.disabled { + background-color: transparent; + } + } + + /* Skill name */ + .ds-skill-name { + cursor: default; + text-transform: lowercase; + + &:first-letter { + text-transform: uppercase; + } + } +} /* Post Vue Globals */ .markdown-source-view.is-live-preview .ds-vue-wrapper { - margin-left: 20px; + margin-left: 20px; } :root { - --stamina-bar-color: limegreen; - --stamina-bar-color-winded: yellow; - --stamina-bar-color-dying: red; - /* --stamina-bar-color: var(--text-accent) */ + --stamina-bar-color: limegreen; + --stamina-bar-color-winded: yellow; + --stamina-bar-color-dying: red; + /* --stamina-bar-color: var(--text-accent) */ }