0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-26 13:52:38 +00:00

Merge branch 'master' into Issue_1958

This commit is contained in:
David Bolack
2024-01-25 00:18:47 -06:00
committed by GitHub
24 changed files with 15980 additions and 15634 deletions

View File

@@ -45,12 +45,13 @@ let rawPages = [];
const BrewRenderer = (props)=>{
props = {
text : '',
style : '',
renderer : 'legacy',
theme : '5ePHB',
lang : '',
errors : [],
text : '',
style : '',
renderer : 'legacy',
theme : '5ePHB',
lang : '',
errors : [],
currentEditorPage : 0,
...props
};
@@ -94,6 +95,9 @@ const BrewRenderer = (props)=>{
if(Math.abs(index - state.viewablePageNumber) <= 3)
return true;
if(index + 1 == props.currentEditorPage)
return true;
return false;
};
@@ -143,6 +147,9 @@ const BrewRenderer = (props)=>{
if(props.errors && props.errors.length)
return renderedPages;
if(rawPages.length != renderedPages.length) // Re-render all pages when page count changes
renderedPages.length = 0;
_.forEach(rawPages, (page, index)=>{
if((shouldRender(index) || !renderedPages[index]) && typeof window !== 'undefined'){
renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range

View File

@@ -3,9 +3,9 @@
.brewRenderer {
will-change : transform;
overflow-y : scroll;
.pages {
:where(.pages) {
margin : 30px 0px;
& > .page {
& > :where(.page) {
width : 215.9mm;
height : 279.4mm;
margin-right : auto;

View File

@@ -180,7 +180,7 @@ const Editor = createClass({
// Highlight injectors {style}
if(line.includes('{') && line.includes('}')){
const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm;
const regex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm;
let match;
while ((match = regex.exec(line)) != null) {
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' });
@@ -188,7 +188,7 @@ const Editor = createClass({
}
// Highlight inline spans {{content}}
if(line.includes('{{') && line.includes('}}')){
const regex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g;
const regex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g;
let match;
let blockCount = 0;
while ((match = regex.exec(line)) != null) {
@@ -207,7 +207,7 @@ const Editor = createClass({
// Highlight block divs {{\n Content \n}}
let endCh = line.length+1;
const match = line.match(/^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *$|^ *}}$/);
const match = line.match(/^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/);
if(match)
endCh = match.index+match[0].length;
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });

View File

@@ -2,7 +2,7 @@
.metadataEditor{
position : absolute;
z-index : 10000;
z-index : 5;
box-sizing : border-box;
width : 100%;
padding : 25px;

View File

@@ -74,6 +74,7 @@ const Snippetbar = createClass({
}
},
mergeCustomizer : function(valueA, valueB, key) {
if(key == 'snippets') {
const result = _.reverse(_.unionBy(_.reverse(valueB), _.reverse(valueA), 'name')); // Join snippets together, with preference for the current theme over the base theme
@@ -102,10 +103,12 @@ const Snippetbar = createClass({
this.props.onInject(injectedText);
},
toggleThemeSelector : function(){
this.setState({
themeSelector : !this.state.themeSelector
});
toggleThemeSelector : function(e){
if(e.target.tagName != 'SELECT'){
this.setState({
themeSelector : !this.state.themeSelector
});
}
},
changeTheme : function(e){
@@ -119,7 +122,7 @@ const Snippetbar = createClass({
renderThemeSelector : function(){
return <div className='themeSelector'>
<select value={this.props.currentEditorTheme} onChange={this.changeTheme} onMouseDown={(this.changeTheme)}>
<select value={this.props.currentEditorTheme} onChange={this.changeTheme} >
{EditorThemes.map((theme, key)=>{
return <option key={key} value={theme}>{theme}</option>;
})}
@@ -176,8 +179,9 @@ const Snippetbar = createClass({
<div className={`editorTool editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
</div>
{this.state.themeSelector && this.renderThemeSelector()}
<div className='divider'></div>
<div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}>
@@ -228,7 +232,7 @@ const SnippetGroup = createClass({
return _.map(snippets, (snippet)=>{
return <div className='snippet' key={snippet.name} onClick={(e)=>this.handleSnippetClick(e, snippet)}>
<i className={snippet.icon} />
<span className='name'>{snippet.name}</span>
<span className='name'title={snippet.name}>{snippet.name}</span>
{snippet.experimental && <span className='beta'>beta</span>}
{snippet.subsnippets && <>
<i className='fas fa-caret-right'></i>

View File

@@ -1,177 +1,190 @@
@import (less) './client/icons/customIcons.less';
.snippetBar{
@import (less) '././././themes/fonts/5e/fonts.less';
.snippetBar {
@menuHeight : 25px;
position : relative;
height : @menuHeight;
background-color : #ddd;
.editors{
color : black;
background-color : #DDDDDD;
.editors {
position : absolute;
display : flex;
top : 0px;
right : 0px;
height : @menuHeight;
display : flex;
justify-content : space-between;
&>div{
height : @menuHeight;
height : @menuHeight;
& > div {
width : @menuHeight;
cursor : pointer;
height : @menuHeight;
line-height : @menuHeight;
text-align : center;
&:hover,&.selected{
background-color : #999;
}
&.text{
cursor : pointer;
&:hover,&.selected { background-color : #999999; }
&.text {
.tooltipLeft('Brew Editor');
}
&.style{
&.style {
.tooltipLeft('Style Editor');
}
&.meta{
&.meta {
.tooltipLeft('Properties');
}
&.undo{
&.undo {
.tooltipLeft('Undo');
font-size : 0.75em;
color : grey;
&.active{
color : black;
}
&.active { color : inherit; }
}
&.redo{
&.redo {
.tooltipLeft('Redo');
font-size : 0.75em;
color : grey;
&.active{
color : black;
}
&.active { color : inherit; }
}
&.foldAll{
&.foldAll {
.tooltipLeft('Fold All');
font-size : 0.75em;
color : grey;
&.active{
color : black;
}
color : inherit;
}
&.unfoldAll{
&.unfoldAll {
.tooltipLeft('Unfold All');
font-size : 0.75em;
color : grey;
&.active{
color : black;
}
color : inherit;
}
&.editorTheme{
&.editorTheme {
.tooltipLeft('Editor Themes');
font-size : 0.75em;
color : black;
&.active{
color : white;
background-color: black;
&.active {
position : relative;
background-color : #999999;
}
}
&.divider {
background: linear-gradient(#000, #000) no-repeat center/1px 100%;
width: 5px;
&:hover{
background-color: inherit;
}
width : 5px;
background : linear-gradient(currentColor, currentColor) no-repeat center/1px 100%;
&:hover { background-color : inherit; }
}
}
.themeSelector{
position: absolute;
left: -65px;
top: 30px;
z-index: 999;
width: 170px;
background-color: black;
border-radius: 5px;
.themeSelector {
position : absolute;
top : 25px;
right : 0;
z-index : 10;
display : flex;
align-items : center;
justify-content : center;
width : 170px;
height : inherit;
background-color : inherit;
}
}
.snippetBarButton{
height : @menuHeight;
line-height : @menuHeight;
.snippetBarButton {
display : inline-block;
height : @menuHeight;
padding : 0px 5px;
font-weight : 800;
font-size : 0.625em;
font-weight : 800;
line-height : @menuHeight;
text-transform : uppercase;
cursor : pointer;
&:hover, &.selected{
background-color : #999;
}
i{
vertical-align : middle;
&:hover, &.selected { background-color : #999999; }
i {
margin-right : 3px;
font-size : 1.4em;
vertical-align : middle;
}
}
.toggleMeta{
position : absolute;
top : 0px;
right : 0px;
border-left : 1px solid black;
.tooltipLeft("Edit Brew Properties");
.toggleMeta {
position : absolute;
top : 0px;
right : 0px;
border-left : 1px solid black;
.tooltipLeft('Edit Brew Properties');
}
.snippetGroup{
border-right : 1px solid black;
&:hover{
&>.dropdown{
visibility : visible;
}
.snippetGroup {
border-right : 1px solid currentColor;
&:hover {
& > .dropdown { visibility : visible; }
}
.dropdown{
.dropdown {
position : absolute;
top : 100%;
visibility : hidden;
z-index : 1000;
margin-left : -5px;
padding : 0px;
background-color : #ddd;
.snippet{
position: relative;
.animate(background-color);
margin-left : -5px;
visibility : hidden;
background-color : #DDDDDD;
.snippet {
position : relative;
display : flex;
align-items : center;
min-width : max-content;
padding : 5px;
cursor : pointer;
font-size : 10px;
i{
cursor : pointer;
.animate(background-color);
i {
height : 1.2em;
margin-right : 8px;
font-size : 1.2em;
height : 1.2em;
&~i{
margin-right: 0;
margin-left: 5px;
& ~ i {
margin-right : 0;
margin-left : 5px;
}
/* Fonts */
&.font {
height : auto;
&::before {
font-size : 1.4em;
content : 'ABC';
}
&.OpenSans {font-family : 'OpenSans';}
&.CodeBold {font-family : 'CodeBold';}
&.CodeLight {font-family : 'CodeLight';}
&.ScalySansRemake {font-family : 'ScalySansRemake';}
&.BookInsanityRemake {font-family : 'BookInsanityRemake';}
&.MrEavesRemake {font-family : 'MrEavesRemake';}
&.SolberaImitationRemake {font-family : 'SolberaImitationRemake';}
&.ScalySansSmallCapsRemake {font-family : 'ScalySansSmallCapsRemake';}
&.WalterTurncoat {font-family : 'WalterTurncoat';}
&.Lato {font-family : 'Lato';}
&.Courier {font-family : 'Courier';}
&.NodestoCapsCondensed {font-family : 'NodestoCapsCondensed';}
&.Overpass {font-family : 'Overpass';}
&.Davek {font-family : 'Davek';}
&.Iokharic {font-family : 'Iokharic';}
&.Rellanic {font-family : 'Rellanic';}
&.TimesNewRoman {font-family : 'Times New Roman';}
}
}
.name {
margin-right : auto;
}
.name { margin-right : auto; }
.beta {
color : white;
padding : 4px 6px;
line-height : 1em;
margin-left : 5px;
align-self : center;
padding : 4px 6px;
margin-left : 5px;
font-family : monospace;
line-height : 1em;
color : white;
background : grey;
border-radius : 12px;
font-family : monospace;
}
&:hover{
background-color : #999;
&>.dropdown{
&:hover {
background-color : #999999;
& > .dropdown {
visibility : visible;
&.side {
left: 100%;
top: 0%;
margin-left:0;
box-shadow: -1px 1px 2px 0px #999;
top : 0%;
left : 100%;
margin-left : 0;
box-shadow : -1px 1px 2px 0px #999999;
}
}
}
}
}
}
}
}

View File

@@ -21,10 +21,11 @@ const ErrorNavItem = createClass({
this.props.parent.setState(state);
};
const error = this.props.error;
const response = error.response;
const status = response.status;
const message = response.body?.message;
const error = this.props.error;
const response = error.response;
const status = response.status;
const HBErrorCode = response.body?.HBErrorCode;
const message = response.body?.message;
let errMsg = '';
try {
errMsg += `${error.toString()}\n\n`;
@@ -40,7 +41,9 @@ const ErrorNavItem = createClass({
{message ?? 'Conflict: please refresh to get latest changes'}
</div>
</Nav.item>;
} else if(status === 412) {
}
if(status === 412) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
@@ -48,6 +51,36 @@ const ErrorNavItem = createClass({
</div>
</Nav.item>;
}
if(HBErrorCode === '04') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
You are no longer signed in as an author of
this brew! Were you signed out from a different
window? Visit our log in page, then try again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
if(response.body?.errors?.[0].reason == 'storageQuotaExceeded') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Can't save because your Google Drive seems to be full!
</div>
</Nav.item>;
}
if(response.req.url.match(/^\/api.*Google.*$/m)){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
@@ -57,6 +90,7 @@ const ErrorNavItem = createClass({
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>

View File

@@ -4,6 +4,7 @@ const Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){
return <Nav.item
href='/new'
newTab={true}
color='purple'
icon='fas fa-plus-square'>
new

View File

@@ -220,6 +220,7 @@ const ListPage = createClass({
render : function(){
return <div className='listPage sitePage'>
{/*<style>@layer V3_5ePHB, bundle;</style>*/}
<link href='/themes/V3/Blank/style.css' rel='stylesheet'/>
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet'/>
{this.props.navItems}
{this.renderSortOptions()}

View File

@@ -2,17 +2,18 @@
.noColumns(){
column-count : auto;
column-fill : auto;
column-gap : auto;
column-gap : normal;
column-width : auto;
-webkit-column-count : auto;
-moz-column-count : auto;
-webkit-column-width : auto;
-moz-column-width : auto;
-webkit-column-gap : auto;
-moz-column-gap : auto;
-webkit-column-gap : normal;
-moz-column-gap : normal;
height : auto;
min-height : 279.4mm;
margin : 20px auto;
contain : unset;
}
.listPage{
.content{

View File

@@ -50,7 +50,8 @@ const EditPage = createClass({
url : '',
autoSave : true,
autoSaveWarning : false,
unsavedTime : new Date()
unsavedTime : new Date(),
currentEditorPage : 0
};
},
savedBrew : null,
@@ -109,9 +110,10 @@ const EditPage = createClass({
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
this.setState((prevState)=>({
brew : { ...prevState.brew, text: text },
isPending : true,
htmlErrors : htmlErrors
brew : { ...prevState.brew, text: text },
isPending : true,
htmlErrors : htmlErrors,
currentEditorPage : this.refs.editor.getCurrentPage()
}), ()=>{if(this.state.autoSave) this.trySave();});
},
@@ -405,6 +407,7 @@ const EditPage = createClass({
theme={this.state.brew.theme}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
currentEditorPage={this.state.currentEditorPage}
/>
</SplitPane>
</div>

30032
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.10.0",
"engines": {
"npm": "^10.2.x",
"npm": "^10.2.x",
"node": "^20.8.x"
},
"repository": {
@@ -79,11 +79,11 @@
]
},
"dependencies": {
"@babel/core": "^7.23.5",
"@babel/plugin-transform-runtime": "^7.23.4",
"@babel/preset-env": "^7.23.5",
"@babel/core": "^7.23.7",
"@babel/plugin-transform-runtime": "^7.23.7",
"@babel/preset-env": "^7.23.8",
"@babel/preset-react": "^7.23.3",
"@googleapis/drive": "^8.4.0",
"@googleapis/drive": "^8.6.0",
"body-parser": "^1.20.2",
"classnames": "^2.3.2",
"codemirror": "^5.65.6",
@@ -98,26 +98,26 @@
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "5.1.1",
"marked-extended-tables": "^1.0.7",
"marked": "11.1.1",
"marked-extended-tables": "^1.0.8",
"marked-gfm-heading-id": "^3.1.2",
"marked-smartypants-lite": "^1.0.1",
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.4",
"mongoose": "^8.0.2",
"moment": "^2.30.1",
"mongoose": "^8.1.0",
"nanoid": "3.3.4",
"nconf": "^0.12.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-frame-component": "^4.1.3",
"react-router-dom": "6.20.1",
"react-router-dom": "6.21.3",
"sanitize-filename": "1.6.3",
"superagent": "^8.1.2",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
},
"devDependencies": {
"eslint": "^8.55.0",
"eslint-plugin-jest": "^27.6.0",
"eslint": "^8.56.0",
"eslint-plugin-jest": "^27.6.3",
"eslint-plugin-react": "^7.33.2",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
@@ -126,6 +126,6 @@
"stylelint-config-recess-order": "^4.4.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3",
"supertest": "^6.3.3"
"supertest": "^6.3.4"
}
}

View File

@@ -26,7 +26,6 @@
"codemirror/addon/edit/trailingspace.js",
"codemirror/addon/selection/active-line.js",
"moment",
"superagent",
"marked"
"superagent"
]
}

View File

@@ -476,9 +476,18 @@ const getPureError = (error)=>{
};
app.use(async (err, req, res, next)=>{
const status = err.status || err.code || 500;
err.originalUrl = req.originalUrl;
console.error(err);
if(err.originalUrl?.startsWith('/api/')) {
// console.log('API error');
res.status(err.status || err.response?.status || 500).send(err);
return;
}
// console.log('non-API error');
const status = err.status || err.code || 500;
req.ogMeta = { ...defaultMetaTags,
title : 'Error Page',
description : 'Something went wrong!'

View File

@@ -7,7 +7,7 @@ const cx = require('classnames');
const closeTag = require('./close-tag');
let CodeMirror;
if(typeof navigator !== 'undefined'){
if(typeof window !== 'undefined'){
CodeMirror = require('codemirror');
//Language Modes

View File

@@ -28,13 +28,35 @@ renderer.paragraph = function(text){
return `<p>${text}</p>\n`;
};
//Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (href, title, text) {
let self = false;
if(href[0] == '#') {
self = true;
}
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
if(href === null) {
return text;
}
let out = `<a href="${escape(href)}"`;
if(title) {
out += ` title="${title}"`;
}
if(self) {
out += ' target="_self"';
}
out += `>${text}</a>`;
return out;
};
const mustacheSpans = {
name : 'mustacheSpans',
level : 'inline', // Is this a block-level or inline-level tokenizer?
start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token
const inlineRegex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g;
const inlineRegex = /{{(?=((?:[:=](?:"['\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *|}}/g;
const match = completeSpan.exec(src);
if(match) {
//Find closing delimiter
@@ -45,7 +67,7 @@ const mustacheSpans = {
let delim;
while (delim = inlineRegex.exec(match[0])) {
if(!tags) {
tags = ` ${processStyleTags(delim[0].substring(2))}`;
tags = `${processStyleTags(delim[0].substring(2))}`;
endTags = delim[0].length;
}
if(delim[0].startsWith('{{')) {
@@ -84,7 +106,7 @@ const mustacheDivs = {
start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token
const blockRegex = /^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *$|^ *}}$/gm;
const blockRegex = /^ *{{(?=((?:[:=](?:"['\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm;
const match = completeBlock.exec(src);
if(match) {
//Find closing delimiter
@@ -95,8 +117,8 @@ const mustacheDivs = {
let delim;
while (delim = blockRegex.exec(match[0])?.[0].trim()) {
if(!tags) {
tags = ` ${processStyleTags(delim.substring(2))}`;
endTags = delim.length;
tags = `${processStyleTags(delim.substring(2))}`;
endTags = delim.length + src.indexOf(delim);
}
if(delim.startsWith('{{')) {
blockCount++;
@@ -132,14 +154,14 @@ const mustacheInjectInline = {
level : 'inline',
start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/g;
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/g;
const match = inlineRegex.exec(src);
if(match) {
const lastToken = tokens[tokens.length - 1];
if(!lastToken || lastToken.type == 'mustacheInjectInline')
return false;
const tags = ` ${processStyleTags(match[1])}`;
const tags = `${processStyleTags(match[1])}`;
lastToken.originalType = lastToken.type;
lastToken.type = 'mustacheInjectInline';
lastToken.tags = tags;
@@ -167,7 +189,7 @@ const mustacheInjectBlock = {
level : 'block',
start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/ym;
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/ym;
const match = inlineRegex.exec(src);
if(match) {
const lastToken = tokens[tokens.length - 1];
@@ -175,7 +197,7 @@ const mustacheInjectBlock = {
return false;
lastToken.originalType = 'mustacheInjectBlock';
lastToken.tags = ` ${processStyleTags(match[1])}`;
lastToken.tags = `${processStyleTags(match[1])}`;
return {
type : 'mustacheInjectBlock', // Should match "name" above
raw : match[0], // Text to consume from the source
@@ -271,28 +293,6 @@ Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, mangle: false });
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());
//Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (href, title, text) {
let self = false;
if(href[0] == '#') {
self = true;
}
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
if(href === null) {
return text;
}
let out = `<a href="${escape(href)}"`;
if(title) {
out += ` title="${title}"`;
}
if(self) {
out += ' target="_self"';
}
out += `>${text}</a>`;
return out;
};
const nonWordAndColonTest = /[^\w:]/g;
const cleanUrl = function (sanitize, base, href) {
if(sanitize) {
@@ -354,16 +354,19 @@ const voidTags = new Set([
]);
const processStyleTags = (string)=>{
//split tags up. quotes can only occur right after colons.
//split tags up. quotes can only occur right after : or =.
//TODO: can we simplify to just split on commas?
const tags = string.match(/(?:[^, ":]+|:(?:"[^"]*"|))+/g);
const tags = string.match(/(?:[^, ":=]+|[:=](?:"[^"]*"|))+/g);
if(!tags) return '"';
const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0];
const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('=')));
const attributes = _.remove(tags, (tag)=>(tag.includes('='))).map((tag)=>tag.replace(/="?([^"]*)"?/g, '="$1"'));
const styles = tags?.length ? tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()) : [];
const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0];
const classes = _.remove(tags, (tag)=>!tag.includes(':'));
const styles = tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;'));
return `${classes.join(' ')}" ${id ? `id="${id}"` : ''} ${styles.length ? `style="${styles.join(' ')}"` : ''}`;
return `${classes?.length ? ` ${classes.join(' ')}` : ''}"` +
`${id ? ` id="${id}"` : ''}` +
`${styles?.length ? ` style="${styles.join(' ')}"` : ''}` +
`${attributes?.length ? ` ${attributes.join(' ')}` : ''}`;
};
module.exports = {

View File

@@ -13,137 +13,134 @@ String.prototype.trimReturns = function(){
// 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() {
it('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('<span class="inline-block">text</span>');
});
it.failing('Renders a mustache span with text only, but with spaces', function() {
it('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('<span class="inline-block">this is a text</span>');
});
it.failing('Renders an empty mustache span', function() {
it('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('<span class="inline-block"></span>');
});
it.failing('Renders a mustache span with just a space', function() {
it('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('<span class="inline-block"></span>');
});
it.failing('Renders a mustache span with a few spaces only', function() {
it('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('<span class="inline-block"></span>');
});
it.failing('Renders a mustache span with text and class', function() {
it('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('<span class="inline-block my-class">text</span>');
});
it.failing('Renders a mustache span with text and two classes', function() {
it('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('<span class="inline-block my-class my-class2">text</span>');
});
it.failing('Renders a mustache span with text with spaces and class', function() {
it('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('<span class="inline-block my-class">this is a text</span>');
});
it.failing('Renders a mustache span with text and id', function() {
it('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('<span class="inline-block" id="my-span">text</span>');
});
it.failing('Renders a mustache span with text and two ids', function() {
it('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('<span class="inline-block" id="my-span">text</span>');
});
it.failing('Renders a mustache span with text and css property', function() {
it('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('<span class="inline-block" style="color:red;">text</span>');
});
it.failing('Renders a mustache span with text and two css properties', function() {
it('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('<span class="inline-block" style="color:red; padding:5px;">text</span>');
});
it.failing('Renders a mustache span with text and css property which contains quotes', function() {
it('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('<span class="inline-block" style="font-family:trebuchet ms;">text</span>');
});
it.failing('Renders a mustache span with text and two css properties which contains quotes', function() {
it('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('<span class="inline-block" style="font-family:trebuchet ms; padding:5px 10px;">text</span>');
});
it.failing('Renders a mustache span with text with quotes and css property which contains quotes', function() {
it('Renders a mustache span with text with quotes and css property which contains double 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('<span class="inline-block" style="font-family:trebuchet ms;">text “with quotes”</span>');
});
it('Renders a mustache span with text with quotes and css property which contains double and simple quotes', function() {
const source = `{{--stringVariable:"'string'" text "with quotes"}}`;
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text “with quotes”</span>`);
});
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('<span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;">text</span>');
});
it('Renders a span with added attributes', function() {
const source = 'Text and {{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e, text}} and more text!';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Text and <span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e">text</span> and more text!</p>\n');
});
});
// BLOCK SYNTAX
describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
it.failing('Renders a div with text only', function() {
it('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(`<div class="block"><p>text</p></div>`);
});
it.failing('Renders an empty div', function() {
it('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(`<div class="block"></div>`);
});
@@ -151,52 +148,62 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{
}}`;
const rendered = Markdown.render(source).trimReturns();
// this actually renders in HB as '{{ }}'...
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>{{}}</p>`);
});
it.failing('Renders a div with a single class', function() {
it('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(`<div class="block cat"></div>`);
});
it.failing('Renders a div with a single class and text', function() {
it('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(`<div class="block cat"><p>Sample text.</p></div>`);
});
it.failing('Renders a div with two classes and text', function() {
it('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(`<div class="block cat dog"><p>Sample text.</p></div>`);
});
it.failing('Renders a div with a style and text', function() {
it('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(`<div class="block" style="color:red;"><p>Sample text.</p></div>`);
});
it.failing('Renders a div with a class, style and text', function() {
it('Renders a div with a style that has a string variable, and text', function() {
const source = dedent`{{--stringVariable:"'string'"
Sample text.
}}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
});
it('Renders a div with a style that has a string variable, and text', function() {
const source = dedent`{{--stringVariable:"'string'"
Sample text.
}}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
});
it('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(`<div class="block cat" style="color:red;"><p>Sample text.</p></div>`);
});
@@ -208,14 +215,27 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" id="dog" style="color:red;"><p>Sample text.</p></div>`);
});
it.failing('Renders a div with a single ID', function() {
it('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(`<div class="block" id="cat"><p>Sample text.</p></div>`);
});
it('Renders a div with an ID, class, style and text, and a variable assignment', function() {
const source = dedent`{{color:red,cat,#dog,a="b and c",d="e"
Sample text.
}}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class=\"block cat\" id=\"dog\" style=\"color:red;\" a=\"b and c\" d=\"e\"><p>Sample text.</p></div>`);
});
it('Renders a div with added attributes', function() {
const source = '{{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e\nText and text and more text!\n}}\n';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e"><p>Text and text and more text!</p>\n</div>');
});
});
// MUSTACHE INJECTION SYNTAX
@@ -235,12 +255,24 @@ describe('Injection: When an injection tag follows an element', ()=>{
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block ClassName">text</span>');
});
it.failing('Renders a span "text" with injected attribute', function() {
const source = '{{ text}}{a="b and c"}';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span a="b and c" class="inline-block ">text</span>');
});
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('<span class="inline-block" style="color:red;">text</span>');
});
it.failing('Renders a span "text" with injected style using a string variable', function() {
const source = `{{ text}}{--stringVariable:"'string'"}`;
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text</span>`);
});
it.failing('Renders a span "text" with two injected styles', function() {
const source = '{{ text}}{color:red,background:blue}';
const rendered = Markdown.render(source);
@@ -270,6 +302,12 @@ describe('Injection: When an injection tag follows an element', ()=>{
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><span class="inline-block" style="color:red;">text</span>{background:blue}</p>');
});
it('Renders an image with added attributes', function() {
const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img class="" style="position:absolute; bottom:20px; left:130px; width:220px;" a="b and c" d="e" src="https://i.imgur.com/hMna6G0.png" alt="homebrew mug"></p>`);
});
});
describe('and that element is a block', ()=>{
@@ -297,7 +335,16 @@ describe('Injection: When an injection tag follows an element', ()=>{
}}
{color:red,background:blue}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red; background:blue;"><p>text</p></div>');
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red; background:blue"><p>text</p></div>`);
});
it.failing('renders a div "text" with injected variable string', function() {
const source = dedent`{{
text
}}
{--stringVariable:"'string'"}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string'"><p>text</p></div>`);
});
it.failing('renders an h2 header "text" with injected class name', function() {

View File

@@ -42,8 +42,8 @@ module.exports = function(classname){
#### Equipment
You start with the following equipment, in addition to the equipment granted by your background:
- *(a)* a martial weapon and a shield or *(b)* two martial weapons
- *(a)* five javelins or *(b)* any simple melee weapon
- (*a*) a martial weapon and a shield or (*b*) two martial weapons
- (*a*) five javelins or (*b*) any simple melee weapon
- ${_.sample(['10 lint fluffs', '1 button', 'a cherished lost sock'])}
`;
};

View File

@@ -1,5 +1,6 @@
@import (less) './themes/fonts/5e/fonts.less';
@import (less) './themes/assets/assets.less';
@import (less) './themes/fonts/icon fonts/font-icons.less';
:root {
//Colors
@@ -14,16 +15,13 @@
--HB_Color_Footnotes : #C9AD6A; // Gold
}
@page { margin : 0; }
body { counter-reset : phb-page-numbers; }
* { -webkit-print-color-adjust : exact; }
.useSansSerif() {
font-family : 'ScalySansRemake';
font-size : 0.318cm;
line-height : 1.2em;
p,dl,ul,ol { line-height : 1.2em; }
ul, ol { padding-left : 1em; }
em { font-style : italic; }
em { font-style : italic; }
strong {
font-weight : 800;
letter-spacing : -0.02em;
@@ -42,55 +40,31 @@ body { counter-reset : phb-page-numbers; }
-webkit-column-gap : 0.9cm;
-moz-column-gap : 0.9cm;
}
.columnWrapper {
column-gap : inherit;
max-height : 100%;
column-span : all;
columns : inherit;
}
.page {
.useColumns();
position : relative;
z-index : 15;
box-sizing : border-box;
width : 215.9mm;
height : 279.4mm;
padding : 1.4cm 1.9cm 1.7cm;
overflow : hidden;
font-family : 'BookInsanityRemake';
font-size : 0.34cm;
counter-increment : phb-page-numbers;
background-color : var(--HB_Color_Background);
background-image : @backgroundImage;
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
font-family : 'BookInsanityRemake';
font-size : 0.34cm;
background-image : @backgroundImage;
}
//*****************************
// *****************************
// * BASE
// *****************************/
// *****************************/
.page {
p {
display : block;
line-height : 1.25em;
overflow-wrap : break-word; //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS
& + * { margin-top : 0.325cm; }
line-height : 1.25em;
& + * { margin-top : 0.325cm; } //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS
& + p { margin-top : 0; }
}
ul {
padding-left : 1.4em;
margin-bottom : 0.8em;
line-height : 1.25em;
list-style-position : outside;
list-style-type : disc;
padding-left : 1.4em;
margin-bottom : 0.8em;
line-height : 1.25em;
}
ol {
padding-left : 1.4em;
margin-bottom : 0.8em;
line-height : 1.25em;
list-style-position : outside;
list-style-type : decimal;
padding-left : 1.4em;
margin-bottom : 0.8em;
line-height : 1.25em;
}
//Indents after p or lists
p + p, ul + p, ol + p { text-indent : 1em; }
@@ -99,24 +73,12 @@ body { counter-reset : phb-page-numbers; }
font-weight : bold;
letter-spacing : -0.02em;
}
em { font-style : italic; }
sup {
font-size : smaller;
line-height : 0;
vertical-align : super;
}
sub {
font-size : smaller;
line-height : 0;
vertical-align : sub;
}
//*****************************
// *****************************
// * HEADERS
// *****************************/
h1,h2,h3,h4 {
font-family : 'MrEavesRemake';
font-weight : 800;
color : var(--HB_Color_HeaderText);
font-family : 'MrEavesRemake';
color : var(--HB_Color_HeaderText);
}
h1 {
margin-bottom : 0.18cm; //Margin-bottom only because this is WIDE
@@ -145,8 +107,8 @@ body { counter-reset : phb-page-numbers; }
h2 {
//margin-top : 0px; //Font is misaligned. Shift up slightly
//margin-bottom : 0.05cm;
font-size : 0.75cm;
line-height : 0.988em; //Font is misaligned. Shift up slightly
font-size : 0.75cm;
line-height : 0.988em; //Font is misaligned. Shift up slightly
}
h3 {
//margin-top : -0.1cm; //Font is misaligned. Shift up slightly
@@ -162,8 +124,8 @@ body { counter-reset : phb-page-numbers; }
h4 {
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly
//margin-bottom : 0.02cm;
font-size : 0.458cm;
line-height : 0.971em; //Font is misaligned. Shift up slightly
font-size : 0.458cm;
line-height : 0.971em; //Font is misaligned. Shift up slightly
& + * { margin-top : 0.09cm; }
}
* + h4 {
@@ -172,19 +134,17 @@ body { counter-reset : phb-page-numbers; }
h5 {
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly
//margin-bottom : 0.02cm;
font-family : 'ScalySansSmallCapsRemake';
font-size : 0.423cm;
font-weight : 900;
line-height : 0.951em; //Font is misaligned. Shift up slightly
font-family : 'ScalySansSmallCapsRemake';
font-size : 0.423cm;
line-height : 0.951em; //Font is misaligned. Shift up slightly
& + * { margin-top : 0.2cm; }
}
//*****************************
// *****************************
// * TABLE
// *****************************/
table {
.useSansSerif();
width : 100%;
line-height : 16px;
line-height : 16px;
& + * { margin-top : 0.325cm; }
thead {
display : table-row-group;
@@ -200,15 +160,15 @@ body { counter-reset : phb-page-numbers; }
tr {
td {
//padding : 0.14em 0.4em;
padding : 0px 1.5px; // Both of these are temporary, just to force
padding : 0px 1.5px; // Both of these are temporary, just to force
//line-height : 16px; // PDF to render at same height until Chrome 108
}
&:nth-child(odd) { background-color : var(--HB_Color_Accent); }
}
}
}
//*****************************
// * QUOTE
// *****************************
// * QUOTE
// *****************************/
.quote {
@@ -241,9 +201,7 @@ body { counter-reset : phb-page-numbers; }
}
//*****************************
// *****************************
// * NOTE
// *****************************/
.note {
@@ -257,7 +215,7 @@ body { counter-reset : phb-page-numbers; }
border-image-outset : 9px 0px;
box-shadow : 1px 4px 14px #888888;
.page :where(&) {
margin-top : 9px; //Prevent top border getting cut off on colbreak
margin-top : 9px; //Prevent top border getting cut off on colbreak
}
& + * { margin-top : 0.45cm; }
h5 { font-size : 0.375cm; }
@@ -267,7 +225,7 @@ body { counter-reset : phb-page-numbers; }
}
:last-child { margin-bottom : 0; }
}
//************************************
// ************************************
// * DESCRIPTIVE TEXT BOX
// ************************************/
.descriptive {
@@ -291,7 +249,7 @@ body { counter-reset : phb-page-numbers; }
}
:last-child { margin-bottom : 0; }
}
//*****************************
// *****************************
// * Images Snippets
// *****************************/
@@ -320,42 +278,10 @@ body { counter-reset : phb-page-numbers; }
}
/* Watermark */
.watermark {
position : absolute;
top : 0;
left : 0;
z-index : 500;
display : grid !important;
place-items : center;
justify-content : center;
width : 100%;
height : 100%;
font-size : 120px;
color : black;
text-transform : uppercase;
mix-blend-mode : overlay;
opacity : 30%;
transform : rotate(-45deg);
p { margin-bottom : none; }
}
.watermark { color : black; }
/* Watercolor */
[class*='watercolor'] {
position : absolute;
z-index : -2;
width : 2000px; /* dimensions need to be real big so the user can set */
height : 2000px; /* height or width and the image will maintain aspect ratio */
background-color : var(--HB_Color_WatercolorStain); /* default color */
background-size : cover;
-webkit-mask-image : var(--wc);
-webkit-mask-size : contain;
-webkit-mask-repeat : no-repeat;
mask-image : var(--wc);
mask-size : contain;
mask-repeat : no-repeat;
--wc : @watercolor1; /* default image */
}
.watercolor1 { --wc : @watercolor1; }
.watercolor2 { --wc : @watercolor2; }
.watercolor3 { --wc : @watercolor3; }
@@ -369,7 +295,7 @@ body { counter-reset : phb-page-numbers; }
.watercolor11 { --wc : @watercolor11; }
.watercolor12 { --wc : @watercolor12; }
//*****************************
// *****************************
// * MONSTER STAT BLOCK
// *****************************/
.monster {
@@ -390,24 +316,24 @@ body { counter-reset : phb-page-numbers; }
box-shadow : 1px 4px 14px #888888;
}
position : relative;
padding : 0px;
margin-bottom : 0.325cm;
position : relative;
padding : 0px;
margin-bottom : 0.325cm;
//Headers
h2 {
margin : 0;
font-size : 0.62cm;
line-height : 1em;
margin : 0;
font-size : 0.62cm;
line-height : 1em;
& + p {
margin-bottom : 0;
font-size : 0.304cm; //Monster size and type subtext
}
}
h3 {
font-family : 'ScalySansSmallCapsRemake';
font-size : 0.45cm;
border-bottom : 1.5px solid var(--HB_Color_HeaderText);
font-family : 'ScalySansSmallCapsRemake';
font-size : 0.45cm;
border-bottom : 1.5px solid var(--HB_Color_HeaderText);
}
//Triangle dividers
@@ -454,10 +380,10 @@ body { counter-reset : phb-page-numbers; }
.useColumns(0.96, @fillMode: balance);
}
//*****************************
// *****************************
// * FOOTER
// *****************************/
&:after {
&::after {
position : absolute;
bottom : 0px;
left : 0px;
@@ -497,23 +423,18 @@ body { counter-reset : phb-page-numbers; }
color : var(--HB_Color_Footnotes);
text-align : right;
}
//************************************
// ************************************
// * CODE BLOCKS
// ************************************/
code {
padding : 0px 4px;
font-family : 'Courier New', 'Courier', monospace;
font-size : 0.325;
font-size : 0.325cm;
color : #58180D;
overflow-wrap : break-word;
white-space : pre-wrap;
background-color : #FAF7EA;
border-radius : 4px;
}
pre code {
display : inline-block;
width : 100%;
padding : 0.15cm;
margin-bottom : 2px;
border-style : solid;
@@ -527,26 +448,13 @@ body { counter-reset : phb-page-numbers; }
}
& + * { margin-top : 0.325cm; }
}
//*****************************
// *****************************
// * EXTRAS
// *****************************/
hr {
margin : 0px;
visibility : hidden;
}
.columnSplit {
visibility : hidden;
-webkit-column-break-after : always;
break-after : always;
-moz-column-break-after : always;
}
//Avoid breaking up
blockquote,table {
z-index : 15;
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
//Text indent right after table
table + p { text-indent : 1em; }
// Nested lists
@@ -554,18 +462,13 @@ body { counter-reset : phb-page-numbers; }
margin-bottom : 0px;
margin-left : 1.5em;
}
li {
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
}
//*****************************
// *****************************
// * SPELL LIST
// *****************************/
.page .spellList {
.useSansSerif();
column-count : 2;
column-count : 2;
ul + h5 { margin-top : 15px; }
p, ul {
font-size : 0.352cm;
@@ -583,7 +486,7 @@ body { counter-reset : phb-page-numbers; }
&.wide { column-count : 4; }
}
//*****************************
// *****************************
// * CLASS TABLE
// *****************************/
.page .classTable {
@@ -630,7 +533,7 @@ body { counter-reset : phb-page-numbers; }
}
h5 + table { margin-top : 0.2cm; }
}
//*****************************
// *****************************
// * FRONT COVER PAGE
// *****************************/
.page:has(.frontCover) {
@@ -724,7 +627,7 @@ body { counter-reset : phb-page-numbers; }
}
}
}
//*****************************
// *****************************
// * INSIDE COVER PAGE
// *****************************/
.page:has(.insideCover) {
@@ -769,7 +672,7 @@ body { counter-reset : phb-page-numbers; }
}
}
}
//*****************************
// *****************************
// * BACK COVER
// *****************************/
.page:has(.backCover) {
@@ -851,10 +754,10 @@ body { counter-reset : phb-page-numbers; }
}
}
//*****************************
// *****************************
// * PART COVER
// *****************************/
.page:has(.partCover) {
// *****************************/
.page:has(.partCover) {
padding-top : 0;
text-align : center;
columns : 1;
@@ -890,7 +793,7 @@ body { counter-reset : phb-page-numbers; }
}
}
//*****************************
// *****************************
// * TABLE OF CONTENTS
// *****************************/
.page {
@@ -959,33 +862,25 @@ body { counter-reset : phb-page-numbers; }
}
}
//*****************************
// *****************************
// * DEFINITION LISTS
// *****************************/
.page {
dl {
padding-left : 1em;
line-height : 1.25em;
white-space : pre-line;
& + * { margin-top : 0.17cm; }
& + * { margin-top : 0.17cm; }
}
p + dl { margin-top : 0.17cm; }
dt {
display : inline;
margin-right : 5px;
margin-left : -1em;
}
dd {
display : inline;
margin-left : 0px;
text-indent : 0px;
}
}
//*****************************
// *****************************
// * WIDE
// *****************************/
.page .wide { margin-bottom : 0.325cm; }
.page .wide { margin-bottom : 0.325cm; }
.page h1 + * { margin-top : 0; }
@@ -1024,7 +919,7 @@ body { counter-reset : phb-page-numbers; }
}
}
}
//*****************************
// *****************************
// * INDEX
// *****************************/
.page {

View File

@@ -111,6 +111,21 @@ module.exports = [
icon : 'fas fa-code',
gen : '<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->'
},
{
name : 'Homebrewery Credit',
icon : 'fas fa-dice-d20',
gen : function(){
return dedent`
{{homebreweryCredits
Made With
{{homebreweryIcon}}
The Homebrewery
[Homebrewery.Naturalcrit.com](https://homebrewery.naturalcrit.com)
}}\n\n`;
},
}
]
},
{
@@ -289,6 +304,99 @@ module.exports = [
}
]
},
/**************** FONTS *************/
{
groupName : 'Fonts',
icon : 'fas fa-keyboard',
view : 'text',
snippets : [
{
name : 'Open Sans',
icon : 'font OpenSans',
gen : dedent`{{font-family:OpenSans Dummy Text}}`
},
{
name : 'Code Bold',
icon : 'font CodeBold',
gen : dedent`{{font-family:CodeBold Dummy Text}}`
},
{
name : 'Code Light',
icon : 'font CodeLight',
gen : dedent`{{font-family:CodeLight Dummy Text}}`
},
{
name : 'Scaly Sans Remake',
icon : 'font ScalySansRemake',
gen : dedent`{{font-family:ScalySansRemake Dummy Text}}`
},
{
name : 'Book Insanity Remake',
icon : 'font BookInsanityRemake',
gen : dedent`{{font-family:BookInsanityRemake Dummy Text}}`
},
{
name : 'Mr Eaves Remake',
icon : 'font MrEavesRemake',
gen : dedent`{{font-family:MrEavesRemake Dummy Text}}`
},
{
name: 'Solbera Imitation Remake',
icon: 'font SolberaImitationRemake',
gen: dedent`{{font-family:SolberaImitationRemake Dummy Text}}`
},
{
name: 'Scaly Sans Small Caps Remake',
icon: 'font ScalySansSmallCapsRemake',
gen: dedent`{{font-family:ScalySansSmallCapsRemake Dummy Text}}`
},
{
name: 'Walter Turncoat',
icon: 'font WalterTurncoat',
gen: dedent`{{font-family:WalterTurncoat Dummy Text}}`
},
{
name: 'Lato',
icon: 'font Lato',
gen: dedent`{{font-family:Lato Dummy Text}}`
},
{
name: 'Courier',
icon: 'font Courier',
gen: dedent`{{font-family:Courier Dummy Text}}`
},
{
name: 'Nodesto Caps Condensed',
icon: 'font NodestoCapsCondensed',
gen: dedent`{{font-family:NodestoCapsCondensed Dummy Text}}`
},
{
name: 'Overpass',
icon: 'font Overpass',
gen: dedent`{{font-family:Overpass Dummy Text}}`
},
{
name: 'Davek',
icon: 'font Davek',
gen: dedent`{{font-family:Davek Dummy Text}}`
},
{
name: 'Iokharic',
icon: 'font Iokharic',
gen: dedent`{{font-family:Iokharic Dummy Text}}`
},
{
name: 'Rellanic',
icon: 'font Rellanic',
gen: dedent`{{font-family:Rellanic Dummy Text}}`
},
{
name: 'Times New Roman',
icon: 'font TimesNewRoman',
gen: dedent`{{font-family:"Times New Roman" Dummy Text}}`
}
]
},
/**************** PAGE *************/

View File

@@ -7,13 +7,9 @@
--HB_Color_WatercolorStain : #000000; // Black
}
@page { margin: 0; }
body {
counter-reset : phb-page-numbers;
}
*{
-webkit-print-color-adjust : exact;
}
@page { margin : 0; }
body { counter-reset : phb-page-numbers; }
* { -webkit-print-color-adjust : exact; }
//*****************************
// * MUSTACHE DIVS/SPANS
@@ -23,9 +19,7 @@ body {
break-inside : avoid;
display : inline-block;
width : 100%;
img {
z-index : 0;
}
img { z-index : 0; }
}
.inline-block {
display : inline-block;
@@ -33,99 +27,81 @@ body {
}
}
.useColumns(@multiplier : 1, @fillMode: auto){
.useColumns(@multiplier : 1, @fillMode: auto) {
column-fill : @fillMode;
column-count : 2;
}
.columnWrapper{
.columnWrapper {
column-gap : inherit;
max-height : 100%;
column-span : all;
columns : inherit;
column-gap : inherit;
column-fill : inherit;
}
.page{
.page {
.useColumns();
height : 279.4mm;
width : 215.9mm;
padding : 1.4cm 1.9cm 1.7cm;
counter-increment : phb-page-numbers;
background-color : var(--HB_Color_Background);
position : relative;
z-index : 15;
box-sizing : border-box;
width : 215.9mm;
height : 279.4mm;
padding : 1.4cm 1.9cm 1.7cm;
overflow : hidden;
counter-increment : phb-page-numbers;
background-color : var(--HB_Color_Background);
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
contain : size;
}
//*****************************
// * BASE
//*****************************
// * BASE
// *****************************/
.page{
p{
overflow-wrap : break-word;
.page {
p {
display : block;
overflow-wrap : break-word;
}
strong{
font-weight : bold;
}
em{
font-style : italic;
}
sup{
strong { font-weight : bold; }
em { font-style : italic; }
sup {
font-size : smaller;
line-height : 0;
vertical-align : super;
font-size : smaller;
line-height : 0;
}
sub{
vertical-align : sub;
sub {
font-size : smaller;
line-height : 0;
vertical-align : sub;
}
ul {
padding-left : 1.4em;
list-style-position : outside; //Needed for multiline list items
list-style-type : disc;
padding-left : 1.4em;
}
ol {
padding-left : 1.4em;
list-style-position : outside;
list-style-type : decimal;
padding-left : 1.4em;
}
img{
z-index : -1;
}
img { z-index : -1; }
//*****************************
// * HEADERS
// *****************************/
h1,h2,h3,h4,h5,h6{
h1,h2,h3,h4,h5,h6 {
font-weight : bold;
line-height : 1.2em;
}
h1{
font-size : 2em;
}
h2{
font-size : 1.5em;
}
h3{
font-size : 1.17em;
}
h4{
font-size : 1em;
}
h5{
font-size : 0.83em;
}
h1 { font-size : 2em; }
h2 { font-size : 1.5em; }
h3 { font-size : 1.17em; }
h4 { font-size : 1em; }
h5 { font-size : 0.83em; }
//*****************************
// * TABLE
// *****************************/
table{
table {
width : 100%;
thead{
thead {
display : table-row-group;
font-weight : bold;
}
@@ -137,42 +113,40 @@ body {
//************************************
// * CODE BLOCKS
// ************************************/
code{
font-family : "Courier New", Courier, monospace;
white-space : pre-wrap;
code {
font-family : 'Courier New', "Courier", monospace;
overflow-wrap : break-word;
white-space : pre-wrap;
}
pre code{
width : 100%;
pre code {
display : inline-block;
width : 100%;
}
//*****************************
// * EXTRAS
// *****************************/
.columnSplit {
margin-top : 0;
visibility : hidden;
-webkit-column-break-after : always;
break-after : always;
-moz-column-break-after : always;
margin-top : 0;
& + * {
margin-top : 0;
}
& + * { margin-top : 0; }
}
//Avoid breaking up
blockquote,table{
blockquote,table {
z-index : 15;
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
// Nested lists
ul ul,ol ol,ul ol,ol ul{
ul ul,ol ol,ul ol,ol ul {
margin-bottom : 0px;
margin-left : 1.5em;
}
li{
li {
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
@@ -180,69 +154,66 @@ body {
/* Watermark */
.watermark {
display : grid !important;
place-items : center;
justify-content : center;
position : absolute;
margin : 0;
top : 0;
left : 0;
z-index : 500;
display : grid !important;
place-items : center;
justify-content : center;
width : 100%;
height : 100%;
margin : 0;
font-size : 120px;
text-transform : uppercase;
text-transform : uppercase;
mix-blend-mode : overlay;
opacity : 30%;
transform : rotate(-45deg);
z-index : 500;
p {
margin-bottom : none;
}
p { margin-bottom : none; }
}
/* Watercolor */
[class*="watercolor"] {
[class*='watercolor'] {
position : absolute;
z-index : -2;
width : 2000px; /* dimensions need to be real big so the user can set */
height : 2000px; /* height or width and the image will maintain aspect ratio */
background-color : var(--HB_Color_WatercolorStain); /* default color */
background-size : cover;
-webkit-mask-image : var(--wc);
-webkit-mask-size : contain;
-webkit-mask-repeat : no-repeat;
mask-image : var(--wc);
mask-size : contain;
mask-repeat : no-repeat;
background-size : cover;
background-color : var(--HB_Color_WatercolorStain); /*default color*/
--wc : @watercolor1; /*default image*/
z-index : -2;
--wc : @watercolor1; /* default image */
}
.watercolor1 { --wc : @watercolor1; }
.watercolor2 { --wc : @watercolor2; }
.watercolor3 { --wc : @watercolor3; }
.watercolor4 { --wc : @watercolor4; }
.watercolor5 { --wc : @watercolor5; }
.watercolor6 { --wc : @watercolor6; }
.watercolor7 { --wc : @watercolor7; }
.watercolor8 { --wc : @watercolor8; }
.watercolor9 { --wc : @watercolor9; }
.watercolor1 { --wc : @watercolor1; }
.watercolor2 { --wc : @watercolor2; }
.watercolor3 { --wc : @watercolor3; }
.watercolor4 { --wc : @watercolor4; }
.watercolor5 { --wc : @watercolor5; }
.watercolor6 { --wc : @watercolor6; }
.watercolor7 { --wc : @watercolor7; }
.watercolor8 { --wc : @watercolor8; }
.watercolor9 { --wc : @watercolor9; }
.watercolor10 { --wc : @watercolor10; }
.watercolor11 { --wc : @watercolor11; }
.watercolor12 { --wc : @watercolor12; }
/* Image Masks */
[class*="imageMask"] {
[class*='imageMask'] {
position : absolute;
height : 200%;
width : 200%;
left : 50%;
bottom : 50%;
--rotation : 0;
--revealer : none;
--checkerboard : none;
--scaleX : 1;
--scaleY : 1;
left : 50%;
z-index : -1;
width : 200%;
height : 200%;
background-image : var(--checkerboard);
background-size : 20px;
transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
-webkit-mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : repeat-x;
-webkit-mask-size : 50%; //Scale only X to fit page width, leave height at aspect ratio, designed to hang off the edge
@@ -251,61 +222,63 @@ body {
mask-repeat : repeat-x;
mask-size : 50%;
mask-position : 50% calc(50% - var(--offset));
background-image : var(--checkerboard);
background-size : 20px;
z-index : -1;
transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
--rotation : 0;
--revealer : none;
--checkerboard : none;
--scaleX : 1;
--scaleY : 1;
& > p:has(img) {
position : absolute;
width : 50%;
height : 50%;
bottom : 50%;
left : 50%;
width : 50%;
height : 50%;
transform : translateX(-50%) translateY(50%) rotate(calc(-1deg * var(--rotation))) scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY)));
}
& img {
position : absolute;
display : block;
bottom : 0;
display : block;
}
&.bottom {
--rotation : 0;
& img {bottom: 0;}
& img {bottom : 0;}
}
&.top {
--rotation : 180;
& img {top: 0;}
& img {top : 0;}
}
&.left {
--rotation : 90;
& img {left: 0;}
& img {left : 0;}
}
&.right {
--rotation : -90;
& img {right: 0;}
& img {right : 0;}
}
&.revealImage {
--revealer : linear-gradient(0deg, rgba(0,0,0,.2) 0%, rgba(0,0,0,0.2));
--checkerboard : url(/assets/waterColorMasks/missingImage.png); //shows any masked regions not filled by image
--revealer : linear-gradient(0deg, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.2));
--checkerboard : url("/assets/waterColorMasks/missingImage.png"); //shows any masked regions not filled by image
}
}
.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); }
&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"] {
[class*='imageMaskCenter'] {
bottom : calc(var(--offsetY));
left : calc(var(--offsetX));
width : 100%;
height : 100%;
left : calc(var(--offsetX));
bottom : calc(var(--offsetY));
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
-webkit-mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : no-repeat;
-webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
@@ -314,48 +287,48 @@ 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));
& > p:has(img) {
position : absolute;
width : 100%;
height : 100%;
bottom : 0;
left : 0;
width : 100%;
height : 100%;
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)));
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); }
&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%;
width : 200%;
left : calc(-50% + var(--offsetX));
[class*='imageMaskCorner'] {
bottom : calc(-50% + var(--offsetY));
left : calc(-50% + var(--offsetX));
width : 200%;
height : 200%;
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
-webkit-mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : no-repeat;
-webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
@@ -364,56 +337,55 @@ 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));
& > p:has(img) {
bottom : 25%;
left : 25%;
width : 50%;
height : 50%; //Complex transform below to handle mix of % and cm offsets
left : 25%;
bottom : 25%;
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)));
rotate(calc(-1deg * var(--rotation)))
translateX(calc(-1 * var(--offsetX)))
translateY(calc(1 * var(--offsetY)));
}
}
.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); }
&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"); }
}
}
@@ -425,16 +397,16 @@ body {
padding-left : 1em;
white-space : pre-line;
}
dt {
display : inline;
margin-right : 0.5ch;
dt {
display : inline;
margin-right : 0.5ch;
margin-left : -1em;
}
dd {
}
dd {
display : inline;
margin-left : 0;
text-indent : 0;
}
}
}
//*****************************
@@ -444,9 +416,7 @@ body {
.blank {
height : 1em;
margin-top : 0;
& + * {
margin-top : 0;
}
& + * { margin-top : 0; }
}
}
@@ -454,12 +424,39 @@ body {
// * WIDE
// *****************************/
.page {
.wide{
.wide {
column-span : all;
display : block;
margin-bottom : 1em;
&+* {
margin-top : 0;
}
& + * { margin-top : 0; }
}
}
//*****************************
//* CREDITS
//*****************************/
.page .homebreweryCredits {
p {
font-family : 'NodestoCapsWide';
font-size : 0.4cm;
line-height : 1em;
text-align : center;
text-indent : 0;
letter-spacing : 0.08em;
}
a {
color : inherit;
text-decoration : none;
&:hover { text-decoration : underline; }
}
.homebreweryIcon {
display : block;
height : 1.5cm;
margin : 0 auto;
background-color : black;
-webkit-mask : url("/assets/naturalCritLogoWhite.svg") center / contain no-repeat;
mask : url("/assets/naturalCritLogoWhite.svg") center / contain no-repeat;
}
.homebreweryIcon.red { background-color : red; }
.homebreweryIcon.gold { background-image : linear-gradient(to top left, brown 22.5%, gold 40%, white 60%, gold 67.5%, brown 82.5%); }
}

Binary file not shown.

View File

@@ -0,0 +1,224 @@
/* Main Font, serif */
@font-face {
font-family : 'Eldeberry-Inn';
font-style : normal;
font-weight : normal;
src : url('../../../fonts/icon fonts/Elderberry-Inn-Icons.woff2');
}
.page {
span.ei {
display : inline-block;
margin-right : 3px;
font-family : 'Eldeberry-Inn';
line-height : 1;
vertical-align : baseline;
-moz-osx-font-smoothing : grayscale;
-webkit-font-smoothing : antialiased;
text-rendering : auto;
&.book::before { content : '\E900'; }
&.screen::before { content : '\E901'; }
/* Spell levels */
&.spell-0::before { content : '\E902'; }
&.spell-1::before { content : '\E903'; }
&.spell-2::before { content : '\E904'; }
&.spell-3::before { content : '\E905'; }
&.spell-4::before { content : '\E906'; }
&.spell-5::before { content : '\E907'; }
&.spell-6::before { content : '\E908'; }
&.spell-7::before { content : '\E909'; }
&.spell-8::before { content : '\E90A'; }
&.spell-9::before { content : '\E90B'; }
/* Damage types */
&.acid::before { content : '\E90C'; }
&.bludgeoning::before { content : '\E90D'; }
&.cold::before { content : '\E90E'; }
&.fire::before { content : '\E90F'; }
&.force::before { content : '\E910'; }
&.lightning::before { content : '\E911'; }
&.necrotic::before { content : '\E912'; }
&.piercing::before { content : '\E914'; }
&.poison::before { content : '\E913'; }
&.psychic::before { content : '\E915'; }
&.radiant::before { content : '\E916'; }
&.slashing::before { content : '\E917'; }
&.thunder::before { content : '\E918'; }
/* DnD Conditions */
&.blinded::before { content : '\E919'; }
&.charmed::before { content : '\E91A'; }
&.deafened::before { content : '\E91B'; }
&.exhaust-1::before { content : '\E91C'; }
&.exhaust-2::before { content : '\E91D'; }
&.exhaust-3::before { content : '\E91E'; }
&.exhaust-4::before { content : '\E91F'; }
&.exhaust-5::before { content : '\E920'; }
&.exhaust-6::before { content : '\E921'; }
&.frightened::before { content : '\E922'; }
&.grappled::before { content : '\E923'; }
&.incapacitated::before { content : '\E924'; }
&.invisible::before { content : '\E926'; }
&.paralyzed::before { content : '\E927'; }
&.petrified::before { content : '\E928'; }
&.poisoned::before { content : '\E929'; }
&.prone::before { content : '\E92A'; }
&.restrained::before { content : '\E92B'; }
&.stunned::before { content : '\E92C'; }
&.unconscious::before { content : '\E925'; }
/* Character Classes and Features */
&.barbarian-rage::before { content : '\E92D'; }
&.barbarian-reckless-attack::before { content : '\E92E'; }
&.bardic-inspiration::before { content : '\E92F'; }
&.cleric-channel-divinity::before { content : '\E930'; }
&.druid-wild-shape::before { content : '\E931'; }
&.fighter-action-surge::before { content : '\E932'; }
&.fighter-second-wind::before { content : '\E933'; }
&.monk-flurry-blows::before { content : '\E934'; }
&.monk-patient-defense::before { content : '\E935'; }
&.monk-step-of-the-wind::before { content : '\E936'; }
&.monk-step-of-the-wind-2::before { content : '\E937'; }
&.monk-step-of-the-wind-3::before { content : '\E938'; }
&.monk-stunning-strike::before { content : '\E939'; }
&.monk-stunning-strike-2::before { content : '\E939'; }
&.paladin-divine-smite::before { content : '\E93B'; }
&.paladin-lay-on-hands::before { content : '\E93C'; }
&.barbarian-abilities::before { content : '\E93D'; }
&.barbarian::before { content : '\E93E'; }
&.bard-abilities::before { content : '\E93F'; }
&.bard::before { content : '\E940'; }
&.cleric-abilities::before { content : '\E941'; }
&.cleric::before { content : '\E942'; }
&.druid-abilities::before { content : '\E943'; }
&.druid::before { content : '\E944'; }
&.fighter-abilities::before { content : '\E945'; }
&.fighter::before { content : '\E946'; }
&.monk-abilities::before { content : '\E947'; }
&.monk::before { content : '\E948'; }
&.paladin-abilities::before { content : '\E949'; }
&.paladin::before { content : '\E94A'; }
&.ranger-abilities::before { content : '\E94B'; }
&.ranger::before { content : '\E94C'; }
&.rogue-abilities::before { content : '\E94D'; }
&.rogue::before { content : '\E94E'; }
&.sorcerer-abilities::before { content : '\E94F'; }
&.sorcerer::before { content : '\E950'; }
&.warlock-abilities::before { content : '\E951'; }
&.warlock::before { content : '\E952'; }
&.wizard-abilities::before { content : '\E953'; }
&.wizard::before { content : '\E954'; }
/* Types of actions */
&.movement::before { content : '\E955'; }
&.action::before { content : '\E956'; }
&.bonus-action::before { content : '\E957'; }
&.reaction::before { content : '\E958'; }
/* SRD Spells */
&.acid-arrow::before { content : '\E959'; }
&.action-1::before { content : '\E95A'; }
&.alter-self::before { content : '\E95B'; }
&.alter-self-2::before { content : '\E95C'; }
&.animal-friendship::before { content : '\E95E'; }
&.animate-dead::before { content : '\E95F'; }
&.animate-objects::before { content : '\E960'; }
&.animate-objects-2::before { content : '\E961'; }
&.bane::before { content : '\E962'; }
&.bless::before { content : '\E963'; }
&.blur::before { content : '\E964'; }
&.bonus::before { content : '\E965'; }
&.branding-smite::before { content : '\E966'; }
&.burning-hands::before { content : '\E967'; }
&.charm-person::before { content : '\E968'; }
&.chill-touch::before { content : '\E969'; }
&.cloudkill::before { content : '\E96A'; }
&.comprehend-languages::before { content : '\E96B'; }
&.cone-of-cold::before { content : '\E96C'; }
&.conjure-elemental::before { content : '\E96D'; }
&.conjure-minor-elemental::before { content : '\E96E'; }
&.control-water::before { content : '\E96F'; }
&.counterspell::before { content : '\E970'; }
&.cure-wounds::before { content : '\E971'; }
&.dancing-lights::before { content : '\E972'; }
&.darkness::before { content : '\E973'; }
&.detect-magic::before { content : '\E974'; }
&.disguise-self::before { content : '\E975'; }
&.disintegrate::before { content : '\E976'; }
&.dispel-evil-and-good::before { content : '\E977'; }
&.dispel-magic::before { content : '\E978'; }
&.dominate-monster::before { content : '\E979'; }
&.dominate-person::before { content : '\E97A'; }
&.eldritch-blast::before { content : '\E97B'; }
&.enlarge-reduce::before { content : '\E97C'; }
&.entangle::before { content : '\E97D'; }
&.faerie-fire::before { content : '\E97E'; }
&.faerie-fire2::before { content : '\E97F'; }
&.feather-fall::before { content : '\E980'; }
&.find-familiar::before { content : '\E981'; }
&.finger-of-death::before { content : '\E982'; }
&.fireball::before { content : '\E983'; }
&.floating-disk::before { content : '\E984'; }
&.fly::before { content : '\E985'; }
&.fog-cloud::before { content : '\E986'; }
&.gaseous-form::before { content : '\E987'; }
&.gaseous-form2::before { content : '\E988'; }
&.gentle-repose::before { content : '\E989'; }
&.gentle-repose2::before { content : '\E98A'; }
&.globe-of-invulnerability::before { content : '\E98B'; }
&.guiding-bolt::before { content : '\E98C'; }
&.healing-word::before { content : '\E98D'; }
&.heat-metal::before { content : '\E98E'; }
&.hellish-rebuke::before { content : '\E98F'; }
&.heroes-feast::before { content : '\E990'; }
&.heroism::before { content : '\E991'; }
&.hideous-laughter::before { content : '\E992'; }
&.identify::before { content : '\E993'; }
&.illusory-script::before { content : '\E994'; }
&.inflict-wounds::before { content : '\E995'; }
&.light::before { content : '\E996'; }
&.longstrider::before { content : '\E997'; }
&.mage-armor::before { content : '\E998'; }
&.mage-hand::before { content : '\E999'; }
&.magic-missile::before { content : '\E99A'; }
&.mass-cure-wounds::before { content : '\E99B'; }
&.mass-healing-word::before { content : '\E99C'; }
&.Mending::before { content : '\E99D'; }
&.message::before { content : '\E99E'; }
&.Minor-illusion::before { content : '\E99F'; }
&.movement1::before { content : '\E9A0'; }
&.polymorph::before { content : '\E9A1'; }
&.power-word-kill::before { content : '\E9A2'; }
&.power-word-stun::before { content : '\E9A3'; }
&.prayer-of-healing::before { content : '\E9A4'; }
&.prestidigitation::before { content : '\E9A5'; }
&.protection-from-evil-and-good::before { content : '\E9A6'; }
&.raise-read::before { content : '\E9A7'; }
&.raise-read2::before { content : '\E9A8'; }
&.reaction1::before { content : '\E9A9'; }
&.resurrection::before { content : '\E9AA'; }
&.resurrection2::before { content : '\E9AB'; }
&.revivify::before { content : '\E9AC'; }
&.revivify2::before { content : '\E9AD'; }
&.sacred-flame::before { content : '\E9AE'; }
&.sanctuary::before { content : '\E9AF'; }
&.scorching-ray::before { content : '\E9B0'; }
&.sending::before { content : '\E9B1'; }
&.shatter::before { content : '\E9B2'; }
&.shield::before { content : '\E9B3'; }
&.silent-image::before { content : '\E9B4'; }
&.sleep::before { content : '\E9B5'; }
&.speak-with-animals::before { content : '\E9B6'; }
&.telekinesis::before { content : '\E9B7'; }
&.true-strike::before { content : '\E9B8'; }
&.vicious-mockery::before { content : '\E9B9'; }
&.wall-of-fire::before { content : '\E9BA'; }
&.wall-of-force::before { content : '\E9BB'; }
&.wall-of-ice::before { content : '\E9BC'; }
&.wall-of-stone::before { content : '\E9BD'; }
&.wall-of-thorns::before { content : '\E9BE'; }
&.wish::before { content : '\E9BF'; }
}
}