diff --git a/under_the_neighborhood/.vscode/settings.json b/under_the_neighborhood/.vscode/settings.json new file mode 100644 index 0000000000..d88db28e20 --- /dev/null +++ b/under_the_neighborhood/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.autoClosingBrackets": "always", + "editor.fontFamily": "Fira Code", + "editor.fontLigatures": true +} \ No newline at end of file diff --git a/under_the_neighborhood/README.md b/under_the_neighborhood/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/under_the_neighborhood/prepros.config b/under_the_neighborhood/prepros.config new file mode 100644 index 0000000000..4f4e8a67b9 --- /dev/null +++ b/under_the_neighborhood/prepros.config @@ -0,0 +1,946 @@ +{ + "version": "7", + "about": "This is a Prepros (https://prepros.io) configuration file. You can commit this file to a git repo to backup and sync project configurations.", + "config": { + "proxy": { + "enable": false, + "target": "", + "useLocalAssets": false + }, + "reload": { + "enable": true, + "delay": 0, + "animate": true, + "afterUpload": false + }, + "sync": { + "enable": false, + "mouse": true, + "keyboard": true, + "form": true, + "scroll": true + }, + "watcher": { + "enable": true, + "maxFiles": 2000, + "usePolling": true, + "pollingInterval": 500, + "extensions": [ + ".html", + ".htm", + ".php" + ], + "ignore": { + "patterns": [ + ".*", + "wp-admin", + "wp-includes", + "node_modules", + "Prepros Export", + "bower_components" + ], + "exceptions": [] + } + }, + "exporter": { + "ignore": { + "patterns": [ + ".*", + "desktop.ini", + "prepros.cfg", + "node_modules", + "Prepros Export", + "prepros.config", + "prepros-6.config", + "*-original.jpg", + "*-original.jpeg", + "*-original.png", + "*-original.svg", + "*.scss", + "*.sass", + "*.less", + "*.pug", + "*.jade", + "*.styl", + "*.haml", + "*.slim", + "*.coffee", + "*.kit", + "*.turf", + "*.ts" + ], + "exceptions": [] + } + }, + "uploader": { + "remotePath": "", + "timeout": 20000, + "autoUpload": false, + "reuseConnection": true, + "connectionType": "ftp", + "history": [] + }, + "packages": { + "createPackageLock": true + }, + "images": { + "preserveOriginal": true + }, + "tasks": { + "autoprefixer": { + "cascade": true, + "add": true, + "remove": true, + "supports": true, + "flexbox": true, + "grid": "autoplace", + "browsers": [ + "last 2 versions" + ], + "sourceMap": false + }, + "babel": { + "sourceMap": false, + "presets": { + "@babel/preset-env": { + "enable": true, + "options": { + "targets": [ + "last 2 versions" + ], + "preserveImports": false, + "polyfills": false + } + }, + "@babel/preset-react": true, + "@babel/preset-flow": false + }, + "plugins": { + "@babel/plugin-proposal-class-static-block": false, + "@babel/plugin-proposal-class-properties": false, + "@babel/plugin-proposal-decorators": { + "enable": false, + "options": { + "decoratorsBeforeExport": true + } + }, + "@babel/plugin-proposal-export-namespace-from": false, + "@babel/plugin-proposal-function-sent": false, + "@babel/plugin-proposal-logical-assignment-operators": false, + "@babel/plugin-proposal-nullish-coalescing-operator": false, + "@babel/plugin-proposal-numeric-separator": false, + "@babel/plugin-proposal-optional-chaining": false, + "@babel/plugin-proposal-private-methods": false, + "@babel/plugin-proposal-throw-expressions": false + }, + "customPresets": [], + "customPlugins": [] + }, + "bundle-js": { + "sourceMap": false, + "exclude": [ + "node_modules", + "bower_components" + ], + "devMode": true, + "globals": [], + "externals": [], + "babel": { + "enable": true, + "options": { + "sourceMap": false, + "presets": { + "@babel/preset-env": { + "enable": true, + "options": { + "targets": [ + "last 2 versions" + ], + "preserveImports": false, + "polyfills": false + } + }, + "@babel/preset-react": true, + "@babel/preset-flow": false + }, + "plugins": { + "@babel/plugin-proposal-class-static-block": false, + "@babel/plugin-proposal-class-properties": false, + "@babel/plugin-proposal-decorators": { + "enable": false, + "options": { + "decoratorsBeforeExport": true + } + }, + "@babel/plugin-proposal-export-namespace-from": false, + "@babel/plugin-proposal-function-sent": false, + "@babel/plugin-proposal-logical-assignment-operators": false, + "@babel/plugin-proposal-nullish-coalescing-operator": false, + "@babel/plugin-proposal-numeric-separator": false, + "@babel/plugin-proposal-optional-chaining": false, + "@babel/plugin-proposal-private-methods": false, + "@babel/plugin-proposal-throw-expressions": false + }, + "customPresets": [], + "customPlugins": [] + } + }, + "css": { + "enable": true + }, + "fonts": { + "enable": true + } + }, + "coffeescript": { + "header": false, + "bare": false, + "sourceMap": false + }, + "command": { + "command": "", + "rootDir": "" + }, + "concat-js": { + "sourceMap": false, + "rootDir": "" + }, + "copy": { + "sourceMap": false + }, + "dart-sass": { + "indentType": "space", + "allowWildcardImports": false, + "indentWidth": 2, + "linefeed": "lf", + "sourceMap": false + }, + "haml": { + "doubleQuoteAttributes": true + }, + "jpg": { + "quality": 90 + }, + "less": { + "javascriptEnabled": false, + "strictImports": false, + "insecure": false, + "math": "always", + "strictUnits": false, + "dumpLineNumbers": false, + "sourceMap": false + }, + "markdown": { + "githubFlavored": true, + "wrapWithHtml": false + }, + "minify-css": { + "sourceMap": false + }, + "minify-html": { + "caseSensitive": false, + "collapseBooleanAttributes": true, + "collapseInlineTagWhitespace": false, + "collapseWhitespace": true, + "conservativeCollapse": false, + "decodeEntities": false, + "html5": true, + "includeAutoGeneratedTags": true, + "keepClosingSlash": false, + "minifyCSS": true, + "minifyJS": true, + "preserveLineBreaks": false, + "preventAttributesEscaping": false, + "processConditionalComments": false, + "removeAttributeQuotes": false, + "removeComments": true, + "removeEmptyAttributes": false, + "removeEmptyElement": false, + "removeOptionalTags": false, + "removeRedundantAttributes": false, + "removeScriptTypeAttributes": false, + "removeStyleLinkTypeAttributes": false, + "removeTagWhitespace": false, + "sortAttributes": false, + "sortClassName": false, + "useShortDoctype": true + }, + "minify-js": { + "parse": { + "bare_returns": false + }, + "compress": { + "arrows": true, + "arguments": false, + "booleans": true, + "booleans_as_integers": false, + "collapse_vars": true, + "comparisons": true, + "computed_props": true, + "conditionals": true, + "dead_code": true, + "directives": true, + "drop_console": false, + "drop_debugger": true, + "evaluate": true, + "expression": false, + "global_defs": [], + "hoist_funs": false, + "hoist_props": true, + "hoist_vars": false, + "if_return": true, + "inline": 3, + "join_vars": true, + "keep_fargs": true, + "keep_infinity": false, + "loops": true, + "negate_iife": true, + "properties": true, + "pure_funcs": [], + "pure_getters": false, + "reduce_funcs": true, + "reduce_vars": true, + "sequences": true, + "side_effects": true, + "switches": true, + "top_retain": [], + "typeofs": true, + "unsafe": false, + "unsafe_arrows": false, + "unsafe_comps": false, + "unsafe_Function": false, + "unsafe_math": false, + "unsafe_proto": false, + "unsafe_regexp": false, + "unsafe_undefined": false, + "unused": true + }, + "mangle": { + "eval": false, + "reserved": [] + }, + "output": { + "ascii_only": false, + "braces": false, + "comments": "none", + "inline_script": true, + "keep_numbers": false, + "keep_quoted_props": false, + "preamble": null, + "quote_keys": false, + "quote_style": 0, + "semicolons": true, + "shebang": true, + "webkit": false, + "wrap_iife": false, + "wrap_func_args": true + }, + "sourceMap": false, + "toplevel": false, + "ie8": false, + "keep_classnames": false, + "keep_fnames": false, + "safari10": false + }, + "node-sass": { + "indentType": "space", + "allowWildcardImports": false, + "indentWidth": 2, + "linefeed": "lf", + "outputStyle": "expanded", + "precision": 10, + "sourceMap": false, + "sourceComments": false + }, + "png": { + "quality": 90 + }, + "postcss-import": { + "ignoreKeywords": [], + "sourceMap": false + }, + "postcss-preset-env": { + "stage": 2, + "browsers": [ + "last 2 versions" + ], + "sourceMap": false + }, + "pug": { + "pretty": true + }, + "slim": { + "indent": "space", + "indentSize": 2, + "pretty": true + }, + "stylus": { + "useNib": true, + "sourceMap": false, + "linenos": false + }, + "svg": { + "cleanupAttrs": true, + "removeDoctype": true, + "removeXMLProcInst": true, + "removeComments": true, + "removeMetadata": true, + "removeTitle": true, + "removeDesc": true, + "removeUselessDefs": true, + "removeEditorsNSData": true, + "removeEmptyAttrs": true, + "removeHiddenElems": true, + "removeEmptyText": true, + "removeEmptyContainers": true, + "removeViewBox": false, + "cleanupEnableBackground": true, + "convertStyleToAttrs": true, + "convertColors": true, + "convertPathData": true, + "convertTransform": true, + "removeUnknownsAndDefaults": true, + "removeNonInheritableGroupAttrs": true, + "removeUselessStrokeAndFill": true, + "removeUnusedNS": true, + "cleanupIDs": true, + "cleanupNumericValues": true, + "moveElemsAttrsToGroup": true, + "moveGroupAttrsToElems": true, + "collapseGroups": true, + "removeRasterImages": false, + "mergePaths": true, + "convertShapeToPath": true, + "sortAttrs": true, + "removeDimensions": true + }, + "tailwindcss": { + "rootDir": "", + "content": [ + "**/*.html", + "**/*.php", + "**/*.js", + "!wp-admin", + "!wp-includes", + "!node_modules", + "!Prepros Export" + ] + }, + "turf": { + "rootDir": "" + }, + "typescript": { + "allowJs": false, + "allowSyntheticDefaultImports": true, + "allowUmdGlobalAccess": false, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "alwaysStrict": false, + "charset": "utf8", + "checkJs": false, + "declaration": false, + "disableSizeLimit": false, + "downlevelIteration": false, + "emitBOM": false, + "emitDecoratorMetadata": false, + "experimentalDecorators": false, + "forceConsistentCasingInFileNames": false, + "importHelpers": false, + "jsx": "React", + "keyofStringsOnly": false, + "lib": [], + "maxNodeModuleJsDepth": 0, + "module": "ES2015", + "moduleResolution": "NodeJs", + "newLine": "LineFeed", + "noFallthroughCasesInSwitch": false, + "noImplicitAny": false, + "noImplicitReturns": false, + "noImplicitThis": false, + "noStrictGenericChecks": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitUseStrict": false, + "noLib": false, + "noResolve": false, + "preserveConstEnums": false, + "jsxFactory": "React.createElement", + "removeComments": false, + "skipLibCheck": false, + "sourceMap": false, + "strict": false, + "strictFunctionTypes": false, + "strictBindCallApply": false, + "strictNullChecks": false, + "strictPropertyInitialization": false, + "suppressExcessPropertyErrors": false, + "suppressImplicitAnyIndexErrors": false, + "target": "ES3", + "resolveJsonModule": false, + "esModuleInterop": false, + "useDefineForClassFields": false + } + }, + "fileTypes": { + "sass": { + "extensions": [ + ".scss", + ".sass" + ], + "autoCompile": true, + "sourceMap": false, + "tasks": [ + { + "task": "dart-sass", + "enable": true + }, + { + "task": "autoprefixer", + "enable": false + }, + { + "task": "minify-css", + "enable": false + } + ], + "output": { + "extension": ".css", + "type": "SOURCE_RELATIVE", + "segments": [ + { + "segment": "scss", + "replaceWith": "css" + }, + { + "segment": "sass", + "replaceWith": "css" + } + ] + } + }, + "less": { + "extensions": [ + ".less" + ], + "autoCompile": true, + "sourceMap": false, + "tasks": [ + { + "task": "less", + "enable": true + }, + { + "task": "autoprefixer", + "enable": true + }, + { + "task": "minify-css", + "enable": false + } + ], + "output": { + "extension": ".css", + "type": "REPLACE_SEGMENTS", + "segments": [ + { + "segment": "less", + "replaceWith": "css" + } + ] + } + }, + "pug": { + "extensions": [ + ".pug", + ".jade" + ], + "autoCompile": true, + "tasks": [ + { + "task": "pug", + "enable": true + }, + { + "task": "minify-html", + "enable": false + } + ], + "output": { + "extension": ".html", + "type": "SOURCE_RELATIVE", + "segments": [ + { + "segment": "pug", + "replaceWith": "html" + } + ] + } + }, + "css": { + "extensions": [ + ".css" + ], + "autoCompile": false, + "sourceMap": false, + "tasks": [ + { + "task": "copy", + "enable": true + }, + { + "task": "tailwindcss", + "enable": false + }, + { + "task": "postcss-import", + "enable": false + }, + { + "task": "postcss-preset-env", + "enable": false + }, + { + "task": "autoprefixer", + "enable": false + }, + { + "task": "minify-css", + "enable": false + } + ], + "output": { + "extension": ".css", + "type": "SOURCE_RELATIVE", + "relativePath": "", + "suffix": "-dist", + "alwaysSuffix": false + } + }, + "javascript": { + "extensions": [ + ".js", + ".jsx" + ], + "autoCompile": false, + "sourceMap": false, + "tasks": [ + { + "task": "copy", + "enable": true + }, + { + "task": "concat-js", + "enable": false + }, + { + "task": "babel", + "enable": false + }, + { + "task": "bundle-js", + "enable": false + }, + { + "task": "minify-js", + "enable": true + } + ], + "output": { + "extension": ".js", + "type": "SOURCE_RELATIVE", + "relativePath": "", + "suffix": "-dist", + "alwaysSuffix": false + } + }, + "stylus": { + "extensions": [ + ".styl" + ], + "autoCompile": true, + "sourceMap": false, + "tasks": [ + { + "task": "stylus", + "enable": true + }, + { + "task": "autoprefixer", + "enable": true + }, + { + "task": "minify-css", + "enable": false + } + ], + "output": { + "extension": ".css", + "type": "REPLACE_SEGMENTS", + "segments": [ + { + "segment": "stylus", + "replaceWith": "css" + }, + { + "segment": "styl", + "replaceWith": "css" + } + ] + } + }, + "markdown": { + "extensions": [ + ".md", + ".markdown", + ".mkd" + ], + "autoCompile": false, + "tasks": [ + { + "task": "markdown", + "enable": true + }, + { + "task": "minify-html", + "enable": false + } + ], + "output": { + "extension": ".html", + "type": "REPLACE_SEGMENTS", + "segments": [ + { + "segment": "markdown", + "replaceWith": "html" + } + ] + } + }, + "haml": { + "extensions": [ + ".haml" + ], + "autoCompile": true, + "tasks": [ + { + "task": "haml", + "enable": true + }, + { + "task": "minify-html", + "enable": false + } + ], + "output": { + "extension": ".html", + "type": "REPLACE_SEGMENTS", + "segments": [ + { + "segment": "haml", + "replaceWith": "html" + } + ] + } + }, + "slim": { + "extensions": [ + ".slim" + ], + "autoCompile": true, + "tasks": [ + { + "task": "slim", + "enable": true + }, + { + "task": "minify-html", + "enable": false + } + ], + "output": { + "extension": ".html", + "type": "REPLACE_SEGMENTS", + "segments": [ + { + "segment": "slim", + "replaceWith": "html" + } + ] + } + }, + "coffeescript": { + "extensions": [ + ".coffee" + ], + "autoCompile": true, + "sourceMap": false, + "tasks": [ + { + "task": "coffeescript", + "enable": true + }, + { + "task": "babel", + "enable": false + }, + { + "task": "bundle-js", + "enable": false + }, + { + "task": "minify-js", + "enable": false + } + ], + "output": { + "extension": ".js", + "type": "REPLACE_SEGMENTS", + "segments": [ + { + "segment": "coffee-script", + "replaceWith": "js" + }, + { + "segment": "coffeescript", + "replaceWith": "js" + }, + { + "segment": "coffee", + "replaceWith": "js" + } + ] + } + }, + "turf": { + "extensions": [ + ".turf", + ".kit" + ], + "autoCompile": true, + "tasks": [ + { + "task": "turf", + "enable": true + }, + { + "task": "minify-html", + "enable": false + } + ], + "output": { + "extension": ".html", + "type": "REPLACE_SEGMENTS", + "segments": [ + { + "segment": "turf", + "replaceWith": "html" + } + ] + } + }, + "typescript": { + "extensions": [ + ".ts", + ".tsx" + ], + "autoCompile": true, + "sourceMap": false, + "tasks": [ + { + "task": "typescript", + "enable": true + }, + { + "task": "babel", + "enable": false + }, + { + "task": "bundle-js", + "enable": false + }, + { + "task": "minify-js", + "enable": false + } + ], + "output": { + "extension": ".js", + "type": "REPLACE_SEGMENTS", + "segments": [ + { + "segment": "typescript", + "replaceWith": "js" + }, + { + "segment": "ts", + "replaceWith": "js" + } + ] + } + }, + "jpg": { + "extensions": [ + ".jpg", + ".jpeg" + ], + "tasks": [ + { + "task": "jpg", + "enable": true + } + ], + "output": { + "extension": ".jpg", + "type": "SOURCE_RELATIVE", + "relativePath": "" + } + }, + "png": { + "extensions": [ + ".png" + ], + "tasks": [ + { + "task": "png", + "enable": true + } + ], + "output": { + "extension": ".png", + "type": "SOURCE_RELATIVE", + "relativePath": "" + } + }, + "svg": { + "extensions": [ + ".svg" + ], + "tasks": [ + { + "task": "svg", + "enable": true + } + ], + "output": { + "extension": ".svg", + "type": "SOURCE_RELATIVE", + "relativePath": "" + } + } + }, + "files": [ + { + "file": "source/system.pug", + "config": { + "customOutput": "system.html" + } + }, + { + "file": "source/system.scss", + "config": { + "customOutput": "system.css" + } + } + ] + } +} diff --git a/under_the_neighborhood/preview.png b/under_the_neighborhood/preview.png new file mode 100644 index 0000000000..201b0ea172 Binary files /dev/null and b/under_the_neighborhood/preview.png differ diff --git a/under_the_neighborhood/sheet.json b/under_the_neighborhood/sheet.json new file mode 100644 index 0000000000..622a3e9bc2 --- /dev/null +++ b/under_the_neighborhood/sheet.json @@ -0,0 +1,9 @@ +{ + "html": "system.html", + "css": "system.css", + "authors": "Scott Casey", + "roll20userid": "459831", + "preview": "preview.png", + "instructions": "The official sheet for [Under the Neighborhood](https://www.drivethrurpg.com/product/390786/Under-the-Neighborhood?manufacturers_id=15101) by the [Quest Friends crew](https://www.questfriendspodcast.com/).", + "legacy": false +} \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/_character.pug b/under_the_neighborhood/source/articles/_character.pug new file mode 100644 index 0000000000..41a7502884 --- /dev/null +++ b/under_the_neighborhood/source/articles/_character.pug @@ -0,0 +1,9 @@ +article#character.character.navigable-section.navigable-section--character.character-type.character-type--character.character-type--mon + include sections/_header.pug + include sections/_attributes.pug + include sections/_skills.pug + include sections/_playbook.pug + include sections/_compatibility.pug + include sections/_meter.pug + include sections/_modifiers.pug + include sections/_advancement.pug \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/_game_screen.pug b/under_the_neighborhood/source/articles/_game_screen.pug new file mode 100644 index 0000000000..a01209785a --- /dev/null +++ b/under_the_neighborhood/source/articles/_game_screen.pug @@ -0,0 +1,4 @@ +article#gamescreen.gamescreen.navigable-section.navigable-section--gamescreen.character-type.character-type--gamescreen + include sections/_header.pug + include sections/_lifeslice.pug + include sections/_clock.pug \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/_settings.pug b/under_the_neighborhood/source/articles/_settings.pug new file mode 100644 index 0000000000..d9df3ca88b --- /dev/null +++ b/under_the_neighborhood/source/articles/_settings.pug @@ -0,0 +1,53 @@ +article#settings.settings.navigable-section.navigable-section--settings.active + h1(data-i18n='settings') + +select-label({ + inputObj:{name:'sheet type',class:'underlined'}, + spanObj:{'data-i18n':'sheet type',role:'heading','aria-level':4} + }) + +option({value:'character','data-i18n':'character',selected:'',trigger:{triggeredFuncs:['characterDisplay']}}) + +option({value:'mon','data-i18n':'mon'}) + +option({value:'gamescreen','data-i18n':'game screen'}) + +input-label({ + inputObj:{name:'is weird',type:'checkbox',value:1,trigger:{triggeredFuncs:['sectionDisplay']}}, + spanObj:{'data-i18n':'is weird',role:'heading','aria-level':4} + }) + +input-label({ + inputObj:{name:'use initiative',type:'checkbox',value:1,trigger:{triggeredFuncs:['sectionDisplay']}}, + spanObj:{'data-i18n':'use initiative',role:'heading','aria-level':4} + }) + +input-label({ + inputObj:{name:'has arc',type:'checkbox',value:1,trigger:{triggeredFuncs:['sectionDisplay']}}, + spanObj:{'data-i18n':'has arc/flaw',role:'heading','aria-level':4} + }) + +input-label({ + inputObj:{name:'use roll modifiers',type:'checkbox',value:1,checked:'',trigger:{triggeredFuncs:['modifierDisplay']}}, + spanObj:{'data-i18n':'use roll modifiers',role:'heading','aria-level':4} + }) + +input-label({ + inputObj:{name:'use physical dice',type:'checkbox',value:1}, + spanObj:{'data-i18n':'use physical dice',role:'heading','aria-level':4} + }) + +input-label({ + inputObj:{name:'skill basic move',type:'checkbox',value:1,checked:''}, + spanObj:{'data-i18n':'skills use moves',role:'heading','aria-level':4} + }) + +select-label({ + inputObj:{name:'whisper',class:'underlined'}, + spanObj:{'data-i18n':'whisper to gm',role:'heading','aria-level':4} + }) + +option({value:'','data-i18n':'never',selected:''}) + +option({value:'/w gm','data-i18n':'always'}) + .span-all.move-stats + h2(data-i18n='basic move stats').span-all + each obj,name in basicMoves + +select-label({ + inputObj:{name:`${name} stat`,class:'underlined'}, + spanObj:{'data-i18n':name,role:'heading','aria-level':4} + }) + +attributeOptions(obj.stat) + +select-label({ + inputObj:{name:`initiative stat`,class:'underlined'}, + spanObj:{'data-i18n':'initiative',role:'heading','aria-level':4}, + divObj:{class:'initiative'} + }) + +attributeOptions('fierce') \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/sections/_advancement.pug b/under_the_neighborhood/source/articles/sections/_advancement.pug new file mode 100644 index 0000000000..69200d4661 --- /dev/null +++ b/under_the_neighborhood/source/articles/sections/_advancement.pug @@ -0,0 +1,8 @@ +section#advancement.advancement.span-all.character-type.character-type--character + h3(data-i18n='advancements') + each section in ['basic','super','ultra'] + .repeating-container + h4(data-i18n=section).header + +customControlFieldset({name:`${section}-advancement`}) + +checkbox({name:'picked',class:'boxed ratio1-1'}) + +adaptiveTextarea({name:'description',class:'underlined'}) \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/sections/_attributes.pug b/under_the_neighborhood/source/articles/sections/_attributes.pug new file mode 100644 index 0000000000..7da694f2ac --- /dev/null +++ b/under_the_neighborhood/source/articles/sections/_attributes.pug @@ -0,0 +1,9 @@ + +section#attributes.attributes + h3(data-i18n='stats').header + each name in attributes + +roller-label({ + buttonObj:{name,role:'heading','aria-level':4,'data-i18n':name,trigger:{triggeredFuncs:['rollStat']}}, + inputObj:{name,type:'number',class:'underlined',value:0,min:-3,max:3,trigger:{affects:[`${name}_mod`]}}, + divObj:{class:name} + }) \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/sections/_clock.pug b/under_the_neighborhood/source/articles/sections/_clock.pug new file mode 100644 index 0000000000..33c898dff5 --- /dev/null +++ b/under_the_neighborhood/source/articles/sections/_clock.pug @@ -0,0 +1,12 @@ +section(aria-labelledby='clock-header')#clock.clock.span-2--if-wide.repeating-container + h3(data-i18n='clocks')#clock-header.header + +customControlFieldset({name:'clock'}) + +hidden({name:'value',value:0,class:'clock-control--current'}) + +hidden({name:'value max',value:10,class:'clock-control--max'}) + .clock-progress + .clock-label + +radio({name:'value',value:0,class:'clear-x'}) + +number({name:'value',value:0}) + span.slash / + +number({name:'value max',value:10,min:2,max:10}) + +text({name:'name',class:'underlined',role:'heading','aria-level':5}) \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/sections/_compatibility.pug b/under_the_neighborhood/source/articles/sections/_compatibility.pug new file mode 100644 index 0000000000..44f7630738 --- /dev/null +++ b/under_the_neighborhood/source/articles/sections/_compatibility.pug @@ -0,0 +1,13 @@ +section#compatibility.compatibility.repeating-container + h3(data-i18n='compatibility scores') + .repeat-column + each header in ['character','score'] + h4(data-i18n=header class=replaceSpaces(header)) + +customControlFieldset({name:'compatibility'}) + .input-label + +checkbox({name:'activate',value:1}) + +text({name:'name',class:'underlined',role:'heading','aria-level':5}) + +number({name:'score',class:'underlined'}) + label.input-label + +checkbox({name:'interference',value:1}) + h5(data-i18n='interference').underlined.underlined--invisible \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/sections/_header.pug b/under_the_neighborhood/source/articles/sections/_header.pug new file mode 100644 index 0000000000..81ee44a80b --- /dev/null +++ b/under_the_neighborhood/source/articles/sections/_header.pug @@ -0,0 +1,41 @@ +header#character-header.header.span-all + img(src="https://s3.amazonaws.com/files.d20.io/images/277217218/yuDLFwlFjVStADz6M5aNLg/original.png" data-i18n-alt="Under the Neighborhood by Kyle Decker" role='heading' aria-level=1).header-image + if headerType === 'character' + .sentence.character-type.character-type--character(data-i18n-list='character sentence order') + +adaptiveInput({name:'character name',type:'text',class:'underlined',role:'heading','aria-level':2}).character-name + .input-label + span(data-i18n='sentence the' data-list-item='the') + +adaptiveInput({name:'playbook',type:'text',class:'underlined',' data-list-item':'playbook','data-i18n-placeholder':'playbook placeholder'}) + .input-label + span(data-i18n='sentence who' data-list-item='who') + +adaptiveInput({name:'descriptor',type:'text',class:'underlined',' data-list-item':'descriptor','data-i18n-placeholder':'descriptor placeholder'}) + .character-summary.character-type.character-type--character + .generic-details.span-all + each name, index in ['pronouns','age','appearance','homeworld','want'] + - const divObj = {}; + if index > 1 + - divObj.class='span-2'; + .input-label&attributes(divObj) + +span({role:'heading','aria-level':5,'data-i18n':name}) + +adaptiveInput({name,type:'text',class:'underlined'}) + .headed-textarea.arc + h5(data-i18n='character arc/flaw') + +adaptiveTextarea({name:'arc flaw',class:'underlined'}) + +input-label({ + label:'adventure points', + inputObj:{name:'ap',type:'number',class:'underlined'}, + divObj:{class:'stacked ap adventure-points'}, + spanObj:{role:'heading','aria-level':4} + }) + .character-summary.character-type.character-type--mon + +adaptiveInput({name:'character name',type:'text',class:'underlined',role:'heading','aria-level':2}).character-name + +select-label({ + inputObj:{name:'signature stat',class:'underlined'}, + spanObj:{'data-i18n':'signature stat',role:'heading','aria-level':4} + }) + +option({value:'','data-i18n':'select one'}) + each name in attributes + +option({value:name,'data-i18n':name,class:name}) + .input-label + +span({'data-i18n':'element',role:'heading','aria-level':4}) + +text({name:'element',class:'underlined'}) \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/sections/_lifeslice.pug b/under_the_neighborhood/source/articles/sections/_lifeslice.pug new file mode 100644 index 0000000000..de595e7460 --- /dev/null +++ b/under_the_neighborhood/source/articles/sections/_lifeslice.pug @@ -0,0 +1,5 @@ +section(aria-labelledby='lifesliceheader')#lifeslice.lifeslice.span-all.repeating-container + h3(data-i18n='slice of life complications')#lifesliceheader + +customControlFieldset({name:'complication'}) + +checkbox({name:'activate',value:1}) + +adaptiveTextarea({name:'description',class:'underlined'}) \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/sections/_meter.pug b/under_the_neighborhood/source/articles/sections/_meter.pug new file mode 100644 index 0000000000..df51ce1822 --- /dev/null +++ b/under_the_neighborhood/source/articles/sections/_meter.pug @@ -0,0 +1,23 @@ +section(aria-labelledby='meter-header')#meter.meter.span-2--if-wide.repeating-container + h3(data-i18n='meters')#meter-header.header + .repeat-column + h4(data-i18n='name') + each num in meterValues + h4 #{num} + +customControlFieldset({name:'meter'}) + +collapse + +text({name:'name',class:'underlined',role:'heading','aria-level':5}) + +hidden({name:'value max',value:10,class:'fill-left__max-control'}) + +fillLeft({ + radioObj:{name:'value',class:'square',value:0,trigger:{calculation:'limitMeterValue'}}, + valueArray:varObjects.meterValues + }) + h5(data-i18n='max').expanded + +fillLeft({ + radioObj:{name:'value max',class:'square',value:10,'data-i18n-title':'set the meter\'s maximum value.',trigger:{affects:['repeating_meter_$X_value']}}, + valueArray:meterValues, + noClear:true + }).expanded.no-fill + .headed-textarea.description.expanded.span-all + +h5({'data-i18n':'description'}) + +adaptiveTextarea({name:'description',class:'underlined'}) \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/sections/_modifiers.pug b/under_the_neighborhood/source/articles/sections/_modifiers.pug new file mode 100644 index 0000000000..9819880353 --- /dev/null +++ b/under_the_neighborhood/source/articles/sections/_modifiers.pug @@ -0,0 +1,9 @@ +section(aria-labelledby='modifier-header')#modifiers.repeating-container + h3(data-i18n='roll modifiers')#modifier-header + +customControlFieldset({name:'modifier'}) + +collapse + +checkbox({name:'activate',value:1}) + +text({name:'name',class:'underlined'}) + .headed-textarea.description.expanded.span-all + h5(data-i18n='effects') + +adaptiveTextarea({name:'effect',class:'underlined'}) \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/sections/_playbook.pug b/under_the_neighborhood/source/articles/sections/_playbook.pug new file mode 100644 index 0000000000..cc6e392b4e --- /dev/null +++ b/under_the_neighborhood/source/articles/sections/_playbook.pug @@ -0,0 +1,29 @@ +section#playbook.playbook.repeating-container.span-2--if-wide.character-type.character-type--character + h3(data-i18n='character moves').header + .repeat-column + h4(data-i18n='name') + h4(data-i18n='cost') + h4(data-i18n='source') + +customControlFieldset({name:'move'}) + +collapse + +roller-label({ + buttonObj:{name:'roll',trigger:{triggeredFuncs:['rollCharacterMove']}}, + inputObj:{name:'name',type:'text',class:'underlined',role:'heading','aria-level':5} + }) + +text({name:'cost',class:'underlined'}) + +select({name:'source',class:'underlined'}) + +option({value:'','data-i18n':'select one'}) + each value in ['playbook','descriptor','arc'] + +option({value,'data-i18n':value}) + .collapse-container.expanded.span-all + +checkbox({name:'result collapse',class:'collapse text-collapse__check',value:1,checked:'',hidden:''}) + label.text-collapse + +checkbox({name:'result collapse',hidden:'',value:1,checked:'',class:'text-collapse__check'}) + h5(data-i18n='customize results').text-collapse__text + each result in ['critical success','full success','mixed success','failure','critical failure'] + .headed-textarea.expanded.span-all + h5(data-i18n=result) + +adaptiveTextarea({name:result,class:'underlined'}) + .headed-textarea.description.expanded.span-all + +h5({'data-i18n':'description'}) + +adaptiveTextarea({name:'description',class:'underlined'}) \ No newline at end of file diff --git a/under_the_neighborhood/source/articles/sections/_skills.pug b/under_the_neighborhood/source/articles/sections/_skills.pug new file mode 100644 index 0000000000..0873a681f6 --- /dev/null +++ b/under_the_neighborhood/source/articles/sections/_skills.pug @@ -0,0 +1,57 @@ +section(aria-labelledby='skill-header').span-2--if-wide + #skills.skills.repeating-container + h3(data-i18n='skills and inabilities')#skill-header.header + .repeat-column + h4(data-i18n='skill') + h4(data-i18n='inability') + +customControlFieldset({name:'skill'}) + +collapse + +roller-label({ + buttonObj:{name:'roll',trigger:{triggeredFuncs:['rollSkill']}}, + inputObj:{name:'name',type:'text',class:'underlined'} + }) + +roller-label({ + buttonObj:{name:'inability',trigger:{triggeredFuncs:['rollSkill']}}, + inputObj:{name:'inability',type:'text',class:'underlined'} + }) + .expanded + +select-label({ + inputObj:{name:'skill stat',class:'underlined'}, + spanObj:{'data-i18n':'stat',role:'heading','aria-level':5} + }) + +attributeOptions + +input-label({ + inputObj:{name:'super',type:'checkbox',value:1,class:'boxed ratio1-1',class:'underlined'}, + spanObj:{'data-i18n':'super',role:'heading','aria-level':5}, + divObj:{class:'expanded super'} + }) + .expanded + +select-label({ + inputObj:{name:'inability stat',class:'underlined'}, + spanObj:{'data-i18n':'stat',role:'heading','aria-level':5} + }) + +attributeOptions + +input-label({ + inputObj:{name:'inability super',type:'checkbox',value:1,class:'boxed ratio1-1',class:'underlined'}, + spanObj:{'data-i18n':'super',role:'heading','aria-level':5}, + divObj:{class:'expanded super'} + }) + #signature-item.signature-item.repeating-container.character-type.character-type--character + h4(data-i18n='signature item') + +customControlFieldset({name:'signature'}) + +collapse + +roller-label({ + inputObj:{name:'name',type:'text',class:'underlined'}, + buttonObj:{name:'roll',role:'heading',trigger:{triggeredFuncs:['rollSkill']}} + }) + .expanded + +select-label({ + inputObj:{name:'stat',class:'underlined'}, + spanObj:{'data-i18n':'stat',role:'heading','aria-level':5} + }) + +attributeOptions + +input-label({ + inputObj:{name:'super',type:'checkbox',value:1,class:'boxed ratio1-1',class:'underlined'}, + spanObj:{'data-i18n':'super',role:'heading','aria-level':5}, + divObj:{class:'expanded super'} + }) \ No newline at end of file diff --git a/under_the_neighborhood/source/assets/undertheneighborhood_logo no Kyle Name.png b/under_the_neighborhood/source/assets/undertheneighborhood_logo no Kyle Name.png new file mode 100644 index 0000000000..93df622165 Binary files /dev/null and b/under_the_neighborhood/source/assets/undertheneighborhood_logo no Kyle Name.png differ diff --git a/under_the_neighborhood/source/assets/undertheneighborhood_logo.png b/under_the_neighborhood/source/assets/undertheneighborhood_logo.png new file mode 100644 index 0000000000..76117f7e4e Binary files /dev/null and b/under_the_neighborhood/source/assets/undertheneighborhood_logo.png differ diff --git a/under_the_neighborhood/source/javascript/calculators.js b/under_the_neighborhood/source/javascript/calculators.js new file mode 100644 index 0000000000..6c02ffb0b6 --- /dev/null +++ b/under_the_neighborhood/source/javascript/calculators.js @@ -0,0 +1,7 @@ +/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/ +/*jshint -W014,-W084,-W030,-W033*/ +const limitMeterValue = function({trigger,attributes,sections}){ + const [section,id,field] = k.parseTriggerName(trigger.name); + return Math.min(attributes[`${section}_${id}_${field}`],attributes[`${section}_${id}_${field}_max`]) +}; +k.registerFuncs({limitMeterValue}); \ No newline at end of file diff --git a/under_the_neighborhood/source/javascript/rollfunctions.js b/under_the_neighborhood/source/javascript/rollfunctions.js new file mode 100644 index 0000000000..a4a8f5624c --- /dev/null +++ b/under_the_neighborhood/source/javascript/rollfunctions.js @@ -0,0 +1,344 @@ +/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/ +/*jshint -W014,-W084,-W030,-W033*/ +//Roll Listeners +const rollBasicMove = async function({trigger,attributes,sections}){ + const [,,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections}); + let statRef = getMoveResults(field.replace(/-/g,' '),rollObj,attributes); + rollObj.roll = assembleRoll(dice,statRef,activeEffects,attributes.is_weird); + executeRoll({rollObj}); +}; +k.registerFuncs({rollBasicMove}); + +const getMoveResults = function(move,rollObj,attributes){ + const possibleResults = basicMoves[move]; + ['critical','success','mixed','failure','fumble'].forEach((result)=>{ + if(result !== 'stat'){ + rollObj[`${result}_result`] = possibleResults[result] ? + `^{${possibleResults[result]}}` : + ''; + } + }); + return attributes[`${move.replace(/\s+/g,'_')}_stat`]; +}; + +const rollStat = async function({trigger,attributes,sections}){ + const [,,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections}); + rollObj.roll = assembleRoll(dice,`@{${field}}`,activeEffects,attributes.is_weird); + executeRoll({rollObj}); +}; +k.registerFuncs({rollStat}); + +const rollSkill = async function({trigger,attributes,sections}){ + const [section,rowID,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections,isSkill:true}); + let statRef; + rollObj.header = attributes[`${section}_${rowID}_${field === 'inability' ? field : 'name'}`]; + if(attributes.skill_basic_move){ + const query = k.capitalize(getTranslationByKey('move query')); + const options = Object.keys(basicMoves) + .map((key) => { + return `${k.capitalize(getTranslationByKey(key))},${key}` + }) + .join('|'); + const move = await k.extractQueryResult(`${query}|${options}`); + statRef = getMoveResults(move,rollObj,attributes); + }else{ + await k.pseudoQuery(''); + statRef = section === 'repeating_skill' ? + (/inability/.test(field) ? + 'inability_stat' : + 'skill_stat' + ) : + 'stat'; + statRef = attributes[`${section}_${rowID}_${statRef}`]; + } + k.debug({section,rowID,field}); + + rollObj.roll = assembleRoll(dice,statRef,activeEffects,attributes.is_weird); + k.debug({skillObj:rollObj}); + executeRoll({rollObj}); +}; +k.registerFuncs({rollSkill}); + +const rollHelpFriend = async function({trigger,attributes,sections}){ + const [section,rowID,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections}); + const relationScore = attributes[`${section}_${rowID}_score`]; + rollObj.header = getTranslationByKey('help a friend roll header').replace('{{0}}',attributes[`${section}_${rowID}_name`]); + k.debug({activeEffects}); + rollObj.roll = assembleRoll(dice,relationScore,activeEffects,attributes.is_weird); + executeRoll({rollObj}); +}; +k.registerFuncs({rollHelpFriend}); + +const rollCharacterMove = async function({trigger,attributes,sections}){ + const [section,rowID,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections}); + rollObj.roll = assembleRoll(dice,'?{query}',activeEffects,attributes.is_weird); + const fieldSwitch = { + critical_result:attributes[`${section}_${rowID}_critical_success`] || attributes[`${section}_${rowID}_full_success`] || basicMoves['another move'].critical, + success_result:attributes[`${section}_${rowID}_full_success`] || basicMoves['another move'].success, + mixed_result:attributes[`${section}_${rowID}_mixed_success`] || basicMoves['another move'].mixed, + failure_result:attributes[`${section}_${rowID}_failure`] || basicMoves['another move'].failure, + fumble_result:attributes[`${section}_${rowID}_critical_failure`] || attributes[`${section}_${rowID}_failure`] || basicMoves['another move'].fumble + }; + rollObj.header = attributes[`${section}_${rowID}_name`]; + Object.entries(fieldSwitch).forEach(([fieldName,content])=>{ + rollObj[fieldName] = content; + }); + executeRoll({rollObj}); +}; +k.registerFuncs({rollCharacterMove}); + +const rollInitiative = async function({trigger,attributes,sections}){ + const [section,rowID,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections}); + rollObj.roll = assembleRoll(dice,attributes.fierce,activeEffects,attributes.is_weird) + .replace(/\]\]/,'&{tracker}]]'); + executeRoll({rollObj}); +}; +k.registerFuncs({rollInitiative}); + +//Parse Functions +const baseRollInfo = async function({trigger,attributes,sections,isSkill}){ + const [section,rowID,button] = k.parseTriggerName(trigger.name); + const field = button.replace(/-?action/,''); + const baseEffects = {advantage:0,bonus:0,text:[]}; + if(isSkill){ + baseEffects.advantage = field === 'inability' ? + -1 - attributes[`${section}_${rowID}_${field}_super`] : + 1 + attributes[`${section}_${rowID}_super`]; + } + const activeEffects = await countEffects(attributes,sections,baseEffects); + const rollObj = { + header:`^{${field.replace(/-/g,' ')}}`, + result:`[[0[computed value]]]` + }; + if(baseEffects.text?.length){ + rollObj.description = baseEffects.text.map((text)=>`^{${text}}`).join('\n'); + } + Object.entries(basicMoves['another move']).forEach(([result,effect])=>{ + if(result !== 'stat'){ + rollObj[`${result}_result`] = `^{${effect}}`; + } + }); + const dice = await parseAdvantage(attributes.advantage,activeEffects,attributes,rollObj); + return [section,rowID,field,activeEffects,rollObj,dice]; +}; + +/* + The Compatibility Scores and their effects are as follows: + -1 (Frenemies) + The other PC rolls with Disadvantage. Each of you gains 1 AP. + + +0 (Acquaintances) + The other PC gets +1 on their roll. + + +1 (Friends) + CHOOSE 1 + The other PC gets +1 on their roll. + The consequences of the other PC’s actions are “bigger” (Full Successes come with a small bonus, Failures come with an extra complication). + + +2 (Good Friends) + CHOOSE 1 + The other PC gets +2 on their roll. + The other PC rolls with Advantage. + The consequences of the other PC’s actions are “bigger” (Full Successes come with a small bonus, Failures come with an extra complication). + + +3 (Family) + CHOOSE UP TO 2 + The other PC gets +2 on their roll. + The other PC rolls with Advantage. + The consequences of the other PC’s move are “bigger” (Full Successes come with a small bonus, Failures come with an extra complication). +*/ +const compatibilityQuery = async function(baseEffects,attributes,sections){ + const compatibleEffects = { + '-1':{optNum:1,options:['disadvantage']}, + '0':{optNum:1,options:['+1']}, + '1':{optNum:1,options:['+1','bigger consequences']}, + '2':{optNum:1,options:['+1','advantage','bigger consequences']}, + '3':{optNum:2,options:['+2','advantage','bigger consequences']} + } + const activeID = sections.repeating_compatibility.reduce((memo,id)=>{ + return attributes[`repeating_compatibility_${id}_activate`] && + (!memo || attributes[`repeating_compatibility_${id}_score`] < attributes[`repeating_compatibility_${id}_score`]) ? + id : + memo; + },''); + const ordinals = { + '1':'1st', + '2':'2nd' + } + if(activeID){ + const activeEffect = compatibleEffects[attributes[`repeating_compatibility_${activeID}_score`]]; + k.debug({activeID,[`compatibleEffects[${attributes[`repeating_compatibility_${activeID}_score`]}]`]:activeEffect}) + const selectedEffects = []; + if(activeEffect.options.length > 1){ + const effectWorker = async (iteration = 1)=>{ + const availableOptions = activeEffect.options.filter((opt)=>selectedEffects.indexOf(opt) === -1); + const promptKey = activeEffect.optNum === 1 ? + 'Which compatibility effect' : + `${ordinals[iteration]} compatible effect`; + const selectedOption = await k.extractQueryResult(`${getTranslationByKey(promptKey)}|${availableOptions.map((opt)=>`${getTranslationByKey(`compatibility prompt ${opt}`)},${opt}`).join('|')}`); + k.debug({selectedOption}); + selectedOption.replace(/\+?(.+)/,(match,effect)=>{ + if(+effect){ + baseEffects.bonus += +effect; + }else if(/dis/.test(effect)){ + baseEffects.advantage--; + }else if(/advantage/.test(effect)){ + baseEffects.advantage++; + }else{ + baseEffects.text.push(effect); + } + }); + selectedEffects.push(selectedOption); + iteration++; + if(iteration > activeEffect.optNum){ + return true; + }else{ + return effectWorker(iteration); + } + } + await effectWorker(); + }else{ + selectedEffects.push(activeEffect.options[0]); + await k.pseudoQuery(); + } + }else{ + if(attributes.interference){ + baseEffects.advantage--; + baseEffects.text.push('interference roll message'); + } + await k.pseudoQuery(); + } +}; + +const countEffects = async function(attributes,sections,effectObj = {advantage:0,bonus:0}){ + let idArray; + const baseEffects = {...effectObj}; + if(attributes.use_roll_modifiers){ + idArray = sections.repeating_modifier; + }else{ + idArray = []; + baseEffects.bonus = attributes.generic_bonus; + } + await compatibilityQuery(baseEffects,attributes,sections); + k.debug({baseEffects}); + return idArray.reduce((memo,id)=>{ + k.debug({[`attributes[repeating_modifier_${id}_activate]`]:attributes[`repeating_modifier_${id}_activate`]}); + if(attributes[`repeating_modifier_${id}_activate`]){ + const effect = attributes[`repeating_modifier_${id}_effect`]; + k.debug({effect}); + const advs = effect.match(/(?:^|\s)(?:super )?advantage/ig) || []; + const disadvs = effect.match(/(?:super )?disadvantage/ig) || []; + const bonuses = effect.match(/(?:[+\-]\s*)?\d+/g) || []; + k.debug({advs,disadvs,bonuses}); + memo.advantage = advs.reduce((total,adv)=>total += /super/.test(adv) ? 2 : 1,memo.advantage); + memo.advantage = disadvs.reduce((total,adv)=>total -= /super/.test(adv) ? 2 : 1,memo.advantage); + memo.bonus = bonuses.reduce((total,bonus)=>{ + const val = +bonus.replace(/\+/,'').trim(); + return total += val; + },memo.bonus); + } + return memo; + },{...baseEffects}); +}; + +const parseAdvantage = async function(advantageState,activeEffects,attributes,rollObj){ + if(attributes.use_physical_dice){ + const result = +(await k.extractQueryResult(getTranslationByKey('physical roll query'))); + if(result === 12){ + rollObj.result = '[[1]]'; + }else if(result === 2){ + rollObj.result = '[[-1]]'; + }else{ + rollObj.result = '[[0]]'; + } + return result; + }else{ + await k.pseudoQuery(''); + if(advantageState.startsWith('?{')){ + //If the advantage state is a query, translate the query. + const options = Object.entries(advantageTypes).map(([aType,aValue])=>{ + return `${k.capitalize(getTranslationByKey(`${aType} option`))},${aValue}`; + }).join('|'); + return `?{${k.capitalize(getTranslationByKey('roll'))}|${options}}`; + }else{ + //otherwise, just use the advantage as is. + return activeEffects.advantage ? + (() => { + const diceNum = 2 + Math.abs(activeEffects.advantage); + const keep = activeEffects.advantage > 0 ? + 'kh' : + 'kl'; + return `${diceNum}d6${keep}2`; + })() : + advantageState; + } + } +}; + +const assembleRoll = function(dice,chosenStat,activeEffects,isWeird){ + let stat; + k.debug({chosenStat,activeEffects}); + if(`${chosenStat}`.startsWith('?{')){ + //If the advantage state is a query, translate the query. + const options = attributeNames.map((attr)=>{ + if(attr !== 'weird' || isWeird){ + return `${k.capitalize(getTranslationByKey(attr))},@{${attr}}`; + }else{ + return null; + } + }) + .filter((a)=>a) + .join('|'); + stat = `+ ?{${k.capitalize(getTranslationByKey('which stat'))}|${options}}`; + }else{ + //otherwise, just use the advantage as is. + stat = `+ ${chosenStat}`; + } + const bonus = stringifyBonus(activeEffects.bonus); + return `[[${dice}${stat}${bonus}]]`; +}; + +const stringifyBonus = function(bonus){ + k.debug({bonus}); + return bonus < 0 ? + bonus : + `+ ${bonus}`.replace(/\+ 0/,''); +}; + +const executeRoll = async function({rollObj}){ + k.debug({toExecute:rollObj}); + let message = `@{template_start} ${Object.entries(rollObj).map(([field,content])=>`{{${field}=${content}}}`).join('')}`; + k.debug({message}); + const rolled = await startRoll(message); + const computeObj = {}; + if(rolled.results.roll && rolled.results.result && /computed value/.test(rollObj.result)){ + const expression = rolled.results.roll.expression; + let rollState; + let rollQuant = 2; + expression.replace(/d6k([hl])(\d+)/,(match,target,num)=>{ + rollState = target; + rollQuant = +num || 2; + }); + const diceRoll = rolled.results.roll.rolls[0]; + const rollTotal = diceRoll.results.sort((a,b)=>{ + if(rollState === 'l'){ + return a - b; + }else if(rollState === 'h'){ + return b - a; + }else{ + return 0; + } + }) + .slice(0,rollQuant); + k.debug({rollTotal}); + const uniqueRolls = [...new Set(rollTotal)]; + if(uniqueRolls.length === 1){ + if(uniqueRolls[0] === 6){ + computeObj.result = 1; + }else if(uniqueRolls[0] === 1){ + computeObj.result = -1; + } + } + } + finishRoll(rolled.rollId,computeObj); +}; \ No newline at end of file diff --git a/under_the_neighborhood/source/javascript/setupfunctions.js b/under_the_neighborhood/source/javascript/setupfunctions.js new file mode 100644 index 0000000000..faf5e20ad4 --- /dev/null +++ b/under_the_neighborhood/source/javascript/setupfunctions.js @@ -0,0 +1,14 @@ +/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/ +/*jshint -W014,-W084,-W030,-W033*/ +const setupSheet = function({attributes,sections}){ + Object.entries(systemDefaults).forEach(([section,fields])=>{ + fields.forEach((fieldObj)=>{ + Object.entries(fieldObj).forEach(([field,content])=>{ + const row = k.generateRowID(section,sections); + k.debug({row,fields}); + attributes[`${row}_${field}`] = content; + }); + }); + }); +}; +k.registerFuncs({setupSheet},{type:['new']}); \ No newline at end of file diff --git a/under_the_neighborhood/source/javascript/triggeredfunctions.js b/under_the_neighborhood/source/javascript/triggeredfunctions.js new file mode 100644 index 0000000000..4521e3cd95 --- /dev/null +++ b/under_the_neighborhood/source/javascript/triggeredfunctions.js @@ -0,0 +1,60 @@ +/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/ +/*jshint -W014,-W084,-W030,-W033*/ +const navigateSheet = function({trigger,attributes}){ + k.debug('navigating sheet'); + let triggerName = trigger ? + trigger.name : + attributes.sheet_state; + let target = triggerName.replace(/clicked:|nav-/g,''); + k.debug({target}); + $20('.navigable-section,.sheet-nav__tab').removeClass('active'); + $20(`.sheet-nav__tab.${target},.navigable-section--${target}`).addClass('active'); + attributes.sheet_state = target; +}; +k.registerFuncs({navigateSheet},{type:['opener']}); + +const characterDisplay = function({attributes}){ + $20('.character-type').addClass('character-type--inactive'); + k.debug({sheet_type:attributes.sheet_type}) + const target = `.character-type--${attributes.sheet_type}`; + k.debug({target}); + $20(target).removeClass('character-type--inactive'); +} +k.registerFuncs({characterDisplay},{type:['opener']}) + +const sectionDisplay = function({trigger,attributes}){ + const fieldSwitch = { + is_weird:'weird', + mon_trainer:'mon', + use_initiative:'initiative', + has_arc:'arc' + }; + k.debug({trigger}); + let fields; + if(trigger){ + let[,,attr] = k.parseTriggerName(trigger.name); + fields = [attr]; + }else{ + fields = Object.keys(fieldSwitch); + } + fields.forEach((field)=>{ + const action = attributes[field] ? + 'remove' : + 'add'; + $20(`.${fieldSwitch[field]}`)[`${action}Class`]('inactive'); + }); +} +k.registerFuncs({sectionDisplay},{type:['opener']}); + +const modifierDisplay = function({attributes}){ + if(attributes.use_roll_modifiers){ + $20('#modifiers').removeClass('inactive'); + $20('.roll-options').addClass('inactive'); + attributes.advantage = '2d6'; + attributes.double = 0; + }else{ + $20('#modifiers').addClass('inactive'); + $20('.roll-options').removeClass('inactive'); + } +}; +k.registerFuncs({modifierDisplay},{type:['opener']}) \ No newline at end of file diff --git a/under_the_neighborhood/source/javascript/variables.js b/under_the_neighborhood/source/javascript/variables.js new file mode 100644 index 0000000000..104b7948fd --- /dev/null +++ b/under_the_neighborhood/source/javascript/variables.js @@ -0,0 +1,33 @@ +/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/ +/*jshint -W014,-W084,-W030,-W033*/ +k.version = 0.1; +k.sheetName = 'Under the Neighborhood'; +const systemDefaults = { + 'repeating_basic-advancement':[ + {description:'Increase a stat by 1 (max:+3)'}, + {description:'Increase a stat by 1 (max:+3)'}, + {description:'Increase a stat by 1 (max:+3)'}, + {description:'Increase a stat by 1 (max:+3)'}, + {description:'Add Another Skill'}, + {description:'Add Another Signature Item'}, + {description:'Add a Move from your Playbook'}, + {description:'Add a Move from your Playbook'}, + {description:'Add a Move from your Playbook Or a different Playbook'}, + {description:'Add a Move from your Playbook Or a different Playbook'} + ], + 'repeating_super-advancement':[ + {description:'Upgrade two Playbook Moves to advanced'}, + {description:'Upgrade two Playbook Moves to advanced'}, + {description:'Upgrade one Descriptor to advanced'}, + {description:'Upgrade two Skills into Super Skills'}, + {description:'Add Another Descriptor'}, + {description:'Increase the Maximum number of AP stored between adventures by 1'}, + ], + 'repeating_ultra-advancement':[ + {description:'Increase a stat by 1 (max:+4)'}, + {description:'Increase the Weird stat by 2 (max:+1)'}, + {description:'Upgrade all remaining Playbook Moves to Advanced'}, + {description:'Upgrade all remaining Descriptors to Advanced'}, + {description:'Add Another Skill'} + ] +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/_sheetmixins.pug b/under_the_neighborhood/source/mixins/_sheetmixins.pug new file mode 100644 index 0000000000..da448952b2 --- /dev/null +++ b/under_the_neighborhood/source/mixins/_sheetmixins.pug @@ -0,0 +1,19 @@ +//- An array of all the attributes for the system. We'll iterate through this so we don't have to write the same code 6 times. +- const attributes = ['heart','books','fierce','slick','weird']; +- const attributeOpts = attributes.map((attr)=>`${capitalize(attr)},@{${attr}}`).join('|'); +- varObjects.attributeNames = attributes; +//- A mixin that will create a checkbox for use in controlling the display of extra information on an item. Takes an optional name property to override the name of the checkbox. +mixin collapse(name='collapse') + +checkbox({name,value:1,class:'collapse'}) +//- End Mixin +mixin attributeOptions(defaultValue) + - const baseObj = {value:`?{Which Stat|${attributeOpts}}`,'data-i18n':'query'}; + if !defaultValue + - baseObj.selected = ''; + +option(baseObj) + each value in varObjects.attributeNames + - const optObj = {value:`@{${value}}`,'data-i18n':value} + if value === defaultValue + - optObj.selected = ''; + +option(optObj) +//- End Mixin \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_advancement.scss b/under_the_neighborhood/source/mixins/scss/_advancement.scss new file mode 100644 index 0000000000..4260790735 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_advancement.scss @@ -0,0 +1,27 @@ +#advancement{ + grid-template-columns:repeat(auto-fill,minmax(250px,1fr)); + h3{ + grid-column: 1 / -1; + } + .repeating-container{ + align-self:start; + gap:var(--half-gap) var(--grid-gap); + } + .repitem{ + display:grid; + grid-template-columns:1rem 1fr; + align-items:start; + } + input:checked + .adaptive textarea{ + text-decoration:{ + line:line-through; + style:wavy; + color:var(--selectedColor); + thickness:2px; + } + } + :is(.adaptive--text) { + min-height: 1.5rem; + height: auto; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_attributes.scss b/under_the_neighborhood/source/mixins/scss/_attributes.scss new file mode 100644 index 0000000000..581ae09e42 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_attributes.scss @@ -0,0 +1,9 @@ +#attributes{ + .input-label{ + justify-content: space-between; + button{ + width:auto; + flex:0; + } + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_clock.scss b/under_the_neighborhood/source/mixins/scss/_clock.scss new file mode 100644 index 0000000000..f3fb40e964 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_clock.scss @@ -0,0 +1,102 @@ +@use '_mixinsandplaceholders'; +$clockSizes:2,3,4,5,6,7,8,9,10; +$meterValues:( + 1:(2,3,4,5,6,7,8,9,10), + 2:(3,4,5,6,7,8,9,10), + 3:(4,5,6,7,8,9,10), + 4:(5,6,7,8,9,10), + 5:(6,7,8,9,10), + 6:(7,8,9,10), + 7:(8,9,10), + 8:(9,10), + 9:(10) +); +/// Mixin to place items on a circle +/// Adapted from on-circle by the original Authors to work with CSS variables based on a changing control attribute and to make pie slices instead of circular elements on the circle +/// @author Adapted by Scott Casey +/// @author Kitty Giraudel +/// @author Ana Tudor +@mixin on-circle($item-count) { + > * { + $angle: (360 / $item-count); + + @for $i from 1 through $item-count { + &:nth-of-type(#{$i}) { + $rot:($angle * ($i - 1)); + transform: + rotate($rot * 1deg) + translate(calc(var(--circleSize) / 4)); + } + } + } +} + +#clock{ + --circleSize:150px; + --itemDeg:0deg; + --itemCount:10; + .repcontainer{ + display:block; + gap:var(--half-gap); + column:{ + width:200px; + } + } + .repitem{ + display:grid; + gap:var(--half-gap); + grid-template-areas: + 'clock' + 'name' + ; + break-inside:avoid; + > input{ + text-align:center; + } + } + .clock-control--current[value="1"] ~ .clock-progress{ + --itemDeg:calc(1 / var(--itemCount) * 360deg); + } + @each $clockSize in $clockSizes{ + .clock-control--max[value="#{$clockSize}"] + .clock-progress{ + --itemCount:#{$clockSize}; + } + .clock-control--current[value="#{$clockSize}"] ~ .clock-progress{ + --itemDeg:calc(#{$clockSize} / var(--itemCount) * 360deg); + } + } + .clock-label{ + grid-area:clock; + max-width:var(--circleSize); + box-sizing:border-box; + display:flex; + place-self:center; + align-items:center; + z-index:10; + > * { + color:var(--selectedColor); + font-weight:bold; + } + } + .clock-progress{ + grid-area:clock; + border-radius:50%; + place-self:center; + width: var(--circleSize); + height: var(--circleSize); + display:grid; + grid-template-areas:'content'; + background: conic-gradient(var(--selectedColor) var(--itemDeg),transparent var(--itemDeg)); + border:2px solid var(--selectedColor); + &:before{ + content:''; + box-sizing:border-box; + width:75%; + height:75%; + place-self:center; + border-radius:50%; + background-color:var(--backColor); + border:2px solid var(--selectedColor); + } + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_compatibility.scss b/under_the_neighborhood/source/mixins/scss/_compatibility.scss new file mode 100644 index 0000000000..6bbda5bf30 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_compatibility.scss @@ -0,0 +1,20 @@ +@use '_mixinsandplaceholders'; +#compatibility{ + .repitem, + .repeat-column{ + display:grid; + grid-template-columns:1fr 3rem; + align-items:end; + gap:var(--half-gap); + } + .repcontainer{ + gap:var(--half-gap); + } + h4{ + white-space: pre-wrap; + } + button{ + width:auto; + flex:0; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_generalsetup.scss b/under_the_neighborhood/source/mixins/scss/_generalsetup.scss new file mode 100644 index 0000000000..874c52dc51 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_generalsetup.scss @@ -0,0 +1,136 @@ +//This file will store all of our generic styling for the sheet. Styling for specific sections will be done in individual scss files to make the code easier to read. +@use '_mixinsandplaceholders'; +@import url('https://fonts.googleapis.com/css?family=Material+Icons&display=swap'); +html{ + font-size:16px; +} +.ui-dialog{ + @extend %universalVariables; + .tab-content .charsheet{ + /* + First of all we apply our default styles. + Note that because we are using the placeholder, these styles would be first up in the cascade even if we put the extend declaration last. + This is done first so that when we read the code, we can easily see that it is applied, and anything after it will overwrite those default styles. + */ + + // Clears the default Roll20 styling from all elements so that we can style them as we want more easily. We are using 3 classes here because we have to use at least that level of specificity to overcome many of the default Roll20 styles. + padding-top:0; + //End roll20 clear + @extend %defaultStyles; + .roller{ + @extend %h4; + display:flex; + gap:var(--half-gap); + align-items:center; + &:before{ + font-family:dicefontd6; + content:'L' + } + } + button{ + cursor:pointer; + } + h3{ + grid-area:header; + } + .fill-left{ + display:grid; + align-self:center; + box-sizing:border-box; + box-shadow:inset 0 0 0 1px var(--selectedColor); + &:is(:hover,:focus-within,:focus){ + .fill-left__radio--clearer{ + background-image: linear-gradient(45deg, transparent 0% 48%, white 48% calc(48% + 1px), transparent calc(48% + 1px)), linear-gradient(-45deg, transparent 0% 48%, white 48% calc(48% + 1px), transparent calc(48% + 1px)); + border-color: transparent; + } + } + &:not(.no-fill){ + .fill-left__radio:checked{ + display:none; + } + } + } + .fill-left__radio{ + appearance:none; + -webkit-appearance:none; + background-color:var(--selectedColor); + order:1; + width:100%; + height:100%; + cursor:pointer; + &:checked ~ .fill-left__radio{ + background-color:var(--unselectedColor2); + order:3; + } + } + .fill-left__radio--clearer{ + order:2; + &:checked{ + display:none; + } + } + .styled-button{ + @extend %sectionBoxShadow; + background-color: var(--backColor); + border: 0; + border-radius: .5rem; + box-sizing: border-box; + cursor:pointer; + transition:{ + property:background-color,color; + duration:var(--revealTime); + } + padding-inline:var(--half-gap); + text-align:center; + + &:hover { + background-color: var(--selectedColor); + &, + & *{ + color:var(--dark-secondarytext); + } + } + + &:focus { + outline: 2px solid transparent; + outline-offset: 2px; + } + + &:focus-visible { + box-shadow: none; + } + } + .styled-button--controlled, + .styled-button--controlled *{ + @extend %h4; + } + .styled-button-control:is(:checked,[type='hidden']:not([value='']):not([value='0'])) + .styled-button--controlled{ + background-color:var(--selectedColor); + &, + & *{ + color:var(--dark-secondarytext); + } + } + } +} +.ui-dialog .sheetform{ + background-color:var(--backColor); + transition:{ + property:background-color; + duration:var(--revealTime); + } +} +.sheet-darkmode .ui-dialog{ + @extend %darkmodeStyle; +} +//the declarations for styles specifically applying to our main element. +#main{ + display:grid; +} +#character, +#gamescreen{ + display:grid; + gap:var(--grid-gap); + grid-auto-flow:dense; + grid-template-columns:repeat(auto-fill,minmax(16rem,1fr));//This one line is our secret sauce for making our sheet responsive to just about any screen width that our sheet might encounter from tiny phone screens up to stretching across several superwide monitors. For more information about auto-fit and auto-fill, I recommend the CSS-tricks summary: https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit/. +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_header.scss b/under_the_neighborhood/source/mixins/scss/_header.scss new file mode 100644 index 0000000000..fefdebd04d --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_header.scss @@ -0,0 +1,58 @@ +//This file will define how our header section is laid out. +@use '_mixinsandplaceholders'; +#character-header{ + display:grid; + gap:var(--half-gap); + grid-template-columns: 20rem 1fr; + grid-template-rows: repeat(2,auto); + grid-template-areas: + 'image summary' + 'sentence summary' + ; + .header-image{ + grid-area:image; + } + > .sentence{ + display:flex; + grid-area:sentence; + grid-area:auto; + justify-self:center; + flex-wrap:wrap; + gap:var(--half-gap); + > .character-name{ + min-width:8rem; + > input{ + text-transform:none; + } + } + } + .adaptive--input{ + min-width:6rem; + } + .input-label, + .adaptive--input{ + flex:1 0 auto; + } + .character-summary{ + grid-area:summary; + display:grid; + grid-template-columns:repeat(2,1fr); + grid-template-rows: auto 1fr; + gap:var(--grid-gap); + } + .generic-details{ + display:flex; + gap:var(--grid-gap); + flex-wrap:wrap; + .adaptive, + .input-label{ + flex:1 0 auto; + } + } + .arc.inactive + .adventure-points{ + grid-column:1 / -1; + } + .adventure-points{ + place-self:center; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_lifeslice.scss b/under_the_neighborhood/source/mixins/scss/_lifeslice.scss new file mode 100644 index 0000000000..5b8818e990 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_lifeslice.scss @@ -0,0 +1,16 @@ +@use '_mixinsandplaceholders'; +#lifeslice{ + .repcontainer{ + display:block; + gap:var(--half-gap); + column:{ + width:200px; + }; + } + .repitem{ + display:grid; + grid-template-columns:auto 1fr; + gap:var(--half-gap); + break-inside:avoid; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_media.scss b/under_the_neighborhood/source/mixins/scss/_media.scss new file mode 100644 index 0000000000..401c2d2c2a --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_media.scss @@ -0,0 +1,6 @@ +@use '_mixinsandplaceholders'; +@media (min-width:597px){ + .span-2--if-wide{ + grid-column:span 2; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_meter.scss b/under_the_neighborhood/source/mixins/scss/_meter.scss new file mode 100644 index 0000000000..46c91ffc93 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_meter.scss @@ -0,0 +1,59 @@ +@use '_mixinsandplaceholders'; +$meterValues:( + 1:(2,3,4,5,6,7,8,9,10), + 2:(3,4,5,6,7,8,9,10), + 3:(4,5,6,7,8,9,10), + 4:(5,6,7,8,9,10), + 5:(6,7,8,9,10), + 6:(7,8,9,10), + 7:(8,9,10), + 8:(9,10), + 9:(10) +); +#meter{ + --meterSquare:2rem; + --meterColumns:repeat(10,var(--meterSquare)); + .repitem, + .repeat-column{ + display:grid; + grid-template-columns:1fr var(--half-gap) var(--meterColumns); + >h5{ + place-self:center end; + } + } + .fill-left{ + grid-column:3 / -1; + justify-self:start; + border-radius: 15px; + overflow: hidden; + align-self: center; + height: 1rem; + align-items:center; + grid-template-columns:repeat(10,var(--meterSquare)); + grid-template-rows: var(--grid-gap); + align-content:start; + } + .fill-left.no-fill{ + --selectedColor:lightgrey; + } + h4:first-child{ + grid-column:span 2; + margin-right:var(--half-gap); + } + button{ + width:auto; + flex:0; + } + .fill-left__max-control{ + @each $value,$affected in $meterValues{ + &[value="#{$value}"] + .fill-left{ + grid-template-columns: repeat($value,var(--meterSquare)); + @each $target in $affected{ + .fill-left__radio[value="#{$target}"]{ + display:none; + } + } + } + } + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_mixinsandplaceholders.scss b/under_the_neighborhood/source/mixins/scss/_mixinsandplaceholders.scss new file mode 100644 index 0000000000..08eb1e49a9 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_mixinsandplaceholders.scss @@ -0,0 +1,782 @@ +//- This file will store all mixins and placeholders that have generic use + +//- A placeholder for creating a stackable grid of elements. Particularly for making stacked input labels. +%darkmodeStyle{ + --borderColor: var(--dark-secondarytext);//Our basic border color + --fontColor: var(--dark-secondarytext);//Our basic font color + --backColor: var(--dark-surface1);//Set our background to match the dark mode background of the sheet + --dark-primary:#c33d00; + --dark-primary-highlight:#9a644c; + --selectedColor:var(--dark-primary); + --subHeadBackColor:var(--dark-primary); + img{ + filter: brightness(.8) contrast(1.2); + } +} +%stackedGrid{ + &.stacked{ + flex-direction:column-reverse; + &.center>*{ + text-align: center; + } + > [name]{ + width:100%; + } + } +} +//- Some styling to highlight which input is currently being interacted with. +%inputHighlight{ + border-width: 1px 3px; + border-style: solid; + border-color: var(--borderColor); + border-radius: 5px; + box-sizing: border-box; +} +//- Placeholder that allows us to easily set a square aspect ratio +%ratio1_1{ + aspect-ratio: 1/1; +} +//- Placeholder for applying grid display +%grid{ + display:grid; +} + +//Our various fonts that can be used on the sheet. For now, these are all set to Arial until we find fonts we like, which won't be done until the very end. +%font-vars{ + --topHeaderFont:Arial; + --midHeaderFont:Arial; + --contentFont:Arial; +} + +//- Placeholder for applying inline-grid display +%inline-grid{ + display: inline-grid; +} +//- Placeholder for creating circular shapes easily. Combined with %ratio1_1, makes a perfect circle. +%circle{ + border-radius: 50%; +} +//- Creates our basic border for inputs +%base-border{ + border-width: 1px 3px; + border-style: solid; + border-radius: 5px; + border-color: transparent; + box-sizing: border-box; +} +//- Our basic button styling +%base-button{ + background-color: #DCDCDC33; + border-radius: 5px; + box-shadow: 0 2px 4px black; + border-width: 0; + transition:{ + property:box-shadow,backdrop-filter; + duration:200ms; + timing-function:ease-out; + }; + backdrop-filter:blur(1px); + overflow:hidden; + &:is(:hover,:focus){ + background-color: #85858580; + box-shadow: 0 4px 6px black; + backdrop-filter:blur(2px); + } + &:active{ + background-color: #858585ff; + box-shadow: 0 1px 2px black; + backdrop-filter:blur(0px); + } +} +//- Styling for our dice buttons +%die-button{ + @extend %base-button; + line-height: 14px; + /*height to vertically center a 2rem dicefontd10*/ + font-size: 2rem; + font-weight: normal; + font-style: normal; + padding: 5px 3px 7px; +} +//- styling for buttons that have standard text in them +%text-button{ + padding: 5px 7px; + @extend %base-button; +} +//- Styling for our adaptive text fields +%adaptiveText{ + .adaptive{ + @extend %grid; + grid-template-columns: auto; + grid-template-rows: auto; + grid-template-areas: "content"; + position:relative; + >*{ + grid-area: content; + } + > span{ + opacity: 0; + z-index: -10; + @extend %base-border; + text-transform: initial; + border-radius:0px; + } + } + .adaptive--text{ + min-height:4rem; + } + .adaptive--text__span{ + white-space: pre-wrap; + padding:2px; + } + .adaptive--text__textarea{ + width:100%; + height:100%; + resize: none; + } + .adaptive--text__textarea, + .adaptive--input__input{ + position:absolute; + } + .adaptive--input__input{ + width:100%; + } + .adaptive--input__span{ + padding:2px; + min-height:1.5rem; + } +} + +//- placeholder that can be used to apply uppercase letters to an element +%uppercase{ + text-transform:uppercase; +} + +//- Styling for our roll buttons +%roller{ + display: inline-flex; + align-items: center; + align-self:start; + gap: var(--half-gap); + color:var(--fontColor); + &:before{ + content:'L'; + font-family:dicefontd6; + } +} + +//- Styling to hide elements +%hidden{ + display: none!important;//!important is usually a thing to avoid, but if we're applying this, we want it to be gone no matter what. +} + +//- styling for italic words +%italics{ + font-style: italic; +} + +//- Our basic setup for text on the sheet. +%baseText{ + line-height: normal; +} + +//- Basic styling for headers. +%baseHeader{ + @extend %baseText; + color:var(--fontColor); + display: block; + white-space: nowrap; + margin-top: 0px; + margin-bottom: 0px; + font-weight:normal; +} + +//- Headers that should pop! +%importantHeader{ + text-transform: uppercase; + font-weight: normal; +} + +//- Headers that should be important, but not eye-catching +%midHeader{ + &:not(:where(input)){ + text-transform:capitalize; + } +} + +//- The next several placeholders are for styling our various levels of headers (1-5). +%h1-style{ + @extend %baseHeader; + @extend %importantHeader; + font-size: 2rem; + font-family: var(--topHeaderFont); + &:not(input){ + text-align: center; + } +} +%h1{ + @extend %h1-style; + *{ + @extend %h1-style; + } +} +%h2-style{ + @extend %baseHeader; + @extend %importantHeader; + font-size: 1.75rem; + font-family: var(--topHeaderFont); + &:not(input){ + text-align: center; + } +} +%h2{ + @extend %h2-style; + *{ + @extend %h2-style; + } +} +%h3-style{ + @extend %baseHeader; + @extend %importantHeader; + font-size: 1.5rem; + font-family: var(--topHeaderFont); + &:not(input){ + text-align: center; + } +} +%h3{ + @extend %h3-style; + *{ + @extend %h3-style; + } +} +%h4-style{ + @extend %baseHeader; + @extend %midHeader; + font-size: 1.25rem; + font-family: var(--midHeaderFont); + &:not(input){ + text-align: center; + } +} +%h4{ + @extend %h4-style; + *{ + @extend %h4-style; + } +} +%h5-style{ + @extend %baseHeader; + @extend %midHeader; + font-size: 1rem; + font-style:normal; + font-family: var(--midHeaderFont); + &:not(input){ + text-align: center; + } +} +%h5{ + @extend %h5-style; + *{ + @extend %h5-style; + } +} + +//- Placeholder for applying our checked styling to something +%checked{ + &:before{ + content: '\2713'; + grid-area:content; + font-weight:bold; + place-self:start center; + color: var(--selectedColor); + font-size: 1.5rem; + line-height: 0.5rem; + } +} + +//- Styling for elements that should have a box around them +%boxed{ + border: 1px solid black; + border-radius:0; + box-sizing:border-box; +} +//- Styling for items that need the pictos fonts +%pictos{ + font-family: pictos; + text-transform: initial; +} +%pictos3{ + font-family: pictos three; + text-transform: initial; +} +%pictosCustom{ + font-family: pictos custom; + text-transform: initial; +} + +%sectionBoxShadow{ + box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; +} + +%universalVariables{ + --grid-gap: 1rem;//Variable to define our basic gap between layout elements. Followed by several gaps that are mathematically related to it. + --half-gap: calc(var(--grid-gap) / 2); + --tiny-gap: calc(var(--half-gap) / 2); + --big-gap: calc(var(--grid-gap) * 2); + + --revealTime: 500ms;//Our basic reveal time for transitions + --delayTime: 200ms;//Our basic delay time before a transition occurs + --inputSwap: opacity var(--revealTime) ease var(--delayTime);//Transition properties for swapping the visibility of two elements + --focusTiming: 500ms ease;//our basic timing for the transition used when an element is focused or hovered on + --focusTrans: all var(--focusTiming);//The full transition properties for focusing/hovering on an element + --borderColor: black;//Our basic border color + --fontColor: black;//Our basic font color + --backColor: #ffffff;//Our basic background color + --spanSectionColumn:1/-1;//How a section should span when it is meant to span large numbers of columns (default span all) + --displayBorderWidth: 2px;//Our default border width + --subHeadBackColor:grey;//Our sub header background color + --unselectedColor1:lightgrey;//Our default font color for items that are currently inactive + --unselectedColor2:transparent; + --selectedColor:black; + @extend %font-vars; +} + +//- Default styles. This Placeholder gets a little complicated. We're going to define several CSS variables in the first lines that will allow us to easily use the same layout principles across the sheet. Additionally, this placeholder is going to apply several styles to various elements. The reason we do this as a placeholder instead of directly in a class' CSS declaration is so that we can easily apply the same styling to our sheet as a whole and to the rolltemplate (note that many of these class specifications have versions prefixed and unprefixed with `.sheet-` +%defaultStyles{ + gap: var(--grid-gap); + button{ + cursor: pointer; + } + .flex-box, + .sheet-flex-box{ + display:flex; + } + .space-around, + .sheet-space-around{ + justify-content:space-around; + } + .space-between, + .sheet-space-between{ + justify-content:space-between; + } + .center, + .sheet-center{ + place-self:center; + } + textarea{ + resize: vertical; + white-space: pre-wrap; + &.fixed{ + resize: none; + overflow: auto; + } + } + .span-2, + .sheet-span-2{ + grid-column:span 2; + } + .span-all, + .sheet-span-all{ + grid-column:1/-1; + } + select, + .pseudo-select span, + .sheet-pseudo-select span, + textarea, + input:not(:is([type='radio'],[type='checkbox'])), + .uneditable-input{ + padding: 2px; + font-size:1rem; + } + input:is([type="text"],[type="number"]),textarea{ + cursor:auto; + } + select, + .pseudo-select span, + .sheet-pseudo-select span, + input:not(:where([type='checkbox'], [type='radio'])), + .uneditable-input, + textarea{ + @extend %base-border; + } + select, + .sheet-pseudo-select span, + .pseudo-select span{ + -webkit-apperance: none; + appearance: none; + text-transform: capitalize; + overflow: hidden!important; + white-space: nowrap; + text-overflow: ellipsis; + text-align: center; + color:var(--fontColor); + } + input{ + width: auto; + &:placeholder{ + color: #ededed80; + } + &.plus-control:not([value*="-"])+span:before{ + content: '+'; + } + } + span, + input, + textarea{ + font-size: 1rem; + color:var(--fontColor); + @extend %baseText; + } + span[name],input,textarea,button,select{ + font-family:var(--contentFont); + } + .sheet-hidden, + .hidden, + *[hidden]{ + @extend %hidden; + } + .sheet-italics, + .italics{ + @extend %italics; + } + h1, + *[aria-level='1']{ + @extend %h1; + } + h2, + *[aria-level='2']{ + @extend %h2; + } + h3, + *[aria-level='3']{ + @extend %h3; + } + h4, + *[aria-level='4']{ + @extend %h4; + } + h5, + *[aria-level='5']{ + @extend %h5; + } + input{ + &[type='checkbox']{ + border: 1px solid var(--borderColor); + cursor: pointer; + -webkit-appearance:none; + appearance:none; + cursor:pointer; + width: 14px; + min-width: 14px; + height: 14px; + min-height: 14px; + display:grid; + grid-template-columns:1fr; + grid-template-areas:"content"; + &:not(.collapse):checked{ + @extend %checked; + } + } + &[type='number']{ + width: 3rem; + -moz-appearance: textfield !important; + text-align: center; + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button{ + -webkit-appearance: none; + margin: 0; + } + } + } + .sheet-number, + .number{ + width:2rem; + box-sizing:border-box; + } + section, + header{ + @extend %grid; + gap:var(--half-gap); + align-self:start; + grid-template-areas:'header'; + border-radius:5px; + padding:var(--half-gap); + transition:{ + property:box-shadow; + duration:var(--revealTime); + timing-function:ease; + } + &:is(:hover,:focus-within){ + @extend %sectionBoxShadow; + } + } + .inactive, + .character-type--inactive, + .navigable-section:not(.active){ + display:none !important; + } + .sheet-pictos, + .pictos{ + @extend %pictos; + } + .pictos3, + .sheet-pictos3{ + @extend %pictos3; + } + .pictoscustom, + .sheet-pictoscustom{ + @extend %pictosCustom; + } + .pseudo-button{ + display: inline; + cursor: pointer; + border-radius: 5px; + text-transform: uppercase; + border: 1px solid black; + text-align: center; + padding: 0 5px; + } + .input-label{ + display: inline-flex; + gap: var(--half-gap); + align-items:center; + @extend %stackedGrid; + &:not(.stacked){ + > input[type='text']{ + width:0; + flex:1 1 0; + } + >:not([name]){ + flex:0 0 auto; + } + &.large > [name]{ + @extend h2; + } + } + } + .input-label--dual{ + grid-template-columns: 1fr auto 1fr; + grid-template-areas: 'over over over' 'under under under'; + >span:last-of-type{ + grid-column: 1 / -1; + } + >input{ + width: 100%; + } + >.slash{ + line-height: 0rem; + font-weight: normal; + } + } + .headed-textarea{ + @extend %grid; + grid-template-rows: auto 1fr; + grid-template-columns: auto; + h5{ + justify-self: start; + } + } + .boxed, + .sheet-boxed{ + @extend %boxed; + &.thick-left{ + border-left-width: 5px; + } + &.thick-bottom{ + border-bottom-width: 5px; + } + &.thick-right{ + border-right-width: 5px; + } + &.thick-top{ + border-top-width: 5px; + } + } + .underlined, + .sheet-underlined{ + @extend %base-border; + border-radius: 0; + border-bottom: 1px solid var(--borderColor); + transition: var(--focusTrans); + } + :is(.underlined,.boxed){ + &:not([readonly]):not([type='checkbox']):not([type='radio']):is(:hover, :focus,:focus-within){ + @extend %inputHighlight; + } + &:not([readonly]):not([type='checkbox']):not([type='radio']):is(:focus,:focus-within){ + background-color: var(--subHeadBackColor); + } + } + .underlined--invisible{ + border-color:transparent !important; + } + @extend %adaptiveText; + .ratio1-1, + .sheet-ratio1-1{ + @extend %ratio1_1; + } + .short-text, + .sheet-short-text{ + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + text-transform: initial; + } + .roll-container{ + @extend %grid; + align-self:center; + opacity:0; + grid-row:1 / 2; + } + .collapse-container{ + display:grid; + position:relative; + } + .text-collapse{ + cursor:pointer; + display:flex; + justify-content:flex-start; + align-items:center; + &:before{ + content:'unfold_less'; + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + } + } + .text-collapse__text{ + display:inline; + } + .text-collapse__check{ + &:not(:checked) + .text-collapse{ + @extend %inputHighlight; + } + &:checked + .text-collapse:before{ + content:'unfold_more'; + } + } + .repitem, + .collapse-container{ + &:hover{ + .collapse,.roll-container{ + opacity:1; + } + } + .collapse{ + opacity:0; + position:absolute; + right:-10px; + top:0px; + border:0px solid black; + border-radius:0; + color:var(--selectedColor); + text-transform:none; + background-color:transparent; + &:before{ + content:'y'; + font-family:pictos; + } + &:checked{ + color:var(--unselectedColor1); + background-color:transparent; + ~ .expanded, + ~ .collapse-container .expanded{ + display:none !important; + } + ~ .expanded--empty:is(:not([value]),[value='']) + *, + ~ .collapse-container ~.expanded--empty:is(:not([value]),[value='']) + *{ + display:none !important; + } + } + &:not(:checked){ + ~ .collapsed{ + display:none !important; + } + } + &:hover{ + color:var(--selectedColor); + } + } + } + .repitem{ + > input:not([type='checkbox']){ + width:100%; + } + .headed-textarea{ + grid-column:1/-1; + } + } + .repcontainer.editmode{ + .collapse{ + display:none; + } + } + .repcontainer{ + display:grid; + gap:var(--grid-gap); + } + .repeating-container{ + display:grid; + grid-template-areas:'header'; + > .header{ + grid-area:header; + } + &:is(:hover,:focus-within,:focus) .repcontrol-button{ + opacity:1; + } + } + .repcontrol-button{ + @extend %text-button; + grid-area:header; + align-self:center; + text-transform:none; + opacity:0; + transition:{ + property:opacity,background-color; + duration: var(--revealTime); + }; + ~ .repcontrol{ + display:none; + } + } + .repcontrol_move{ + display:none; + } + .repcontrol-button--add{ + justify-self:start; + &:before{ + font-family:pictos; + content:'&'; + } + } + .repcontrol-button--edit{ + justify-self:end; + &:before{ + font-family:pictos; + content:'p'; + } + } + .sheet-box, + .box{ + border-radius:0; + width:10px; + height:10px; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_modifiers.scss b/under_the_neighborhood/source/mixins/scss/_modifiers.scss new file mode 100644 index 0000000000..bd1fe56794 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_modifiers.scss @@ -0,0 +1,7 @@ +@use '_mixinsandplaceholders'; +#modifiers{ + .repitem{ + display:grid; + grid-template-columns:auto 1fr; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_mon.scss b/under_the_neighborhood/source/mixins/scss/_mon.scss new file mode 100644 index 0000000000..585daa4c80 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_mon.scss @@ -0,0 +1,13 @@ +@use '_mixinsandplaceholders'; +#mon{ + .repitem, + .repeat-column{ + display:grid; + grid-template-columns:1fr 1fr 8rem 1fr; + gap:var(--half-gap); + } + button{ + width:auto; + flex:0; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_nav.scss b/under_the_neighborhood/source/mixins/scss/_nav.scss new file mode 100644 index 0000000000..e0d178b95b --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_nav.scss @@ -0,0 +1,48 @@ +@use '_mixinsandplaceholders'; +//Sets the bio/charsheet/a&a tab container to be sticky and float at the top +ul.nav-tabs.nav{ + position:sticky; + top:0; + background-color:var(--backColor); + z-index:9999; + margin-bottom:0px; +} +#main-nav{ + display:grid; + gap:var(--half-gap); + grid-template-columns:1fr auto; + align-items:center; + position:sticky; + top:44px; + margin-inline:-10px; + margin-bottom:10px; + padding-inline:10px; + padding-block:var(--half-gap); + background-color:var(--backColor); + z-index:1000; + transition:{ + property:background-color; + duration:var(--revealTime); + } + .sheet-nav__tab{ + align-self:end; + grid-column:2; + font-size:1.5rem; + &.active{ + display:none; + } + } + .roll-options, + .basic-moves{ + display:flex; + flex-wrap:wrap; + grid-gap:var(--half-gap); + } + .basic-moves{ + justify-content: space-between; + } + .roll-options{ + grid-column:1; + justify-content: center; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_playbook.scss b/under_the_neighborhood/source/mixins/scss/_playbook.scss new file mode 100644 index 0000000000..e403540c5b --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_playbook.scss @@ -0,0 +1,13 @@ +@use '_mixinsandplaceholders'; +#playbook{ + .repitem, + .repeat-column{ + display:grid; + grid-template-columns:2fr 8rem 6rem; + gap:var(--half-gap); + } + button{ + width:auto; + flex:0; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_roll20clear.scss b/under_the_neighborhood/source/mixins/scss/_roll20clear.scss new file mode 100644 index 0000000000..6cfb5b031b --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_roll20clear.scss @@ -0,0 +1,10 @@ +.ui-dialog .tab-content .charsheet{ + select, + textarea, + input, + .uneditable-input, + label, + button{ + all: initial; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_rolltemplate.scss b/under_the_neighborhood/source/mixins/scss/_rolltemplate.scss new file mode 100644 index 0000000000..ceb882702d --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_rolltemplate.scss @@ -0,0 +1,132 @@ +//Styling for our rolltemplate. This won't be written yet. +@use '_mixinsandplaceholders'; +.sheet-rolltemplate-utn{ + @extend %universalVariables; + @extend %defaultStyles; + font-size:16px; + margin:{ + left: -40px; + right: -5px; + } + --headerBackground:var(--selectedColor); + --templatePad:var(--grid-gap); + --criticalColor:green; + --fumbleColor:red; + --importantColor:blue; + &:not(:first-child){ + margin-top:10px; + } + &:first-child{ + .sheet-continuation{ + margin-top:-9px; + } + } + &.sheet-rolltemplate-darkmode{ + @extend %darkmodeStyle; + } + .sheet-header{ + padding: 5px; + display:grid; + gap:var(--half-gap); + background-color:var(--headerBackground); + padding:calc(var(--templatePad) / 2); + margin:{ + left: calc(var(--templatePad) * -1); + right: calc(var(--templatePad) * -1); + top: calc(var(--templatePad) * -1); + }; + border-radius:5px 5px 0 0; + *{ + color:#e6e6e6; + } + .sheet-character-name, + h3{ + grid-column:1/-1; + white-space: pre-wrap; + } + .sheet-character_name{ + place-self:center end; + } + h3{ + place-self:center start; + } + } + > .sheet-template{ + @extend %sectionBoxShadow; + border-radius:5px; + background-color:var(--backColor); + padding:var(--templatePad); + h3, + h3 *{ + font-size:1.5em; + } + h4, + h4 *{ + font-size:1.25em; + } + h5, + h5 *{ + font-weight:bold; + } + span,h5{ + font-size:1em; + } + .inlinerollresult{ + font-size:1.25em; + &[title*="[computed value]"], + &[original-title*="[computed value]"]{ + pointer-events: none; + } + &, + &.fullcrit, + &.fullfail, + &.importantroll{ + border: none; + background-color: transparent; + } + &.fullcrit{ + color: var(--criticalColor); + } + &.fullfail{ + color: var(--fumbleColor); + } + &.importantroll{ + color: var(--importantColor); + } + } + .sheet-crit .inlinerollresult{ + color:var(--criticalColor); + } + .sheet-fumble .inlinerollresult{ + color:var(--fumbleColor); + } + .sheet-regular-roll .inlinerollresult{ + color:var(--fontColor); + } + } + .sheet-content{ + display:grid; + gap:var(--grid-gap); + } + .sheet-template-row{ + display:flex; + flex-wrap:wrap; + gap:var(--half-gap); + justify-content: space-between; + align-items:center; + &.sheet-center{ + justify-content:center; + } + } + .sheet-success{ + color:var(--criticalColor); + } + .sheet-failure{ + color:var(--fumbleColor); + } + .sheet-critical{ + text-decoration:{ + line:underline overline; + } + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_settings.scss b/under_the_neighborhood/source/mixins/scss/_settings.scss new file mode 100644 index 0000000000..5fc40a50b2 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_settings.scss @@ -0,0 +1,17 @@ +@use '_mixinsandplaceholders'; +#settings{ + display:grid; + grid-template-columns:repeat(auto-fill,minmax(250px,1fr)); + gap:var(--grid-gap); + h1{ + grid-column:1 / -1; + } + .move-stats{ + display:grid; + gap:var(--grid-gap); + grid-template-columns:repeat(auto-fill,minmax(250px,1fr)); + .input-label{ + justify-content: space-between; + } + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/mixins/scss/_skills.scss b/under_the_neighborhood/source/mixins/scss/_skills.scss new file mode 100644 index 0000000000..67da8ea3e0 --- /dev/null +++ b/under_the_neighborhood/source/mixins/scss/_skills.scss @@ -0,0 +1,33 @@ +@use '_mixinsandplaceholders'; +#skills, +#signature-item{ + .super{ + justify-self:center; + } + button{ + width:auto; + flex:0; + } + .repitem, + .repeat-column{ + display:grid; + } + .expanded{ + display:flex; + justify-content:space-around; + } +} +#skills{ + .repitem, + .repeat-column{ + grid-template-columns:repeat(2,1fr); + gap:var(--half-gap); + } +} +#signature-item{ + justify-self:center; + input{ + width:initial; + flex:0; + } +} \ No newline at end of file diff --git a/under_the_neighborhood/source/rolltemplates/_utntemplate.pug b/under_the_neighborhood/source/rolltemplates/_utntemplate.pug new file mode 100644 index 0000000000..a169725d6f --- /dev/null +++ b/under_the_neighborhood/source/rolltemplates/_utntemplate.pug @@ -0,0 +1,53 @@ ++templateWrapper(templateName) + .header + +templateConditionalDisplay('header') + h3 {{header}} + +characterLink + .content + +templateConditionalDisplay('roll') + .template-row.center + +templateConditionalDisplay('roll_name') + h5 {{roll_name}} + +templateConditionalDisplay('roll_name','invert') + h5(data-i18n='roll') + +rollGreater({values:'computed::result 0'}) + span.crit {{roll}} + +rollLess({values:'computed::result 0'}) + span.fumble {{roll}} + +rollBetween({values:'computed::result 0 0'}) + span.regular-roll {{roll}} + +templateConditionalDisplay('result') + .template-row + //- Failure Handling + +rollLess({values:'roll 7'}) + //- Fumble Handling + +rollLess({values:'computed::result 0'}) + h5(data-i18n='critical failure').critical.failure + +templateConditionalDisplay('fumble_result') + span {{fumble_result}} + +templateConditionalDisplay('fumble_result','invert') + span {{failure_result}} + +rollLess({values:'computed::result 0',invert:true}) + h5(data-i18n='failure').failure + span {{failure_result}} + +rollBetween({values:'roll 7 9'}) + h5(data-i18n='mixed success') + span {{mixed_result}} + +rollGreater({values:'roll 9'}) + +rollGreater({values:'computed::result 0'}) + h5(data-i18n='critical success').critical.success + +templateConditionalDisplay('critical_result') + span {{critical_result}} + +templateConditionalDisplay('critical_result','invert') + span {{success_result}} + +rollGreater({values:'computed::result 0',invert:true}) + h5(data-i18n='full success').success + span {{success_result}} + +templateConditionalDisplay('description') + .template-row + h5(data-i18n='description') + span {{description}} + +allProps({values:'header character_name character_id roll_name roll result fumble_result failure_result mixed_result success_result critical_result description'}) + .template-row + h5 {{key}} + span {{value}} \ No newline at end of file diff --git a/under_the_neighborhood/source/scaffold/_htmlelements.pug b/under_the_neighborhood/source/scaffold/_htmlelements.pug new file mode 100644 index 0000000000..1faae3247e --- /dev/null +++ b/under_the_neighborhood/source/scaffold/_htmlelements.pug @@ -0,0 +1,1101 @@ +- const varObjects = {repeatingSectionDetails:[],actionAttributes:[],cascades:{attr_character_name:{name:'character_name',type:'text',defaultValue:'',affects:[],triggeredFuncs:['setActionCalls'],listenerFunc:'accessSheet',listener:'change:character_name'}},docs:{js:{},pug:{}}}; +- let selectName = null; +- let selectTitle = null; +- let repeatingPrefix = ''; +- const pugDoc = require +//- Element shortcut mixins +- + varObjects.docs.pug.trigger = { + type:'argument', + description:"Many of the K-scaffold's mixins utilize the scaffold's trigger system. Triggers can be passed in any mixin that creates an attribute backed element, which is any element that you would give the `name` property to. The trigger is passed as a property of the object that defines the attribute backed element. See the [input mixin](#input) below for a simple example.\nTriggers are how the K-scaffold connects the attributes created in the html of the sheet with the javscript listeners to trigger sheetworker code. When accessed from a sheetworker, a trigger has all of the below properties. When passing a trigger into a mixin, it should only have any or all of the first four properties; `affects`,`initialFunc`, `calculation`, `triggeredFuncs`, and `listenerFunc`. Note that except for `listenerFunc`, the function that is called is passed arguments using the [destructuring assignment pattern](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment). This means that the functions referenced in ,`initialFunc`, `calculation`, and `triggeredFuncs` must be passed in an object associated with a key named as indicated in the arguments below.", + arguments:[ + {type:'Array',name:'affects',description:'An array of attribute names that this attribute might affect. As an example, the `strength` attribute in a 5e based sheet would have `strength_mod` in its affects array.'}, + {type:'string',name:'initialFunc',description:'The name of a function that has been registered with the K-scaffold using [k.registerFuncs](https://github.com/Kurohyou/Roll20-Snippets/blob/main/K_Scaffold/readme_docs/k_scaffold_js_documentation.md#registerFuncs). This function will only be called if the change to the attribute is directly caused by the user as opposed to being changed by the sheetworkers.\nThe K-scaffold sends this function the following arguments:\n- `trigger`: The trigger object for the attribute that is being changed\n- `attributes`: the attributes object containing the entire state of the sheet.\n- `sections`: An object containing the row IDs of all the repeating sections, indexed by section name (e.g. repeating_equipment).'}, + {type:'string',name:'calculation',description:'The name of a function that has been registered with the K-scaffold using [k.registerFuncs](https://github.com/Kurohyou/Roll20-Snippets/blob/main/K_Scaffold/readme_docs/k_scaffold_js_documentation.md#registerFuncs). This function must return the calculated value of the attribute it is for, and is only called if the attribute is not changed directly by the user. Ideally, this function should be written as a pure function, reacting only to the arguments it is passed, and returning a value without modifying any of the arguments it is passed.\nThe K-scaffold sends this function the following arguments:\n- `trigger`: The trigger object for the attribute that is being changed\n- `attributes`: the attributes object containing the entire state of the sheet.\n- `sections`: An object containing the row IDs of all the repeating sections, indexed by section name (e.g. repeating_equipment).'}, + {type:'Array',name:'triggeredFuncs',description:'An array of function names that have been registered with the K-scaffold using [k.registerFuncs](https://github.com/Kurohyou/Roll20-Snippets/blob/main/K_Scaffold/readme_docs/k_scaffold_js_documentation.md#registerFuncs). These functions are called any time an attribute is changed, regardless of the source of the change, do not return any values, and will likely modify the arguments that are passed to them. These functions are used for doing complex logic to determine what should be done in response to a change.\nThe K-scaffold sends these functions the following arguments:\n- `trigger`: The trigger object for the attribute that is being changed\n- `attributes`: the attributes object containing the entire state of the sheet.\n- `sections`: An object containing the row IDs of all the repeating sections, indexed by section name (e.g. repeating_equipment), and in the order that they appear on the sheet.'}, + {type:'string',name:'listenerFunc',description:'The name of a function that has been registered with the K-scaffold using [k.registerFuncs](https://github.com/Kurohyou/Roll20-Snippets/blob/main/K_Scaffold/readme_docs/k_scaffold_js_documentation.md#registerFuncs). This function will be called directly from the event listener for this attribute instead of the default [accessSheet](https://github.com/Kurohyou/Roll20-Snippets/blob/main/K_Scaffold/readme_docs/k_scaffold_js_documentation.md#accesssheet) listener function. If a `listenerFunc` is specified, the automated handling of the rest of the previous trigger properties will be disabled. You can still use these properties, but will need to code that handling yourself.\nThe K-scaffold sends this function the following argument:\n- `event`: The event information object that is passed to the callback in the [sheet worker event listener](https://wiki.roll20.net/Sheet_Worker_Scripts#Event_listener). This is **NOT** passed using the object destructuring pattern, unlike the previous three function types.'}, + {type:'string',name:'name',description:'The name of the attribute. When accessed from inside the default function cascade of the K-scaffold, or the callback of [getAllAttrs](https://github.com/Kurohyou/Roll20-Snippets/blob/main/K_Scaffold/readme_docs/k_scaffold_js_documentation.md#getAllAttrs), repeating attributes will include their specific row id for that instance of the attribute.'}, + {type:'string',name:'type',description:'What type of attribute is this. This is determined by the type of element that first created the trigger. Can be `text`,`number`,`checkbox`,`select`,or `radio`, but will usually be `text` or `number` based on the type of default value that is assigned to the attribute.'}, + {type:'string|number',name:'defaultValue',description:'What the default value of the attribute is. The k-scaffold uses this to return the default value in the event the value of the attribute becomes corrupted. For checkboxes and radios, this is determined based on the check state of the element. For selects, this is determined by which option has the `selected` property.'} + ] + }; +- + varObjects.docs.pug.input = { + type:'mixin', + description:'A generic mixin to create an input. The mixin will replace spaces in the attribute name with underscores and will add a title property if one isn\'t supplied that will inform the user what the attribute call for the attribute is.', + arguments:[ + {type:'object',name:'obj',description:'An object containing all of the properties to apply to the element. Must have the properties; `name` and `type`. Can have any property that is valid for an input element. May also have a [trigger](#trigger) property'}, + {type:'string',name:'obj.name',} + ], + example:[ + [ + `+input({name:'my attribute',type:'text',class:'some-class',trigger:{affects:['other_attribute']}})'`, + `'` + ] + ] + }; +mixin input(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + - obj.name = replaceSpaces(obj.name); + - obj.title = obj.title || attrTitle(obj.name); + - obj.name = `attr_${obj.name}`; + - const elementObj = makeElementObj(obj); + - addFieldToFieldsetObj(obj); + - storeTrigger(obj); + input&attributes(elementObj)&attributes(attributes) +//-End Mixin +- + varObjects.docs.pug.text = { + type:'mixin', + description:'Alias for [input](#input) that makes a text input.', + example:[ + [ + `+text({name:'my text',class:'some-class',trigger:{affects:['other_attribute']}})`, + '' + ] + ] + }; +mixin text(obj) + - obj.type = 'text'; + +input(obj) +//-End Mixin +- + varObjects.docs.pug.checkbox = { + type:'mixin', + description:'Alias for [input](#input) that makes a checkbox input.', + example:[ + [ + `+checkbox({name:'my checkbox',class:'some-class',trigger:{affects:['other_attribute']}})`, + '' + ] + ] + }; +mixin checkbox(obj) + - obj.type = 'checkbox'; + +input(obj)&attributes(attributes) +//-End Mixin +- + varObjects.docs.pug.collapse = { + type:'mixin', + description:'Alias for [checkbox](#checkbox) that creates a checkbox for use in collapsing/expanding a section. Sets the checkbox to unchecked with a checked value of `1` and a class of `collapse`. Additional classes/ids can be applied by applying them inline to the mixin call.', + arguments:[ + {type:'string',name:'name',description:'The name to assign to the collapse button. Defaults to `collapse`'} + ] + }; +mixin collapse(name='collapse') + +checkbox({name,value:1,class:'collapse'})&attributes(attributes) +- + varObjects.docs.pug.radio = { + type:'mixin', + description:'Alias for [input](#input) that makes a radio input.', + example:[ + [ + `+radio({name:'my radio',class:'some-class',trigger:{affects:['other_attribute']}})`, + '' + ] + ] + }; +mixin radio(obj) + - obj.type = 'radio'; + +input(obj) +//-End Mixin +- + varObjects.docs.pug.number = { + type:'mixin', + description:'Alias for [input](#input) that makes a number input.', + example:[ + [ + `+number({name:'my number',class:'some-class',trigger:{affects:['other_attribute']}})`, + '' + ] + ] + }; +mixin number(obj) + - obj.type = 'number'; + +input(obj) +//-End Mixin +- + varObjects.docs.pug.range = { + type:'mixin', + description:'Alias for [input](#input) that makes a range input.', + example:[ + [ + `+range({name:'my range',class:'some-class',trigger:{affects:['other_attribute']}})`, + '' + ] + ] + }; +mixin range(obj) + - obj.type = 'range'; + +input(obj) +//-End Mixin +- + varObjects.docs.pug.hidden = { + type:'mixin', + description:'Alias for [input](#input) that makes a hidden input.', + example:[ + [ + `+hidden({name:'my hidden',class:'some-class',trigger:{affects:['other_attribute']}})`, + '' + ] + ] + }; +mixin hidden(obj) + - obj.type = 'hidden'; + +input(obj) +//-End Mixin +mixin radioGroup(objArray) + each obj in objArray + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + - obj.type='radio'; + +input(obj) +//-End Mixin +- + varObjects.docs.pug.fillLeft = { + type:'mixin', + description:`A mixin that creates a construction that can be used to recreate sheet mechanics that fill to the left. Will apply BEM style classes in addition to the classes specified. BEM classes that can be applied are + - \`fill-left\`: The class for the fill-left container. + - \`fill-left__radio\`: The class applied to all the radio buttons in the container + - \`fill-left__radio--clearer\`: The class applied to the clear value radio button`, + arguments:[ + {type:'object',name:'radioObj',description:'The object containing the details of the radio input to create. Similar to the [radio mixin](#radio), but the value property passed is used as the default checked value.'}, + {type:'object',name:'divObj',description:'Optional object containing any details of the div to be applied such as class, id, or other properties. Class and ID can also be supplied by attaching them to the mixin invocation just like with a regular div.'}, + {type:'array',name:'valueArray',description:'Array containing the values to be used for the fill to left construction. These should be in the order that they should be displayed left to right.'}, + {name:'noClear',type:'boolean',description:'Optional argument that tells the mixin whether or not to apply the `fill-left__radio--clearer` class to the first radio button value. If falsy (or not passed), the class is applied. If truthy, the class is not applied.'} + ], + example:[ + [ + `+fillLeft({radioObj:{name:'track',class:'some-class',value:"0"},valueArray:[0,1,2]})`, + `
\n \n \n \n
` + ], + [ + `+fillLeft({radioObj:{name:'track',class:'some-class',value:"0"},valueArray:[0,1,2],noClear:true})`, + `
\n \n \n \n
` + ], + [ + `+fillLeft({radioObj:{name:'track',class:'some-class',value:"0"},valueArray:[0,1,2],divObj:{'aria-label':'Accessible tracker'}}).custom-tracker-class`, + `
\n \n \n \n
` + ], + ] + }; +mixin fillLeft({radioObj,divObj,valueArray,noClear}) + - divObj = divObj || {}; + .fill-left&attributes(divObj)&attributes(attributes) + each value,index in valueArray + - const usedObj = {...radioObj,value}; + - + usedObj.class = usedObj.class ? + `${usedObj.class} fill-left__radio` : + `fill-left__radio`; + if value === radioObj.value + - usedObj.checked = ''; + if !noClear && index === 0 + - usedObj.class = `${usedObj.class} fill-left__radio--clearer`; + + +radio(usedObj) +//- End Mixin +- + varObjects.docs.pug.textarea = { + type:'mixin', + description:'Functions like [input](#input), but creates a textarea instead.', + example:[ + [ + `+textarea({name:'my hidden',class:'some-class',placeholder:'Placeholder text',trigger:{affects:['other_attribute']}})`, + '' + ] + ] + }; +mixin textarea(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + - obj.name = replaceSpaces(obj.name); + - obj.title = obj.title || attrTitle(obj.name); + - obj.name = `attr_${obj.name}`; + - addFieldToFieldsetObj(obj); + - storeTrigger(obj); + - const elementObj = makeElementObj(obj); + textarea&attributes(elementObj) +//-End Mixin +- + varObjects.docs.pug.option = { + type:'mixin', + description:'Creates an option attribute. Also stores the trigger for the select that the option is part of. See [select](#select) for example uses.' + }; +mixin option(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + - const triggerObj = {...obj,name:selectName,title:selectTitle,type:'select'}; + - const elementObj = makeElementObj(obj); + - storeTrigger(triggerObj); + option&attributes(elementObj) + block +//-End Mixin +- + varObjects.docs.pug.select = { + type:'mixin', + description:'Functions like [input](#input), but creates a select instead.', + example:[ + [ + `+select({name:'my select',class:'some-class'})\n +option({value:'option 1','data-i18n':'some-text',trigger:{affects:['some-attribute']}})\n +option({value:'option 2','data-i18n':'other-text'})`, + '' + ] + ] + }; +mixin select(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + - obj.name = replaceSpaces(obj.name); + - obj.title = obj.title || attrTitle(obj.name); + - obj.name = `attr_${obj.name}`; + - selectName = obj.name; + - selectTitle = obj.title; + - addFieldToFieldsetObj(obj); + select&attributes(obj) + if block + block + else + +option({value:'empty select, PEBKAC',selected:true}) +//-End Mixin +- + varObjects.docs.pug.img = { + type:'mixin', + description:'Functions like [input](#input), but creates an img instead. The name property is optional.', + example:[ + [ + `+img({name:'my image',src:'my_image.jpg',class:'some-class'})`, + '' + ] + ] + }; +mixin img(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + - obj['data-i18n-alt'] = obj['data-i18n-alt'] || obj.name; + - obj.name = replaceSpaces(obj.name); + - obj.title = obj.title || attrTitle(obj.name); + - obj.name = `attr_${obj.name}`; + img&attributes(obj) +//-End Mixin +- + varObjects.docs.pug.img = { + type:'mixin', + description:'Functions like [input](#input), but creates a span instead. The name property is optional.', + example:[ + [ + `+span({name:'my span',class:'some-class'})`, + '' + ] + ] + }; +mixin span(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + if obj.name + - obj.name = replaceSpaces(obj.name); + - obj.title = obj.title || attrTitle(obj.name); + - obj.name = `attr_${obj.name}`; + - addFieldToFieldsetObj(obj); + - const elementObj = makeElementObj(obj); + span&attributes(elementObj) + block + if obj.name + - obj.type = 'span'; + - storeTrigger(obj); +//-End Mixin +- + varObjects.docs.pug.datalist = { + type:'mixin', + description:'Functions like [input](#input), but creates a datalist instead. Note that an ID should never be put inside a repeating section, although you can reference one from inside the repeating section.', + example:[ + [ + `+select({name:'my select',list:'my-data'})\n+datalist({id:'my-data'})\n +option({value:'option 1','data-i18n':'some-text',trigger:{affects:['some-attribute']}})\n +option({value:'option 2','data-i18n':'other-text'})`, + '\n \n \n' + ] + ] + }; +mixin datalist(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + datalist&attributes(obj) + block +//-End Mixin +mixin div(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + if obj.name + - obj.name = replaceSpaces(obj.name); + - obj.title = obj.title || attrTitle(obj.name); + - obj.name = `attr_${obj.name}`; + div&attributes(obj) + block +//-End Mixin +mixin h1(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + h1&attributes(obj) + block +//-End Mixin +mixin h2(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + h2&attributes(obj) + block +//-End Mixin +mixin h3(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + h3&attributes(obj) + block +//-End Mixin +mixin h4(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + h4&attributes(obj) + block +//-End Mixin +mixin h5(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + h5&attributes(obj) + block +//-End Mixin +mixin h6(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + h6&attributes(obj) + block +//-End Mixin +mixin p(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + p&attributes(obj) + block +//-End Mixin +- + varObjects.docs.pug.button = { + type:'mixin', + description:'Creates a button element. Valid types are `roll` or `action`. If a type is not specified in the object argument, a roll button is created. If an action button is created, spaces in the name are replaced with dashes instead of underscores.', + example:[ + [ + `+button({name:'my button',value:'/r 3d10'})`, + '' + ], + [ + `+button({name:'my button',type:'action','data-i18n':'action button'})`, + '' + ] + ] + }; +mixin button(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + - obj.name = replaceSpaces(obj.name); + - obj.title = obj.title || buttonTitle(obj.name); + if obj.type === 'action' + - obj.name = `act_${obj.name}`; + else + - obj.type = 'roll'; + - obj.name = `roll_${obj.name}`; + - const elementObj = makeElementObj(obj); + if obj.type !== 'roll' + - storeTrigger(obj); + button&attributes(elementObj) + block +//-End Mixin +- + varObjects.docs.pug.action = { + type:'mixin', + description:'Alias for [button](#button) that creates a button element with a type of `action`. Spaces in the name are replaced with dashes instead of underscores.', + example:[ + [ + `+action({name:'my button','data-i18n':'action button'})`, + '' + ] + ] + }; +mixin action(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + - obj.type = 'action'; + - obj.name = obj.name.replace(/\s+/g,'-'); + +button(obj) + block +//- End Mixin +- + varObjects.docs.pug.navButton = { + type:'mixin', + description:'Alias for [button](#button) that creates a button element with a type of `action` for use in nav buttons. Spaces in the name are replaced with dashes instead of underscores. The name is prefixed with `nav_`. A [trigger](#trigger) object should be passed', + example:[ + [ + `+navButton({name:'my button','data-i18n':'action button'})`, + '' + ] + ] + }; +mixin navButton(obj) + - addIfUnique(obj.name,'navButtons'); + - obj.name = `nav ${obj.name}`; + +action(obj) + block +//- End Mixin +mixin rollerExtras(obj) + - obj.class = obj.class ? `${replaceProblems(obj.class)} roller` : 'roller'; + - let attrName = replaceSpaces(actionInputName(obj.name)); + - let actionName = actionButtonName(attrName); + - obj.value = `@{${attrName}}`; + - actionObj = {name:actionName,hidden:''}; + - actionObj.trigger = obj.trigger || {listenerFunc:'initiateRoll'}; + block + - addIfUnique(`${repeatingPrefix}${attrName}`,'actionAttributes'); + +action(actionObj) + +hidden({name:attrName}) +//- End Mixin +- + varObjects.docs.pug.navButton = { + type:'mixin', + description:'Alias for [button](#button) that creates a combination roll button and action button element to get around the limitation that action buttons cannot be dragged to the macro quickbar. Requires sheetworker infrastructure to work, which should be triggered `on(sheet:opened)` and `on("change:character_name")`.', + example:[ + [ + `+roller({name:'my roll'})`, + '\n\n' + ] + ] + }; +mixin roller(obj) + +rollerExtras(obj) + - let rollObj = {...obj}; + - delete rollObj.trigger; + +button(obj) + block +mixin section(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + section&attributes(obj) + block +//-End Mixin +mixin main(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + main&attributes(obj) + block +//-End Mixin +mixin header(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + header&attributes(obj) + block +//-End Mixin +mixin footer(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + footer&attributes(obj) + block +//-End Mixin +mixin details(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + details&attributes(obj) + block +//-End Mixin +mixin summary(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + summary&attributes(obj) + block +//-End Mixin +mixin label(obj) + - obj.class = obj.class ? replaceProblems(obj.class) : undefined; + label&attributes(obj) + block +//-End Mixin +mixin repeating_section(name,header,columnArr,id) + section(class=`repeating-container--${name} repeating-container section` id=`${(id||name).replace(/\s+/g,'-')}`) + if header + h2(data-i18n=header) + if columnArr + .repeat-columns + each head in columnArr + h5(data-i18n=head) + +customControlFieldset(name) + block +//- End Mixin +- + varObjects.docs.pug.customControlFieldset = { + type:'mixin', + description:'Alias for [fieldset](#fieldset) that creates to custom action buttons to add/remove rows to the repeating section. Useful when you need to trigger a sheetworker when a row is added. This also prevents the occassional error of a new row disappearing immediately after the user has clicked the button to create one. Proper use of this will require css to hide the default buttons that fieldsets create automatically. Note that currently this assumes the existence of an addItem and editSection sheetworker function.', + arguments:[ + {name:'name',type:'string',description:'The name of the repeating section. Will be prefixed with `repeating_` and spaces will be replaced with dashes (`-`).'}, + {name:'trigger',type:'object',description:'Trigger that defines how to handle the removal of a row from the fieldset. `Optional`'}, + {name:'addClass',type:'string',description:'Any additional classes that should be used for the repeating section. Note that these are not added to the fieldset itself as adding additional classes to the fieldset itself interferes with calling action buttons from chat, but are added to a span that precedes the fieldset. This allows styling of the repcontainer via a css declaration like `.bonus-class + fieldset + .repcontainer`.'} + ], + example:[ + [ + `+customControlFieldset({name:'equipment'})\n +text({name:'name',class:'underlined'})`, + + '\n\n
\n \n
' + ] + ] + }; +mixin customControlFieldset({name,trigger,addClass}) + +action({name:`add ${name}`,class:'repcontrol-button repcontrol-button--add',trigger:{listenerFunc:'addItem'}}) + +action({name:`edit ${name}`,class:'repcontrol-button repcontrol-button--edit',trigger:{listenerFunc:'editSection'}}) + +fieldset({name,trigger,addClass}) + block +//- End Mixin +mixin inlineFieldset({name,trigger,addClass}) + - addClass = addClass ? `${addClass} inline-fieldset` : 'inline-fieldset'; + - varObjects.inlineFieldsets = varObjects.inlineFieldsets || []; + - varObjects.inlineFieldsets.push(name); + +fieldset({name,trigger,addClass}) + +radio({name:'display state',class:'display-control',value:'short-display',hidden:''}) + .inline-fieldset__summary.display-target + label.pointer + +checkbox({name:'collapse',value:1,hidden:'',trigger:{triggeredFuncs:['collapseSection']}}) + +span({name:'name',class:'inline-fieldset__summary__text'}) + +radio({name:'display state',class:'display-control',value:'display',checked:'',hidden:''}) + .inline-fieldset__detail.display-target + +pseudoCollapse('collapse',{'data-i18n-title':collapseTitle}) + block + +action({name:`add ${name}`,class:'repcontrol-button repcontrol-button--add repcontrol-button--inline',trigger:{listenerFunc:'sectionInteract'}}) + +action({name:`edit ${name}`,class:'repcontrol-button repcontrol-button--edit repcontrol-button--inline',trigger:{listenerFunc:'sectionInteract'}}) +//- End Mixin +- + varObjects.docs.pug.fieldset = { + type:'mixin', + description:'A mixin that creates a fieldset for the creation of a repeating section. The mixin prefixes the name with `repeating_` and replaces problem characters (e.g. spaces are replaced with dashes). Additionally, the auto-generated title properties from the K-scaffold\'s mixins will include the proper repeating section information.', + arguments:[ + {name:'name',type:'string',description:'The name of the repeating section. Will be prefixed with `repeating_` and spaces will be replaced with dashes (`-`).'}, + {name:'trigger',type:'object',description:'Trigger that defines how to handle the removal of a row from the fieldset. `Optional`'}, + {name:'addClass',type:'string',description:'Any additional classes that should be used for the repeating section. Note that these are not added to the fieldset itself as adding additional classes to the fieldset itself interferes with calling action buttons from chat, but are added to a span that precedes the fieldset. This allows styling of the repcontainer via a css declaration like `.bonus-class + fieldset + .repcontainer`.'} + ], + example:[ + [ + `+fieldset({name:'equipment'})\n +text({name:'name',class:'underlined'})`, + + '
\n \n
' + ], + [ + `+fieldset({name:'equipment',trigger:{listenerFunc:'handleDeletedRow'},addClass:'class-1 class-2'})\n +text({name:'name',class:'underlined'})`, + + `\n
\n \n
` + ] + ] + }; +mixin fieldset({name,trigger,addClass}) + - name = name.replace(/\s+/g,'-'); + - let section = `repeating_${name}`; + - repeatingPrefix = `${section}_$X_`; + - createFieldsetObj(section) + if trigger + - storeTrigger({name:section,type:'fieldset',trigger}) + if addClass + span(hidden="" class=addClass) + fieldset(class=`${section}`) + block + - repeatingPrefix = ''; +//-End Mixin +- + varObjects.docs.pug['pseudo-button'] = { + type:'mixin', + description:'A mixin for creating pseudo buttons of paired checkboxes/radios and spans such as those that had to be used to create [tabbed sheets](https://wiki.roll20.net/CSS_Wizardry#Show.2FHide_Areas) before action buttons were introduced.', + arguments:[ + {name:'label',type:'string',description:'The `data-i18n` translation key to use for the displayed text.'}, + {name:'inputObj',type:'object',description:'An object describing the input to be paired with the span. This is the same object that you would pass to [input](#input).'}, + ], + example:[ + [ + `+pseudo-button('page 1',{name:'page',type:'radio',value:1,trigger:{triggeredFuncs:['changePage']}})`, + + `\n` + ] + ] + }; +mixin pseudo-button({label,inputObj,divObj,spanObj}) + - inputObj.class = inputObj.class ? replaceProblems(inputObj.class) : undefined; + label.pseudo-button&attributes(divObj) + - inputObj.class = inputObj.class ? `${inputObj.class} pseudo-button__input` : 'pseudo-button__input'; + +input(inputObj) + span(data-i18n=label class='pseudo-button__span')&attributes(spanObj) +//-End Mixin +- + varObjects.docs.pug['button-label'] = { + type:'mixin', + description:'A mixin to create a combined button and input that are within the same container. Similar to [input-label](#input-label), but does not use a label.', + arguments:[ + {name:'inputObj',type:'object',description:'An object describing the input to be paired with the button. This is the same object that you would pass to [input](#input).'}, + {name:'buttonObj',type:'object',description:'An object describing the button to be paired with the input. This is the same object that you would pass to [button](#button).'}, + {name:'divObj',type:'object',description:'An object describing the container div. Similar to the first two objects, but will most likely only have a `class` property if it is passed at all.'} + ], + example:[ + [ + `+button-label({inputObj:{name:'strength',type:'number',class:'underlined',value:10,trigger:{affects:['athletics']}},buttonObj:{name:'strength_roll',type:'roll',value:'/r 1d20+@{strength}'},divObj:{class:'strength'}})`, + + `
\n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + +
+ + + + + + + + + +
+
{{#header}} +

{{header}}

{{/header}}{{#character_name}}{{#character_id}} +

[{{character_name}}](http://journal.roll20.net/character/{{character_id}})

{{/character_id}}{{^character_id}} +

{{character_name}}

{{/character_id}}{{/character_name}} +
+
{{#roll}} +
{{#roll_name}} +
{{roll_name}}
{{/roll_name}}{{^roll_name}} +
{{/roll_name}}{{#rollGreater() computed::result 0}}{{roll}}{{/rollGreater() computed::result 0}}{{#rollLess() computed::result 0}}{{roll}}{{/rollLess() computed::result 0}}{{#rollBetween() computed::result 0 0}}{{roll}}{{/rollBetween() computed::result 0 0}} +
{{#result}} +
{{#rollLess() roll 7}}{{#rollLess() computed::result 0}} +
{{#fumble_result}}{{fumble_result}}{{/fumble_result}}{{^fumble_result}}{{failure_result}}{{/fumble_result}}{{/rollLess() computed::result 0}}{{#^rollLess() computed::result 0}} +
{{failure_result}}{{/^rollLess() computed::result 0}}{{/rollLess() roll 7}}{{#rollBetween() roll 7 9}} +
{{mixed_result}}{{/rollBetween() roll 7 9}}{{#rollGreater() roll 9}}{{#rollGreater() computed::result 0}} +
{{#critical_result}}{{critical_result}}{{/critical_result}}{{^critical_result}}{{success_result}}{{/critical_result}}{{/rollGreater() computed::result 0}}{{#^rollGreater() computed::result 0}} +
{{success_result}}{{/^rollGreater() computed::result 0}}{{/rollGreater() roll 9}} +
{{/result}}{{/roll}}{{#description}} +
+
{{description}} +
{{/description}}{{#allprops() header character_name character_id roll_name roll result fumble_result failure_result mixed_result success_result critical_result description}} +
+
{{key}}
{{value}} +
{{/allprops() header character_name character_id roll_name roll result fumble_result failure_result mixed_result success_result critical_result description}} +
+
+
+ \ No newline at end of file diff --git a/under_the_neighborhood/translation.json b/under_the_neighborhood/translation.json new file mode 100644 index 0000000000..9290a306a5 --- /dev/null +++ b/under_the_neighborhood/translation.json @@ -0,0 +1,142 @@ +{ + "character sentence order":"the,playbook,who,descriptor", + "sentence the":"the", + "sentence who":"who", + "character sheet":"character sheet", + "npc sheet":"npc sheet", + "pronouns":"pronouns", + "age":"age", + "appearance":"appearance", + "homeworld":"homeworld", + "want":"want", + "adventure points":"adventure points", + "stats":"stats", + "stat":"stat", + "heart":"heart", + "books":"books", + "fierce":"fierce", + "slick":"slick", + "weird":"weird", + "skills and inabilities":"skills and inabilities", + "skill":"skill", + "inability":"inability", + "signature item":"signature item", + "compatibility scores":"compatibility", + "character":"character", + "character moves":"character moves", + "name":"name", + "cost":"cost", + "source":"source", + "select one":"select one", + "playbook":"playbook", + "descriptor":"descriptor", + "arc":"arc", + "description":"description", + "meters":"meters", + "advancements":"advancements", + "playbook placeholder":"playbook", + "descriptor placeholder":"descriptor", + "mon information":"mon information", + "element":"element", + "basic":"basic", + "super":"super", + "ultra":"ultra", + "max":"max", + "open the settings page":"open the settings page", + "open the character sheet":"open the character sheet", + "settings":"settings", + "is weird":"is weird", + "mon trainer":"mon trainer", + "use initiative":"use initiative", + "see into their heart":"see into their heart", + "help somebody":"help somebody", + "touch the other side":"touch the other side", + "understand":"understand", + "take a swing":"take a swing", + "take action":"take action", + "sneak":"sneak", + "keep your cool":"keep your cool", + "convince somebody":"convince somebody", + "another move":"another move", + "double":"double", + "advantage":"advantage", + "normal":"normal", + "disadvantage":"disadvantage", + "query":"query", + "score":"score", + "initiative":"initiative", + "basic move stats":"basic move stats", + "whisper to gm":"whisper to gm", + "never":"never", + "always":"always", + "roll":"roll", + "critical failure":"critical failure", + "failure":"failure", + "mixed success":"mixed success", + "full success":"full success", + "critical success":"critical success", + "which stat":"which stat", + "effects":"effects", + "roll modifiers":"roll modifiers", + "use roll modifiers":"use roll modifiers", + "use physical dice":"use physical dice", + "physical roll query":"what was the result of your roll (without modifiers)", + "bonus":"bonus", + "help a friend roll header":"Help {{0}}", + "basic critical result":"You succeed beyond your expectations.", + "basic success result":"You succeed.", + "basic mixed result":"You succeed, but at a cost or not quite in the way you want. The GM makes a Soft Move.", + "basic failure result":"The result is bad for you. The GM makes a Hard Move, and you gain 1 AP (see: Adventure Points).", + "basic fumble result":"The result is REALLY bad for you. The GM makes a Hard Move, and you gain 0 AP.", + "see into their heart success result":"Ask the GM a simple question about your target’s inner thoughts. They must answer truthfully.", + "see into their heart mixed result":"Ask the GM a simple question about your target’s inner thoughts. They cannot lie and they cannot say nothing, but they can omit important information.", + "see into their heart failure result":"Your target gets important insight into you. The GM is able to ask you a simple question about your inner thoughts on behalf of your target, which you must answer truthfully.", + "help somebody success result":"You help your target succeed at their goal.", + "help somebody mixed result":"With your help, your target succeeds, but not fully or at a cost.", + "help somebody failure result":"Despite your help (or perhaps due to it), your target fails.", + "touch the other side success result":"You “Touch the Other Side” successfully, but the unfamiliar process still comes with complications. The PC chooses 1 Drawback\n• It takes longer than expected to work\n• It works, but not quite how you expected\n• It breaks after being used.", + "touch the other side mixed result":"You touch the other side and do… fine. The PC chooses 2 Drawbacks\n• It takes longer than expected to work\n• It works, but not quite how you expected\n• It breaks after being used.", + "touch the other side failure result":"You “Touch the Other Side,” but at great cost. The GM chooses 2 Drawbacks\n• It takes much longer than expected to work\n• It works, but in a way that’s harmful to you\n• It breaks after or while being used.", + "understand success result":"Ask a simple question about your target.", + "understand mixed result":"Ask a simple question about your target. The GM holds another question; this is something that your PC fails to understand.", + "understand failure result":"You misunderstand something vital about your target.", + "take a swing success result":"You hit with precision and force. The PC chooses 1 Benefit\n• You hit exactly where you want. Take Advantage Forward or +1 Ongoing against your target for the rest of the scene\n• You temporarily immobilize your target. If it’s a device, it stops functioning how it should for an appropriate amount of time\n• Your target moves exactly where you want them to. Choose a nearby position. That’s exactly where your target is.", + "take a swing mixed result":"On a Mixed Success: You smack your target, but not super well. The PC chooses 1 Benefit and 1 Drawback\n**Benefit*\n• You hit where you want. Take +1 Forward against your target\n• You temporarily daze your target. If it’s a device, it functions about half as well as it should for an appropriate amount of time\n• Your target moves where you want them to. Choose a nearby position. Your target is somewhere near there\n**Drawback*\n• You use too much or too little force\n• You hit someone or something you didn’t intend to\n• You injure yourself or leave yourself in a bad position in the process. Take -1 Forward.", + "take a swing failure result":"Not only do you fail to effectively hit your target, but your attack backfires. The GM chooses 1 Drawback\n• You use way too much or too little force\n• You hit someone or something you didn’t intend to.\n• You injure yourself or leave yourself in a very bad position in the process. Take Disadvantage Forward or -1 Ongoing for the rest of the scene.", + "take action success result":"You spring into action and are able to react just in time.", + "take action mixed result":"You’re able to act, but just a few seconds later than you’d like.", + "take action failure result":"You’re caught with your pants down. Any actions you take are far too late or they make the situation worse.", + "sneak success result":"You sneak through, and no one is the wiser.", + "sneak mixed result":"You sneak through, but aren’t silent in the process. The PC chooses 1 Drawback\n• You arouse the suspicions of those around you. You haven’t been seen, but those around you are on high alert.\n• You leave behind evidence of your presence\n• Someone other than your target(s) is aware of your stealth, but doesn’t say anything yet.", + "sneak failure result":"You’re spotted. Everyone (or at least the worst possible people) around you are aware of your presence.", + "keep your cool success result":"You successfully keep your cool, and, if you were trying to complete a task, you complete it.", + "keep your cool mixed result":"You almost keep your cool, but your composure slips, just a little bit. If you were trying to complete a task, you complete it, but not as effectively as you would have liked. The PC chooses 1 Drawback\n• You were sloppy\n• You were loud\n• You were slow.", + "keep your cool failure result":"You fail to keep your cool. If you were trying to complete a task, you fail.", + "convince somebody success result":"Your target agrees with what you have to say and helps however they can.", + "convince somebody mixed result":"Your target mostly agrees with you, but there’s a cost. The PC chooses 1 Drawback\n• Your target is convinced to do what you want, but something is preventing them from doing so\n• Your target asks for something in return\n• Your target mostly understands you, but makes one inconvenient misunderstanding\n• Your target is upset with you and will act appropriately until time has passed or you’ve made up for your actions. Take -1 Forward for any roll related to your target.", + "convince somebody failure result":"You fail to convince your target. Even worse, simply trying to convince them has made things worse. The GM chooses 1 Drawback\n• Your target actively believes the opposite of what you’re saying, and, if you were asking for help, actively tries to interfere with you\n• Your target demands something as a result of your meddling\n• Your target makes a vital misunderstanding of what you are trying to say\n• Your target is VERY upset with you and will act appropriately until significant time has passed or you’ve made up for your actions. Take -1 Ongoing for all rolls related to your target until you reconcile with them.", + "customize results":"customize results", + "character arc/flaw":"character arc/flaw", + "has arc/flaw":"has arc/flaw", + "skills use moves":"skills use moves", + "move query":"which move would you like to use", + "interference":"interference", + "signature stat":"signature stat", + "sheet type":"sheet type", + "mon":"mon", + "game screen":"game screen", + "Under the Neighborhood by Kyle Decker":"Under the Neighborhood by Kyle Decker", + "slice of life complications":"slice of life complications", + "clocks":"clocks", + "Which compatibility effect":"Which compatibility effect", + "1st compatible effect":"Select the 1st compatible effect", + "2nd compatible effect":"Select the 2nd compatible effect", + "compatibility prompt disadvantage":"You roll with disadvantage", + "compatibility prompt advantage":"You roll with advantage", + "compatibility prompt +1":"You receive a +1 bonus to your roll", + "compatibility prompt +2":"You receive a +2 bonus to your roll", + "compatibility prompt bigger consequences":"The effect of your roll is bigger", + "bigger consequences":"The consequences of your actions are “bigger” (Full Successes come with a small bonus, Failures come with an extra complication).", + "interference roll message":"Interference: You gain 1 AP", + "":"" +} \ No newline at end of file