+ return
{this.props.children}
;
}
diff --git a/shared/naturalcrit/nav/nav.less b/shared/naturalcrit/nav/nav.less
index 43eaa0819..e01715a95 100644
--- a/shared/naturalcrit/nav/nav.less
+++ b/shared/naturalcrit/nav/nav.less
@@ -13,6 +13,7 @@ nav{
position : relative;
display : flex;
justify-content : space-between;
+ z-index : 2;
}
.navSection{
display : flex;
diff --git a/stylelint_plugins/declaration-block-multi-line-min-declarations.js b/stylelint_plugins/declaration-block-multi-line-min-declarations.js
new file mode 100644
index 000000000..7144a923b
--- /dev/null
+++ b/stylelint_plugins/declaration-block-multi-line-min-declarations.js
@@ -0,0 +1,58 @@
+const stylelint = require('stylelint');
+const { isNumber } = require('stylelint/lib/utils/validateTypes');
+
+const { report, ruleMessages, validateOptions } = stylelint.utils;
+const ruleName = 'naturalcrit/declaration-block-multi-line-min-declarations';
+const messages = ruleMessages(ruleName, {
+ expected : (decls)=>`Rule with ${decls} declaration${decls == 1 ? '' : 's'} should be single line`,
+});
+
+
+module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
+ return function lint(postcssRoot, postcssResult) {
+
+ const validOptions = validateOptions(
+ postcssResult,
+ ruleName,
+ {
+ actual : primaryOption,
+ possible : [isNumber],
+ }
+ );
+
+ if(!validOptions) { //If the options are invalid, don't lint
+ return;
+ }
+ const isAutoFixing = Boolean(context.fix);
+
+ postcssRoot.walkRules((rule)=>{ //Iterate CSS rules
+
+ //Apply rule only if all children are decls (no further nested rules)
+ if(rule.nodes.length > primaryOption || !rule.nodes.every((node)=>node.type === 'decl')) {
+ return;
+ }
+
+ //Ignore if already one line
+ if(!rule.nodes.some((node)=>node.raws.before.includes('\n')) && !rule.raws.after.includes('\n'))
+ return;
+
+ if(isAutoFixing) { //We are in “fix” mode
+ rule.each((decl)=>{
+ decl.raws.before = ' ';
+ });
+ rule.raws.after = ' ';
+ } else {
+ report({
+ ruleName,
+ result : postcssResult,
+ message : messages.expected(rule.nodes.length), // Build the reported message
+ node : rule, // Specify the reported node
+ word : rule.selector, // Which exact word caused the error? This positions the error properly
+ });
+ }
+ });
+ };
+});
+
+module.exports.ruleName = ruleName;
+module.exports.messages = messages;
diff --git a/stylelint_plugins/declaration-colon-align.js b/stylelint_plugins/declaration-colon-align.js
new file mode 100644
index 000000000..f1f5269d3
--- /dev/null
+++ b/stylelint_plugins/declaration-colon-align.js
@@ -0,0 +1,68 @@
+const stylelint = require('stylelint');
+
+const { report, ruleMessages, validateOptions } = stylelint.utils;
+const ruleName = 'naturalcrit/declaration-colon-align';
+const messages = ruleMessages(ruleName, {
+ expected : (rule)=>`Expected colons aligned within rule "${rule}"`,
+});
+
+
+module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
+ return function lint(postcssRoot, postcssResult) {
+
+ const validOptions = validateOptions(
+ postcssResult,
+ ruleName,
+ {
+ actual : primaryOption,
+ possible : [
+ true,
+ false
+ ]
+ }
+ );
+
+ if(!validOptions) { //If the options are invalid, don't lint
+ return;
+ }
+ const isAutoFixing = Boolean(context.fix);
+ postcssRoot.walkRules((rule)=>{ //Iterate CSS rules
+
+ let maxColonPos = 0;
+ let misaligned = false;
+ rule.each((declaration)=>{
+
+ if(declaration.type != 'decl')
+ return;
+
+ const colonPos = declaration.prop.length + declaration.raws.between.indexOf(':');
+ if(maxColonPos > 0 && colonPos != maxColonPos) {
+ misaligned = true;
+ }
+ maxColonPos = Math.max(maxColonPos, colonPos);
+ });
+
+ if(misaligned) {
+ if(isAutoFixing) { //We are in “fix” mode
+ rule.each((declaration)=>{
+ if(declaration.type != 'decl')
+ return;
+
+ declaration.raws.between = `${' '.repeat(maxColonPos - declaration.prop.length)}:${declaration.raws.between.split(':')[1]}`;
+ });
+ } else { //We are in “report only” mode
+ report({
+ ruleName,
+ result : postcssResult,
+ message : messages.expected(rule.selector), // Build the reported message
+ node : rule, // Specify the reported node
+ word : rule.selector, // Which exact word caused the error? This positions the error properly
+ });
+ }
+ }
+ });
+ };
+});
+
+module.exports.ruleName = ruleName;
+module.exports.messages = messages;
diff --git a/stylelint_plugins/declaration-colon-min-space-before.js b/stylelint_plugins/declaration-colon-min-space-before.js
new file mode 100644
index 000000000..962084aa1
--- /dev/null
+++ b/stylelint_plugins/declaration-colon-min-space-before.js
@@ -0,0 +1,52 @@
+const stylelint = require('stylelint');
+const { isNumber } = require('stylelint/lib/utils/validateTypes');
+
+const { report, ruleMessages, validateOptions } = stylelint.utils;
+const ruleName = 'naturalcrit/declaration-colon-min-space-before';
+const messages = ruleMessages(ruleName, {
+ expected : (num)=>`Expected at least ${num} space${num == 1 ? '' : 's'} before ":"`
+});
+
+
+module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
+ return function lint(postcssRoot, postcssResult) {
+
+ const validOptions = validateOptions(
+ postcssResult,
+ ruleName,
+ {
+ actual : primaryOption,
+ possible : [isNumber],
+ }
+ );
+
+ if(!validOptions) { //If the options are invalid, don't lint
+ return;
+ }
+ const isAutoFixing = Boolean(context.fix);
+
+ postcssRoot.walkDecls((decl)=>{ //Iterate CSS declarations
+
+ const between = decl.raws.between;
+ const colonIndex = between.indexOf(':');
+
+ if(between.slice(0, colonIndex).length >= primaryOption) {
+ return;
+ }
+ if(isAutoFixing) { //We are in “fix” mode
+ decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/, ' '.repeat(primaryOption)) + between.slice(colonIndex);
+ } else {
+ report({
+ ruleName,
+ result : postcssResult,
+ message : messages.expected(primaryOption), // Build the reported message
+ node : decl, // Specify the reported node
+ word : ':', // Which exact word caused the error? This positions the error properly
+ });
+ }
+ });
+ };
+});
+
+module.exports.ruleName = ruleName;
+module.exports.messages = messages;
diff --git a/tests/markdown/basic.test.js b/tests/markdown/basic.test.js
index 459bb22c4..ca56bdc02 100644
--- a/tests/markdown/basic.test.js
+++ b/tests/markdown/basic.test.js
@@ -5,7 +5,7 @@ const Markdown = require('naturalcrit/markdown.js');
test('Escapes ';
const rendered = Markdown.render(source);
- expect(rendered).toMatch('<script></script>');
+ expect(rendered).toMatch('
<script></script>
\n');
});
test('Processes the markdown within an HTML block if its just a class wrapper', function() {
diff --git a/tests/markdown/mustache-span.test.js b/tests/markdown/mustache-span.test.js
deleted file mode 100644
index 6d249ebcb..000000000
--- a/tests/markdown/mustache-span.test.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/* eslint-disable max-lines */
-
-const Markdown = require('naturalcrit/markdown.js');
-
-test('Renders a mustache span with text only', function() {
- const source = '{{ text}}';
- const rendered = Markdown.render(source);
- expect(rendered).toBe('
text ');
-});
-
-test('Renders a mustache span with text only, but with spaces', function() {
- const source = '{{ this is a text}}';
- const rendered = Markdown.render(source);
- expect(rendered).toBe('
this is a text ');
-});
-
-test('Renders an empty mustache span', function() {
- const source = '{{}}';
- const rendered = Markdown.render(source);
- expect(rendered).toBe('
');
-});
-
-test('Renders a mustache span with just a space', function() {
- const source = '{{ }}';
- const rendered = Markdown.render(source);
- expect(rendered).toBe('
');
-});
-
-test('Renders a mustache span with a few spaces only', function() {
- const source = '{{ }}';
- const rendered = Markdown.render(source);
- expect(rendered).toBe('
');
-});
-
-test('Renders a mustache span with text and class', function() {
- const source = '{{my-class text}}';
- const rendered = Markdown.render(source);
- // FIXME: why do we have those two extra spaces after closing "?
- expect(rendered).toBe('
text ');
-});
-
-test('Renders a mustache span with text and two classes', function() {
- const source = '{{my-class,my-class2 text}}';
- const rendered = Markdown.render(source);
- // FIXME: why do we have those two extra spaces after closing "?
- expect(rendered).toBe('
text ');
-});
-
-test('Renders a mustache span with text with spaces and class', function() {
- const source = '{{my-class this is a text}}';
- const rendered = Markdown.render(source);
- // FIXME: why do we have those two extra spaces after closing "?
- expect(rendered).toBe('
this is a text ');
-});
-
-test('Renders a mustache span with text and id', function() {
- const source = '{{#my-span text}}';
- const rendered = Markdown.render(source);
- // FIXME: why do we have that one extra space after closing "?
- expect(rendered).toBe('
text ');
-});
-
-test('Renders a mustache span with text and two ids', function() {
- const source = '{{#my-span,#my-favorite-span text}}';
- const rendered = Markdown.render(source);
- // FIXME: do we need to report an error here somehow?
- expect(rendered).toBe('
text ');
-});
-
-test('Renders a mustache span with text and css property', function() {
- const source = '{{color:red text}}';
- const rendered = Markdown.render(source);
- expect(rendered).toBe('
text ');
-});
-
-test('Renders a mustache span with text and two css properties', function() {
- const source = '{{color:red,padding:5px text}}';
- const rendered = Markdown.render(source);
- expect(rendered).toBe('
text ');
-});
-
-test('Renders a mustache span with text and css property which contains quotes', function() {
- const source = '{{font:"trebuchet ms" text}}';
- const rendered = Markdown.render(source);
- // FIXME: is it correct to remove quotes surrounding css property value?
- expect(rendered).toBe('
text ');
-});
-
-test('Renders a mustache span with text and two css properties which contains quotes', function() {
- const source = '{{font:"trebuchet ms",padding:"5px 10px" text}}';
- const rendered = Markdown.render(source);
- expect(rendered).toBe('
text ');
-});
-
-
-test('Renders a mustache span with text with quotes and css property which contains quotes', function() {
- const source = '{{font:"trebuchet ms" text "with quotes"}}';
- const rendered = Markdown.render(source);
- expect(rendered).toBe('
text “with quotes” ');
-});
-
-test('Renders a mustache span with text, id, class and a couple of css properties', function() {
- const source = '{{pen,#author,color:orange,font-family:"trebuchet ms" text}}';
- const rendered = Markdown.render(source);
- expect(rendered).toBe('
text ');
-});
-
-// TODO: add tests for ID with accordance to CSS spec:
-//
-// From https://drafts.csswg.org/selectors/#id-selectors:
-//
-// > An ID selector consists of a “number sign” (U+0023, #) immediately followed by the ID value, which must be a CSS identifier.
-//
-// From: https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier:
-//
-// > In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9]
-// > and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_);
-// > they cannot start with a digit, two hyphens, or a hyphen followed by a digit.
-// > Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item).
-// > For instance, the identifier "B&W?" may be written as "B\&W\?" or "B\26 W\3F".
-// > Note that Unicode is code-by-code equivalent to ISO 10646 (see [UNICODE] and [ISO10646]).
-
-// TODO: add tests for class with accordance to CSS spec:
-//
-// From: https://drafts.csswg.org/selectors/#class-html:
-//
-// > The class selector is given as a full stop (. U+002E) immediately followed by an identifier.
-
diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js
new file mode 100644
index 000000000..d9e1ce6f9
--- /dev/null
+++ b/tests/markdown/mustache-syntax.test.js
@@ -0,0 +1,375 @@
+/* eslint-disable max-lines */
+
+const dedent = require('dedent-tabs').default;
+const Markdown = require('naturalcrit/markdown.js');
+
+// Marked.js adds line returns after closing tags on some default tokens.
+// This removes those line returns for comparison sake.
+String.prototype.trimReturns = function(){
+ return this.replace(/\r?\n|\r/g, '');
+};
+
+// Adding `.failing()` method to `describe` or `it` will make failing tests "pass" as long as they continue to fail.
+// Remove the `.failing()` method once you have fixed the issue.
+
+describe('Inline: When using the Inline syntax {{ }}', ()=>{
+ it.failing('Renders a mustache span with text only', function() {
+ const source = '{{ text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a mustache span with text only, but with spaces', function() {
+ const source = '{{ this is a text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
this is a text ');
+ });
+
+ it.failing('Renders an empty mustache span', function() {
+ const source = '{{}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
');
+ });
+
+ it.failing('Renders a mustache span with just a space', function() {
+ const source = '{{ }}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
');
+ });
+
+ it.failing('Renders a mustache span with a few spaces only', function() {
+ const source = '{{ }}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
');
+ });
+
+ it.failing('Renders a mustache span with text and class', function() {
+ const source = '{{my-class text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds two extra \s before closing `>` in opening tag.
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a mustache span with text and two classes', function() {
+ const source = '{{my-class,my-class2 text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds two extra \s before closing `>` in opening tag.
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a mustache span with text with spaces and class', function() {
+ const source = '{{my-class this is a text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds two extra \s before closing `>` in opening tag
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
this is a text ');
+ });
+
+ it.failing('Renders a mustache span with text and id', function() {
+ const source = '{{#my-span text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s before closing `>` in opening tag, and another after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a mustache span with text and two ids', function() {
+ const source = '{{#my-span,#my-favorite-span text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s before closing `>` in opening tag, and another after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a mustache span with text and css property', function() {
+ const source = '{{color:red text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a mustache span with text and two css properties', function() {
+ const source = '{{color:red,padding:5px text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a mustache span with text and css property which contains quotes', function() {
+ const source = '{{font-family:"trebuchet ms" text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a mustache span with text and two css properties which contains quotes', function() {
+ const source = '{{font-family:"trebuchet ms",padding:"5px 10px" text}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+
+ it.failing('Renders a mustache span with text with quotes and css property which contains quotes', function() {
+ const source = '{{font-family:"trebuchet ms" text "with quotes"}}';
+ const rendered = Markdown.render(source);
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text “with quotes” ');
+ });
+
+ it('Renders a mustache span with text, id, class and a couple of css properties', function() {
+ const source = '{{pen,#author,color:orange,font-family:"trebuchet ms" text}}';
+ const rendered = Markdown.render(source);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+});
+
+// BLOCK SYNTAX
+
+describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
+ it.failing('Renders a div with text only', function() {
+ const source = dedent`{{
+ text
+ }}`;
+ const rendered = Markdown.render(source).trimReturns();
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ });
+
+ it.failing('Renders an empty div', function() {
+ const source = dedent`{{
+
+ }}`;
+ const rendered = Markdown.render(source).trimReturns();
+ // FIXME: adds extra \s after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ });
+
+ it('Renders a single paragraph with opening and closing brackets', function() {
+ const source = dedent`{{
+ }}`;
+ const rendered = Markdown.render(source).trimReturns();
+ // this actually renders in HB as '{{ }}'...
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
{{}}
`);
+ });
+
+ it.failing('Renders a div with a single class', function() {
+ const source = dedent`{{cat
+
+ }}`;
+ const rendered = Markdown.render(source).trimReturns();
+ // FIXME: adds two extra \s before closing `>` in opening tag
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ });
+
+ it.failing('Renders a div with a single class and text', function() {
+ const source = dedent`{{cat
+ Sample text.
+ }}`;
+ const rendered = Markdown.render(source).trimReturns();
+ // FIXME: adds two extra \s before closing `>` in opening tag
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ });
+
+ it.failing('Renders a div with two classes and text', function() {
+ const source = dedent`{{cat,dog
+ Sample text.
+ }}`;
+ const rendered = Markdown.render(source).trimReturns();
+ // FIXME: adds two extra \s before closing `>` in opening tag
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ });
+
+ it.failing('Renders a div with a style and text', function() {
+ const source = dedent`{{color:red
+ Sample text.
+ }}`;
+ const rendered = Markdown.render(source).trimReturns();
+ // FIXME: adds two extra \s before closing `>` in opening tag
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ });
+
+ it.failing('Renders a div with a class, style and text', function() {
+ const source = dedent`{{cat,color:red
+ Sample text.
+ }}`;
+ const rendered = Markdown.render(source).trimReturns();
+ // FIXME: adds extra \s after the class attribute
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ });
+
+ it('Renders a div with an ID, class, style and text (different order)', function() {
+ const source = dedent`{{color:red,cat,#dog
+ Sample text.
+ }}`;
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ });
+
+ it.failing('Renders a div with a single ID', function() {
+ const source = dedent`{{#cat,#dog
+ Sample text.
+ }}`;
+ const rendered = Markdown.render(source).trimReturns();
+ // FIXME: adds extra \s before closing `>` in opening tag, and another after class names
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ });
+});
+
+// MUSTACHE INJECTION SYNTAX
+
+describe('Injection: When an injection tag follows an element', ()=>{
+ // FIXME: Most of these fail because injections currently replace attributes, rather than append to. Or just minor extra whitespace issues.
+ describe('and that element is an inline-block', ()=>{
+ it.failing('Renders a span "text" with no injection', function() {
+ const source = '{{ text}}{}';
+ const rendered = Markdown.render(source);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a span "text" with injected Class name', function() {
+ const source = '{{ text}}{ClassName}';
+ const rendered = Markdown.render(source);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a span "text" with injected style', function() {
+ const source = '{{ text}}{color:red}';
+ const rendered = Markdown.render(source);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders a span "text" with two injected styles', function() {
+ const source = '{{ text}}{color:red,background:blue}';
+ const rendered = Markdown.render(source);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('Renders an emphasis element with injected Class name', function() {
+ const source = '*emphasis*{big}';
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
emphasis
');
+ });
+
+ it.failing('Renders a code element with injected style', function() {
+ const source = '`code`{background:gray}';
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
code
');
+ });
+
+ it.failing('Renders an image element with injected style', function() {
+ const source = '{position:absolute}';
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
');
+ });
+
+ it.failing('Renders an element modified by only the first of two consecutive injections', function() {
+ const source = '{{ text}}{color:red}{background:blue}';
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text {background:blue}
');
+ });
+ });
+
+ describe('and that element is a block', ()=>{
+ it.failing('renders a div "text" with no injection', function() {
+ const source = '{{\ntext\n}}\n{}';
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
');
+ });
+
+ it.failing('renders a div "text" with injected Class name', function() {
+ const source = '{{\ntext\n}}\n{ClassName}';
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
');
+ });
+
+ it.failing('renders a div "text" with injected style', function() {
+ const source = '{{\ntext\n}}\n{color:red}';
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
');
+ });
+
+ it.failing('renders a div "text" with two injected styles', function() {
+ const source = dedent`{{
+ text
+ }}
+ {color:red,background:blue}`;
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
');
+ });
+
+ it.failing('renders an h2 header "text" with injected class name', function() {
+ const source = dedent`## text
+ {ClassName}`;
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
text ');
+ });
+
+ it.failing('renders a table with injected class name', function() {
+ const source = dedent`| Experience Points | Level |
+ |:------------------|:-----:|
+ | 0 | 1 |
+ | 300 | 2 |
+
+ {ClassName}`;
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
Experience Points Level 0 1 300 2
`);
+ });
+
+ // it('renders a list with with a style injected into the
tag', function() {
+ // const source = dedent`- Cursed Ritual of Bad Hair
+ // - Eliminate Vindictiveness in Gym Teacher
+ // - Ultimate Rite of the Confetti Angel
+ // - Dark Chant of the Dentists
+ // - Divine Spell of Crossdressing
+ // {color:red}`;
+ // const rendered = Markdown.render(source).trimReturns();
+ // expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`...`); // FIXME: expect this to be injected into ? Currently injects into last
+ // });
+
+ it.failing('renders an h2 header "text" with injected class name, and "secondInjection" as regular text on the next line.', function() {
+ const source = dedent`## text
+ {ClassName}
+ {secondInjection}`;
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('text {secondInjection}
');
+ });
+
+ it.failing('renders a div nested into another div, the inner with class=innerDiv and the other class=outerDiv', function() {
+ const source = dedent`{{
+ outer text
+ {{
+ inner text
+ }}
+ {innerDiv}
+ }}
+ {outerDiv}`;
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('');
+ });
+ });
+});
+
+
+// TODO: add tests for ID with accordance to CSS spec:
+//
+// From https://drafts.csswg.org/selectors/#id-selectors:
+//
+// > An ID selector consists of a “number sign” (U+0023, #) immediately followed by the ID value, which must be a CSS identifier.
+//
+// From: https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier:
+//
+// > In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9]
+// > and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_);
+// > they cannot start with a digit, two hyphens, or a hyphen followed by a digit.
+// > Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item).
+// > For instance, the identifier "B&W?" may be written as "B\&W\?" or "B\26 W\3F".
+// > Note that Unicode is code-by-code equivalent to ISO 10646 (see [UNICODE] and [ISO10646]).
+
+// TODO: add tests for class with accordance to CSS spec:
+//
+// From: https://drafts.csswg.org/selectors/#class-html:
+//
+// > The class selector is given as a full stop (. U+002E) immediately followed by an identifier.
diff --git a/themes/V3/5eDMG/style.less b/themes/V3/5eDMG/style.less
index 50a3af0a3..ffc73d992 100644
--- a/themes/V3/5eDMG/style.less
+++ b/themes/V3/5eDMG/style.less
@@ -1,3 +1,5 @@
+@import (less) './themes/assets/assets.less';
+
:root {
//Colors
--HB_Color_Accent : #EBCEC3; // Salmon
@@ -17,3 +19,10 @@
bottom : 40px;
}
}
+
+.page:has(.partCover) {
+
+ .partCover {
+ background-image: @partCoverHeaderDMG;
+ }
+}
\ No newline at end of file
diff --git a/themes/V3/5ePHB/snippets.js b/themes/V3/5ePHB/snippets.js
index 68d5cc596..dba4ad8d5 100644
--- a/themes/V3/5ePHB/snippets.js
+++ b/themes/V3/5ePHB/snippets.js
@@ -3,10 +3,11 @@
const MagicGen = require('./snippets/magic.gen.js');
const ClassTableGen = require('./snippets/classtable.gen.js');
const MonsterBlockGen = require('./snippets/monsterblock.gen.js');
-const scriptGen = require('./snippets/script.gen.js');
+const scriptGen = require('./snippets/script.gen.js');
const ClassFeatureGen = require('./snippets/classfeature.gen.js');
const CoverPageGen = require('./snippets/coverpage.gen.js');
const TableOfContentsGen = require('./snippets/tableOfContents.gen.js');
+const indexGen = require('./snippets/index.gen.js');
const dedent = require('dedent-tabs').default;
@@ -32,6 +33,12 @@ module.exports = [
name : 'Table of Contents',
icon : 'fas fa-book',
gen : TableOfContentsGen
+ },
+ {
+ name : 'Index',
+ icon : 'fas fa-bars',
+ gen : indexGen,
+ experimental : true
}
]
},
@@ -170,9 +177,27 @@ module.exports = [
gen : MonsterBlockGen.monster('monster,frame,wide', 4),
},
{
- name : 'Cover Page',
+ name : 'Front Cover Page',
icon : 'fac book-front-cover',
- gen : CoverPageGen,
+ gen : CoverPageGen.front,
+ experimental : true
+ },
+ {
+ name : 'Inside Cover Page',
+ icon : 'fac book-inside-cover',
+ gen : CoverPageGen.inside,
+ experimental : true
+ },
+ {
+ name : 'Part Cover Page',
+ icon : 'fac book-part-cover',
+ gen : CoverPageGen.part,
+ experimental : true
+ },
+ {
+ name : 'Back Cover Page',
+ icon : 'fac book-back-cover',
+ gen : CoverPageGen.back,
experimental : true
},
{
@@ -191,7 +216,7 @@ module.exports = [
}}
\n`;
},
- },
+ }
]
},
diff --git a/themes/V3/5ePHB/snippets/coverpage.gen.js b/themes/V3/5ePHB/snippets/coverpage.gen.js
index 5351f5db5..865269f92 100644
--- a/themes/V3/5ePHB/snippets/coverpage.gen.js
+++ b/themes/V3/5ePHB/snippets/coverpage.gen.js
@@ -13,7 +13,7 @@ const titles = [
'The Living Dead Above the Fearful Cage', 'Bahamut\'s Demonspawn',
'Across Gruumsh\'s Elemental Chaos', 'The Blade of Orcus',
'Beyond Revenge', 'Brain of Insanity',
- 'Breed Battle!, A New Beginning', 'Evil Lake, A New Beginning',
+ 'A New Beginning', 'Evil Lake of the Merfolk',
'Invasion of the Gigantic Cat, Part II', 'Kraken War 2020',
'The Body Whisperers', 'The Doctor from Heaven',
'The Diabolical Tales of the Ape-Women', 'The Doctor Immortal',
@@ -23,7 +23,7 @@ const titles = [
'Sky of Zelda: The Thunder of Force', 'Core Battle',
'Ruby of Atlantis: The Quake of Peace', 'Deadly Amazement III',
'Dry Chaos IX', 'Gate Thunder',
- 'Vyse\'s Skies', 'White Greatness III',
+ 'Vyse\'s Skies', 'Blue Greatness III',
'Yellow Divinity', 'Zidane\'s Ghost'
];
@@ -68,23 +68,89 @@ const footnote = [
'In an amazing kingdom, in an age of sorcery and lost souls, eight space pirates quest for freedom.'
];
-module.exports = ()=>{
- return dedent`
- {{coverPage }}
+const coverText = [
+ 'Embark on a thrilling journey across a vast and varied world, where magic and mystery await you at every turn. Encounter strange creatures and ancient secrets, and forge your own destiny with your choices. The world is yours to shape and explore.',
+ 'Join a band of brave adventurers and set out to explore the unknown lands beyond the horizon. Along the way, you’ll face perilous challenges, make new friends and enemies, and uncover a plot that threatens to destroy everything you hold dear. The fate of the world rests in your hands.',
+ 'Create your own character and enter a realm of endless possibilities, where you can be whoever you want to be. Whether you prefer to fight, sneak, charm, or craft your way through the game, you’ll find a style that suits you. The only limit is your imagination.',
+ 'Experience a rich and immersive story that adapts to your actions and decisions. Every choice you make has consequences, for good or ill. Will you be a hero or a villain? A leader or a follower? A friend or a foe? The choice is yours.',
+ 'Dive into a world of epic fantasy and adventure, where you can explore ancient civilizations, dark dungeons, and hidden secrets. Along the way, you’ll meet colorful characters, collect powerful items, and learn new skills. The more you play, the more you’ll discover.',
+ 'Explore a vast and dynamic world that changes according to your actions. You can shape the environment, influence the politics, and alter the history of the game world. But be careful, as every change has a ripple effect that may have unforeseen consequences.',
+ 'Enter a world of wonder and danger, where you can find allies and enemies among the various races and factions that inhabit it. You can choose to join or oppose any of them, or forge your own path. The game world is alive and responsive to your actions.'
+];
- {{logo }}
+module.exports = {
- # ${_.sample(titles)}
- ## ${_.sample(subtitles)}
- __________
+ front : function() {
+ return dedent`
+ {{frontCover}}
- {{banner HOMEBREW}}
+ {{logo }}
- {{footnote
- ${_.sample(footnote)}
- }}
+ # ${_.sample(titles)}
+ ## ${_.sample(subtitles)}
+ ___
- 
+ {{banner HOMEBREW}}
- \page`;
+ {{footnote
+ ${_.sample(footnote)}
+ }}
+
+ {position:absolute,bottom:0,left:0,height:100%}
+
+ \page`;
+ },
+
+ inside : function() {
+ return dedent`
+ {{insideCover}}
+
+ # ${_.sample(titles)}
+ ## ${_.sample(subtitles)}
+ ___
+
+ {{imageMaskCenter${_.random(1, 16)},--offsetX:0%,--offsetY:0%,--rotation:0
+ {position:absolute,bottom:0,left:0,height:100%}
+ }}
+
+ {{logo }}
+
+ \page`;
+ },
+
+ part : function() {
+ return dedent`
+ {{partCover}}
+
+ # PART X
+ ## ${_.sample(subtitles)}
+
+ {{imageMaskEdge${_.random(1, 8)},--offset:10cm,--rotation:180
+ {position:absolute,bottom:0,left:0,height:100%}
+ }}
+
+ \page`;
+ },
+
+ back : function() {
+ return dedent`
+ {{backCover}}
+
+ # ${_.sample(subtitles)}
+
+ ${_.sampleSize(coverText, 3).join('\n:\n')}
+ ___
+
+ For use with any fantasy roleplaying ruleset. Play the best game of your life!
+
+ {position:absolute,bottom:0,left:0,height:100%}
+
+ {{logo
+ 
+
+ Homebrewery.Naturalcrit.com
+ }}
+
+ \page`;
+ }
};
diff --git a/themes/V3/5ePHB/snippets/index.gen.js b/themes/V3/5ePHB/snippets/index.gen.js
new file mode 100644
index 000000000..8de5df14c
--- /dev/null
+++ b/themes/V3/5ePHB/snippets/index.gen.js
@@ -0,0 +1,85 @@
+const dedent = require('dedent-tabs').default;
+
+module.exports = ()=>{
+ return dedent`
+ {{index,wide,columns:5;
+ ##### Index
+ - Ankhesh-Bort
+ - city map, 7
+ - city watch, 12
+ - guilds, 19
+ - Cheese
+ - types of cheese, 8
+ - cheese-related magic, 14
+ - cheese-related quests, 26-27
+ - Death
+ - appearance, 10
+ - personality, 13
+ - hobbies, 23
+ - Elves
+ - types of elves, 15
+ - elvish magic, 24
+ - elvish curses, 28
+ - Footnotes
+ - types of footnotes, 16-17
+ - footnote rules, 20-21
+ - footnote humor, 29-30
+ - Gods
+ - types of gods, 12
+ - godly interventions, 25
+ - godly conflicts, 31
+ - Heroes
+ - class features, 11-12
+ - heroic deeds, 26-27
+ - Inns
+ - types of inns, 9
+ - inn amenities, 18
+ - Jokes
+ - types of jokes, 11-12
+ - joke delivery, 25
+ - Knives
+ - types of knives, 16-17
+ - knife skills, 22-23
+ - knife fights, 28-29
+ - Luggage
+ - appearance, 10
+ - personality, 13
+ - abilities, 23
+ - Magic
+ - types of magic, 15
+ - magic rules, 24
+ - magic mishaps, 28
+ - Socks
+ - types of socks, 9
+ - sock-related magic (yes, really), 15
+ - sock-related quests (no, really), 26
+ - Trolls
+ - appearance and biology, 11
+ - culture and language, 18
+ - troll rights and activism, 31
+ - Unknown University
+ - history and architecture, 12
+ - faculty and staff, 20
+ - courses and exams, 33
+ - Vampires
+ - types and origins, 13
+ - vampiric powers and weaknesses, 21
+ - vampiric etiquette and politics, 34
+ - Witches
+ - types and traditions, 14
+ - witchcraft and headology, 22
+ - witch trials and tribulations, 35
+ - Xylophones
+ - musical instruments or weapons?, 15
+ - xylophone-related magic and lore, 23
+ - xylophone-related quests and puzzles, 36
+ - Yetis
+ - appearance and behavior, 16
+ - yeti philosophy and religion, 24
+ - yeti encounters and stories, 37
+ - Zombies
+ - types and causes, 17
+ - zombie rights and duties, 25
+ - zombie survival and prevention, 38
+ }}`;
+};
\ No newline at end of file
diff --git a/themes/V3/5ePHB/style.less b/themes/V3/5ePHB/style.less
index c75cf0a1a..c6e87c40a 100644
--- a/themes/V3/5ePHB/style.less
+++ b/themes/V3/5ePHB/style.less
@@ -678,33 +678,14 @@ h5 + table{
}
}
//*****************************
-// * COVER PAGE
+// * FRONT COVER PAGE
// *****************************/
-.page:has(.coverPage) {
+.page:has(.frontCover) {
columns : 1;
text-align : center;
&:after {
all: unset;
}
- .logo {
- position : absolute;
- top : 0.5cm;
- left : 0;
- right : 0;
- filter :drop-shadow(0 0 0.075cm black);
- img {
- height : 2cm;
- width : 100%;
- }
- }
- .columnWrapper > p img {
- position : absolute;
- bottom : 0;
- left : 0;
- height : 100%;
- min-width : 100%;
- z-index : -1;
- }
h1 {
text-shadow: unset;
filter : drop-shadow(0 0 1.5px black) drop-shadow(0 0 0 black)
@@ -713,7 +694,6 @@ h5 + table{
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black);
text-transform : uppercase;
font-weight : normal;
- display : block;
margin-top : 1.2cm;
margin-bottom : 0;
color : white;
@@ -781,8 +761,193 @@ h5 + table{
width : 70%;
font-family : Overpass;
}
+ .logo {
+ position : absolute;
+ top : 0.5cm;
+ left : 0;
+ right : 0;
+ filter : drop-shadow(0 0 0.075cm black);
+ img {
+ height : 2cm;
+ width : 100%;
+ }
+ }
+}
+//*****************************
+// * INSIDE COVER PAGE
+// *****************************/
+.page:has(.insideCover) {
+ columns : 1;
+ text-align : center;
+ &:after {
+ all : unset;
+ }
+ h1 {
+ font-family : NodestoCapsCondensed;
+ font-weight : normal;
+ font-size : 2.1cm;
+ margin-top : 1.2cm;
+ margin-bottom : 0;
+ text-transform : uppercase;
+ line-height : 0.85em;
+ }
+ h2 {
+ font-family : NodestoCapsCondensed;
+ font-weight : normal;
+ font-size : 0.85cm;
+ letter-spacing : 0.5cm;
+ }
+ hr {
+ display : block;
+ position : relative;
+ background-image : @horizontalRule;
+ background-size : 100% 100%;
+ visibility : visible;
+ height : 0.5cm;
+ width : 12cm;
+ border : none;
+ margin : auto;
+ }
+ .logo {
+ position : absolute;
+ bottom : 1cm;
+ left : 0;
+ right : 0;
+ height : 2cm;
+ img {
+ height : 2cm;
+ width : 100%;
+ }
+ }
+}
+//*****************************
+// * BACK COVER
+// *****************************/
+.page:has(.backCover) {
+ color: #fff;
+ columns: 1;
+ padding: 2.25cm 1.3cm 2cm 1.3cm;
+ &:after {
+ all: unset;
+ }
+ .columnWrapper {
+ width: 7.6cm;
+ }
+ .backCover {
+ position: absolute;
+ inset: 0;
+ width: 11cm;
+ background-image: @backCover;
+ background-repeat: no-repeat;
+ background-size: contain;
+ z-index: -1;
+ }
+ .blank {
+ height: 1.4em;
+ }
+ h1 {
+ margin-bottom: .3cm;
+ font-size: 1.35cm;
+ line-height: 0.95em;
+ font-family: NodestoCapsCondensed;
+ text-align: center;
+ color: #ED1C24;
+ }
+ h1+p::first-line,
+ h1+p::first-letter {
+ all: unset;
+ }
+ img {
+ position: absolute;
+ top: 0px;
+ height: 100%;
+ z-index: -2;
+ }
+ hr {
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 1.1cm;
+ height: .53cm;
+ width: 4.5cm;
+ visibility: visible;
+ background-image: @horizontalRule;
+ background-size: 100% 100%;
+ border: none;
+ }
+ p {
+ font-family: Overpass;
+ line-height: 1.5em;
+ font-size: 0.332cm;
+ }
+ hr+p {
+ text-align: center;
+ margin-top: .6cm;
+ }
+ .logo {
+ position: absolute;
+ z-index: 0;
+ height: 1.5cm;
+ left: 1.2cm;
+ bottom: 2cm;
+ width: 7.6cm;
+ img {
+ position: relative;
+ height : 1.5cm;
+ width : 100%;
+ z-index : 0;
+ }
+ p {
+ position: relative;
+ color: #fff;
+ font-family: NodestoCapsWide;
+ font-size: .4cm;
+ letter-spacing: 0.08em;
+ line-height: 1em;
+ text-align: center;
+ text-indent: 0;
+ width: 100%;
+ }
+ }
}
+//*****************************
+ // * PART COVER
+ // *****************************/
+ .page:has(.partCover) {
+ columns : 1;
+ text-align : center;
+ padding-top: 0;
+
+ .partCover {
+ background-image: @partCoverHeaderPHB;
+ background-repeat: no-repeat;
+ position: absolute;
+ background-size: 100%;
+ top: 0;
+ left: 0;
+ height: 6cm;
+ width: 100%;
+ }
+
+ h1 {
+ position: relative;
+ text-align: center;
+ text-transform: uppercase;
+ font-size: 2.3cm;
+ font-family: NodestoCapsCondensed;
+ margin-top: .4cm;
+ }
+
+ h2 {
+ font-family: Overpass;
+ font-size: 0.45cm;
+ position: relative;
+ margin-top: -0.7em;
+ line-height: 1.1em;
+ margin-left : auto;
+ margin-right : auto;
+ }
+ }
//*****************************
// * TABLE OF CONTENTS
@@ -941,3 +1106,26 @@ break-inside : avoid;
}
}
}
+//*****************************
+// * INDEX
+// *****************************/
+.page {
+ .index {
+ font-size : 0.218cm;
+
+ ul ul {
+ margin : 0;
+ }
+
+ ul {
+ padding-left : 0;
+ text-indent : 0;
+ list-style-type : none;
+ }
+
+ & > ul > li {
+ text-indent : -1.5em;
+ padding-left : 1.5em;
+ }
+ }
+}
diff --git a/themes/V3/Blank/snippets.js b/themes/V3/Blank/snippets.js
index 33b819057..9d64496c3 100644
--- a/themes/V3/Blank/snippets.js
+++ b/themes/V3/Blank/snippets.js
@@ -101,6 +101,12 @@ module.exports = [
icon : 'fas fa-fill-drip',
gen : WatercolorGen,
},
+ {
+ name : 'Watercolor Center',
+ icon : 'fac mask-center',
+ gen : ImageMaskGen.center,
+ experimental : true,
+ },
{
name : 'Watercolor Edge',
icon : 'fac mask-edge',
diff --git a/themes/V3/Blank/snippets/imageMask.gen.js b/themes/V3/Blank/snippets/imageMask.gen.js
index 5c6193a05..323f89a1f 100644
--- a/themes/V3/Blank/snippets/imageMask.gen.js
+++ b/themes/V3/Blank/snippets/imageMask.gen.js
@@ -2,6 +2,16 @@ const _ = require('lodash');
const dedent = require('dedent-tabs').default;
module.exports = {
+ center : ()=>{
+ return dedent`
+ {{imageMaskCenter${_.random(1, 16)},--offsetX:0%,--offsetY:0%,--rotation:0
+ {height:100%}
+ }}
+ \n\n`;
+ },
+
edge : (side = 'bottom')=>{
const rotation = {
'bottom' : 0,
@@ -10,10 +20,10 @@ module.exports = {
'right' : 270
}[side];
return dedent`
- {{imageMaskEdge${_.random(1, 8)},--offset:0cm,--rotation:${rotation}
+ {{imageMaskEdge${_.random(1, 8)},--offset:0%,--rotation:${rotation}
{height:100%}
}}
- \n\n`;
},
diff --git a/themes/V3/Blank/style.less b/themes/V3/Blank/style.less
index 6dda308e9..38aa42f20 100644
--- a/themes/V3/Blank/style.less
+++ b/themes/V3/Blank/style.less
@@ -23,6 +23,9 @@ body {
break-inside : avoid;
display : inline-block;
width : 100%;
+ img {
+ z-index : 0;
+ }
}
.inline-block {
display : inline-block;
@@ -187,7 +190,6 @@ body {
height : 100%;
font-size : 120px;
text-transform : uppercase;
- color : black;
mix-blend-mode : overlay;
opacity : 30%;
transform : rotate(-45deg);
@@ -228,6 +230,7 @@ body {
.watercolor12 { --wc : @watercolor12; }
/* Image Masks */
+
[class*="imageMask"] {
position : absolute;
height : 200%;
@@ -251,7 +254,6 @@ body {
background-size : 20px;
z-index : -1;
transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
- transition : transform 2s;
& > p:has(img) {
position : absolute;
width : 50%;
@@ -259,7 +261,6 @@ body {
bottom : 50%;
left : 50%;
transform : translateX(-50%) translateY(50%) rotate(calc(-1deg * var(--rotation))) scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY)));
- transition : transform 2s;
}
& img {
position : absolute;
@@ -288,14 +289,66 @@ body {
}
}
- .imageMaskEdge1 { --wc : url(/assets/waterColorMasks/edge/0001.webp); }
- .imageMaskEdge2 { --wc : url(/assets/waterColorMasks/edge/0002.webp); }
- .imageMaskEdge3 { --wc : url(/assets/waterColorMasks/edge/0003.webp); }
- .imageMaskEdge4 { --wc : url(/assets/waterColorMasks/edge/0004.webp); }
- .imageMaskEdge5 { --wc : url(/assets/waterColorMasks/edge/0005.webp); }
- .imageMaskEdge6 { --wc : url(/assets/waterColorMasks/edge/0006.webp); }
- .imageMaskEdge7 { --wc : url(/assets/waterColorMasks/edge/0007.webp); }
- .imageMaskEdge8 { --wc : url(/assets/waterColorMasks/edge/0008.webp); }
+ .imageMaskEdge {
+ &1 { --wc : url(/assets/waterColorMasks/edge/0001.webp); }
+ &2 { --wc : url(/assets/waterColorMasks/edge/0002.webp); }
+ &3 { --wc : url(/assets/waterColorMasks/edge/0003.webp); }
+ &4 { --wc : url(/assets/waterColorMasks/edge/0004.webp); }
+ &5 { --wc : url(/assets/waterColorMasks/edge/0005.webp); }
+ &6 { --wc : url(/assets/waterColorMasks/edge/0006.webp); }
+ &7 { --wc : url(/assets/waterColorMasks/edge/0007.webp); }
+ &8 { --wc : url(/assets/waterColorMasks/edge/0008.webp); }
+ }
+
+ [class*="imageMaskCenter"] {
+ width : 100%;
+ height : 100%;
+ left : calc(var(--offsetX));
+ bottom : calc(var(--offsetY));
+ -webkit-mask-image : var(--wc), var(--revealer);
+ -webkit-mask-repeat : no-repeat;
+ -webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
+ -webkit-mask-position : 0% 0%;
+ mask-image : var(--wc), var(--revealer);
+ mask-repeat : no-repeat;
+ mask-size : 100% 100%; //Scale both dimensions to fit page size
+ mask-position : 50% 50%;
+ transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
+
+ & > p:has(img) {
+ position : absolute;
+ width : 100%;
+ height : 100%;
+ bottom : 0;
+ left : 0;
+ transform : unset;
+ transform : scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY)))
+ rotate(calc(-1deg * var(--rotation)))
+ translateX(calc(-1 * var(--offsetX)))
+ translateY(calc(1 * var(--offsetY)));
+ }
+ }
+
+ .imageMaskCenter {
+ &1 { --wc : url(/assets/waterColorMasks/center/0001.webp); }
+ &2 { --wc : url(/assets/waterColorMasks/center/0002.webp); }
+ &3 { --wc : url(/assets/waterColorMasks/center/0003.webp); }
+ &4 { --wc : url(/assets/waterColorMasks/center/0004.webp); }
+ &5 { --wc : url(/assets/waterColorMasks/center/0005.webp); }
+ &6 { --wc : url(/assets/waterColorMasks/center/0006.webp); }
+ &7 { --wc : url(/assets/waterColorMasks/center/0007.webp); }
+ &8 { --wc : url(/assets/waterColorMasks/center/0008.webp); }
+ &9 { --wc : url(/assets/waterColorMasks/center/0009.webp); }
+ &10 { --wc : url(/assets/waterColorMasks/center/0010.webp); }
+ &11 { --wc : url(/assets/waterColorMasks/center/0011.webp); }
+ &12 { --wc : url(/assets/waterColorMasks/center/0012.webp); }
+ &13 { --wc : url(/assets/waterColorMasks/center/0013.webp); }
+ &14 { --wc : url(/assets/waterColorMasks/center/0014.webp); }
+ &15 { --wc : url(/assets/waterColorMasks/center/0015.webp); }
+ &16 { --wc : url(/assets/waterColorMasks/center/0016.webp); }
+ &special { --wc : url(/assets/waterColorMasks/center/special.webp); }
+ }
+
[class*="imageMaskCorner"] {
height : 200%;
@@ -310,7 +363,7 @@ body {
mask-repeat : no-repeat;
mask-size : 100% 100%; //Scale both dimensions to fit page size
mask-position : 50% 50%;
- transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));;
+ transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
& > p:has(img) {
width : 50%;
height : 50%; //Complex transform below to handle mix of % and cm offsets
@@ -322,44 +375,45 @@ body {
translateY(calc(1 * var(--offsetY)));
}
}
-
- .imageMaskCorner1 { --wc : url(/assets/waterColorMasks/corner/0001.webp); }
- .imageMaskCorner2 { --wc : url(/assets/waterColorMasks/corner/0002.webp); }
- .imageMaskCorner3 { --wc : url(/assets/waterColorMasks/corner/0003.webp); }
- .imageMaskCorner4 { --wc : url(/assets/waterColorMasks/corner/0004.webp); }
- .imageMaskCorner5 { --wc : url(/assets/waterColorMasks/corner/0005.webp); }
- .imageMaskCorner6 { --wc : url(/assets/waterColorMasks/corner/0006.webp); }
- .imageMaskCorner7 { --wc : url(/assets/waterColorMasks/corner/0007.webp); }
- .imageMaskCorner8 { --wc : url(/assets/waterColorMasks/corner/0008.webp); }
- .imageMaskCorner9 { --wc : url(/assets/waterColorMasks/corner/0009.webp); }
- .imageMaskCorner10 { --wc : url(/assets/waterColorMasks/corner/0010.webp); }
- .imageMaskCorner11 { --wc : url(/assets/waterColorMasks/corner/0011.webp); }
- .imageMaskCorner12 { --wc : url(/assets/waterColorMasks/corner/0012.webp); }
- .imageMaskCorner13 { --wc : url(/assets/waterColorMasks/corner/0013.webp); }
- .imageMaskCorner14 { --wc : url(/assets/waterColorMasks/corner/0014.webp); }
- .imageMaskCorner15 { --wc : url(/assets/waterColorMasks/corner/0015.webp); }
- .imageMaskCorner16 { --wc : url(/assets/waterColorMasks/corner/0016.webp); }
- .imageMaskCorner17 { --wc : url(/assets/waterColorMasks/corner/0017.webp); }
- .imageMaskCorner18 { --wc : url(/assets/waterColorMasks/corner/0018.webp); }
- .imageMaskCorner19 { --wc : url(/assets/waterColorMasks/corner/0019.webp); }
- .imageMaskCorner20 { --wc : url(/assets/waterColorMasks/corner/0020.webp); }
- .imageMaskCorner21 { --wc : url(/assets/waterColorMasks/corner/0021.webp); }
- .imageMaskCorner22 { --wc : url(/assets/waterColorMasks/corner/0022.webp); }
- .imageMaskCorner23 { --wc : url(/assets/waterColorMasks/corner/0023.webp); }
- .imageMaskCorner24 { --wc : url(/assets/waterColorMasks/corner/0024.webp); }
- .imageMaskCorner25 { --wc : url(/assets/waterColorMasks/corner/0025.webp); }
- .imageMaskCorner26 { --wc : url(/assets/waterColorMasks/corner/0026.webp); }
- .imageMaskCorner27 { --wc : url(/assets/waterColorMasks/corner/0027.webp); }
- .imageMaskCorner28 { --wc : url(/assets/waterColorMasks/corner/0028.webp); }
- .imageMaskCorner29 { --wc : url(/assets/waterColorMasks/corner/0029.webp); }
- .imageMaskCorner30 { --wc : url(/assets/waterColorMasks/corner/0030.webp); }
- .imageMaskCorner31 { --wc : url(/assets/waterColorMasks/corner/0031.webp); }
- .imageMaskCorner32 { --wc : url(/assets/waterColorMasks/corner/0032.webp); }
- .imageMaskCorner33 { --wc : url(/assets/waterColorMasks/corner/0033.webp); }
- .imageMaskCorner34 { --wc : url(/assets/waterColorMasks/corner/0034.webp); }
- .imageMaskCorner35 { --wc : url(/assets/waterColorMasks/corner/0035.webp); }
- .imageMaskCorner36 { --wc : url(/assets/waterColorMasks/corner/0036.webp); }
- .imageMaskCorner37 { --wc : url(/assets/waterColorMasks/corner/0037.webp); }
+ .imageMaskCorner {
+ &1 { --wc : url(/assets/waterColorMasks/corner/0001.webp); }
+ &2 { --wc : url(/assets/waterColorMasks/corner/0002.webp); }
+ &3 { --wc : url(/assets/waterColorMasks/corner/0003.webp); }
+ &4 { --wc : url(/assets/waterColorMasks/corner/0004.webp); }
+ &5 { --wc : url(/assets/waterColorMasks/corner/0005.webp); }
+ &6 { --wc : url(/assets/waterColorMasks/corner/0006.webp); }
+ &7 { --wc : url(/assets/waterColorMasks/corner/0007.webp); }
+ &8 { --wc : url(/assets/waterColorMasks/corner/0008.webp); }
+ &9 { --wc : url(/assets/waterColorMasks/corner/0009.webp); }
+ &10 { --wc : url(/assets/waterColorMasks/corner/0010.webp); }
+ &11 { --wc : url(/assets/waterColorMasks/corner/0011.webp); }
+ &12 { --wc : url(/assets/waterColorMasks/corner/0012.webp); }
+ &13 { --wc : url(/assets/waterColorMasks/corner/0013.webp); }
+ &14 { --wc : url(/assets/waterColorMasks/corner/0014.webp); }
+ &15 { --wc : url(/assets/waterColorMasks/corner/0015.webp); }
+ &16 { --wc : url(/assets/waterColorMasks/corner/0016.webp); }
+ &17 { --wc : url(/assets/waterColorMasks/corner/0017.webp); }
+ &18 { --wc : url(/assets/waterColorMasks/corner/0018.webp); }
+ &19 { --wc : url(/assets/waterColorMasks/corner/0019.webp); }
+ &20 { --wc : url(/assets/waterColorMasks/corner/0020.webp); }
+ &21 { --wc : url(/assets/waterColorMasks/corner/0021.webp); }
+ &22 { --wc : url(/assets/waterColorMasks/corner/0022.webp); }
+ &23 { --wc : url(/assets/waterColorMasks/corner/0023.webp); }
+ &24 { --wc : url(/assets/waterColorMasks/corner/0024.webp); }
+ &25 { --wc : url(/assets/waterColorMasks/corner/0025.webp); }
+ &26 { --wc : url(/assets/waterColorMasks/corner/0026.webp); }
+ &27 { --wc : url(/assets/waterColorMasks/corner/0027.webp); }
+ &28 { --wc : url(/assets/waterColorMasks/corner/0028.webp); }
+ &29 { --wc : url(/assets/waterColorMasks/corner/0029.webp); }
+ &30 { --wc : url(/assets/waterColorMasks/corner/0030.webp); }
+ &31 { --wc : url(/assets/waterColorMasks/corner/0031.webp); }
+ &32 { --wc : url(/assets/waterColorMasks/corner/0032.webp); }
+ &33 { --wc : url(/assets/waterColorMasks/corner/0033.webp); }
+ &34 { --wc : url(/assets/waterColorMasks/corner/0034.webp); }
+ &35 { --wc : url(/assets/waterColorMasks/corner/0035.webp); }
+ &36 { --wc : url(/assets/waterColorMasks/corner/0036.webp); }
+ &37 { --wc : url(/assets/waterColorMasks/corner/0037.webp); }
+ }
}
//*****************************
diff --git a/themes/assets/assets.less b/themes/assets/assets.less
index f50799916..cdef32c7c 100644
--- a/themes/assets/assets.less
+++ b/themes/assets/assets.less
@@ -13,6 +13,10 @@
@naturalCritLogo : url('/assets/naturalCritLogo.svg');
@coverPageBanner : url('/assets/coverPageBanner.svg');
@horizontalRule : url('/assets/horizontalRule.svg');
+@partCoverHeaderPHB : url('/assets/partCoverHeaderPHB.png');
+@partCoverHeaderDMG : url('/assets/partCoverHeaderDMG.svg');
+@insideCoverMask : url('/assets/insideCoverMask.png');
+@backCover : url('/assets/backCover.png');
@scriptBorder : url('/assets/scriptBorder.png');
// Watercolor Images
diff --git a/themes/assets/backCover.png b/themes/assets/backCover.png
new file mode 100644
index 000000000..5c6a14d25
Binary files /dev/null and b/themes/assets/backCover.png differ
diff --git a/themes/assets/insideCoverMask.png b/themes/assets/insideCoverMask.png
new file mode 100644
index 000000000..d28a93f70
Binary files /dev/null and b/themes/assets/insideCoverMask.png differ
diff --git a/themes/assets/naturalCritLogo.svg b/themes/assets/naturalCritLogoRed.svg
similarity index 100%
rename from themes/assets/naturalCritLogo.svg
rename to themes/assets/naturalCritLogoRed.svg
diff --git a/themes/assets/naturalCritLogoWhite.svg b/themes/assets/naturalCritLogoWhite.svg
new file mode 100644
index 000000000..56b820776
--- /dev/null
+++ b/themes/assets/naturalCritLogoWhite.svg
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+ NaturalCritLogo
+
+
+
+
+
+
diff --git a/themes/assets/partCoverHeaderDMG.svg b/themes/assets/partCoverHeaderDMG.svg
new file mode 100644
index 000000000..b7defc541
--- /dev/null
+++ b/themes/assets/partCoverHeaderDMG.svg
@@ -0,0 +1 @@
+Asset 2
\ No newline at end of file
diff --git a/themes/assets/partCoverHeaderPHB.png b/themes/assets/partCoverHeaderPHB.png
new file mode 100644
index 000000000..f359668ba
Binary files /dev/null and b/themes/assets/partCoverHeaderPHB.png differ
diff --git a/themes/assets/waterColorMasks/center/0001.webp b/themes/assets/waterColorMasks/center/0001.webp
new file mode 100644
index 000000000..7e3d73476
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0001.webp differ
diff --git a/themes/assets/waterColorMasks/center/0002.webp b/themes/assets/waterColorMasks/center/0002.webp
new file mode 100644
index 000000000..d60bbeaf5
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0002.webp differ
diff --git a/themes/assets/waterColorMasks/center/0003.webp b/themes/assets/waterColorMasks/center/0003.webp
new file mode 100644
index 000000000..69d96c4cc
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0003.webp differ
diff --git a/themes/assets/waterColorMasks/center/0004.webp b/themes/assets/waterColorMasks/center/0004.webp
new file mode 100644
index 000000000..781de4fac
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0004.webp differ
diff --git a/themes/assets/waterColorMasks/center/0005.webp b/themes/assets/waterColorMasks/center/0005.webp
new file mode 100644
index 000000000..e6d14b48a
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0005.webp differ
diff --git a/themes/assets/waterColorMasks/center/0006.webp b/themes/assets/waterColorMasks/center/0006.webp
new file mode 100644
index 000000000..e4b606d68
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0006.webp differ
diff --git a/themes/assets/waterColorMasks/center/0007.webp b/themes/assets/waterColorMasks/center/0007.webp
new file mode 100644
index 000000000..1af90ce25
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0007.webp differ
diff --git a/themes/assets/waterColorMasks/center/0008.webp b/themes/assets/waterColorMasks/center/0008.webp
new file mode 100644
index 000000000..4487aff50
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0008.webp differ
diff --git a/themes/assets/waterColorMasks/center/0009.webp b/themes/assets/waterColorMasks/center/0009.webp
new file mode 100644
index 000000000..3524c76b8
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0009.webp differ
diff --git a/themes/assets/waterColorMasks/center/0010.webp b/themes/assets/waterColorMasks/center/0010.webp
new file mode 100644
index 000000000..ea7da913f
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0010.webp differ
diff --git a/themes/assets/waterColorMasks/center/0011.webp b/themes/assets/waterColorMasks/center/0011.webp
new file mode 100644
index 000000000..74bb72526
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0011.webp differ
diff --git a/themes/assets/waterColorMasks/center/0012.webp b/themes/assets/waterColorMasks/center/0012.webp
new file mode 100644
index 000000000..5ee3e668f
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0012.webp differ
diff --git a/themes/assets/waterColorMasks/center/0013.webp b/themes/assets/waterColorMasks/center/0013.webp
new file mode 100644
index 000000000..cdc6cec28
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0013.webp differ
diff --git a/themes/assets/waterColorMasks/center/0014.webp b/themes/assets/waterColorMasks/center/0014.webp
new file mode 100644
index 000000000..0801848c5
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0014.webp differ
diff --git a/themes/assets/waterColorMasks/center/0015.webp b/themes/assets/waterColorMasks/center/0015.webp
new file mode 100644
index 000000000..90f36fe21
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0015.webp differ
diff --git a/themes/assets/waterColorMasks/center/0016.webp b/themes/assets/waterColorMasks/center/0016.webp
new file mode 100644
index 000000000..0d7e68597
Binary files /dev/null and b/themes/assets/waterColorMasks/center/0016.webp differ
diff --git a/themes/assets/waterColorMasks/center/special.webp b/themes/assets/waterColorMasks/center/special.webp
new file mode 100644
index 000000000..1a3f6240a
Binary files /dev/null and b/themes/assets/waterColorMasks/center/special.webp differ
diff --git a/themes/fonts/5e/Martel Sans Black.woff2 b/themes/fonts/5e/Martel Sans Black.woff2
new file mode 100644
index 000000000..44580467d
Binary files /dev/null and b/themes/fonts/5e/Martel Sans Black.woff2 differ
diff --git a/themes/fonts/5e/Nodesto Caps Wide.woff2 b/themes/fonts/5e/Nodesto Caps Wide.woff2
new file mode 100644
index 000000000..d50a19915
Binary files /dev/null and b/themes/fonts/5e/Nodesto Caps Wide.woff2 differ
diff --git a/themes/fonts/5e/Scaly Sans Caps.woff2 b/themes/fonts/5e/Scaly Sans Caps.woff2
index 38ee910c8..95231a609 100644
Binary files a/themes/fonts/5e/Scaly Sans Caps.woff2 and b/themes/fonts/5e/Scaly Sans Caps.woff2 differ
diff --git a/themes/fonts/5e/fonts.less b/themes/fonts/5e/fonts.less
index a83399567..8f089b51c 100644
--- a/themes/fonts/5e/fonts.less
+++ b/themes/fonts/5e/fonts.less
@@ -107,6 +107,13 @@
font-style: italic;
}
+@font-face {
+ font-family: NodestoCapsWide;
+ src: url('../../../fonts/5e/Nodesto Caps Wide.woff2');
+ font-weight: normal;
+ font-style: normal
+}
+
@font-face {
font-family: Overpass;
src: url('../../../fonts/5e/Overpass Medium.woff2');