diff --git a/css/kidsonbrooms.css b/css/kidsonbrooms.css new file mode 100644 index 0000000..8e242f1 --- /dev/null +++ b/css/kidsonbrooms.css @@ -0,0 +1,423 @@ +@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"); +/* Global styles */ +@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"); +.window-app { + font-family: "Roboto", sans-serif; +} + +.rollable:hover, .rollable:focus { + color: #000; + text-shadow: 0 0 10px rgb(146, 0, 225); + cursor: pointer; +} + +.grid { + display: grid; + gap: 10px; + margin: 10px 0; + padding: 0; +} + +.grid-start-2 { + grid-column-start: 2; +} + +.grid-span-2 { + grid-column-end: span 2; +} + +.grid-2col { + grid-column: span 2/span 2; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.grid-start-3 { + grid-column-start: 3; +} + +.grid-span-3 { + grid-column-end: span 3; +} + +.grid-3col { + grid-column: span 3/span 3; + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.grid-start-4 { + grid-column-start: 4; +} + +.grid-span-4 { + grid-column-end: span 4; +} + +.grid-4col { + grid-column: span 4/span 4; + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.grid-start-5 { + grid-column-start: 5; +} + +.grid-span-5 { + grid-column-end: span 5; +} + +.grid-5col { + grid-column: span 5/span 5; + grid-template-columns: repeat(5, minmax(0, 1fr)); +} + +.grid-start-6 { + grid-column-start: 6; +} + +.grid-span-6 { + grid-column-end: span 6; +} + +.grid-6col { + grid-column: span 6/span 6; + grid-template-columns: repeat(6, minmax(0, 1fr)); +} + +.grid-start-7 { + grid-column-start: 7; +} + +.grid-span-7 { + grid-column-end: span 7; +} + +.grid-7col { + grid-column: span 7/span 7; + grid-template-columns: repeat(7, minmax(0, 1fr)); +} + +.grid-start-8 { + grid-column-start: 8; +} + +.grid-span-8 { + grid-column-end: span 8; +} + +.grid-8col { + grid-column: span 8/span 8; + grid-template-columns: repeat(8, minmax(0, 1fr)); +} + +.grid-start-9 { + grid-column-start: 9; +} + +.grid-span-9 { + grid-column-end: span 9; +} + +.grid-9col { + grid-column: span 9/span 9; + grid-template-columns: repeat(9, minmax(0, 1fr)); +} + +.grid-start-10 { + grid-column-start: 10; +} + +.grid-span-10 { + grid-column-end: span 10; +} + +.grid-10col { + grid-column: span 10/span 10; + grid-template-columns: repeat(10, minmax(0, 1fr)); +} + +.grid-start-11 { + grid-column-start: 11; +} + +.grid-span-11 { + grid-column-end: span 11; +} + +.grid-11col { + grid-column: span 11/span 11; + grid-template-columns: repeat(11, minmax(0, 1fr)); +} + +.grid-start-12 { + grid-column-start: 12; +} + +.grid-span-12 { + grid-column-end: span 12; +} + +.grid-12col { + grid-column: span 12/span 12; + grid-template-columns: repeat(12, minmax(0, 1fr)); +} + +.flex-group-center { + display: flex; + flex-direction: center; + flex-wrap: center; + justify-content: flex-start; + align-items: stretch; + text-align: center; +} + +.flex-group-left { + display: flex; + flex-direction: flex-start; + flex-wrap: center; + justify-content: flex-start; + align-items: stretch; + text-align: left; +} + +.flex-group-right { + display: flex; + flex-direction: flex-end; + flex-wrap: center; + justify-content: flex-start; + align-items: stretch; + text-align: right; +} + +.flexshrink { + flex: 0; +} + +.flex-between { + justify-content: space-between; +} + +.flexlarge { + flex: 2; +} + +.align-left { + display: flex; + flex-direction: flex-start; + flex-wrap: center; + justify-content: flex-start; + align-items: stretch; + text-align: left; +} + +.align-right { + display: flex; + flex-direction: flex-end; + flex-wrap: center; + justify-content: flex-start; + align-items: stretch; + text-align: right; +} + +.align-center { + display: flex; + flex-direction: center; + flex-wrap: center; + justify-content: flex-start; + align-items: stretch; + text-align: center; +} + +.right-align-input { + flex: 1; + margin-left: auto; + max-width: 260px; +} + +.window-app { + font-family: "Roboto", sans-serif; +} + +.rollable:hover, .rollable:focus { + color: #000; + text-shadow: 0 0 10px rgb(179, 7, 217); + cursor: pointer; +} + +/* Styles limited to kidsonbrooms sheets */ +.kids-on-brooms .item-form { + font-family: "Roboto", sans-serif; +} +.kids-on-brooms .sheet-header { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + align-items: stretch; + flex: 0 auto; + overflow: hidden; + margin-bottom: 10px; +} +.kids-on-brooms .sheet-header .profile-img { + flex: 0 0 100px; + height: 100px; + margin-right: 10px; +} +.kids-on-brooms .sheet-header .header-fields { + flex: 1; +} +.kids-on-brooms .sheet-header h1.charname { + height: 50px; + padding: 0; + margin: 5px 0; + border-bottom: 0; +} +.kids-on-brooms .sheet-header h1.charname input { + width: 100%; + height: 100%; + margin: 0; +} +.kids-on-brooms div.editor-border { + border: 2px solid rgb(81, 81, 81); + border-radius: 10px; +} +.kids-on-brooms .sheet-tabs { + flex: 0; +} +.kids-on-brooms .sheet-body, +.kids-on-brooms .sheet-body .tab, +.kids-on-brooms .sheet-body .tab .editor { + height: 100%; +} +.kids-on-brooms .tox .tox-editor-container { + background: #fff; +} +.kids-on-brooms .tox .tox-edit-area { + padding: 0 8px; +} +.kids-on-brooms .selection-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} +.kids-on-brooms .resource-label { + font-weight: bold; +} +.kids-on-brooms .items-header { + height: 28px; + margin: 2px 0; + padding: 0; + align-items: center; + background: rgba(0, 0, 0, 0.05); + border: 2px groove #eeede0; + font-weight: bold; +} +.kids-on-brooms .items-header > * { + font-size: 14px; + text-align: center; +} +.kids-on-brooms .items-header .item-name { + font-weight: bold; + padding-left: 5px; + text-align: left; + display: flex; +} +.kids-on-brooms .items-list { + list-style: none; + margin: 0; + padding: 0; + overflow-y: auto; + scrollbar-width: thin; + color: #444; +} +.kids-on-brooms .items-list .item-list { + list-style: none; + margin: 0; + padding: 0; +} +.kids-on-brooms .items-list .item { + display: flex; + align-items: center; + padding: 0 2px; + border-bottom: 1px solid #c9c7b8; +} +.kids-on-brooms .items-list .item:last-child { + border-bottom: none; +} +.kids-on-brooms .items-list .item .item-name { + flex: 2; + margin: 0; + overflow: hidden; + font-size: 13px; + text-align: left; + display: flex; + color: #191813; +} +.kids-on-brooms .items-list .item .item-name h3, .kids-on-brooms .items-list .item .item-name h4 { + margin: 0; + white-space: nowrap; + overflow-x: hidden; +} +.kids-on-brooms .items-list .item .item-name .item-image { + flex: 0 0 30px; + height: 30px; + background-size: 30px; + border: none; + margin-right: 5px; +} +.kids-on-brooms .items-list .item-controls { + display: flex; + flex: 0 0 100px; + justify-content: flex-end; +} +.kids-on-brooms .items-list .item-controls a { + font-size: 12px; + text-align: center; + margin: 0 6px; +} +.kids-on-brooms .items-list .item-prop { + text-align: center; + border-left: 1px solid #c9c7b8; + border-right: 1px solid #c9c7b8; + font-size: 12px; +} +.kids-on-brooms .items-list .items-header { + height: 28px; + margin: 2px 0; + padding: 0; + align-items: center; + background: rgba(0, 0, 0, 0.05); + border: 2px groove #eeede0; + font-weight: bold; +} +.kids-on-brooms .items-list .items-header > * { + font-size: 12px; + text-align: center; +} +.kids-on-brooms .items-list .items-header .item-name { + padding-left: 5px; + text-align: left; +} +.kids-on-brooms .item-formula { + flex: 0 0 200px; + padding: 0 8px; +} +.kids-on-brooms .effects .item .effect-source, +.kids-on-brooms .effects .item .effect-duration, +.kids-on-brooms .effects .item .effect-controls { + text-align: center; + border-left: 1px solid #c9c7b8; + border-right: 1px solid #c9c7b8; + font-size: 12px; +} +.kids-on-brooms .effects .item .effect-controls { + border: none; +} +.kids-on-brooms .kids-on-brooms input:focus, +.kids-on-brooms .kids-on-brooms textarea:focus, +.kids-on-brooms .kids-on-brooms select:focus { + outline: none; + border-color: #8102dd; +} + +/*# sourceMappingURL=kidsonbrooms.css.map */ diff --git a/css/kidsonbrooms.css.map b/css/kidsonbrooms.css.map new file mode 100644 index 0000000..a29835b --- /dev/null +++ b/css/kidsonbrooms.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["../scss/kidsonbrooms.scss","../scss/global/_base.scss","../scss/global/_window.scss","../scss/utils/_typography.scss","../scss/global/_grid.scss","../scss/global/_flex.scss","../scss/utils/_mixins.scss","../scss/utils/_variables.scss","../scss/components/_forms.scss","../scss/utils/_colors.scss","../scss/components/_resource.scss","../scss/components/_items.scss","../scss/components/_effects.scss"],"names":[],"mappings":"AACQ;AASR;ACTQ;ACDR;EACE,aCDa;;;ADKb;EAEE;EACA;EACA;;;AERJ;EACE;EACA;EACA;EACA;;;AAKA;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;ACrBJ;ECkBE;EACA,gBDlBiB;ECmBjB,WDnByB;ECoBzB,iBAJkD;EAKlD,aALsE;EDftE;;;AAGF;ECaE;EACA,gBDbiB;ECcjB,WDd6B;ECe7B,iBAJkD;EAKlD,aALsE;EDVtE;;;AAGF;ECQE;EACA,gBDRiB;ECSjB,WDT2B;ECU3B,iBAJkD;EAKlD,aALsE;EDLtE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;ECVE;EACA,gBDUiB;ECTjB,WDS6B;ECR7B,iBAJkD;EAKlD,aALsE;EDatE;;;AAGF;ECfE;EACA,gBDeiB;ECdjB,WDc2B;ECb3B,iBAJkD;EAKlD,aALsE;EDkBtE;;;AAGF;ECpBE;EACA,gBDoBiB;ECnBjB,WDmByB;EClBzB,iBAJkD;EAKlD,aALsE;EDuBtE;;;AAIF;EACE;EACA;EACA;;;AJ7CF;EACE,aMEW;;;ANEX;EAEE;EACA;EACA;;;ADIJ;AQhBA;EACE,aLDa;;AKIf;EFeE;EACA,gBEfiB;EFgBjB,WEhBsB;EFiBtB,iBEjB4B;EFkB5B,aALsE;EEZtE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKN;EACE;EACA;;AAGF;EACE;;AAGF;AAAA;AAAA;EAGE;;AAIA;EACE,YCnDM;;ADsDR;EACE;;AAKJ;EACE;EACA;EACA;EACA;;AEhEF;EACE;;ACAF;EACE;EACA;EACA;EACA;EACA;EACA,QJHc;EIId;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,OFxBM;;AE2BN;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,OFvDG;;AEyDH;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAMN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,QJrGc;EIsGd;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAKJ;EACE;EACA;;ACzHA;AAAA;AAAA;EAGE;EACA;EACA;EACA;;AAGF;EACE;;AAKJ;AAAA;AAAA;EAGE;EACA,cHVmB","file":"kidsonbrooms.css"} \ No newline at end of file diff --git a/css/test.css.map b/css/test.css.map new file mode 100644 index 0000000..5147620 --- /dev/null +++ b/css/test.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["../scss/kidsonbrooms.scss","../scss/global/_base.scss","../scss/global/_window.scss","../scss/utils/_typography.scss","../scss/global/_grid.scss","../scss/global/_flex.scss","../scss/utils/_mixins.scss","../scss/utils/_variables.scss","../scss/components/_forms.scss","../scss/utils/_colors.scss","../scss/components/_resource.scss","../scss/components/_items.scss","../scss/components/_effects.scss"],"names":[],"mappings":"AACQ;AASR;ACTQ;ACDR;EACE,aCDa;;;ADKb;EAEE;EACA;EACA;;;AERJ;EACE;EACA;EACA;EACA;;;AAKA;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;ACrBJ;ECkBE;EACA,gBDlBiB;ECmBjB,WDnByB;ECoBzB,iBAJkD;EAKlD,aALsE;EDftE;;;AAGF;ECaE;EACA,gBDbiB;ECcjB,WDd6B;ECe7B,iBAJkD;EAKlD,aALsE;EDVtE;;;AAGF;ECQE;EACA,gBDRiB;ECSjB,WDT2B;ECU3B,iBAJkD;EAKlD,aALsE;EDLtE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;ECVE;EACA,gBDUiB;ECTjB,WDS6B;ECR7B,iBAJkD;EAKlD,aALsE;EDatE;;;AAGF;ECfE;EACA,gBDeiB;ECdjB,WDc2B;ECb3B,iBAJkD;EAKlD,aALsE;EDkBtE;;;AAGF;ECpBE;EACA,gBDoBiB;ECnBjB,WDmByB;EClBzB,iBAJkD;EAKlD,aALsE;EDuBtE;;;AJtCF;EACE,aMEW;;;ANEX;EAEE;EACA;EACA;;;ADIJ;AQhBA;EACE,aLDa;;AKIf;EFeE;EACA,gBEfiB;EFgBjB,WEhBsB;EFiBtB,iBEjB4B;EFkB5B,aALsE;EEZtE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKN;EACE;EACA;;AAGF;EACE;;AAGF;AAAA;AAAA;EAGE;;AAIA;EACE,YCnDM;;ADsDR;EACE;;AEvDJ;EACE;;ACAF;EACE;EACA;EACA;EACA;EACA;EACA,QJHc;EIId;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,OFxBM;;AE2BN;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,OFvDG;;AEyDH;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAMN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,QJrGc;EIsGd;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAKJ;EACE;EACA;;ACzHA;AAAA;AAAA;EAGE;EACA;EACA;EACA;;AAGF;EACE;;AAKJ;AAAA;AAAA;EAGE;EACA,cHVmB","file":"test.css"} \ No newline at end of file diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..9633520 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,9 @@ +{ + + "KIDSONBROOMS.EffectCreate": "Create Effect", + "KIDSONBROOMS.EffectToggle": "Toggle Effect", + "KIDSONBROOMS.EffectEdit": "Edit Effect", + "KIDSONBROOMS.EffectDelete": "Delete Effect", + + "KIDSONBROOMS.Add": "Add" +} \ No newline at end of file diff --git a/lib/some-lib/some-lib.css b/lib/some-lib/some-lib.css new file mode 100644 index 0000000..e69de29 diff --git a/lib/some-lib/some-lib.min.js b/lib/some-lib/some-lib.min.js new file mode 100644 index 0000000..e69de29 diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs new file mode 100644 index 0000000..491ac40 --- /dev/null +++ b/module/documents/actor.mjs @@ -0,0 +1,59 @@ +/** + * Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system. + * @extends {Actor} + */ +export class KidsOnBroomsActor extends Actor { + + /** + * Override getRollData() that's supplied to rolls. + */ + getRollData() { + let data = { ...this.system }; + + // Wand bonuses + data.wandBonus = { + wood: this._getWandBonus(this.system.wand.wood), + core: this._getWandBonus(this.system.wand.core) + }; + + return data; + } + + _getWandBonus(type) { + const bonuses = { + "Wisteria": { stat: "brains", bonus: 1 }, + "Hawthorn": { stat: "brains", bonus: 1 }, + "Pine": { stat: "brawn", bonus: 1 }, + "Oak": { stat: "brawn", bonus: 1 }, + "Crabapple": { stat: "fight", bonus: 1 }, + "Dogwood": { stat: "fight", bonus: 1 }, + "Birch": { stat: "flight", bonus: 1 }, + "Bamboo": { stat: "flight", bonus: 1 }, + "Ironwood": { stat: "grit", bonus: 1 }, + "Maple": { stat: "grit", bonus: 1 }, + "Lilac": { stat: "charm", bonus: 1 }, + "Cherry": { stat: "charm", bonus: 1 }, + "Parchment": { stat: "brains", bonus: 1 }, + "Phoenix Feather": { stat: "brains", bonus: 1 }, + "Owl Feather": { stat: "brains", bonus: 1 }, + "Gorilla Fur": { stat: "brawn", bonus: 1 }, + "Ogre’s Fingernail": { stat: "brawn", bonus: 1 }, + "Hippo’s Tooth": { stat: "brawn", bonus: 1 }, + "Dragon’s Heartstring": { stat: "fight", bonus: 1 }, + "Wolf’s Tooth": { stat: "fight", bonus: 1 }, + "Elk’s Antler": { stat: "fight", bonus: 1 }, + "Hawk’s Feather": { stat: "flight", bonus: 1 }, + "Bat’s Bone": { stat: "flight", bonus: 1 }, + "Changeling’s Hair": { stat: "charm", bonus: 1 }, + "Gold": { stat: "charm", bonus: 1 }, + "Mirror": { stat: "charm", bonus: 1 }, + "Steel": { stat: "grit", bonus: 1 }, + "Diamond": { stat: "grit", bonus: 1 }, + "Lion’s Mane": { stat: "grit", bonus: 1 } + }; + + return bonuses[type] || { stat: "", bonus: 0 }; + } + + +} \ No newline at end of file diff --git a/module/helpers/config.mjs b/module/helpers/config.mjs new file mode 100644 index 0000000..ef6007d --- /dev/null +++ b/module/helpers/config.mjs @@ -0,0 +1,7 @@ +export const KIDSONBROOMS = {}; + +// Define constants here, such as: +KIDSONBROOMS.foobar = { + 'bas': 'KIDSONBROOMS.bas', + 'bar': 'KIDSONBROOMS.bar' +}; \ No newline at end of file diff --git a/module/helpers/templates.mjs b/module/helpers/templates.mjs new file mode 100644 index 0000000..63debb1 --- /dev/null +++ b/module/helpers/templates.mjs @@ -0,0 +1,15 @@ +/** + * Define a set of template paths to pre-load + * Pre-loaded templates are compiled and cached for fast access when rendering + * @return {Promise} + */ + export const preloadHandlebarsTemplates = async function() { + return loadTemplates([ + + // Actor partials. + "systems/kids-on-brooms/templates/actor/parts/actor-features.html", + "systems/kids-on-brooms/templates/actor/parts/actor-adversity.html", + "systems/kids-on-brooms/templates/actor/parts/actor-stats.html", + "systems/kids-on-brooms/templates/actor/parts/actor-npc-stats.html", + ]); +}; diff --git a/module/kidsonbrooms.mjs b/module/kidsonbrooms.mjs new file mode 100644 index 0000000..eb69120 --- /dev/null +++ b/module/kidsonbrooms.mjs @@ -0,0 +1,333 @@ +// Import document classes. +import { KidsOnBroomsActor } from "./documents/actor.mjs"; + +// Import sheet classes. +import { KidsOnBroomsActorSheet } from "./sheets/actor-sheet.mjs"; + +// Import helper/utility classes and constants. +import { preloadHandlebarsTemplates } from "./helpers/templates.mjs"; +import { KIDSONBROOMS } from "./helpers/config.mjs"; + +/* -------------------------------------------- */ +/* Init Hook */ +/* -------------------------------------------- */ + +Hooks.once('init', async function() { + + // Register the helper + Handlebars.registerHelper('capitalizeFirst', function(string) { + if (typeof string === 'string') { + return string.charAt(0).toUpperCase() + string.slice(1); + } + return ''; + }); + + // Add utility classes and functions to the global game object so that they're more easily + // accessible in global contexts. + game.kidsonbrooms = { + KidsOnBroomsActor, + _onTakeAdversityToken: _onTakeAdversityToken, // Add the function to the global object + _onSpendAdversityTokens: _onSpendAdversityTokens // Add the function to the global object + }; + + // Add custom constants for configuration. + CONFIG.KIDSONBROOMS = KIDSONBROOMS; + + /** + * Set an initiative formula for the system + * @type {String} + */ + CONFIG.Combat.initiative = { + formula: "1d20", + decimals: 2 + }; + + // Define custom Document classes + CONFIG.Actor.documentClass = KidsOnBroomsActor; + + // Register sheet application classes + Actors.unregisterSheet("core", ActorSheet); + Actors.registerSheet("kids-on-brooms", KidsOnBroomsActorSheet, { makeDefault: true }); + + //If there is a new chat message that is a roll we add the adversity token controls + Hooks.on("renderChatMessage", (message, html, messageData) => { + const adversityControls = html.find('.adversity-controls'); + if (adversityControls.length > 0) { + const messageToEdit = adversityControls.data("roll-id"); + // Bind event listeners for the controls + adversityControls.find(".take-adversity").off("click").click((event) => { + + const actorId = event.currentTarget.dataset.actorId; + const actor = game.actors.get(actorId); + + // Check if the current user owns the actor - They can not claim if they are not + if (!actor.testUserPermission(game.user, "owner")) { + ui.notifications.warn("You don't own this character and cannot take adversity tokens."); + return; + } + + // Check if the token has already been claimed -- Contigency if the button somehow activates again + if (message.getFlag("kids-on-brooms", "tokenClaimed")) { + ui.notifications.warn("This adversity token has already been claimed."); + return; + } + + _onTakeAdversityToken(event, actor); + if (game.user.isGM) { + let tokenControls = game.messages.get(message.id); + console.log(tokenControls); + // Update the chat message content with the button disabled and text changed + const updatedContent = tokenControls.content.replace( + ``, + `` + ); + console.log("Removing Button"); + // Update the message content + tokenControls.update({ content: updatedContent }); + // Set the flag on the chat message to indicate that the token has been claimed + tokenControls.setFlag("kids-on-brooms", "tokenClaimed", true); + } else { + // Emit a socket request to update the message to show that the token has been claimed + game.socket.emit('system.kids-on-brooms', { + action: "takeToken", + messageID: message.id, + actorID: actor.id, + }); + } + console.log("Send socket message for taking a token"); + }); + + adversityControls.find(".spend-adversity").off("click").click((event) => { + //This entails a lot more, so I offloaded it to a new function + _onSpendAdversityTokens(event, messageToEdit); + }); + } + }); + + + + + + // Preload Handlebars templates. + return preloadHandlebarsTemplates(); +}); + +/*** + * This handles the incoming socket requests. + * If a player wants to spend tokens on another players roll the gm has to approve first + * if a player wants to claim a token we will update the message since they do not have the permissions + */ +Hooks.once('ready', function() { + game.socket.on('system.kids-on-brooms', async (data) => { + console.log("Socket data received:", data); + + if (data.action === "spendTokens") { + console.log(`Request to spend tokens: ${data.tokensToSpend} tokens for ${data.rollActorId} by ${data.spendingActorId}`); + + // Only handle the request if the GM is logged in + if (!game.user.isGM) { + console.log("Not GM, ignoring the token spend request."); + return; + } + + // The actor who made the roll + const rollActor = game.actors.get(data.rollActorId); + // The actor who is spending tokens + const spendingActor = game.actors.get(data.spendingActorId); + + //If these for some reason do not exist + if (!rollActor || !spendingActor) { + console.warn("Actor not found:", data.rollActorId, data.spendingActorId); + return; + } + + // Create a confirmation dialog for the GM + new Dialog({ + title: "Approve Adversity Token Spending?", + content: `

${spendingActor.name} wants to spend ${data.tokenCost} adversity tokens on ${rollActor.name}'s roll to increase it by ${data.tokensToSpend}. Approve?

`, + buttons: { + yes: { + label: "Yes", + callback: async () => { + + + const currentTokens = spendingActor.system.adversityTokens || 0; + + // Update the spending actor's adversity token count + await spendingActor.update({ "system.adversityTokens": currentTokens - data.tokenCost }); + + + // Modify the roll message with the new total + await _updateRollMessage(data.rollMessageId, data.tokensToSpend, false); + + console.log(`${spendingActor.name} spent ${data.tokensToSpend} tokens, updated roll total to ${roll.cumulativeTotal}`); + ui.notifications.info(`${spendingActor.name} successfully spent ${data.tokensToSpend} tokens.`); + } + }, + no: { + label: "No", + callback: () => { + ui.notifications.info(`The GM denied ${spendingActor.name}'s request to spend tokens.`); + } + } + }, + default: "yes" + }).render(true); + } else if (data.action === "takeToken") { + // Only handle the request if the GM is logged in + if (!game.user.isGM) { + console.log("Not GM, ignoring the token spend request."); + return; + } + let tokenControls = game.messages.get(data.messageID); + console.log(tokenControls); + // Update the chat message content with the button disabled and text changed + const updatedContent = tokenControls.content.replace( + ``, + `` + ); + console.log("Removing Button"); + // Update the message content + tokenControls.update({ content: updatedContent }); + // Set the flag on the chat message to indicate that the token has been claimed + tokenControls.setFlag("kids-on-brooms", "tokenClaimed", true); + } + }); +}); + +/*** + * This function adds the adversity token to the actor that made the roll and logs it + * + * @param {Event} e - The button click event + * @param {Actor} actor - The actor object that made the roll + */ +async function _onTakeAdversityToken(e, actor) { + e.preventDefault(); + + + // Get the chat message ID (assuming it's stored in the dataset) + const messageId = e.currentTarget.closest('.message').dataset.messageId; + const message = game.messages.get(messageId); + + // Add an adversity token to the actor + const currentTokens = actor.system.adversityTokens || 0; + await actor.update({ "system.adversityTokens": currentTokens + 1 }); + + + // Notify the user + ui.notifications.info(`You gained 1 adversity token.`); + console.log(`Gave one adversity token to ${actor.id}`) +} + +/*** + * This function allows players to spend tokens to change a roll. This will automatically be calculated in their sheet + * + */ +async function _onSpendAdversityTokens(e, rollMessageId) { + e.preventDefault(); + + // The actor who made the roll + const rollActorId = e.currentTarget.dataset.actorId; + const rollActor = game.actors.get(rollActorId); //technically redundant since it is also done in the main hook, but perfomance is good enuff + + // Get the actor of the player who is spending tokens + const spendingPlayerActor = game.actors.get(game.user.character?.id || game.actors.filter(actor => actor.testUserPermission(game.user, "owner"))[0]?.id); + + if (!spendingPlayerActor) { + ui.notifications.warn("You don't control any actors."); + return; + } + + //Get the tokens to be spend from the input field + const tokenInput = $(e.currentTarget).closest('.adversity-controls').find('.token-input').val(); + const tokensToSpend = parseInt(tokenInput, 10); + + if (isNaN(tokensToSpend) || tokensToSpend <= 0) { + ui.notifications.warn("Please enter a valid number of tokens."); + return; + } + + let tokenCost = tokensToSpend; + + // If the player spending tokens is not the owner of the actor who rolled, they spend double + //(note, this is a rule of mine, I have disabled it by default) + if ((!spendingPlayerActor.testUserPermission(game.user, "owner") || spendingPlayerActor.id !== rollActorId) && false) { + tokenCost = tokensToSpend * 2; + } + + // Ensure the spending actor has enough adversity tokens + if (spendingPlayerActor.system.adversityTokens < tokenCost) { + ui.notifications.warn(`You do not have enough adversity tokens.`); + return; + } + + // Check if the player owns the actor who made the roll + if (spendingPlayerActor.id === rollActorId) { + // The player owns the actor, so they can spend tokens directly without GM approval + const currentTokens = spendingPlayerActor.system.adversityTokens || 0; + + // Deduct the tokens from the player + await spendingPlayerActor.update({ "system.adversityTokens": currentTokens - tokenCost }); + + // Modify the roll message with the new total + await _updateRollMessage(rollMessageId, tokensToSpend, true); + + } else { + // The player does not own the actor, so request GM approval to spend the tokens + console.log(`Requesting to spend ${tokensToSpend} tokens for ${rollActor.name} by ${spendingPlayerActor.name} (cost: ${tokenCost})`); + + // Emit a socket request to spend tokens + game.socket.emit('system.kids-on-brooms', { + action: "spendTokens", + rollActorId: rollActorId, + spendingActorId: spendingPlayerActor.id, // Send the player's actor who is spending the tokens + tokensToSpend: tokensToSpend, + tokenCost: tokenCost, + rollMessageId: rollMessageId // Pass message ID to update the roll result + }); + + ui.notifications.info(`Requested to spend ${tokenCost} tokens for ${rollActor.name}`); + } +} + +// Helper function to send a new message with the updated roll result +async function _updateRollMessage(rollMessageId, tokensToSpend, isPlayerOfActor) { + const message = game.messages.get(rollMessageId); + + if (!message) { + console.error("Message not found with ID:", rollMessageId); + return; + } + + // Retrieve current tokens spent from flags, or initialize to 0 if not found + let cumulativeTokensSpent = message.getFlag("kids-on-brooms", "tokensSpent") || 0; + let newTotal = message.getFlag("kids-on-brooms", "newRollTotal") || message.rolls[0].total; + + /*if(isPlayerOfActor) + { + // Add the new tokens to the cumulative total + cumulativeTokensSpent += tokensToSpend; + } else { + cumulativeTokensSpent += 2*tokensToSpend; + }*/ + cumulativeTokensSpent += tokensToSpend; + newTotal += tokensToSpend; + await message.setFlag("kids-on-brooms", "newRollTotal", newTotal); + + // Update the message's flags to store the cumulative tokens spent + await message.setFlag("kids-on-brooms", "tokensSpent", cumulativeTokensSpent); + let newContent = ""; + if(cumulativeTokensSpent === 1) + { + newContent = `You have now spent ${cumulativeTokensSpent} token. The new roll total is ${newTotal}.`; + } else { + newContent = `You have now spent ${cumulativeTokensSpent} tokens. The new roll total is ${newTotal}.`; + } + + // Create a new chat message to display the updated total + await ChatMessage.create({ + speaker: ChatMessage.getSpeaker({ actor: message.speaker.actor }), + content: newContent, + type: CONST.CHAT_MESSAGE_STYLES.OTHER, + }); +} \ No newline at end of file diff --git a/module/sheets/actor-sheet.mjs b/module/sheets/actor-sheet.mjs new file mode 100644 index 0000000..021f9d9 --- /dev/null +++ b/module/sheets/actor-sheet.mjs @@ -0,0 +1,147 @@ +/** + * Extend the basic ActorSheet with some very simple modifications + * @extends {ActorSheet} + */ +export class KidsOnBroomsActorSheet extends ActorSheet { + + /** @override */ + static get defaultOptions() + { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["kids-on-brooms", "sheet", "actor"], + width: 800, + height: 800, + tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "features" }] + }); + } + + /** @override */ + get template() + { + console.log("template", this.actor) + return `systems/kids-on-brooms/templates/actor/actor-${this.actor.type}-sheet.html`; + } + + /* -------------------------------------------- */ + + /** @override */ + /** @override */ +async getData() +{ + // Retrieve the data structure from the base sheet. + const context = super.getData(); + + // Use a safe clone of the actor data for further operations. + const actorData = this.document.toObject(false); + + // Add the actor's data to context.data for easier access, as well as flags. + context.system = actorData.system; + context.flags = actorData.flags; + + // Add roll data for TinyMCE editors. + context.rollData = context.actor.getRollData(); + // Add roll data for TinyMCE editors. + context.rollData = context.actor.getRollData(); + + + console.log(context); + return context; +} + + /* -------------------------------------------- */ + + /** @override */ + activateListeners(html) + { + super.activateListeners(html); + // ------------------------------------------------------------- + // Everything below here is only needed if the sheet is editable + if (!this.isEditable) return; + + // Rollable abilities. + html.find('.rollable').click(this._onRoll.bind(this)); + + //If the user changes their wand material save that + html.find('select[name="system.wand.wood"]').change(event => { + const value = event.target.value; + this.actor.update({ "system.wand.wood": value }); + }); + + html.find('select[name="system.wand.core"]').change(event => { + const value = event.target.value; + this.actor.update({ "system.wand.core": value }); + }); +} + + /** + * Handle clickable rolls. + * @param {Event} event The originating click event + * @private + */ + async _onRoll(e) { + e.preventDefault(); + const element = e.currentTarget; + const dataset = element.dataset; + + // Handle rolls that supply the formula directly + if (dataset.roll) { + let label = dataset.label ? `${dataset.label}` : ''; + // Get the roll data and include wand bonuses + let rollData = this.actor.getRollData(); + let totalBonus = 0; + console.log(dataset.roll); + // Apply wood bonus if it matches the stat being rolled for + if (rollData.wandBonus.wood.stat === dataset.key) { + totalBonus += rollData.wandBonus.wood.bonus; + } + + // Apply core bonus if it matches the stat being rolled for AND it's different from the wood bonus + if (rollData.wandBonus.core.stat === dataset.key && rollData.wandBonus.core.stat !== rollData.wandBonus.wood.stat) { + totalBonus += rollData.wandBonus.core.bonus; + } + let rollFormula = dataset.roll + `+${totalBonus}`; + let roll = new Roll(rollFormula, rollData); + console.log(rollFormula); + console.log(rollData); + + // Send the roll message to chat + const rollMessage = await roll.toMessage({ + speaker: ChatMessage.getSpeaker({ actor: this.actor }), + flavor: label, + rollMode: game.settings.get('core', 'rollMode'), + }) + + // Now send the follow-up message with the adversity controls + await this._sendAdversityControlsMessage(this.actor.id, rollMessage.id); + + return roll; + } + } + + //This just sends the buttons for the adversity token system + async _sendAdversityControlsMessage(actorId, rollMessageId) { + // Create the content for the adversity controls + const adversityControlsHtml = this._createAdversityControls(actorId, rollMessageId); + + // Send the adversity controls as a follow-up message + const controlMessage = await ChatMessage.create({ + speaker: ChatMessage.getSpeaker({ actor: this.actor }), + content: adversityControlsHtml, + }); + + return controlMessage; + } + + // Create HTML content for adversity controls + _createAdversityControls(actorId, rollMessageId) { + return ` +
+ + + +
+ `; + } + + +} diff --git a/scss/components/_effects.scss b/scss/components/_effects.scss new file mode 100644 index 0000000..3718ab9 --- /dev/null +++ b/scss/components/_effects.scss @@ -0,0 +1,22 @@ +.effects .item { + .effect-source, + .effect-duration, + .effect-controls { + text-align: center; + border-left: 1px solid #c9c7b8; + border-right: 1px solid #c9c7b8; + font-size: 12px; + } + + .effect-controls { + border: none; + } +} + +// _effects.scss +.kids-on-brooms input:focus, +.kids-on-brooms textarea:focus, +.kids-on-brooms select:focus { + outline: none; + border-color: $focus-border-color; +} \ No newline at end of file diff --git a/scss/components/_forms.scss b/scss/components/_forms.scss new file mode 100644 index 0000000..b84222b --- /dev/null +++ b/scss/components/_forms.scss @@ -0,0 +1,66 @@ +.item-form { + font-family: $font-primary; +} + +.sheet-header { + @include flexbox(row, wrap, flex-start); // Use a mixin for flexbox + flex: 0 auto; + overflow: hidden; + margin-bottom: 10px; + + .profile-img { + flex: 0 0 100px; + height: 100px; + margin-right: 10px; + } + + .header-fields { + flex: 1; + } + + h1.charname { + height: 50px; + padding: 0; + margin: 5px 0; + border-bottom: 0; + + input { + width: 100%; + height: 100%; + margin: 0; + } + } +} + +div.editor-border { + border: 2px solid $primary-border-color; // Replace the hardcoded color with a variable + border-radius: 10px; +} + +.sheet-tabs { + flex: 0; +} + +.sheet-body, +.sheet-body .tab, +.sheet-body .tab .editor { + height: 100%; +} + +.tox { + .tox-editor-container { + background: $c-white; + } + + .tox-edit-area { + padding: 0 8px; + } +} + +// Flexbox container for each row +.selection-row { + display: flex; + justify-content: space-between; // Ensures label stays left and input moves to the right + align-items: center; // Vertically center the label and input + margin-bottom: 10px; // Optional: Add some space between rows +} \ No newline at end of file diff --git a/scss/components/_items.scss b/scss/components/_items.scss new file mode 100644 index 0000000..b8f888d --- /dev/null +++ b/scss/components/_items.scss @@ -0,0 +1,124 @@ +// Section Header +.items-header { + height: 28px; + margin: 2px 0; + padding: 0; + align-items: center; + background: rgba(0, 0, 0, 0.05); + border: $border-groove; + font-weight: bold; + + > * { + font-size: 14px; + text-align: center; + } + + .item-name { + font-weight: bold; + padding-left: 5px; + text-align: left; + display: flex; + } +} + +// Items Lists +.items-list { + list-style: none; + margin: 0; + padding: 0; + overflow-y: auto; + scrollbar-width: thin; + color: $c-tan; + + // Child lists + .item-list { + list-style: none; + margin: 0; + padding: 0; + } + + // Individual Item + .item { + display: flex; + align-items: center; + padding: 0 2px; // Align with the header border + border-bottom: 1px solid $c-faint; + + &:last-child { + border-bottom: none; + } + + // Item name and image + .item-name { + flex: 2; + margin: 0; + overflow: hidden; + font-size: 13px; + text-align: left; + display: flex; + color: $c-dark; + + h3, h4 { + margin: 0; + white-space: nowrap; + overflow-x: hidden; + } + + .item-image { + flex: 0 0 30px; + height: 30px; + background-size: 30px; + border: none; + margin-right: 5px; + } + } + } + + // Control Buttons + .item-controls { + display: flex; + flex: 0 0 100px; + justify-content: flex-end; + + a { + font-size: 12px; + text-align: center; + margin: 0 6px; + } + } + + // Item Properties (like stats or details) + .item-prop { + text-align: center; + border-left: 1px solid $c-faint; + border-right: 1px solid $c-faint; + font-size: 12px; + } +} + +// Section Header inside Items List +.items-list .items-header { + height: 28px; + margin: 2px 0; + padding: 0; + align-items: center; + background: rgba(0, 0, 0, 0.05); + border: $border-groove; + font-weight: bold; + + > * { + font-size: 12px; + text-align: center; + } + + .item-name { + padding-left: 5px; + text-align: left; + } +} + +// Optional item formula block +.item-formula { + flex: 0 0 200px; + padding: 0 8px; +} \ No newline at end of file diff --git a/scss/components/_resource.scss b/scss/components/_resource.scss new file mode 100644 index 0000000..5c9b910 --- /dev/null +++ b/scss/components/_resource.scss @@ -0,0 +1,3 @@ +.resource-label { + font-weight: bold; +} \ No newline at end of file diff --git a/scss/global/_base.scss b/scss/global/_base.scss new file mode 100644 index 0000000..fac4e42 --- /dev/null +++ b/scss/global/_base.scss @@ -0,0 +1,15 @@ +// _base.scss +@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"); + +.window-app { + font-family: $font-stack; +} + +.rollable { + &:hover, + &:focus { + color: #000; + text-shadow: $hover-text-shadow; + cursor: pointer; + } +} \ No newline at end of file diff --git a/scss/global/_flex.scss b/scss/global/_flex.scss new file mode 100644 index 0000000..8431b73 --- /dev/null +++ b/scss/global/_flex.scss @@ -0,0 +1,50 @@ +// Flexbox Utility Classes +.flex-group-center { + @include flexbox(center, center); + text-align: center; +} + +.flex-group-left { + @include flexbox(flex-start, center); + text-align: left; +} + +.flex-group-right { + @include flexbox(flex-end, center); + text-align: right; +} + +.flexshrink { + flex: 0; +} + +.flex-between { + justify-content: space-between; +} + +.flexlarge { + flex: 2; +} + +// Alignment Utility Classes +.align-left { + @include flexbox(flex-start, center); + text-align: left; +} + +.align-right { + @include flexbox(flex-end, center); + text-align: right; +} + +.align-center { + @include flexbox(center, center); + text-align: center; +} + +// Only apply the right alignment to specific inputs with this class +.right-align-input { + flex: 1; + margin-left: auto; // Push the input to the far right + max-width: 260px; // Optional: Control the width of the input field +} \ No newline at end of file diff --git a/scss/global/_grid.scss b/scss/global/_grid.scss new file mode 100644 index 0000000..dabc6cc --- /dev/null +++ b/scss/global/_grid.scss @@ -0,0 +1,25 @@ +// _grid.scss +.grid { + display: grid; + gap: 10px; + margin: 10px 0; + padding: 0; +} + +@for $i from 2 through 12 { + // Create grid-start-* classes for offsets + .grid-start-#{$i} { + grid-column-start: #{$i}; + } + + // Create grid-span-* classes for column spans + .grid-span-#{$i} { + grid-column-end: span #{$i}; + } + + // Create grid-*col classes for grid columns + .grid-#{$i}col { + grid-column: span #{$i} / span #{$i}; + grid-template-columns: repeat(#{$i}, minmax(0, 1fr)); + } +} diff --git a/scss/global/_window.scss b/scss/global/_window.scss new file mode 100644 index 0000000..e0b31a2 --- /dev/null +++ b/scss/global/_window.scss @@ -0,0 +1,12 @@ +.window-app { + font-family: $font-primary; +} + +.rollable { + &:hover, + &:focus { + color: #000; + text-shadow: 0 0 10px rgb(146, 0, 225); + cursor: pointer; + } +} \ No newline at end of file diff --git a/scss/kidsonbrooms.scss b/scss/kidsonbrooms.scss new file mode 100644 index 0000000..403c967 --- /dev/null +++ b/scss/kidsonbrooms.scss @@ -0,0 +1,23 @@ +// Add custom fonts by visiting and search https://fonts.google.com +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); +// This is the font used for the book, will not buy it but for refrence https://www.myfonts.com/collections/dreadful-font-aiyari?queryId=undefined&index=universal_search_data&objectIDs=5368854002 +// This is the font used for text https://www.myfonts.com/products/lapidary-333-lapidary-333-434881?queryId=undefined&index=universal_search_data&objectIDs=5468003002 +// Import utilities. +@import 'utils/variables'; +@import 'utils/typography'; +@import 'utils/colors'; +@import 'utils/mixins'; + +/* Global styles */ +@import 'global/window'; +@import 'global/grid'; +@import 'global/flex'; +@import 'global/base'; + +/* Styles limited to kidsonbrooms sheets */ +.kids-on-brooms { + @import 'components/forms'; + @import 'components/resource'; + @import 'components/items'; + @import 'components/effects'; +} diff --git a/scss/utils/_colors.scss b/scss/utils/_colors.scss new file mode 100644 index 0000000..90a3488 --- /dev/null +++ b/scss/utils/_colors.scss @@ -0,0 +1,13 @@ +$c-white: #fff; +$c-black: #000; + +$c-dark: #191813; +$c-faint: #c9c7b8; +$c-beige: #b5b3a4; +$c-tan: #444; + +$primary-border-color: rgb(81, 81, 81); + +$focus-border-color: #8102dd; +$hover-text-shadow: 0 0 10px rgb(179, 7, 217); +$border-color: #ccc; \ No newline at end of file diff --git a/scss/utils/_mixins.scss b/scss/utils/_mixins.scss new file mode 100644 index 0000000..73e09ae --- /dev/null +++ b/scss/utils/_mixins.scss @@ -0,0 +1,25 @@ +@mixin element-invisible { + position: absolute; + + width: 1px; + height: 1px; + margin: -1px; + border: 0; + padding: 0; + + clip: rect(0 0 0 0); + overflow: hidden; +} + +@mixin hide { + display: none; +} + +// Update the mixin to accept 3 arguments: direction, wrap, and justify +@mixin flexbox($direction, $wrap: nowrap, $justify: flex-start, $align: stretch) { + display: flex; + flex-direction: $direction; + flex-wrap: $wrap; + justify-content: $justify; + align-items: $align; +} \ No newline at end of file diff --git a/scss/utils/_typography.scss b/scss/utils/_typography.scss new file mode 100644 index 0000000..0c44234 --- /dev/null +++ b/scss/utils/_typography.scss @@ -0,0 +1,2 @@ +$font-primary: 'Roboto', sans-serif; +$font-secondary: 'Roboto', sans-serif; \ No newline at end of file diff --git a/scss/utils/_variables.scss b/scss/utils/_variables.scss new file mode 100644 index 0000000..5a4abb8 --- /dev/null +++ b/scss/utils/_variables.scss @@ -0,0 +1,10 @@ +$padding-sm: 5px; +$padding-md: 10px; +$padding-lg: 20px; + +$border-groove: 2px groove #eeede0; + +$font-stack: "Roboto", sans-serif; +$padding: 10px; +$border-radius: 5px; +$flex-align: center; diff --git a/templates/actor/actor-character-sheet.html b/templates/actor/actor-character-sheet.html new file mode 100644 index 0000000..f549c89 --- /dev/null +++ b/templates/actor/actor-character-sheet.html @@ -0,0 +1,63 @@ +
+ {{!-- Sheet Header --}} +
+ +
+

+ +
+
+ +
+ +
+
+
+
+
+ + {{!-- Sheet Tab Navigation --}} + + + {{!-- Sheet Body --}} +
+ + {{!-- Owned Features Tab --}} +
+
+
+ {{> "systems/kids-on-brooms/templates/actor/parts/actor-features.html"}} + {{> "systems/kids-on-brooms/templates/actor/parts/actor-adversity.html"}} +
+ + + +
+
+ {{!-- Schoolbag Tab --}} +
+ {{editor schoolbag target="system.schoolbag" engine="prosemirror" button=false collaborate=false editable=true}} +
+ + {{!-- Strengths Tab --}} +
+ {{editor strengths target="system.strengths" engine="prosemirror" button=false collaborate=false editable=true}} +
+ + {{!-- Trope Questions Tab --}} +
+ {{editor tropequestions target="system.tropequestions" engine="prosemirror" button=false collaborate=false editable=true}} +
+ +
+
+ diff --git a/templates/actor/actor-npc-sheet.html b/templates/actor/actor-npc-sheet.html new file mode 100644 index 0000000..eb8c83c --- /dev/null +++ b/templates/actor/actor-npc-sheet.html @@ -0,0 +1,35 @@ +
+ + {{!-- Sheet Header --}} +
+ +
+

+ +
+
+ +
+ +
+
+
+
+
+ + {{!-- Sheet Tab Navigation --}} + + + {{!-- Sheet Body --}} +
+ + {{!-- Owned Features Tab --}} +
+ {{> "systems/kids-on-brooms/templates/actor/parts/actor-npc-stats.html"}} +
+
+
+ diff --git a/templates/actor/parts/actor-adversity.html b/templates/actor/parts/actor-adversity.html new file mode 100644 index 0000000..73b7e92 --- /dev/null +++ b/templates/actor/parts/actor-adversity.html @@ -0,0 +1,11 @@ +
+ Adversity Tokens +
+ + +
+
\ No newline at end of file diff --git a/templates/actor/parts/actor-features.html b/templates/actor/parts/actor-features.html new file mode 100644 index 0000000..2a3d936 --- /dev/null +++ b/templates/actor/parts/actor-features.html @@ -0,0 +1,156 @@ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ Your Broom + + +
+ + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+ + +
+ +
+ + +
+ Wand Selection + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ Animal Familiar +
+ + +
+
+ +
\ No newline at end of file diff --git a/templates/actor/parts/actor-npc-stats.html b/templates/actor/parts/actor-npc-stats.html new file mode 100644 index 0000000..79b648b --- /dev/null +++ b/templates/actor/parts/actor-npc-stats.html @@ -0,0 +1,29 @@ +
+ {{#each system.stats as |stat key|}} +
+ {{key}} + +
+
+ Stat + + +
+
+ Magic + + +
+
+
+ {{/each}} +
\ No newline at end of file diff --git a/templates/actor/parts/actor-stats.html b/templates/actor/parts/actor-stats.html new file mode 100644 index 0000000..9541059 --- /dev/null +++ b/templates/actor/parts/actor-stats.html @@ -0,0 +1,37 @@ +
+ {{#each system.stats as |stat key|}} +
+ {{capitalizeFirst key}} +
+ + + + +
+ Stat + + + + +
+ + +
+ Magic + + + +
+ +
+ +
+ {{/each}} +