From e9a892076dc40aa8beae897672401dca78e8ee39 Mon Sep 17 00:00:00 2001 From: Peaceultime Date: Mon, 26 Jan 2026 00:05:05 +0100 Subject: [PATCH] Add logic tree computation and item enchantment. --- app/types/character.d.ts | 23 +-- db.sqlite | Bin 712704 -> 712704 bytes nuxt.config.ts | 6 +- shared/character-config.json | 179 ++++++++-------- shared/character.ts | 384 ++++++++++++++++++----------------- shared/feature.ts | 21 +- 6 files changed, 325 insertions(+), 288 deletions(-) diff --git a/app/types/character.d.ts b/app/types/character.d.ts index f6bb099..e8e697e 100644 --- a/app/types/character.d.ts +++ b/app/types/character.d.ts @@ -14,6 +14,7 @@ export type DamageType = typeof DAMAGE_TYPES[number]; export type WeaponType = typeof WEAPON_TYPES[number]; export type FeatureID = string; +export type FeatureEffectID = string; export type i18nID = string; export type RecursiveKeyOf = { @@ -57,10 +58,6 @@ export type CharacterVariables = { money: number; }; -export enum TreeFlag { - AUTOMATIC = 1 << 0, - REPEATING = 1 << 1, -}; export type TreeLeaf = { id: FeatureID; to?: FeatureID | Array | Record; @@ -68,14 +65,14 @@ export type TreeLeaf = { }; export type TreeStructure = { name: string; - starts: FeatureID; + start: FeatureID | Array | Record; nodes: Record; }; type CommonState = { capacity?: number; powercost?: number; }; -type ArmorState = { loss: number, health?: number, absorb?: { flat?: number, percent?: number } }; +type ArmorState = { loss: number, health?: number, absorb: { flat?: number, percent?: number } }; type WeaponState = { attack?: number | string, hit?: number }; type WondrousState = { }; type MundaneState = { }; @@ -86,6 +83,7 @@ type ItemState = { charges?: number; equipped?: boolean; state?: (ArmorState | WeaponState | WondrousState | MundaneState) & CommonState; + buffer?: Record }; export type CharacterConfig = { peoples: Record; @@ -196,34 +194,35 @@ export type AspectConfig = { }; export type FeatureValue = { - id: FeatureID; + id: FeatureEffectID; category: "value"; operation: "add" | "set" | "min"; property: RecursiveKeyOf | 'spec' | 'ability' | 'training'; value: number | `modifier/${MainStat}` | false; } export type FeatureEquipment = { - id: FeatureID; + id: FeatureEffectID; category: "value"; operation: "add" | "set" | "min"; property: `item/${RecursiveKeyOf}`; value: number | `modifier/${MainStat}` | false; }; export type FeatureList = { - id: FeatureID; + id: FeatureEffectID; category: "list"; list: "spells" | "sickness" | "action" | "reaction" | "freeaction" | "passive" | "mastery"; action: "add" | "remove"; item: string; }; export type FeatureTree = { - id: FeatureID; + id: FeatureEffectID; category: "tree"; tree: string; option?: string; + priority?: number; }; export type FeatureChoice = { - id: FeatureID; + id: FeatureEffectID; category: "choice"; text: string; //TODO -> TextID settings?: { //If undefined, amount is 1 by default @@ -294,7 +293,7 @@ export type CompiledCharacter = { modifier: Record; abilities: Partial>; level: number; - lists: { [K in FeatureList['list']]?: string[] }; //string => ListItem ID + lists: { [K in Exclude]: string[] }; //string => ListItem ID notes: { public: string, private: string }; }; \ No newline at end of file diff --git a/db.sqlite b/db.sqlite index 83c606297febbf6eaf533f8e52ac9777c2ed03ca..d3cd7aced3f45c77cfd5e1f50f2fdf529bb8f73d 100644 GIT binary patch delta 4563 zcmb`KYitzP6~|}p%-A!Yx%ceD2?paB*4W0z3%0S1A%WQD5kmk20fPyIjCaT03F}>V zA0#x7fkKqFQJY4nB1)yUsUkI16y<>$A{C`HAKEWec}Yc8BSBMDsoE+kd9-RlO3$6y z*`2lhp&u$SLg#nSId{*w|2cPj>hh{nmshKjQ3gAKzwk0VlqBCknEWn=;@zI^iSC*^zB_D_dfSgcE!0{$5c%9iU3#P z6-@O4bavpH_`hT#7p`X8LK5bCTT$xiqtG7QfAIf6s#Ok zYotY5F>mGax-2zm33F!Fw5KzIAg~Mn{1NNk)Wigl{hs}rz0Q8Zo@L))Id+uY%Lds> zb~_W`A8-wGzXd;o=iy0s7;HEITc8(W5N5a7-!2RCQ4yPv%veb)VP~xH5=CsmUZBTRHaBBt5>{w&IVfSx7BDE< zpor}lRLmqXQ2~=BD1)nHLEHX z;Qc%y+T|!3R$pB_tWq>AK2Uq7qTw&{ODCZYMZ?+yrNc@;q?9_+u4q_!RpCaPqAeCx zGnX(krfjq-TBGWyrK+PVFhEr8%n2)Bu&2GMXbWzvseUJD3`0b<)CUq-?(UIKTlRFS zke%X(7ha}lttzIe^ah&U-&W}iy93*RMf1K~xOGh#8U|5lVhsZP5deyCgEhcuco)74 zYhfP@LknDjSK$SiWY<|cyMbE!E!)L1>S!)){-Hq4FVuv_K zSWO`r&ncj*rA)@tF3YMhEQ>Afy05Aw<%m*i7e{5WU98qr1JB}!o~BR_i>NF%ioT+5 z_xAAHc$>FaYGZvwMmbffDe7%m=~x70lu|`WVg3$QDvioohiGP;;2YEhvbJ2TXlNCc zQA56jIzzJ7!dLw&JAC(2kt-uI%Ara=?YfNe@zm31$f$&pbXr|sJ99iBofYnOe#I@W zd#T`<>t3arZ zU%csvT6cOxKP%8H^b&oUK2N_zAEDE9oNlFkG)BR=X?$Y5Z(K5dYCLN^W=tFVjE%-h zqu!A8&-Cm1d-`SlRs9A1oPHK=z{l`D{0d%&AET-thlk)GY=$+k6g2iFs`?6hi@kJl z^=o3i?$8lM#J7~p=1(S47BPm=#G*XY7($CrJ$DiA=95Yzd}Q1C8I!P0-jMVG+vtrt z-wxfyM-m1Ub9Odw7fuqm6A7j-69+aR!7a)pvL5Fh0z4iojU&^-Q5zlfW?&+l!D&$_ z@)=kcT{j5$bcT6Nn>k+&Xv11Ymb#bIt5W#`%HnHpkon8+spu;<B)kWqR+TY4hRD6(%E6J{ZfH3sM1NXfkFqp70%@3ltWc7$7q=NBx{b~9hy-Z)FKcwHGkJ01wFkbFo)j5ohMZB54u{c8cSoFVp@ z0jRB80ICBEz}kKfI#pw-rf&gPW$gk`?p**%Jqtk6%`4(X2ZrLBQk4lO09Tj#RhE|& zpG!zrse@&oRiy@&K_>!!OuhzdJLZ6zl>y-Jv8l8N2nTZ893ZvM0c1t#@cnICN{8>; zG0LY`Y0#;OpjA6rv7D4uV^++Rr92q4>K6&7n7wQth%?`Oz*{cKGMJf(23Nk6l(1fI-u@I*thKf815 z?kA>3Cy(z;PEC&XWsf~{aA)_x`ROd_lnS}x`R`}R(6X*aV|FsOA$DlIm>6GMu#Qda zw)U?b*fEwoeDI#hscqc7X*9NdbZ@r5_;5Cv=>Gb)vEK2`TjPfZ58W4!9qo!)Un|

*BNj1tiPKM*si- delta 2169 zcmZWqU1%It7@d3P&gS0i%>8CJ{YlzxH`{hw+W6DZVyU3S`sRZMTlGZ<)7i=H=`@wiNU%&)LpL%QWGV?=kSPmE5IsgXre%P!@)k+bEv6nVtZUL>o*&gXFFyVTS(N>8?5Gs zHL*+Zd9Q+FLY$0)^_I}Y7~xjawmggJToao)uem|!Hy1~Suz_&bTX2HVty^}I!>zhXU2b#?a{}GY?&iW z>?Q+x0mI9ubX9!;7yGYB;ywXL#qHNG%>0uMug; zh>lAn%EaY8g|xVOjXo^LM46=JQRCl(>h#2+IIk$IuvT{>ts*z(^a~1n4R6D;v6uV{ z=-hh>+*{6>|}(M;@kutODPd9_8v-aRGbh5~m` z{wwe;oQ2cy0UU=K9Dyl#6gELd-jO%u_wpVKIZbF0(kB`WXA>chAM_W-Cgk7_mZqTr( zu`#x2f{hIbIZq(0$G{P8&o^B^aKl9v)IkmfMX-H7xHbC~EdPaR@A`D}ag0vTyx zj`4Wg_b`>=e4NT0#ux{rxg2I02c#*$K;um-&KsfA#H%wC!(1e!PpA|y&*>5>G12k+ z)a#Kds*P&&dkoBSL5Lu1I$kxb86;LafsqGG#%)C&mv#9nyalhp%Wz1ZmtV+F=);KMjp%0l0Ar%()J+lKJurr!wN)S_aTFcJPg)RtyfK2j4>v?F zJXR<6QP&R;bR7;XVt-N*`KE{i5}Zdv4)~~| ({ reaction: [], passive: [], spells: [], + sickness: [], }, aspect: { id: character.aspect ?? "", @@ -279,6 +280,7 @@ export const CharacterValidation = z.object({ type Property = { value: number | string | false, id: string, operation: "set" | "add" | "min" }; export type PropertySum = { list: Array, min: number, value: number, _dirty: boolean }; +type TreeState = { progression: Array<{ id: string, priority: number, path?: string }>, validated: string[], _dirty: boolean }; export class CharacterCompiler { private _dirty: boolean = true; @@ -294,6 +296,7 @@ export class CharacterCompiler 'modifier/charisma': { value: 0, _dirty: false, min: -Infinity, list: [] }, 'modifier/psyche': { value: 0, _dirty: false, min: -Infinity, list: [] }, }; + protected _trees: Record = {}; private _variableDebounce: NodeJS.Timeout = setTimeout(() => {}); constructor(character: Character) @@ -325,7 +328,6 @@ export class CharacterCompiler }); Object.entries(value.abilities).forEach(e => this._buffer[`abilities/${e[0]}`] = { value: 0, _dirty: true, min: -Infinity, list: [{ id: '', operation: 'add', value: e[1] }] }) - //Object.entries(value.abilities).forEach(e => this._result.abilities[e[0] as Ability] = e[1]); } } get character(): Character @@ -334,21 +336,49 @@ export class CharacterCompiler } get compiled(): CompiledCharacter { - Object.entries(this._character.abilities).forEach(e => this._result.abilities[e[0] as Ability] = e[1]); - this._dirty && this.compile(Object.keys(this._buffer)); - this._dirty = false; + if(this._dirty) + { + Object.keys(this._trees).forEach(tree => { + if(!this._trees[tree]!._dirty || !config.trees[tree]) + return; + + this._trees[tree]!.progression.sort((a, b) => a.priority - b.priority); + const validated = validateTree(config.trees[tree]!, this._trees[tree]!.progression); + + this._trees[tree]!.validated.forEach(this.add, this); + validated.forEach(this.add, this); + + this._trees[tree]!.validated = validated; + }); + + this.compile(Object.keys(this._buffer)); + this._dirty = false; + } return this._result; } get values(): Record { - Object.entries(this._character.abilities).forEach(e => this._result.abilities[e[0] as Ability] = e[1]); + if(this._dirty) + { + Object.keys(this._trees).forEach(tree => { + if(!this._trees[tree]!._dirty || !config.trees[tree]) + return; - const keys = Object.keys(this._buffer); - this._dirty && this.compile(keys); - this._dirty = false; + this._trees[tree]!.progression.sort((a, b) => a.priority - b.priority); + const validated = validateTree(config.trees[tree]!, this._trees[tree]!.progression); - return keys.reduce((p, v) => { + this._trees[tree]!.validated.forEach(this.add, this); + validated.forEach(this.add, this); + + this._trees[tree]!.validated = validated; + }); + + this.compile(Object.keys(this._buffer)); + this._dirty = false; + } + + return Object.keys(this._buffer).reduce((p, v) => { p[v] = this._buffer[v]!.value; return p; }, {} as Record); @@ -367,6 +397,29 @@ export class CharacterCompiler return this._character.variables.items.filter(e => config.items[e.id]?.equippable && e.equipped).reduce((p, v) => p + ((config.items[v.id]?.powercost ?? 0) + (v.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0) * v.amount), 0); } + enchant(item: ItemState) + { + if(item.equipped) + item.enchantments?.forEach(e => config.enchantments[e]?.effect.filter(e => e.category !== 'value' || !e.property.startsWith('item')).forEach(_e => this.apply(_e as FeatureValue | FeatureList))); + else + item.enchantments?.forEach(e => config.enchantments[e]?.effect.filter(e => e.category !== 'value' || !e.property.startsWith('item')).forEach(_e => this.undo(_e as FeatureValue | FeatureList))); + + item.buffer ??= {} as Record; + Object.keys(item.buffer).forEach(e => item.buffer![e]!.list = []); + item.enchantments?.forEach(e => (config.enchantments[e]?.effect.filter(e => e.category === 'value' && e.property.startsWith('item')) as FeatureEquipment[]).forEach(feature => { + const property = feature.property.substring(5); + item.buffer![property] ??= { list: [], value: 0, _dirty: true, min: -Infinity }; + + item.buffer![property]!.list.push({ operation: feature.operation, id: feature.id, value: feature.value }); + + item.buffer![property]!.min = -Infinity; + item.buffer![property]!._dirty = true; + })); + Object.keys(item.buffer).forEach(e => setProperty(item.state, e, 0, true)); + this.compile(Object.keys(item.buffer), item.buffer, item.state); + + this.saveVariables(); + } saveVariables() { clearTimeout(this._variableDebounce); @@ -411,10 +464,10 @@ export class CharacterCompiler switch(feature.category) { case "list": - if(feature.action === 'add' && !this._result.lists[feature.list]!.includes(feature.item)) - this._result.lists[feature.list]!.push(feature.item); + if(feature.action === 'add' && !(feature.list === 'mastery' ? this._result.mastery : this._result.lists[feature.list]!).includes(feature.item)) + (feature.list === 'mastery' ? this._result.mastery : this._result.lists[feature.list]!).push(feature.item); else if(feature.action === 'remove') - this._result.lists[feature.list]!.splice(this._result.lists[feature.list]!.findIndex((e: string) => e === feature.item), 1); + (feature.list === 'mastery' ? this._result.mastery : this._result.lists[feature.list]!).splice((feature.list === 'mastery' ? this._result.mastery : this._result.lists[feature.list]!).findIndex((e: string) => e === feature.item), 1); return; case "value": @@ -435,6 +488,15 @@ export class CharacterCompiler if(choice) choice.forEach(e => feature.options[e]!.effects.forEach((effect) => this.apply(effect))); + return; + case "tree": + if(!config.trees[feature.tree]) + return; + + this._trees[feature.tree] ??= { progression: [], validated: [], _dirty: true }; + this._trees[feature.tree]!.progression.push({ id: feature.id, priority: feature.priority ?? 1, path: feature.option }); + + this._trees[feature.tree]!._dirty = true; return; default: return; @@ -449,17 +511,17 @@ export class CharacterCompiler switch(feature.category) { case "list": - if(feature.action === 'remove' && !this._result.lists[feature.list]!.includes(feature.item)) - this._result.lists[feature.list]!.push(feature.item); + if(feature.action === 'remove' && !(feature.list === 'mastery' ? this._result.mastery : this._result.lists[feature.list]!).includes(feature.item)) + (feature.list === 'mastery' ? this._result.mastery : this._result.lists[feature.list]!).push(feature.item); else if(feature.action === 'add') - this._result.lists[feature.list]!.splice(this._result.lists[feature.list]!.findIndex((e: string) => e === feature.item), 1) + (feature.list === 'mastery' ? this._result.mastery : this._result.lists[feature.list]!).splice((feature.list === 'mastery' ? this._result.mastery : this._result.lists[feature.list]!).findIndex((e: string) => e === feature.item), 1) return; case "value": this._buffer[feature.property] ??= { list: [], value: 0, _dirty: true, min: -Infinity }; - const idx = this._buffer[feature.property]!.list.findIndex(e => e.id === feature.id); - idx !== -1 && this._buffer[feature.property]!.list.splice(idx, 1); + const listIdx = this._buffer[feature.property]!.list.findIndex(e => e.id === feature.id); + listIdx !== -1 && this._buffer[feature.property]!.list.splice(listIdx, 1); this._buffer[feature.property]!.min = -Infinity; this._buffer[feature.property]!._dirty = true; @@ -474,19 +536,27 @@ export class CharacterCompiler if(choice) choice.forEach(e => feature.options[e]!.effects.forEach((effect) => this.undo(effect))); + return; + case "tree": + if(!config.trees[feature.tree] || !this._trees[feature.tree]) + return; + + const treeIdx = this._trees[feature.tree]!.progression.findIndex(e => e.id === feature.id); + treeIdx !== -1 && this._trees[feature.tree]!.progression.splice(treeIdx, 1); + this._trees[feature.tree]!._dirty = true; return; default: return; } } - protected compile(queue: string[]) + protected compile(queue: string[], _buffer: Record = this._buffer, _result: Record = this._result) { for(let i = 0; i < queue.length; i++) { if(queue[i] === undefined || queue[i] === "") continue; const property = queue[i]!; - const buffer = this._buffer[property]; + const buffer = _buffer[property]; if(buffer && buffer._dirty === true) { @@ -499,7 +569,7 @@ export class CharacterCompiler if(typeof item.value === 'string') // Add or set a modifier { - const modifier = this._buffer[item.value as string]!; + const modifier = _buffer[item.value as string]!; if(modifier._dirty) { //Put it back in queue since its dependencies haven't been resolved yet @@ -533,20 +603,100 @@ export class CharacterCompiler if(stop === true) continue; - setProperty(this._result, property, Math.max(sum, this._buffer[property]!.min)); + setProperty(_result, property, Math.max(sum, _buffer[property]!.min)); - this._buffer[property]!.value = Math.max(sum, this._buffer[property]!.min); - this._buffer[property]!._dirty = false; + _buffer[property]!.value = Math.max(sum, _buffer[property]!.min); + _buffer[property]!._dirty = false; } } } } -function setProperty(root: any, path: string, value: T | ((old: T) => T)) +export enum TreeFlag { + REPEATING = 1 << 0, + MULTIPLE = 1 << 1, + HIDDEN = 1 << 2, +}; +function validateTree(structure: TreeStructure, progression: Array<{ path?: string }>): string[] +{ + const validated = [] as string[]; + const paths = new Map(), multiples: Record = {}; + + const hasFlag = (node: string, flag: number) => structure.nodes[node] && structure.nodes[node].flags && (structure.nodes[node].flags & flag) === flag; + const addRequirement = (target: string, requirement: string) => { multiples[target] ??= []; multiples[target].push(requirement) }; + + //Precompute the requirements for the nodes with multiples inputs + Object.values(structure.nodes).forEach(node => { + if(Array.isArray(node.to)) + node.to.forEach(to => { if(hasFlag(to, TreeFlag.MULTIPLE)) addRequirement(to, node.id) }); + else if(typeof node.to === 'object') + Object.values(node.to).forEach(to => { if(hasFlag(to, TreeFlag.MULTIPLE)) addRequirement(to, node.id) }); + else if(node.to) + if(hasFlag(node.to, TreeFlag.MULTIPLE)) addRequirement(node.to, node.id); + }); + + const nextPath = (path: string | undefined, node: string): boolean => { + if(!structure.nodes[node]) + return false; + else if(hasFlag(node, TreeFlag.MULTIPLE)) + return false; + else + { + paths.set(path, node); + return true; + } + }; + + if(Array.isArray(structure.start)) + structure.start.some(e => nextPath(undefined, e)); + else if(typeof structure.start === 'object') + Object.keys(structure.start).forEach(e => nextPath(e === "" ? undefined: e, (structure.start as Record)[e]!)); + else if(structure.start) + nextPath(undefined, structure.start); + + for(let i = 0; progression[i] && i < progression.length; i++) + { + const progress = progression[i]; + let path: string | undefined, valid = false; + if(paths.has(progress?.path)) + path = progress?.path; + else + path = undefined; + + const next = paths.get(path); + if(!next || !structure.nodes[next]) + continue; + + const node = structure.nodes[next]; + Object.keys(multiples).forEach(e => { + multiples[e] = multiples[e]!.filter(node => node !== next); + if(multiples[e].length === 0) + { + validated.push(e); + delete multiples[e]; + } + }); + + validated.push(next); + paths.delete(path); + + if(hasFlag(next, TreeFlag.REPEATING)) + paths.set(path, next); + else if(Array.isArray(node.to)) + node.to.some(e => nextPath(path, e)); + else if(typeof node.to === 'object') + Object.keys(node.to).forEach(e => nextPath(e === "" ? undefined: e, (node.to as Record)[e]!)); + else if(node.to) + nextPath(path, node.to); + } + + return validated; +} +function setProperty(root: any, path: string, value: T | ((old: T) => T), force: boolean = false) { const arr = path.split("/"); //Get the property path as an array const object = arr.length === 1 ? root : arr.slice(0, -1).reduce((p, v) => { p[v] ??= {}; return p[v]; }, root); //Get into the second to last property using the property path - if(object.hasOwnProperty(arr.slice(-1)[0]!)) + if(force || object.hasOwnProperty(arr.slice(-1)[0]!)) object[arr.slice(-1)[0]!] = typeof value === 'function' ? (value as (old: T) => T)(object[arr.slice(-1)[0]!]) : value; } export class CharacterBuilder extends CharacterCompiler @@ -1469,9 +1619,9 @@ export class CharacterSheet { id: 'inventory', title: [ text('Inventaire') ], content: this.itemsTab(character) }, - { id: 'aspect', title: [ text('Aspect') ], content: () => [ - text('TODO'), - ] }, + { id: 'aspect', title: [ text('Aspect') ], content: () => this.aspectTab(character) }, + + { id: 'effects', title: [ text('Afflictions') ], content: () => this.effectsTab(character) }, { id: 'notes', title: [ text('Notes') ], content: () => [ div('flex flex-col gap-2', [ @@ -1479,154 +1629,8 @@ export class CharacterSheet div('flex flex-col gap-2', [ span('text-lg font-bold', 'Notes privés'), div('border border-light-35 dark:border-dark-35 bg-light20 dark:bg-dark-20 p-1 h-64', [ privateNotes.dom ]) ]), ]) ] }, + ], { focused: this.tab, class: { container: 'flex-1 gap-4 px-4 max-w-[960px] h-full', content: 'overflow-auto' }, switch: v => { this.tab = v; } }); - { id: 'details', title: [ text('Détails') ], content: () => [ - div('grid grid-cols-3 gap-2', [ - () => character.health ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Vie'), span('font-semibold', text(() => character.health ?? "")) ]) : undefined, - () => character.mana ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Mana'), span('font-semibold', text(() => character.mana ?? "")) ]) : undefined, - () => character.spellslots ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Sorts maitrisés'), span('font-semibold', text(() => character.spellslots ?? "")) ]) : undefined, - () => character.artslots ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Oeuvres maitrisées'), span('font-semibold', text(() => character.artslots ?? "")) ]) : undefined, - () => character.speed ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Vitesse de course'), span('font-semibold', text(() => character.speed === false ? 'Aucun' : character.speed ?? "")) ]) : undefined, - () => character.capacity ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Poids supporté'), span('font-semibold', text(() => character.capacity === false ? 'Aucun' : character.capacity ?? "")) ]) : undefined, - () => character.initiative ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Initiative'), span('font-semibold', text(() => character.initiative ?? "")) ]) : undefined, - () => character.exhaust ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Fatigue max bonus'), span('font-semibold', text(() => character.exhaust ?? "")) ]) : undefined, - () => character.itempower ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Puissance magique max'), span('font-semibold', text(() => character.itempower ?? "")) ]) : undefined, - () => character.aspect.amount ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Nombre de transformation'), span('font-semibold', text(() => character.aspect.amount ?? "")) ]) : undefined, - () => character.aspect.duration ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Durée de transformation'), span('font-semibold', text(() => character.aspect.duration ?? "")) ]) : undefined, - () => character.aspect.shift_bonus ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de transformation'), span('font-semibold', text(() => character.aspect.shift_bonus ?? "")) ]) : undefined, - () => character.aspect.bonus?.defense?.strength ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de force'), span('font-semibold', text(() => character.aspect?.bonus?.defense?.strength ?? "")) ]) : undefined, - () => character.aspect.bonus?.defense?.dexterity ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de dextérité'), span('font-semibold', text(() => character.aspect?.bonus?.defense?.dexterity ?? "")) ]) : undefined, - () => character.aspect.bonus?.defense?.constitution ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de constitution'), span('font-semibold', text(() => character.aspect?.bonus?.defense?.constitution ?? "")) ]) : undefined, - () => character.aspect.bonus?.defense?.intelligence ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance d\'intelligence'), span('font-semibold', text(() => character.aspect?.bonus?.defense?.intelligence ?? "")) ]) : undefined, - () => character.aspect.bonus?.defense?.curiosity ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de curiosité'), span('font-semibold', text(() => character.aspect?.bonus?.defense?.curiosity ?? "")) ]) : undefined, - () => character.aspect.bonus?.defense?.charisma ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de charisme'), span('font-semibold', text(() => character.aspect?.bonus?.defense?.charisma ?? "")) ]) : undefined, - () => character.aspect.bonus?.defense?.psyche ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de psyché'), span('font-semibold', text(() => character.aspect?.bonus?.defense?.psyche ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.athletics ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Athlétisme'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.athletics ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.acrobatics ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Acrobatisme'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.acrobatics ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.intimidation ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Intimidation'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.intimidation ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.sleightofhand ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Doigté'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.sleightofhand ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.stealth ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Discrétion'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.stealth ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.survival ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Survie'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.survival ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.investigation ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Enquête'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.investigation ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.history ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Histoire'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.history ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.religion ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Religion'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.religion ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.arcana ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Arcanes'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.arcana ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.understanding ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Compréhension'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.understanding ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.perception ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Perception'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.perception ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.performance ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Représentation'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.performance ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.medecine ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Médicine'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.medecine ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.persuasion ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Persuasion'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.persuasion ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.animalhandling ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Dressage'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.animalhandling ?? "")) ]) : undefined, - () => character.aspect.bonus?.abilities?.deception ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Mensonge'), span('font-semibold', text(() => character.aspect?.bonus?.abilities?.deception ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.type.instinct ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts d\'instinct'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.type.instinct ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.type.precision ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de précision'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.type.precision ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.type.knowledge ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de savoir'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.type.knowledge ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.type.arts ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux oeuvres'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.type.arts ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.rank[1] ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de rang 1'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.rank[1] ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.rank[2] ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de rang 2'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.rank[2] ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.rank[3] ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de rang 3'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.rank[3] ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.rank[4] ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts spéciaux'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.rank[4] ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.elements.fire ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de feu'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.elements.fire ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.elements.ice ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de glace'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.elements.ice ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.elements.thunder ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de foudre'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.elements.thunder ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.elements.earth ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de terre'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.elements.earth ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.elements.arcana ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts d\'arcane'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.elements.arcana ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.elements.air ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts d\'air'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.elements.air ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.elements.nature ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de nature'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.elements.nature ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.elements.light ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de lumière'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.elements.light ?? "")) ]) : undefined, - () => character.aspect.bonus?.spells?.elements.psyche ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de psy'), span('font-semibold', text(() => character.aspect?.bonus?.spells?.elements.psyche ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.light ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes légères'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.light ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.shield ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux boucliers'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.shield ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.heavy ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes lourdes'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.heavy ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.classic ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes standard'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.classic ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.throw ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes de jet'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.throw ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.natural ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes naturelles'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.natural ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.twohanded ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes à deux mains'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.twohanded ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.finesse ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes maniables'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.finesse ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.reach ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes longues'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.reach ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.projectile ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes à projectiles'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.projectile ?? "")) ]) : undefined, - () => character.aspect.bonus?.weapon?.improvised ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes improvisées'), span('font-semibold', text(() => character.aspect?.bonus?.weapon?.improvised ?? "")) ]) : undefined, - () => character.aspect.bonus?.resistance?.stun ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des hébètements'), span('font-semibold', text(() => character.aspect?.bonus?.resistance?.stun ?? "")) ]) : undefined, - () => character.aspect.bonus?.resistance?.bleed ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des saignements'), span('font-semibold', text(() => character.aspect?.bonus?.resistance?.bleed ?? "")) ]) : undefined, - () => character.aspect.bonus?.resistance?.poison ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des poisons'), span('font-semibold', text(() => character.aspect?.bonus?.resistance?.poison ?? "")) ]) : undefined, - () => character.aspect.bonus?.resistance?.fear ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté de la peur'), span('font-semibold', text(() => character.aspect?.bonus?.resistance?.fear ?? "")) ]) : undefined, - () => character.aspect.bonus?.resistance?.influence ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des influences'), span('font-semibold', text(() => character.aspect?.bonus?.resistance?.influence ?? "")) ]) : undefined, - () => character.aspect.bonus?.resistance?.charm ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des charmes'), span('font-semibold', text(() => character.aspect?.bonus?.resistance?.charm ?? "")) ]) : undefined, - () => character.aspect.bonus?.resistance?.possesion ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des possessions'), span('font-semibold', text(() => character.aspect?.bonus?.resistance?.possesion ?? "")) ]) : undefined, - () => character.aspect.bonus?.resistance?.precision ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des sorts de précision'), span('font-semibold', text(() => character.aspect?.bonus?.resistance?.precision ?? "")) ]) : undefined, - () => character.aspect.bonus?.resistance?.knowledge ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des sorts de savoir'), span('font-semibold', text(() => character.aspect?.bonus?.resistance?.knowledge ?? "")) ]) : undefined, - () => character.aspect.bonus?.resistance?.instinct ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des sorts d\'instinct'), span('font-semibold', text(() => character.aspect?.bonus?.resistance?.instinct ?? "")) ]) : undefined, - () => character.defense.activeparry ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de parade active'), span('font-semibold', text(() => character.defense.activeparry ?? "")) ]) : undefined, - () => character.defense.activedodge ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus d\'esquive active'), span('font-semibold', text(() => character.defense.activedodge ?? "")) ]) : undefined, - () => character.defense.passiveparry ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de parade passive'), span('font-semibold', text(() => character.defense.passiveparry ?? "")) ]) : undefined, - () => character.defense.passivedodge ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus d\'esquive parade passive'), span('font-semibold', text(() => character.defense.passivedodge ?? "")) ]) : undefined, - () => character.bonus.defense.strength ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de force'), span('font-semibold', text(() => character.bonus.defense?.strength ?? "")) ]) : undefined, - () => character.bonus.defense.dexterity ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de dextérité'), span('font-semibold', text(() => character.bonus.defense?.dexterity ?? "")) ]) : undefined, - () => character.bonus.defense.constitution ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de constitution'), span('font-semibold', text(() => character.bonus.defense?.constitution ?? "")) ]) : undefined, - () => character.bonus.defense.intelligence ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance d\'intelligence'), span('font-semibold', text(() => character.bonus.defense?.intelligence ?? "")) ]) : undefined, - () => character.bonus.defense.curiosity ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de curiosité'), span('font-semibold', text(() => character.bonus.defense?.curiosity ?? "")) ]) : undefined, - () => character.bonus.defense.charisma ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de charisme'), span('font-semibold', text(() => character.bonus.defense?.charisma ?? "")) ]) : undefined, - () => character.bonus.defense.psyche ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de jet de résistance de psyché'), span('font-semibold', text(() => character.bonus.defense?.psyche ?? "")) ]) : undefined, - () => character.bonus.abilities.athletics ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Athlétisme'), span('font-semibold', text(() => character.bonus.abilities?.athletics ?? "")) ]) : undefined, - () => character.bonus.abilities.acrobatics ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Acrobatisme'), span('font-semibold', text(() => character.bonus.abilities?.acrobatics ?? "")) ]) : undefined, - () => character.bonus.abilities.intimidation ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Intimidation'), span('font-semibold', text(() => character.bonus.abilities?.intimidation ?? "")) ]) : undefined, - () => character.bonus.abilities.sleightofhand ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Doigté'), span('font-semibold', text(() => character.bonus.abilities?.sleightofhand ?? "")) ]) : undefined, - () => character.bonus.abilities.stealth ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Discrétion'), span('font-semibold', text(() => character.bonus.abilities?.stealth ?? "")) ]) : undefined, - () => character.bonus.abilities.survival ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Survie'), span('font-semibold', text(() => character.bonus.abilities?.survival ?? "")) ]) : undefined, - () => character.bonus.abilities.investigation ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Enquête'), span('font-semibold', text(() => character.bonus.abilities?.investigation ?? "")) ]) : undefined, - () => character.bonus.abilities.history ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Histoire'), span('font-semibold', text(() => character.bonus.abilities?.history ?? "")) ]) : undefined, - () => character.bonus.abilities.religion ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Religion'), span('font-semibold', text(() => character.bonus.abilities?.religion ?? "")) ]) : undefined, - () => character.bonus.abilities.arcana ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Arcanes'), span('font-semibold', text(() => character.bonus.abilities?.arcana ?? "")) ]) : undefined, - () => character.bonus.abilities.understanding ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Compréhension'), span('font-semibold', text(() => character.bonus.abilities?.understanding ?? "")) ]) : undefined, - () => character.bonus.abilities.perception ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Perception'), span('font-semibold', text(() => character.bonus.abilities?.perception ?? "")) ]) : undefined, - () => character.bonus.abilities.performance ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Représentation'), span('font-semibold', text(() => character.bonus.abilities?.performance ?? "")) ]) : undefined, - () => character.bonus.abilities.medecine ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Médicine'), span('font-semibold', text(() => character.bonus.abilities?.medecine ?? "")) ]) : undefined, - () => character.bonus.abilities.persuasion ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Persuasion'), span('font-semibold', text(() => character.bonus.abilities?.persuasion ?? "")) ]) : undefined, - () => character.bonus.abilities.animalhandling ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Dressage'), span('font-semibold', text(() => character.bonus.abilities?.animalhandling ?? "")) ]) : undefined, - () => character.bonus.abilities.deception ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus en Mensonge'), span('font-semibold', text(() => character.bonus.abilities?.deception ?? "")) ]) : undefined, - () => character.bonus.spells.type.instinct ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts d\'instinct'), span('font-semibold', text(() => character.bonus.spells?.type.instinct ?? "")) ]) : undefined, - () => character.bonus.spells.type.precision ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de précision'), span('font-semibold', text(() => character.bonus.spells?.type.precision ?? "")) ]) : undefined, - () => character.bonus.spells.type.knowledge ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de savoir'), span('font-semibold', text(() => character.bonus.spells?.type.knowledge ?? "")) ]) : undefined, - () => character.bonus.spells.type.arts ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux oeuvres'), span('font-semibold', text(() => character.bonus.spells?.type.arts ?? "")) ]) : undefined, - () => character.bonus.spells.rank[1] ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de rang 1'), span('font-semibold', text(() => character.bonus.spells?.rank[1] ?? "")) ]) : undefined, - () => character.bonus.spells.rank[2] ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de rang 2'), span('font-semibold', text(() => character.bonus.spells?.rank[2] ?? "")) ]) : undefined, - () => character.bonus.spells.rank[3] ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de rang 3'), span('font-semibold', text(() => character.bonus.spells?.rank[3] ?? "")) ]) : undefined, - () => character.bonus.spells.rank[4] ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts spéciaux'), span('font-semibold', text(() => character.bonus.spells?.rank[4] ?? "")) ]) : undefined, - () => character.bonus.spells.elements.fire ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de feu'), span('font-semibold', text(() => character.bonus.spells?.elements.fire ?? "")) ]) : undefined, - () => character.bonus.spells.elements.ice ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de glace'), span('font-semibold', text(() => character.bonus.spells?.elements.ice ?? "")) ]) : undefined, - () => character.bonus.spells.elements.thunder ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de foudre'), span('font-semibold', text(() => character.bonus.spells?.elements.thunder ?? "")) ]) : undefined, - () => character.bonus.spells.elements.earth ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de terre'), span('font-semibold', text(() => character.bonus.spells?.elements.earth ?? "")) ]) : undefined, - () => character.bonus.spells.elements.arcana ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts d\'arcane'), span('font-semibold', text(() => character.bonus.spells?.elements.arcana ?? "")) ]) : undefined, - () => character.bonus.spells.elements.air ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts d\'air'), span('font-semibold', text(() => character.bonus.spells?.elements.air ?? "")) ]) : undefined, - () => character.bonus.spells.elements.nature ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de nature'), span('font-semibold', text(() => character.bonus.spells?.elements.nature ?? "")) ]) : undefined, - () => character.bonus.spells.elements.light ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de lumière'), span('font-semibold', text(() => character.bonus.spells?.elements.light ?? "")) ]) : undefined, - () => character.bonus.spells.elements.psyche ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux sorts de psy'), span('font-semibold', text(() => character.bonus.spells?.elements.psyche ?? "")) ]) : undefined, - () => character.bonus.weapon.light ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes légères'), span('font-semibold', text(() => character.bonus.weapon?.light ?? "")) ]) : undefined, - () => character.bonus.weapon.shield ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux boucliers'), span('font-semibold', text(() => character.bonus.weapon?.shield ?? "")) ]) : undefined, - () => character.bonus.weapon.heavy ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes lourdes'), span('font-semibold', text(() => character.bonus.weapon?.heavy ?? "")) ]) : undefined, - () => character.bonus.weapon.classic ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes standard'), span('font-semibold', text(() => character.bonus.weapon?.classic ?? "")) ]) : undefined, - () => character.bonus.weapon.throw ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes de jet'), span('font-semibold', text(() => character.bonus.weapon?.throw ?? "")) ]) : undefined, - () => character.bonus.weapon.natural ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes naturelles'), span('font-semibold', text(() => character.bonus.weapon?.natural ?? "")) ]) : undefined, - () => character.bonus.weapon.twohanded ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes à deux mains'), span('font-semibold', text(() => character.bonus.weapon?.twohanded ?? "")) ]) : undefined, - () => character.bonus.weapon.finesse ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes maniables'), span('font-semibold', text(() => character.bonus.weapon?.finesse ?? "")) ]) : undefined, - () => character.bonus.weapon.reach ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes longues'), span('font-semibold', text(() => character.bonus.weapon?.reach ?? "")) ]) : undefined, - () => character.bonus.weapon.projectile ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes à projectiles'), span('font-semibold', text(() => character.bonus.weapon?.projectile ?? "")) ]) : undefined, - () => character.bonus.weapon.improvised ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus aux armes improvisées'), span('font-semibold', text(() => character.bonus.weapon?.improvised ?? "")) ]) : undefined, - () => character.bonus.resistance.stun ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des hébètements'), span('font-semibold', text(() => character.bonus.resistance?.stun ?? "")) ]) : undefined, - () => character.bonus.resistance.bleed ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des saignements'), span('font-semibold', text(() => character.bonus.resistance?.bleed ?? "")) ]) : undefined, - () => character.bonus.resistance.poison ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des poisons'), span('font-semibold', text(() => character.bonus.resistance?.poison ?? "")) ]) : undefined, - () => character.bonus.resistance.fear ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté de la peur'), span('font-semibold', text(() => character.bonus.resistance?.fear ?? "")) ]) : undefined, - () => character.bonus.resistance.influence ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des influences'), span('font-semibold', text(() => character.bonus.resistance?.influence ?? "")) ]) : undefined, - () => character.bonus.resistance.charm ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des charmes'), span('font-semibold', text(() => character.bonus.resistance?.charm ?? "")) ]) : undefined, - () => character.bonus.resistance.possesion ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des possessions'), span('font-semibold', text(() => character.bonus.resistance?.possesion ?? "")) ]) : undefined, - () => character.bonus.resistance.precision ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des sorts de précision'), span('font-semibold', text(() => character.bonus.resistance?.precision ?? "")) ]) : undefined, - () => character.bonus.resistance.knowledge ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des sorts de savoir'), span('font-semibold', text(() => character.bonus.resistance?.knowledge ?? "")) ]) : undefined, - () => character.bonus.resistance.instinct ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de difficulté des sorts d\'instinct'), span('font-semibold', text(() => character.bonus.resistance?.instinct ?? "")) ]) : undefined, - () => character.craft.level ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Tier de fabrication'), span('font-semibold', text(() => character.craft.level ?? "")) ]) : undefined, - () => character.craft.bonus ? div('flex flex-row gap-1 items-center justify-between', [ span('italic text-light-70 dark:text-dark-70', 'Bonus de fabrication'), span('font-semibold', text(() => character.craft.bonus ?? "")) ]) : undefined, - ]) - ] }, - ], { focused: this.tab, class: { container: 'flex-1 gap-4 px-4 w-[960px] h-full', content: 'overflow-auto' }, switch: v => { this.tab = v; } }); this.container.replaceChildren(div('flex flex-col justify-start gap-1 h-full', [ div("flex flex-row gap-4 justify-between", [ div(), @@ -1711,7 +1715,7 @@ export class CharacterSheet dom("span", { class: "text-sm 2xl:text-base", text: "Initiative" }) ]), div("flex flex-col px-2 items-center", [ - dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(() => character.speed === false ? "Aucun déplacement" : `${character.speed} cases`) ]), + dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(() => character.speed === false ? "N/A" : `${character.speed}`) ]), dom("span", { class: "text-sm 2xl:text-base", text: "Course" }) ]) ]), @@ -1757,7 +1761,7 @@ export class CharacterSheet div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50") ]), - div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", { list: character.mastery, render: (e, _c) => proses('a', preview, [ text('Arme légère') ], { href: 'regles/annexes/equipement#Les armes légères', label: 'Arme légère', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline', }) }), + div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", { list: character.mastery, render: (e, _c) => proses('a', preview, [ text(masteryTexts[e].text) ], { href: masteryTexts[e].href, label: masteryTexts[e].text, class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline', }) }), div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", [ () => character.spellranks.precision > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Précision') ], { href: 'regles/la-magie/magie#Les sorts de précision', label: 'Précision', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.precision)) ]) : undefined, () => character.spellranks.knowledge > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Savoir') ], { href: 'regles/la-magie/magie#Les sorts de savoir', label: 'Savoir', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.knowledge)) ]) : undefined, @@ -2032,7 +2036,7 @@ export class CharacterSheet this.character?.character.campaign ? button(text('Partager'), () => { }, 'px-2 text-sm h-5 box-content') : undefined, - button(icon('radix-icons:minus', { width: 12, height: 12 }), () => { + button(icon(() => e.amount === 1 ? 'radix-icons:trash' : 'radix-icons:minus', { width: 12, height: 12 }), () => { const idx = items.findIndex(_e => _e === e); if(idx === -1) return; @@ -2056,10 +2060,10 @@ export class CharacterSheet }, 'px-2 text-sm h-5 box-content'), ]) ], [ div('flex flex-row justify-between', [ - div('flex flex-row items-center gap-4', [ + div('flex flex-row items-center gap-y-1 gap-x-4 flex-wrap', [ item.equippable ? checkbox({ defaultValue: e.equipped, change: v => { e.equipped = v; - + this.character?.enchant(e); }, class: { container: '!w-5 !h-5' } }) : undefined, div('flex flex-row items-center gap-4', [ span([colorByRarity[item.rarity], 'text-lg'], item.name), div('flex flex-row gap-2 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(item).map(e => span('', e))) ]), item.category === 'armor' ? div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${item.health + ((e.state as ArmorState)?.health ?? 0) - ((e.state as ArmorState)?.loss ?? 0)}/${item.health + ((e.state as ArmorState)?.health ?? 0)} (${[item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0) > 0 ? '-' + (item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0)) : undefined, item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0) > 0 ? '-' + (item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0)) + '%' : undefined].filter(e => !!e).join('/')})`) ]) : @@ -2073,7 +2077,7 @@ export class CharacterSheet e.amount > 1 && !!item.weight ? tooltip(weight, `Poids unitaire: ${item.weight}`, 'bottom') : weight, div('flex flex-row min-w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', () => item.charge ? `${item.charge}` : '-') ]), ]), - ])], { open: false, class: { icon: 'px-2', container: 'p-1 gap-2', content: 'px-4 pb-1 flex flex-col' } }) + ])], { open: false, class: { icon: 'px-2', container: 'p-1 gap-2', content: 'px-4 pb-1 flex flex-col gap-1' } }) }}) ]) ]; @@ -2191,9 +2195,7 @@ export class CharacterSheet else current.item!.enchantments?.splice(idx, 1); - current.item!.state ??= {}; - - this.character?.saveVariables(); + this.character?.enchant(current.item!); }, 'p-1 !border-solid !border-r'), ]), ])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } }) @@ -2210,4 +2212,16 @@ export class CharacterSheet container.setAttribute('data-state', 'inactive'); }}; } + aspectTab(character: CompiledCharacter) + { + return [ + + ] + } + effectsTab(character: CompiledCharacter) + { + return [ + + ] + } } \ No newline at end of file diff --git a/shared/feature.ts b/shared/feature.ts index 9a1c280..4801b3d 100644 --- a/shared/feature.ts +++ b/shared/feature.ts @@ -237,16 +237,19 @@ export class HomebrewBuilder dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(spellTypeTexts[spell.type]) ]), dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(`${spell.cost} mana`) ]), dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(spell.speed === 'action' ? 'Action' : spell.speed === 'reaction' ? 'Réaction' : `${spell.speed} minutes`) ]), - dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(`${elementTexts[spell.elements[0]!].text}${spell.elements.length > 1 ? `+${spell.elements.length - 1}` : ''}`) ]), + dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(`${elementTexts[spell.elements[0]!].text}${spell.elements.length > 1 ? ` (+${spell.elements.length - 1})` : ''}`) ]), dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(spell.range === 'personnal' ? 'Personnel' : spell.range === 0 ? 'Toucher' : `${spell.range} cases`) ]), - dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(`${spell.tags && spell.tags.length > 0 ? spellTagTexts[spell.tags[0]!] : ''}${spell.tags && spell.tags.length > 1 ? `+${spell.tags.length - 1}` : ''}`) ]), + dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(`${spell.tags && spell.tags.length > 0 ? spellTagTexts[spell.tags[0]!] : ''}${spell.tags && spell.tags.length > 1 ? ` (+${spell.tags.length - 1})` : ''}`) ]), dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(spell.concentration ? 'Concentration' : '') ]), ]), div('flex flex-row justify-center gap-2', [ button(icon('radix-icons:pencil-1'), () => editing.id = spell.id, 'p-1'), button(icon('radix-icons:trash'), () => remove(spell), 'p-1') ]) ]) ], { class: { container: 'border-light-35 dark:border-dark-35 py-1', content: 'gap-2 px-4 py-1 flex items-center *:flex-1' }, open: false }); }; - const edit = (spell: SpellConfig) => { + const edit = (id: string) => { + const spell = config.spells[id] ? { ...config.spells[id] } : undefined; + if(!spell) return; + MarkdownEditor.singleton.onChange = v => {}; MarkdownEditor.singleton.content = spell.description; return foldable([ @@ -264,8 +267,14 @@ export class HomebrewBuilder dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Tags'), multiselect([{ text: 'Dégâts', value: 'damage' }, { text: 'Buff', value: 'buff' }, { text: 'Debuff', value: 'debuff' }, { text: 'Support', value: 'support' }, { text: 'Tank', value: 'tank' }, { text: 'Mouvement', value: 'movement' }, { text: 'Utilitaire', value: 'utilitary' }], { change: (value) => spell.tags = value, defaultValue: spell.tags, class: { container: '!m-0 !h-9 w-full' } }), ]), dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Concentration'), toggle({ change: (value) => spell.concentration = value, defaultValue: spell.concentration, class: { container: '!m-0 !flex-none' } }), ]), ]), - div('flex flex-row gap-2', [ ]) - ]) + div('flex flex-row gap-2', [ tooltip(button(icon('radix-icons:check'), () => { + spell.description = MarkdownEditor.singleton.content; + Object.assign(config.spells[spell.id]!, spell); + editing.id = ''; + }, 'p-1'), "Valider", 'left'), tooltip(button(icon('radix-icons:cross-1'), () => { + editing.id = ''; + }, 'p-1'), "Annuler", 'left') ]), + ]) ], { class: { container: 'border-light-35 dark:border-dark-35 py-1', content: 'gap-2 px-4 py-1 flex items-center *:flex-1' }, open: false }); }; const add = () => { @@ -292,7 +301,7 @@ export class HomebrewBuilder } }); } - return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), add, 'p-1') ]), div('flex flex-col divide-y', { list: () => Object.values(config.spells), render: (e, _c) => editing.id === e.id ? edit(e) : _c ?? render(e)}) ] ) ]; + return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), add, 'p-1') ]), div('flex flex-col divide-y', { list: () => Object.values(config.spells), render: (e, _c) => editing.id === e.id ? edit(e.id) : render(e)}) ] ) ]; } actions() {