0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-03-25 12:48:12 +00:00

Merge branch 'master' of https://github.com/naturalcrit/homebrewery into SnippetsReorg

This commit is contained in:
Víctor Losada Hernández
2025-03-24 13:07:16 +01:00
100 changed files with 2722 additions and 2614 deletions

View File

@@ -73,9 +73,6 @@ jobs:
- run: - run:
name: Test - Non-Breaking Spaces name: Test - Non-Breaking Spaces
command: npm run test:non-breaking-spaces command: npm run test:non-breaking-spaces
- run:
name: Test - Paragraph Justification
command: npm run test:paragraph-justification
- run: - run:
name: Test - Variables name: Test - Variables
command: npm run test:variables command: npm run test:variables

View File

@@ -88,6 +88,70 @@ pre {
## changelog ## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery). For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Tuesday 03/18/2025 - v3.18.1
{{taskList
##### G-Ambatte
* [x] Revert colon rendering from br elements to blank divs
##### 5e-Cleric
* [x] Allow for local connections within a same network when running a local version
Fixes issue [#4094](https://github.com/naturalcrit/homebrewery/issues/4094)
* [x] Add US Letter size page snippet
Fixes issue [#3893](https://github.com/naturalcrit/homebrewery/issues/3893)
}}
### Monday 03/10/2025 - v3.18.0
{{taskList
##### dbolack
* [x] Add ability to paste in any Share ID/URL into a brew's {{openSans :fas_circle_info: **Properties** :fas_arrow_right: **THEMES**}} selection, as long as that brew has been tagged as `meta:theme`. You can now share your custom brew themes without needing to make a personal copy.
* [x] Begin migration of custom Markdown extensions into their own NPM packages, for easier adoption by other users or projects
* [x] Fix external HTML appearing in open codeblocks
Fixes issue [#3206](https://github.com/naturalcrit/homebrewery/issues/3206)
* [x] Fix tables not rendering when directly after text
##### G-Ambatte
* [x] Cleanup of "cover pages" in the {{openSans :fas_rectangle_list: **NAVIGATION**}} list
* [x] Fix autosave triggering when no changes are present
Fixes issue [#4051](https://github.com/naturalcrit/homebrewery/issues/4051)
* [x] Remove empty table rows resulting from rowspan
Fixes issue [#1729](https://github.com/naturalcrit/homebrewery/issues/1729)
##### 5e-Cleric
* [x] Style fixes for covers art and logos on A4 size pages
* [x] Fix crash when trying to open brews that don't exist
* [x] Tweaks and style update styling on {{openSans **VAULT** :fas_dungeon:}} page.
Fixes issue [#4079](https://github.com/naturalcrit/homebrewery/issues/4079)
##### Calculuschild
* [x] `` now produces `<br>` instead of a `<div>`
* [x] Fix typos in tables freezing the editor
Fixes issue [#4059](https://github.com/naturalcrit/homebrewery/issues/4059)
##### MollyMaclachlan (New Contributor!)
* [x] Fixed typos in the Monster Stat Block snippet
Fixes issue [#4073](https://github.com/naturalcrit/homebrewery/issues/4073)
##### All
* [x] Update dependencies and scripts
* [x] Refactor components and backend tools
}}
\column
### Thursday 01/30/2025 - v3.17.0 ### Thursday 01/30/2025 - v3.17.0
{{taskList {{taskList
@@ -169,7 +233,7 @@ Fixes issue [#3824](https://github.com/naturalcrit/homebrewery/issues/3824)
* [x] Refactor components and fix various errors * [x] Refactor components and fix various errors
}} }}
\column \page
### Wednesday 11/27/2024 - v3.16.1 ### Wednesday 11/27/2024 - v3.16.1
@@ -217,8 +281,6 @@ Fixes issue [#3744](https://github.com/naturalcrit/homebrewery/issues/3744)
* [x] Multiple code refactors, cleanups, and security fixes * [x] Multiple code refactors, cleanups, and security fixes
}} }}
\page
### Saturday 10/12/2024 - v3.16.0 ### Saturday 10/12/2024 - v3.16.0
{{taskList {{taskList

View File

@@ -1,47 +1,48 @@
require('./admin.less'); import './admin.less';
const React = require('react'); import React, { useEffect, useState } from 'react';
const createClass = require('create-react-class');
const BrewUtils = require('./brewUtils/brewUtils.jsx'); const BrewUtils = require('./brewUtils/brewUtils.jsx');
const NotificationUtils = require('./notificationUtils/notificationUtils.jsx'); const NotificationUtils = require('./notificationUtils/notificationUtils.jsx');
import AuthorUtils from './authorUtils/authorUtils.jsx';
const tabGroups = ['brew', 'notifications']; const tabGroups = ['brew', 'notifications', 'authors'];
const Admin = createClass({ const Admin = ()=>{
getDefaultProps : function() { const [currentTab, setCurrentTab] = useState('brew');
return {};
},
getInitialState : function(){ useEffect(()=>{
return ({ setCurrentTab(localStorage.getItem('hbAdminTab'));
currentTab : 'brew' }, []);
});
},
handleClick : function(newTab){ useEffect(()=>{
if(this.state.currentTab === newTab) return; localStorage.setItem('hbAdminTab', currentTab);
this.setState({ }, [currentTab]);
currentTab : newTab
});
},
render : function(){ return (
return <div className='admin'> <div className='admin'>
<header> <header>
<div className='container'> <div className='container'>
<i className='fas fa-rocket' /> <i className='fas fa-rocket' />
homebrewery admin The Homebrewery Admin Page
<a href='/'>back to homepage</a>
</div> </div>
</header> </header>
<main className='container'> <main className='container'>
<nav className='tabs'> <nav className='tabs'>
{tabGroups.map((tab, idx)=>{ return <button className={tab===this.state.currentTab ? 'active' : ''} key={idx} onClick={()=>{ return this.handleClick(tab); }}>{tab.toUpperCase()}</button>; })} {tabGroups.map((tab, idx)=>(
<button
className={tab === currentTab ? 'active' : ''}
key={idx}
onClick={()=>setCurrentTab(tab)}>
{tab.toUpperCase()}
</button>
))}
</nav> </nav>
{this.state.currentTab==='brew' && <BrewUtils />} {currentTab === 'brew' && <BrewUtils />}
{this.state.currentTab==='notifications' && <NotificationUtils />} {currentTab === 'notifications' && <NotificationUtils />}
{currentTab === 'authors' && <AuthorUtils />}
</main> </main>
</div>; </div>
} );
}); };
module.exports = Admin; module.exports = Admin;

View File

@@ -22,7 +22,7 @@ body {
} }
:where(.admin) { :where(.admin) {
padding-bottom : 50px;
header { header {
padding : 20px 0px; padding : 20px 0px;
margin-bottom : 30px; margin-bottom : 30px;
@@ -30,6 +30,7 @@ body {
color : white; color : white;
background-color : @red; background-color : @red;
i { margin-right : 30px; } i { margin-right : 30px; }
a { float : right; }
} }
hr { margin : 30px 0px; } hr { margin : 30px 0px; }
@@ -48,19 +49,21 @@ body {
} }
dl { dl {
@maxItemWidth : 132px; display : grid;
grid-template-columns : 120px 1fr;
row-gap : 10px;
align-items : center;
justify-items : start;
padding-top : 0.5em;
dt { dt {
float : left; float : left;
width : @maxItemWidth; clear : left;
clear : left; height : fit-content;
text-align : right; font-weight : 900;
text-align : right;
&::after { content : ' : '; } &::after { content : ' : '; }
} }
dd { dd { height : fit-content; }
height : 1em;
padding : 0 0 0.5em 0;
margin-left : @maxItemWidth + 6px;
}
} }
.tabs button { .tabs button {
@@ -90,11 +93,45 @@ body {
} }
} }
table {
padding : 10px;
tr {
border-bottom : 1px solid;
&:last-of-type { border : none; }
&:nth-child(even) { background : #DDDDDD; }
}
thead {
background : rgb(193,236,230);
border-bottom : 2px solid;
}
th, td {
padding : 5px 10px;
vertical-align : middle;
text-align : center;
border-right : 1px solid;
&:last-child { border-right : none; }
}
th { font-weight : 900; }
td {
&:first-child {
font-weight : 900;
text-align : left;
}
}
}
.error { .error {
background: rgb(178, 54, 54); float : right;
color:white; padding : 10px;
font-weight: 900; margin-block : 10px;
margin-block:10px; font-weight : 900;
padding:10px; color : white;
background : rgb(178, 54, 54);
} }
} }

View File

@@ -0,0 +1,87 @@
import './authorLookup.less';
import React from 'react';
import request from 'superagent';
const authorLookup = ()=>{
const [author, setAuthor] = React.useState('');
const [searching, setSearching] = React.useState(false);
const [results, setResults] = React.useState([]);
const lookup = async ()=>{
if(!author) return;
setSearching(true);
setResults([]);
const brews = await request.get(`/admin/user/list/${author}`);
setResults(brews.body);
setSearching(false);
};
const renderResults = ()=>{
if(results.length == 0) return <>
<h2>Results</h2>
<p>None found.</p>
</>;
return <>
<h2>{`Results - ${results.length} brews` }</h2>
<table className='resultsTable'>
<thead>
<tr>
<th>Title</th>
<th>Share</th>
<th>Edit</th>
<th>Last Update</th>
<th>Storage</th>
</tr>
</thead>
<tbody>
{results
.sort((a, b)=>{ // Sort brews from most recently updated
if(a.updatedAt > b.updatedAt) return -1;
return 1;
})
.map((brew, idx)=>{
return <tr key={idx}>
<td><strong>{brew.title}</strong></td>
<td><a href={`/share/${brew.shareId}`}>{brew.shareId}</a></td>
<td>{brew.editId}</td>
<td style={{ width: '200px' }}>{brew.updatedAt}</td>
<td>{brew.googleId ? 'Google' : 'Homebrewery'}</td>
</tr>;
})}
</tbody>
</table>
</>;
};
const handleKeyPress = (evt)=>{
if(evt.key === 'Enter') return lookup();
};
const handleChange = (evt)=>{
setAuthor(evt.target.value);
};
return (
<div className='authorLookup'>
<div className='authorLookupInputs'>
<h2>Author Lookup</h2>
<label className='field'>
Author Name:
<input className='fieldInput' value={author} onKeyDown={handleKeyPress} onChange={handleChange} />
<button onClick={lookup}>
<i className={`fas ${searching ? 'fa-spin fa-spinner' : 'fa-search'}`} />
</button>
</label>
</div>
<div className='authorLookupResults'>
{renderResults()}
</div>
</div>
);
};
module.exports = authorLookup;

View File

@@ -0,0 +1,29 @@
.authorLookup {
position : relative;
display : flex;
flex-direction : column;
.field {
display : flex;
gap : 5px;
align-items : center;
justify-items : stretch;
width : 100%;
margin-bottom : 20px;
input {
height : 33px;
padding : 0px 10px;
margin-bottom : unset;
font-family : monospace;
}
button {
width : 50px;
i { margin-right : 10px; }
}
}
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
import AuthorLookup from './authorLookup/authorLookup.jsx';
const authorUtils = ()=>{
return (
<section className='authorUtils'>
<AuthorLookup />
</section>
);
};
module.exports = authorUtils;

View File

@@ -1,10 +1,8 @@
require('./brewCleanup.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const request = require('superagent'); const request = require('superagent');
const BrewCleanup = createClass({ const BrewCleanup = createClass({
displayName : 'BrewCleanup', displayName : 'BrewCleanup',
getDefaultProps(){ getDefaultProps(){
@@ -39,9 +37,9 @@ const BrewCleanup = createClass({
if(!this.state.primed) return; if(!this.state.primed) return;
if(!this.state.count){ if(!this.state.count){
return <div className='removeBox'>No Matching Brews found.</div>; return <div className='result noBrews'>No Matching Brews found.</div>;
} }
return <div className='removeBox'> return <div className='result'>
<button onClick={this.cleanup} className='remove'> <button onClick={this.cleanup} className='remove'>
{this.state.pending {this.state.pending
? <i className='fas fa-spin fa-spinner' /> ? <i className='fas fa-spin fa-spinner' />
@@ -52,7 +50,7 @@ const BrewCleanup = createClass({
</div>; </div>;
}, },
render(){ render(){
return <div className='BrewCleanup'> return <div className='brewUtil brewCleanup'>
<h2> Brew Cleanup </h2> <h2> Brew Cleanup </h2>
<p>Removes very short brews to tidy up the database</p> <p>Removes very short brews to tidy up the database</p>
@@ -65,7 +63,7 @@ const BrewCleanup = createClass({
{this.renderPrimed()} {this.renderPrimed()}
{this.state.error {this.state.error
&& <div className='error'>{this.state.error.toString()}</div> && <div className='error noBrews'>{this.state.error.toString()}</div>
} }
</div>; </div>;
} }

View File

@@ -1,9 +0,0 @@
.BrewCleanup {
.removeBox {
margin-top : 20px;
button {
margin-right : 10px;
background-color : @red;
}
}
}

View File

@@ -1,10 +1,7 @@
require('./brewCompress.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const request = require('superagent'); const request = require('superagent');
const BrewCompress = createClass({ const BrewCompress = createClass({
displayName : 'BrewCompress', displayName : 'BrewCompress',
getDefaultProps(){ getDefaultProps(){
@@ -53,9 +50,9 @@ const BrewCompress = createClass({
if(!this.state.primed) return; if(!this.state.primed) return;
if(!this.state.count){ if(!this.state.count){
return <div className='removeBox'>No Matching Brews found.</div>; return <div className='result noBrews'>No Matching Brews found.</div>;
} }
return <div className='removeBox'> return <div className='result'>
<button onClick={this.cleanup} className='remove'> <button onClick={this.cleanup} className='remove'>
{this.state.pending {this.state.pending
? <i className='fas fa-spin fa-spinner' /> ? <i className='fas fa-spin fa-spinner' />
@@ -69,7 +66,7 @@ const BrewCompress = createClass({
</div>; </div>;
}, },
render(){ render(){
return <div className='BrewCompress'> return <div className='brewUtil brewCompress'>
<h2> Brew Compression </h2> <h2> Brew Compression </h2>
<p>Compresses the text in brews to binary</p> <p>Compresses the text in brews to binary</p>

View File

@@ -1,9 +0,0 @@
.BrewCompress {
.removeBox {
margin-top : 20px;
button {
margin-right : 10px;
background-color : @red;
}
}
}

View File

@@ -1,5 +1,3 @@
require('./brewLookup.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const cx = require('classnames'); const cx = require('classnames');
@@ -55,7 +53,7 @@ const BrewLookup = createClass({
renderFoundBrew(){ renderFoundBrew(){
const brew = this.state.foundBrew; const brew = this.state.foundBrew;
return <div className='foundBrew'> return <div className='result'>
<dl> <dl>
<dt>Title</dt> <dt>Title</dt>
<dd>{brew.title}</dd> <dd>{brew.title}</dd>
@@ -90,7 +88,7 @@ const BrewLookup = createClass({
}, },
render(){ render(){
return <div className='brewLookup'> return <div className='brewUtil brewLookup'>
<h2>Brew Lookup</h2> <h2>Brew Lookup</h2>
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id' /> <input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id' />
<button onClick={this.lookup}> <button onClick={this.lookup}>
@@ -106,7 +104,7 @@ const BrewLookup = createClass({
{this.state.foundBrew {this.state.foundBrew
? this.renderFoundBrew() ? this.renderFoundBrew()
: <div className='noBrew'>No brew found.</div> : <div className='result noBrew'>No brew found.</div>
} }
</div>; </div>;
} }

View File

@@ -1,6 +0,0 @@
.brewLookup {
.cleanButton {
display : inline-block;
width : 100%;
}
}

View File

@@ -1,6 +1,6 @@
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
require('./brewUtils.less');
const BrewCleanup = require('./brewCleanup/brewCleanup.jsx'); const BrewCleanup = require('./brewCleanup/brewCleanup.jsx');
const BrewLookup = require('./brewLookup/brewLookup.jsx'); const BrewLookup = require('./brewLookup/brewLookup.jsx');

View File

@@ -0,0 +1,29 @@
.brewUtil {
.result {
margin-top : 20px;
button {
margin-right : 10px;
background-color : @red;
}
}
.cleanButton {
display : inline-block;
width : 100%;
}
}
.stats {
position : relative;
.pending {
position : absolute;
top : 0.5em;
left : 100px;
width : 100%;
height : 100%;
}
&:has(.pending) { opacity : 0.5; }
dl { grid-template-columns : 200px 250px; }
}

View File

@@ -1,11 +1,8 @@
require('./stats.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const cx = require('classnames');
const request = require('superagent'); const request = require('superagent');
const Stats = createClass({ const Stats = createClass({
displayName : 'Stats', displayName : 'Stats',
getDefaultProps(){ getDefaultProps(){
@@ -14,7 +11,8 @@ const Stats = createClass({
getInitialState(){ getInitialState(){
return { return {
stats : { stats : {
totalBrews : 0 totalBrews : 0,
totalPublishedBrews : 0
}, },
fetching : false fetching : false
}; };
@@ -29,11 +27,13 @@ const Stats = createClass({
.finally(()=>this.setState({ fetching: false })); .finally(()=>this.setState({ fetching: false }));
}, },
render(){ render(){
return <div className='Stats'> return <div className='brewUtil stats'>
<h2> Stats </h2> <h2> Stats </h2>
<dl> <dl>
<dt>Total Brew Count</dt> <dt>Total Brew Count</dt>
<dd>{this.state.stats.totalBrews}</dd> <dd>{this.state.stats.totalBrews}</dd>
<dt>Total Brews Published</dt>
<dd>{this.state.stats.totalPublishedBrews}</dd>
</dl> </dl>
{this.state.fetching {this.state.fetching

View File

@@ -1,13 +0,0 @@
.Stats {
position : relative;
.pending {
position : absolute;
top : 0px;
left : 0px;
width : 100%;
height : 100%;
background-color : rgba(238,238,238, 0.5);
}
}

View File

@@ -6,31 +6,32 @@
.field { .field {
display : grid; display : grid;
grid-template-columns : 120px 150px; grid-template-columns : 120px 200px;
align-items : center; align-items : center;
justify-items : stretch; justify-items : stretch;
width : 100%; width : 100%;
margin-bottom : 20px; margin-bottom : 20px;
input { input {
height : 33px; height : 33px;
padding : 0px 10px; padding : 0px 10px;
margin-bottom : unset; margin-bottom : unset;
font-family : monospace; font-family : monospace;
&[type='date'] { width : 14ch; }
} }
textarea { textarea {
width : 50ch; width : 50ch;
min-height : 7em; min-height : 7em;
max-height : 20em; max-height : 20em;
resize : vertical;
padding : 10px; padding : 10px;
resize : vertical;
} }
} }
button { button {
width: 200px; width : 200px;
i { margin-right : 10px; } i { margin-right : 10px; }
} }

View File

@@ -1,8 +1,8 @@
.notificationLookup { .notificationLookup {
width : 450px; width : 450px;
height : fit-content; height : fit-content;
.noNotification { margin-block : 20px; }
.notificationList { .notificationList {
display : flex; display : flex;
flex-direction : column; flex-direction : column;
@@ -30,11 +30,6 @@
font-size : 20px; font-size : 20px;
font-weight : 900; font-weight : 900;
} }
dl dt{
font-weight: 900;
}
} }
} }
.noNotification { margin-block : 20px; }
} }

View File

@@ -1,13 +1,11 @@
.anchored-box { .anchored-box {
position:absolute; position : absolute;
@supports (inset-block-start: anchor(bottom)){ visibility : hidden;
inset-block-start: anchor(bottom); justify-self : anchor-center;
} @supports (inset-block-start: anchor(bottom)) {
justify-self: anchor-center; inset-block-start : anchor(bottom);
visibility: hidden;
&.active {
visibility: visible;
} }
&.active { visibility : visible; }
} }

View File

@@ -45,6 +45,7 @@ const Combobox = createClass({
}, },
handleDropdown : function(show){ handleDropdown : function(show){
this.setState({ this.setState({
value : show ? '' : this.props.default,
showDropdown : show, showDropdown : show,
inputFocused : this.props.autoSuggest.clearAutoSuggestOnClick ? show : false inputFocused : this.props.autoSuggest.clearAutoSuggestOnClick ? show : false
}); });
@@ -78,7 +79,7 @@ const Combobox = createClass({
if(!e.target.checkValidity()){ if(!e.target.checkValidity()){
this.setState({ this.setState({
value : this.props.default value : this.props.default
}, ()=>this.props.onEntry(e)); });
} }
}} }}
/> />

View File

@@ -27,7 +27,7 @@
position : relative; position : relative;
padding : 5px; padding : 5px;
margin : 0 3px; margin : 0 3px;
font-family : "Open Sans"; font-family : 'Open Sans';
font-size : 11px; font-size : 11px;
cursor : default; cursor : default;
&:hover { &:hover {

View File

@@ -39,7 +39,7 @@ const BrewPage = (props)=>{
...props ...props
}; };
const pageRef = useRef(null); const pageRef = useRef(null);
const cleanText = safeHTML(props.contents); const cleanText = safeHTML(`${props.contents}\n<div class="columnSplit"></div>\n`);
useEffect(()=>{ useEffect(()=>{
if(!pageRef.current) return; if(!pageRef.current) return;
@@ -189,14 +189,13 @@ const BrewRenderer = (props)=>{
const injectedTags = firstLineTokens.find((obj)=>obj.injectedTags !== undefined)?.injectedTags; const injectedTags = firstLineTokens.find((obj)=>obj.injectedTags !== undefined)?.injectedTags;
if(injectedTags) { if(injectedTags) {
styles = { ...styles, ...injectedTags.styles }; styles = { ...styles, ...injectedTags.styles };
styles = _.mapKeys(styles, (v, k) => k.startsWith('--') ? k : _.camelCase(k)); // Convert CSS to camelCase for React styles = _.mapKeys(styles, (v, k)=>k.startsWith('--') ? k : _.camelCase(k)); // Convert CSS to camelCase for React
classes = [classes, injectedTags.classes].join(' ').trim(); classes = [classes, injectedTags.classes].join(' ').trim();
attributes = injectedTags.attributes; attributes = injectedTags.attributes;
} }
pageText = pageText.includes('\n') ? pageText.substring(pageText.indexOf('\n') + 1) : ''; // Remove the \page line pageText = pageText.includes('\n') ? pageText.substring(pageText.indexOf('\n') + 1) : ''; // Remove the \page line
} }
pageText += `\n\n&nbsp;\n\\column\n&nbsp;`; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
const html = Markdown.render(pageText, index); const html = Markdown.render(pageText, index);
return <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} onVisibilityChange={handlePageVisibilityChange} />; return <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} onVisibilityChange={handlePageVisibilityChange} />;

View File

@@ -1,43 +1,39 @@
@import (multiple, less) 'shared/naturalcrit/styles/reset.less'; @import (multiple, less) 'shared/naturalcrit/styles/reset.less';
.brewRenderer { .brewRenderer {
height : 100vh;
padding-top : 60px;
overflow-y : scroll; overflow-y : scroll;
will-change : transform; will-change : transform;
padding-top : 60px; &:has(.facing, .flow) { padding : 60px 30px; }
height : 100vh; &.deployment { background-color : darkred; }
&:has(.facing, .flow) {
padding : 60px 30px;
}
&.deployment {
background-color: darkred;
}
:where(.pages) { :where(.pages) {
&.facing { &.facing {
display: grid; display : grid;
grid-template-columns: repeat(2, auto); grid-template-rows : repeat(3, auto);
grid-template-rows: repeat(3, auto); grid-template-columns : repeat(2, auto);
gap: 10px 10px; gap : 10px 10px;
justify-content: safe center; justify-content : safe center;
&.recto .page:first-child { &.recto .page:first-child {
// sets first page on 'right' ('recto') of the preview, as if for a Cover page. // sets first page on 'right' ('recto') of the preview, as if for a Cover page.
// todo: add a checkbox to toggle this setting // todo: add a checkbox to toggle this setting
grid-column-start: 2; grid-column-start : 2;
} }
& :where(.page) { & :where(.page) {
margin-left: unset !important; margin-right : unset !important;
margin-right: unset !important; margin-left : unset !important;
} }
} }
&.flow { &.flow {
display: flex; display : flex;
flex-wrap: wrap; flex-wrap : wrap;
gap: 10px; gap : 10px;
justify-content: safe center; justify-content : safe center;
& :where(.page) { & :where(.page) {
flex: 0 0 auto; flex : 0 0 auto;
margin-left: unset !important; margin-right : unset !important;
margin-right: unset !important; margin-left : unset !important;
} }
} }
@@ -50,9 +46,7 @@
margin-left : auto; margin-left : auto;
box-shadow : 1px 4px 14px #000000; box-shadow : 1px 4px 14px #000000;
} }
*[id] { *[id] { scroll-margin-top : 100px; }
scroll-margin-top:100px;
}
} }
&::-webkit-scrollbar { &::-webkit-scrollbar {
width : 20px; width : 20px;
@@ -79,11 +73,9 @@
overflow-y : unset; overflow-y : unset;
.pages { .pages {
margin : 0px; margin : 0px;
zoom: 100% !important; zoom : 100% !important;
& > .page { box-shadow : unset; } & > .page { box-shadow : unset; }
} }
} }
.headerNav { .headerNav { visibility : hidden; }
visibility: hidden;
}
} }

View File

@@ -3,7 +3,6 @@ require('./headerNav.less');
import * as React from 'react'; import * as React from 'react';
import * as _ from 'lodash'; import * as _ from 'lodash';
const MAX_TEXT_LENGTH = 40; const MAX_TEXT_LENGTH = 40;
const HeaderNav = React.forwardRef(({}, pagesRef)=>{ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
@@ -11,11 +10,30 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
const renderHeaderLinks = ()=>{ const renderHeaderLinks = ()=>{
if(!pagesRef.current) return; if(!pagesRef.current) return;
// Top Level Pages
// Pages that contain an element with a specified class (e.g. cover pages, table of contents)
// will NOT have its content scanned for navigation headers, instead displaying a custom label
// ---
// The property name is class that will be used for detecting the page is a top level page
// The property value is a function that returns the text to be used
const topLevelPages = {
'.frontCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Cover: ${text}` : 'Cover Page'; },
'.insideCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Interior: ${text}` : 'Interior Cover Page'; },
'.partCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Section: ${text}` : 'Section Cover Page'; },
'.backCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Back: ${text}` : 'Rear Cover Page'; },
'.toc' : ()=>{ return 'Table of Contents'; },
};
const getHeaderContent = (el)=>el.querySelector('h1')?.textContent;
const topLevelPageSelector = Object.keys(topLevelPages).join(',');
const selector = [ const selector = [
'.pages > .page', // All page elements, which by definition have IDs '.pages > .page', // All page elements, which by definition have IDs
'.page:not(:has(.toc)) > [id]', // All direct children of non-ToC .page with an ID (Legacy) `.page:not(:has(${topLevelPageSelector})) > [id]`, // All direct children of non-excluded .pages with an ID (Legacy)
'.page:not(:has(.toc)) > .columnWrapper > [id]', // All direct children of non-ToC .page > .columnWrapper with an ID (V3) `.page:not(:has(${topLevelPageSelector})) > .columnWrapper > [id]`, // All direct children of non-excluded .page > .columnWrapper with an ID (V3)
'.page:not(:has(.toc)) h2', // All non-ToC H2 titles, like Monster frame titles `.page:not(:has(${topLevelPageSelector})) h2`, // All non-excluded H2 titles, like Monster frame titles
]; ];
const elements = pagesRef.current.querySelectorAll(selector.join(',')); const elements = pagesRef.current.querySelectorAll(selector.join(','));
if(!elements) return; if(!elements) return;
@@ -23,45 +41,35 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
// navList is a list of objects which have the following structure: // navList is a list of objects which have the following structure:
// { // {
// depth : how deeply indented the item should be // depth : how deeply indented the item should be
// text : the text to display in the nav link // text : the text to display in the nav link
// link : the hyperlink to navigate to when clicked // link : the hyperlink to navigate to when clicked
// className : [optional] the class to apply to the nav link for styling // className : [optional] the class to apply to the nav link for styling
// } // }
elements.forEach((el)=>{ elements.forEach((el)=>{
if(el.className.match(/\bpage\b/)) { const navEntry = { // Default structure of a navList entry
let text = `Page ${el.id.slice(1)}`; // The ID of a page *should* always be equal to `p` followed by the page number depth : 7, // All unmatched elements with IDs are set to the maximum depth (7)
if(el.querySelector('.toc')){ // If the page contains a table of contents, add "- Contents" to the display text text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto'
text += ' - Contents';
};
navList.push({
depth : 0, // Pages are always at the least indented level
text : text,
link : el.id,
className : 'pageLink'
});
return;
}
if(el.localName.match(/^h[1-6]/)){ // Header elements H1 through H6
navList.push({
depth : el.localName[1], // Depth is set by the header level
text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto'
link : el.id
});
return;
}
navList.push({
depth : 7, // All unmatched elements with IDs are set to the maximum depth (7)
text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto'
link : el.id link : el.id
}); };
}); if(el.classList.contains('page')) {
let text = `Page ${el.id.slice(1)}`; // Get the page # by trimming off the 'p' from the ID
return _.map(navList, (navItem, index)=>{ const pageType = Object.keys(topLevelPages).find((pageType)=>el.querySelector(pageType));
return <HeaderNavItem {...navItem} key={index} />; if(pageType)
text += ` - ${topLevelPages[pageType](el, pageType)}`; // If a Top Level Page, add extra label
navEntry.depth = 0; // Pages are always at the least indented level
navEntry.text = text;
navEntry.className = 'pageLink';
} else if(el.localName.match(/^h[1-6]/)){ // Header elements H1 through H6
navEntry.depth = el.localName[1]; // Depth is set by the header level
}
navList.push(navEntry);
}); });
return _.map(navList, (navItem, index)=><HeaderNavItem {...navItem} key={index} />
);
}; };
return <nav className='headerNav'> return <nav className='headerNav'>
@@ -69,8 +77,7 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
{renderHeaderLinks()} {renderHeaderLinks()}
</ul> </ul>
</nav>; </nav>;
} });
);
const HeaderNavItem = ({ link, text, depth, className })=>{ const HeaderNavItem = ({ link, text, depth, className })=>{

View File

@@ -1,45 +1,37 @@
.headerNav { .headerNav {
position: fixed; position : fixed;
top: 32px; top : 32px;
left: 0px; left : 0px;
padding: 5px 10px; max-width : 40vw;
background-color: #ccc; max-height : calc(100vh - 32px);
border-radius: 5px; padding : 5px 10px;
max-height: calc(100vh - 32px); overflow-y : auto;
max-width: 40vw; background-color : #CCCCCC;
overflow-y: auto; border-radius : 5px;
&.active { &.active {
padding-bottom: 10px; padding-bottom : 10px;
.navIcon { .navIcon { padding-bottom : 10px; }
padding-bottom: 10px;
}
}
.navIcon {
cursor: pointer;
} }
.navIcon { cursor : pointer; }
li { li {
list-style-type: none; list-style-type : none;
a { a {
display: inline-block; display : inline-block;
width: 100%; width : 100%;
font-family: 'Open Sans'; padding : 2px;
font-size: 12px; font-family : 'Open Sans';
padding: 2px; font-size : 12px;
color: inherit; color : inherit;
text-decoration: none; text-decoration : none;
cursor: pointer; cursor : pointer;
&:hover { &:hover { text-decoration : underline; }
text-decoration: underline; &.pageLink { font-weight : 900; }
}
&.pageLink {
font-weight: 900;
}
@depths: 1,2,3,4,5,6,7; @depths: 0,1,2,3,4,5,6,7;
each(@depths, { each(@depths, {
&.depth-@{value} { &.depth-@{value} {
padding-left: ((@value - 1) * 0.5em); padding-left: ((@value) * 0.5em);
} }
}); });
} }

View File

@@ -86,8 +86,8 @@
width : 100%; width : 100%;
} }
.blank { .blank {
height : 1em; height : 1em;
margin-top : 0; margin-top : 0;
& + * { margin-top : 0; } & + * { margin-top : 0; }
} }
} }

View File

@@ -156,7 +156,7 @@
min-width : 46px; min-width : 46px;
height : 100%; height : 100%;
&:hover { background-color : #444444; } &:hover { background-color : #444444; }
&:focus { border : 1px solid #D3D3D3;outline : none;} &:focus {outline : none; border : 1px solid #D3D3D3;}
&:disabled { &:disabled {
color : #777777; color : #777777;
background-color : unset !important; background-color : unset !important;
@@ -182,8 +182,8 @@
position : absolute; position : absolute;
left : 0; left : 0;
z-index : 5; z-index : 5;
display : flex;
width : 32px; width : 32px;
min-width : unset; min-width : unset;
height : 100%; height : 100%;
display : flex;
} }

View File

@@ -456,6 +456,7 @@ const Editor = createClass({
rerenderParent={this.rerenderParent} /> rerenderParent={this.rerenderParent} />
<MetadataEditor <MetadataEditor
metadata={this.props.brew} metadata={this.props.brew}
themeBundle={this.props.themeBundle}
onChange={this.props.onMetaChange} onChange={this.props.onMetaChange}
reportError={this.props.reportError} reportError={this.props.reportError}
userThemes={this.props.userThemes}/> userThemes={this.props.userThemes}/>

View File

@@ -1,8 +1,8 @@
@import 'themes/codeMirror/customEditorStyles.less'; @import 'themes/codeMirror/customEditorStyles.less';
.editor { .editor {
position : relative; position : relative;
width : 100%; width : 100%;
container: editor / inline-size; container : editor / inline-size;
.codeEditor { .codeEditor {
height : 100%; height : 100%;
@@ -45,26 +45,26 @@
color : green; color : green;
} }
.emoji:not(.cm-comment) { .emoji:not(.cm-comment) {
margin-left : 2px;
color : #360034;
background : #ffc8ff;
border-radius : 6px;
font-weight : bold;
padding-bottom : 1px; padding-bottom : 1px;
margin-left : 2px;
font-weight : bold;
color : #360034;
outline : solid 2px #FF96FC;
outline-offset : -2px; outline-offset : -2px;
outline : solid 2px #ff96fc; background : #FFC8FF;
border-radius : 6px;
} }
.superscript:not(.cm-comment) { .superscript:not(.cm-comment) {
font-weight : bold;
color : goldenrod;
vertical-align : super;
font-size : 0.9em; font-size : 0.9em;
font-weight : bold;
vertical-align : super;
color : goldenrod;
} }
.subscript:not(.cm-comment) { .subscript:not(.cm-comment) {
font-weight : bold;
color : rgb(123, 123, 15);
vertical-align : sub;
font-size : 0.9em; font-size : 0.9em;
font-weight : bold;
vertical-align : sub;
color : rgb(123, 123, 15);
} }
.dl-highlight { .dl-highlight {
&.dl-colon-highlight { &.dl-colon-highlight {

View File

@@ -4,7 +4,6 @@ const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
import request from '../../utils/request-middleware.js'; import request from '../../utils/request-middleware.js';
const Nav = require('naturalcrit/nav/nav.jsx');
const Combobox = require('client/components/combobox.jsx'); const Combobox = require('client/components/combobox.jsx');
const TagInput = require('../tagInput/tagInput.jsx'); const TagInput = require('../tagInput/tagInput.jsx');
@@ -40,6 +39,7 @@ const MetadataEditor = createClass({
theme : '5ePHB', theme : '5ePHB',
lang : 'en' lang : 'en'
}, },
onChange : ()=>{}, onChange : ()=>{},
reportError : ()=>{} reportError : ()=>{}
}; };
@@ -67,6 +67,11 @@ const MetadataEditor = createClass({
const inputRules = validations[name] ?? []; const inputRules = validations[name] ?? [];
const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean); const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean);
const debouncedReportValidity = _.debounce((target, errMessage)=>{
callIfExists(target, 'setCustomValidity', errMessage);
callIfExists(target, 'reportValidity');
}, 300); // 300ms debounce delay, adjust as needed
// if no validation rules, save to props // if no validation rules, save to props
if(validationErr.length === 0){ if(validationErr.length === 0){
callIfExists(e.target, 'setCustomValidity', ''); callIfExists(e.target, 'setCustomValidity', '');
@@ -74,14 +79,16 @@ const MetadataEditor = createClass({
...this.props.metadata, ...this.props.metadata,
[name] : e.target.value [name] : e.target.value
}); });
return true;
} else { } else {
// if validation issues, display built-in browser error popup with each error. // if validation issues, display built-in browser error popup with each error.
const errMessage = validationErr.map((err)=>{ const errMessage = validationErr.map((err)=>{
return `- ${err}`; return `- ${err}`;
}).join('\n'); }).join('\n');
callIfExists(e.target, 'setCustomValidity', errMessage);
callIfExists(e.target, 'reportValidity'); debouncedReportValidity(e.target, errMessage);
return false;
} }
}, },
@@ -102,6 +109,7 @@ const MetadataEditor = createClass({
} }
this.props.onChange(this.props.metadata, 'renderer'); this.props.onChange(this.props.metadata, 'renderer');
}, },
handlePublish : function(val){ handlePublish : function(val){
this.props.onChange({ this.props.onChange({
...this.props.metadata, ...this.props.metadata,
@@ -112,6 +120,14 @@ const MetadataEditor = createClass({
handleTheme : function(theme){ handleTheme : function(theme){
this.props.metadata.renderer = theme.renderer; this.props.metadata.renderer = theme.renderer;
this.props.metadata.theme = theme.path; this.props.metadata.theme = theme.path;
this.props.onChange(this.props.metadata, 'theme');
},
handleThemeWritein : function(e) {
const shareId = e.target.value.split('/').pop(); //Extract just the ID if a URL was pasted in
this.props.metadata.theme = shareId;
this.props.onChange(this.props.metadata, 'theme'); this.props.onChange(this.props.metadata, 'theme');
}, },
@@ -200,7 +216,7 @@ const MetadataEditor = createClass({
if(theme.path == this.props.metadata.shareId) return; if(theme.path == this.props.metadata.shareId) return;
const preview = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`; const preview = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`;
const texture = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`; const texture = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`;
return <div className='item' key={`${renderer}_${theme.name}`} onClick={()=>this.handleTheme(theme)} title={''}> return <div className='item' key={`${renderer}_${theme.name}`} value={`${theme.author ?? renderer} : ${theme.name}`} data={theme} title={''}>
{theme.author ?? renderer} : {theme.name} {theme.author ?? renderer} : {theme.name}
<div className='texture-container'> <div className='texture-container'>
<img src={texture}/> <img src={texture}/>
@@ -210,26 +226,40 @@ const MetadataEditor = createClass({
<img src={preview}/> <img src={preview}/>
</div> </div>
</div>; </div>;
}); }).filter(Boolean);
}; };
const currentRenderer = this.props.metadata.renderer; const currentRenderer = this.props.metadata.renderer;
const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme] const currentThemeDisplay = this.props.themeBundle?.name ? `${this.props.themeBundle.author ?? currentRenderer} : ${this.props.themeBundle.name}` : 'No Theme Selected';
?? { name: `!!! THEME MISSING !!! ID=${this.props.metadata.theme}` };
let dropdown; let dropdown;
if(currentRenderer == 'legacy') { if(currentRenderer == 'legacy') {
dropdown = dropdown =
<Nav.dropdown className='disabled value' trigger='disabled'> <div className='disabled value' trigger='disabled'>
<div> {`Themes are not supported in the Legacy Renderer`} <i className='fas fa-caret-down'></i> </div> <div> Themes are not supported in the Legacy Renderer </div>
</Nav.dropdown>; </div>;
} else { } else {
dropdown = dropdown =
<Nav.dropdown className='value' trigger='click'> <div className='value'>
<div> {currentTheme.author ?? _.upperFirst(currentRenderer)} : {currentTheme.name} <i className='fas fa-caret-down'></i> </div> <Combobox trigger='click'
className='themes-dropdown'
{listThemes(currentRenderer)} default={currentThemeDisplay}
</Nav.dropdown>; placeholder='Select from below, or enter the Share URL or ID of a brew with the meta:theme tag'
onSelect={(value)=>this.handleTheme(value)}
onEntry={(e)=>{
e.target.setCustomValidity(''); //Clear the validation popup while typing
if(this.handleFieldChange('theme', e))
this.handleThemeWritein(e);
}}
options={listThemes(currentRenderer)}
autoSuggest={{
suggestMethod : 'includes',
clearAutoSuggestOnClick : true,
filterOn : ['value', 'title']
}}
/>
<small>Select from the list below (built-in themes and brews you have tagged "meta:theme"), or paste in the Share URL or Share ID of any brew.</small>
</div>;
} }
return <div className='field themes'> return <div className='field themes'>
@@ -251,8 +281,6 @@ const MetadataEditor = createClass({
}); });
}; };
const debouncedHandleFieldChange = _.debounce(this.handleFieldChange, 500);
return <div className='field language'> return <div className='field language'>
<label>language</label> <label>language</label>
<div className='value'> <div className='value'>
@@ -263,7 +291,7 @@ const MetadataEditor = createClass({
onSelect={(value)=>this.handleLanguage(value)} onSelect={(value)=>this.handleLanguage(value)}
onEntry={(e)=>{ onEntry={(e)=>{
e.target.setCustomValidity(''); //Clear the validation popup while typing e.target.setCustomValidity(''); //Clear the validation popup while typing
debouncedHandleFieldChange('lang', e); this.handleFieldChange('lang', e);
}} }}
options={listLanguages()} options={listLanguages()}
autoSuggest={{ autoSuggest={{
@@ -271,8 +299,7 @@ const MetadataEditor = createClass({
clearAutoSuggestOnClick : true, clearAutoSuggestOnClick : true,
filterOn : ['value', 'detail', 'title'] filterOn : ['value', 'detail', 'title']
}} }}
> />
</Combobox>
<small>Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.</small> <small>Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.</small>
</div> </div>
@@ -345,7 +372,7 @@ const MetadataEditor = createClass({
placeholder='add tag' unique={true} placeholder='add tag' unique={true}
values={this.props.metadata.tags} values={this.props.metadata.tags}
onChange={(e)=>this.handleFieldChange('tags', e)} onChange={(e)=>this.handleFieldChange('tags', e)}
/> />
<div className='field systems'> <div className='field systems'>
<label>systems</label> <label>systems</label>
@@ -370,7 +397,7 @@ const MetadataEditor = createClass({
values={this.props.metadata.invitedAuthors} values={this.props.metadata.invitedAuthors}
notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']} notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)} onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}
/> />
<h2>Privacy</h2> <h2>Privacy</h2>

View File

@@ -1,28 +1,31 @@
@import 'naturalcrit/styles/colors.less'; @import 'naturalcrit/styles/colors.less';
.userThemeName {
padding-right : 10px;
padding-left : 10px;
}
.metadataEditor { .metadataEditor {
position : absolute; position : absolute;
z-index : 5;
box-sizing : border-box; box-sizing : border-box;
width : 100%; width : 100%;
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this. height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
padding : 25px; padding : 25px;
overflow-y : auto; overflow-y : auto;
font-size : 13px;
background-color : #999999; background-color : #999999;
font-size : 13px;
h1 { h1 {
margin: 0 0 40px; margin : 0 0 40px;
font-weight: bold; font-weight : bold;
text-transform: uppercase; text-transform : uppercase;
} }
h2 { h2 {
margin : 20px 0; margin : 20px 0;
font-weight : bold; font-weight : bold;
border-bottom: 2px solid gray; color : #555555;
color: #555; border-bottom : 2px solid gray;
} }
& > div { margin-bottom : 10px; } & > div { margin-bottom : 10px; }
@@ -51,10 +54,10 @@
min-width : 200px; min-width : 200px;
& > label { & > label {
width : 80px; width : 80px;
font-size : 0.9em;
font-weight : 800; font-weight : 800;
line-height : 1.8em; line-height : 1.8em;
text-transform : uppercase; text-transform : uppercase;
font-size: .9em;
} }
& > .value { & > .value {
flex : 1 1 auto; flex : 1 1 auto;
@@ -71,8 +74,7 @@
border : 1px solid gray; border : 1px solid gray;
&:focus { outline : 1px solid #444444; } &:focus { outline : 1px solid #444444; }
} }
&.thumbnail { &.thumbnail, &.themes {
height : 1.4em;
label { line-height : 2.0em; } label { line-height : 2.0em; }
.value { .value {
overflow : hidden; overflow : hidden;
@@ -88,6 +90,17 @@
} }
} }
&.themes {
.value {
overflow : visible;
text-overflow : auto;
}
button {
padding-right : 5px;
padding-left : 5px;
}
}
&.description { &.description {
flex : 1; flex : 1;
textarea.value { textarea.value {
@@ -123,8 +136,8 @@
margin-right : 15px; margin-right : 15px;
font-size : 0.9em; font-size : 0.9em;
font-weight : 800; font-weight : 800;
white-space : nowrap;
vertical-align : middle; vertical-align : middle;
white-space : nowrap;
cursor : pointer; cursor : pointer;
user-select : none; user-select : none;
} }
@@ -151,94 +164,74 @@
.colorButton(@red); .colorButton(@red);
} }
} }
.authors.field .value { .authors.field .value { line-height : 1.5em; }
line-height : 1.5em;
}
.themes.field { .themes.field {
.navDropdownContainer { & .dropdown-container {
position : relative; position : relative;
z-index : 100; z-index : 100;
background-color : white; background-color : white;
&.disabled { }
font-style : italic; & .dropdown-options { overflow-y : visible; }
color : dimgray; .disabled {
background-color : darkgray; font-style : italic;
} color : dimgray;
& > div:first-child { background-color : darkgray;
padding : 3px 3px; }
background-color : inherit; .item {
border : 1px solid gray; position : relative;
i { float : right; } padding : 3px 3px;
&:hover { overflow : visible;
color : white; background-color : white;
background-color : @blue; border-top : 1px solid rgb(118, 118, 118);
.preview {
position : absolute;
top : 0;
right : 0;
z-index : 1;
display : flex;
flex-direction : column;
width : 200px;
overflow : hidden;
color : black;
background : #CCCCCC;
border-radius : 5px;
box-shadow : 0 0 5px black;
opacity : 0;
transition : opacity 250ms ease;
h6 {
padding-block : 0.5em;
padding-inline : 1em;
font-weight : 900;
border-bottom : 2px solid hsl(0,0%,40%);
} }
} }
.navDropdown .item > p {
width : 45%; .texture-container {
height : 1.1em; position : absolute;
overflow : hidden; top : 0;
text-overflow : ellipsis; left : 0;
white-space : nowrap; width : 100%;
} height : 100%;
.navDropdown { min-height : 100%;
position : absolute; overflow : hidden;
width : 100%; > img {
box-shadow : 0px 5px 10px rgba(0, 0, 0, 0.3); position : absolute;
.item { top : 0;
position : relative; right : 0;
padding : 3px 3px; width : 50%;
overflow : visible; min-height : 100%;
background-color : white; -webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
border-top : 1px solid rgb(118, 118, 118); mask-image : linear-gradient(90deg, transparent, black 20%);
.preview {
position : absolute;
top : 0;
right : 0;
z-index : 1;
display : flex;
flex-direction : column;
width : 200px;
overflow : hidden;
color : black;
background : #CCCCCC;
border-radius : 5px;
box-shadow : 0 0 5px black;
opacity : 0;
transition : opacity 250ms ease;
h6 {
padding-block : 0.5em;
padding-inline : 1em;
font-weight : 900;
border-bottom : 2px solid hsl(0,0%,40%);
}
}
&:hover {
color : white;
background-color : @blue;
}
&:hover > .preview { opacity : 1; }
.texture-container {
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
min-height : 100%;
overflow : hidden;
> img {
position : absolute;
top : 0px;
right : 0;
width : 50%;
min-height : 100%;
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
mask-image : linear-gradient(90deg, transparent, black 20%);
}
}
} }
} }
&:hover {
color : white;
background-color : @blue;
filter : unset;
}
&:hover > .preview { opacity : 1; }
} }
} }

View File

@@ -27,6 +27,19 @@ module.exports = {
(value)=>{ (value)=>{
return new RegExp(/^([a-zA-Z]{2,3})(-[a-zA-Z]{4})?(-(?:[0-9]{3}|[a-zA-Z]{2}))?$/).test(value) === false && (value.length > 0) ? 'Invalid language code.' : null; return new RegExp(/^([a-zA-Z]{2,3})(-[a-zA-Z]{4})?(-(?:[0-9]{3}|[a-zA-Z]{2}))?$/).test(value) === false && (value.length > 0) ? 'Invalid language code.' : null;
} }
],
theme : [
(value)=>{
const URL = global.config.baseUrl.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); //Escape any regex characters
const shareIDPattern = '[a-zA-Z0-9-_]{12}';
const shareURLRegex = new RegExp(`^${URL}\\/share\\/${shareIDPattern}$`);
const shareIDRegex = new RegExp(`^${shareIDPattern}$`);
if(value?.length === 0) return null;
if(shareURLRegex.test(value)) return null;
if(shareIDRegex.test(value)) return null;
return 'Must be a valid Share URL or a 12-character ID.';
}
] ]
}; };

View File

@@ -210,56 +210,56 @@ const Snippetbar = createClass({
return ( return (
<div className='editors'> <div className='editors'>
{this.props.view !== 'meta' && <><div className='historyTools'> {this.props.view !== 'meta' && <><div className='historyTools'>
<div className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`} <div className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
onClick={this.toggleHistoryMenu} > onClick={this.toggleHistoryMenu} >
<i className='fas fa-clock-rotate-left' /> <i className='fas fa-clock-rotate-left' />
{ this.state.showHistory && this.renderHistoryItems() } { this.state.showHistory && this.renderHistoryItems() }
</div>
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
onClick={this.props.undo} >
<i className='fas fa-undo' />
</div>
<div className={`editorTool redo ${this.props.historySize.redo ? 'active' : ''}`}
onClick={this.props.redo} >
<i className='fas fa-redo' />
</div>
</div> </div>
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`} <div className='codeTools'>
onClick={this.props.undo} > <div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
<i className='fas fa-undo' /> onClick={this.props.foldCode} >
</div> <i className='fas fa-compress-alt' />
<div className={`editorTool redo ${this.props.historySize.redo ? 'active' : ''}`} </div>
onClick={this.props.redo} > <div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
<i className='fas fa-redo' /> onClick={this.props.unfoldCode} >
</div> <i className='fas fa-expand-alt' />
</div> </div>
<div className='codeTools'> <div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
<div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`} onClick={this.toggleThemeSelector} >
onClick={this.props.foldCode} > <i className='fas fa-palette' />
<i className='fas fa-compress-alt' /> {this.state.themeSelector && this.renderThemeSelector()}
</div> </div>
<div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`} </div></>}
onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' />
</div>
<div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
</div>
</div></>}
<div className='tabs'> <div className='tabs'>
<div className={cx('text', { selected: this.props.view === 'text' })} <div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}> onClick={()=>this.props.onViewChange('text')}>
<i className='fa fa-beer' /> <i className='fa fa-beer' />
</div>
<div className={cx('style', { selected: this.props.view === 'style' })}
onClick={()=>this.props.onViewChange('style')}>
<i className='fa fa-paint-brush' />
</div>
<div className={cx('meta', { selected: this.props.view === 'meta' })}
onClick={()=>this.props.onViewChange('meta')}>
<i className='fas fa-info-circle' />
</div>
</div> </div>
<div className={cx('style', { selected: this.props.view === 'style' })}
onClick={()=>this.props.onViewChange('style')}>
<i className='fa fa-paint-brush' />
</div>
<div className={cx('meta', { selected: this.props.view === 'meta' })}
onClick={()=>this.props.onViewChange('meta')}>
<i className='fas fa-info-circle' />
</div>
</div>
</div> </div>
) );
}, },
render : function(){ render : function(){

View File

@@ -22,7 +22,7 @@
justify-content : flex-end; justify-content : flex-end;
min-width : 225px; min-width : 225px;
&:only-child { margin-left : auto;min-width:unset;} &:only-child {min-width : unset; margin-left : auto;}
>div { >div {
display : flex; display : flex;
@@ -39,9 +39,7 @@
text-align : center; text-align : center;
cursor : pointer; cursor : pointer;
&.editorTool:not(.active) { &.editorTool:not(.active) { cursor : not-allowed; }
cursor:not-allowed;
}
&:hover,&.selected { background-color : #999999; } &:hover,&.selected { background-color : #999999; }
&.text { &.text {
@@ -151,9 +149,9 @@
position : absolute; position : absolute;
top : 100%; top : 100%;
z-index : 1000; z-index : 1000;
visibility : hidden;
padding : 0px; padding : 0px;
margin-left : -5px; margin-left : -5px;
visibility : hidden;
background-color : #DDDDDD; background-color : #DDDDDD;
.snippet { .snippet {
position : relative; position : relative;

View File

@@ -3,43 +3,43 @@ const React = require('react');
const { useState, useEffect } = React; const { useState, useEffect } = React;
const _ = require('lodash'); const _ = require('lodash');
const TagInput = ({ unique = true, values = [], ...props }) => { const TagInput = ({ unique = true, values = [], ...props })=>{
const [tempInputText, setTempInputText] = useState(''); const [tempInputText, setTempInputText] = useState('');
const [tagList, setTagList] = useState(values.map((value) => ({ value, editing: false }))); const [tagList, setTagList] = useState(values.map((value)=>({ value, editing: false })));
useEffect(()=>{ useEffect(()=>{
handleChange(tagList.map((context)=>context.value)) handleChange(tagList.map((context)=>context.value));
}, [tagList]) }, [tagList]);
const handleChange = (value)=>{ const handleChange = (value)=>{
props.onChange({ props.onChange({
target : { value } target : { value }
}) });
}; };
const handleInputKeyDown = ({ evt, value, index, options = {} }) => { const handleInputKeyDown = ({ evt, value, index, options = {} })=>{
if (_.includes(['Enter', ','], evt.key)) { if(_.includes(['Enter', ','], evt.key)) {
evt.preventDefault(); evt.preventDefault();
submitTag(evt.target.value, value, index); submitTag(evt.target.value, value, index);
if (options.clear) { if(options.clear) {
setTempInputText(''); setTempInputText('');
} }
} }
}; };
const submitTag = (newValue, originalValue, index) => { const submitTag = (newValue, originalValue, index)=>{
setTagList((prevContext) => { setTagList((prevContext)=>{
// remove existing tag // remove existing tag
if(newValue === null){ if(newValue === null){
return [...prevContext].filter((context, i)=>i !== index); return [...prevContext].filter((context, i)=>i !== index);
} }
// add new tag // add new tag
if(originalValue === null){ if(originalValue === null){
return [...prevContext, { value: newValue, editing: false }] return [...prevContext, { value: newValue, editing: false }];
} }
// update existing tag // update existing tag
return prevContext.map((context, i) => { return prevContext.map((context, i)=>{
if (i === index) { if(i === index) {
return { ...context, value: newValue, editing: false }; return { ...context, value: newValue, editing: false };
} }
return context; return context;
@@ -47,10 +47,10 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
}); });
}; };
const editTag = (index) => { const editTag = (index)=>{
setTagList((prevContext) => { setTagList((prevContext)=>{
return prevContext.map((context, i) => { return prevContext.map((context, i)=>{
if (i === index) { if(i === index) {
return { ...context, editing: true }; return { ...context, editing: true };
} }
return { ...context, editing: false }; return { ...context, editing: false };
@@ -58,24 +58,24 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
}); });
}; };
const renderReadTag = (context, index) => { const renderReadTag = (context, index)=>{
return ( return (
<li key={index} <li key={index}
data-value={context.value} data-value={context.value}
className='tag' className='tag'
onClick={() => editTag(index)}> onClick={()=>editTag(index)}>
{context.value} {context.value}
<button onClick={(evt)=>{evt.stopPropagation(); submitTag(null, context.value, index)}}><i className='fa fa-times fa-fw'/></button> <button onClick={(evt)=>{evt.stopPropagation(); submitTag(null, context.value, index);}}><i className='fa fa-times fa-fw'/></button>
</li> </li>
); );
}; };
const renderWriteTag = (context, index) => { const renderWriteTag = (context, index)=>{
return ( return (
<input type='text' <input type='text'
key={index} key={index}
defaultValue={context.value} defaultValue={context.value}
onKeyDown={(evt) => handleInputKeyDown({evt, value: context.value, index: index})} onKeyDown={(evt)=>handleInputKeyDown({ evt, value: context.value, index: index })}
autoFocus autoFocus
/> />
); );
@@ -86,7 +86,7 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
<label>{props.label}</label> <label>{props.label}</label>
<div className='value'> <div className='value'>
<ul className='list'> <ul className='list'>
{tagList.map((context, index) => { return context.editing ? renderWriteTag(context, index) : renderReadTag(context, index); })} {tagList.map((context, index)=>{ return context.editing ? renderWriteTag(context, index) : renderReadTag(context, index); })}
</ul> </ul>
<input <input
@@ -94,8 +94,8 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
className='value' className='value'
placeholder={props.placeholder} placeholder={props.placeholder}
value={tempInputText} value={tempInputText}
onChange={(e) => setTempInputText(e.target.value)} onChange={(e)=>setTempInputText(e.target.value)}
onKeyDown={(evt) => handleInputKeyDown({ evt, value: null, options: { clear: true } })} onKeyDown={(evt)=>handleInputKeyDown({ evt, value: null, options: { clear: true } })}
/> />
</div> </div>
</div> </div>

View File

@@ -1,36 +1,32 @@
@import 'naturalcrit/styles/core.less'; @import 'naturalcrit/styles/core.less';
.homebrew{ .homebrew {
height : 100%; height : 100%;
.sitePage{ .sitePage {
display : flex; display : flex;
height : 100%;
background-color : @steel;
flex-direction : column; flex-direction : column;
height : 100%;
overflow-y : hidden; overflow-y : hidden;
.content{ background-color : @steel;
.content {
position : relative; position : relative;
height : calc(~"100% - 29px"); //Navbar height
flex : auto; flex : auto;
height : calc(~'100% - 29px'); //Navbar height
overflow-y : hidden; overflow-y : hidden;
} }
&.listPage .content { &.listPage .content {
overflow-y : scroll; overflow-y : scroll;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 20px; width : 20px;
&:horizontal{ &:horizontal {
height: 20px; width : auto;
width:auto; height : 20px;
} }
&-thumb { &-thumb {
background: linear-gradient(90deg, #d3c1af 15px, #00000000 15px); background : linear-gradient(90deg, #D3C1AF 15px, #00000000 15px);
&:horizontal{ &:horizontal { background : linear-gradient(0deg, #D3C1AF 15px, #00000000 15px); }
background: linear-gradient(0deg, #d3c1af 15px, #00000000 15px);
}
}
&-corner {
visibility: hidden;
} }
&-corner { visibility : hidden; }
} }
} }
} }

View File

@@ -116,6 +116,19 @@ const ErrorNavItem = createClass({
</Nav.item>; </Nav.item>;
} }
if(HBErrorCode === '10') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like the brew you have selected
as a theme is not tagged for use as a
theme. Verify that
brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
{response.body.brewId}</a> has the <span className='lowercase'>meta:theme</span> tag!
</div>
</Nav.item>;
}
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'> return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops! Oops!
<div className='errorContainer'> <div className='errorContainer'>

View File

@@ -1,78 +1,70 @@
.navItem.error { .navItem.error {
position : relative; position : relative;
background-color : @red; background-color : @red;
} }
.errorContainer{ .errorContainer {
animation-name: glideDown; position : absolute;
animation-duration: 0.4s; top : 100%;
position : absolute; left : 50%;
top : 100%; z-index : 1000;
left : 50%; width : 140px;
z-index : 1000; padding : 3px;
width : 140px; font-size : 10px;
padding : 3px; font-weight : 800;
color : white; color : white;
background-color : #333; text-align : center;
border : 3px solid #444; text-transform : uppercase;
border-radius : 5px; background-color : #333333;
transform : translate(-50% + 3px, 10px); border : 3px solid #444444;
text-align : center; border-radius : 5px;
font-size : 10px; transform : translate(-50% + 3px, 10px);
font-weight : 800; animation-name : glideDown;
text-transform : uppercase; animation-duration : 0.4s;
.lowercase { .lowercase { text-transform : none; }
text-transform : none; a { color : @teal; }
&::before {
position : absolute;
top : -23px;
left : 53px;
width : 0px;
height : 0px;
content : '';
border-top : 10px solid transparent;
border-right : 10px solid transparent;
border-bottom : 10px solid #444444;
border-left : 10px solid transparent;
}
&::after {
position : absolute;
top : -19px;
left : 53px;
width : 0px;
height : 0px;
content : '';
border-top : 10px solid transparent;
border-right : 10px solid transparent;
border-bottom : 10px solid #333333;
border-left : 10px solid transparent;
}
.deny {
display : inline-block;
width : 48%;
padding : 5px;
margin : 1px;
background-color : #333333;
border-left : 1px solid #666666;
.animate(background-color);
&:hover { background-color : red; }
}
.confirm {
display : inline-block;
width : 48%;
padding : 5px;
margin : 1px;
color : white;
background-color : #333333;
.animate(background-color);
&:hover { background-color : teal; }
} }
a{
color : @teal;
}
&:before {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #444;
left: 53px;
top: -23px;
}
&:after {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #333;
left: 53px;
top: -19px;
}
.deny {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
border-left : 1px solid #666;
.animate(background-color);
&:hover{
background-color : red;
}
}
.confirm {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
color : white;
.animate(background-color);
&:hover{
background-color : teal;
}
}
} }

View File

@@ -24,11 +24,11 @@
} }
.homebrew nav { .homebrew nav {
position : relative;
z-index : 2;
display : flex;
justify-content : space-between;
background-color : #333333; background-color : #333333;
position : relative;
z-index : 2;
display : flex;
justify-content : space-between;
.navSection { .navSection {
display : flex; display : flex;
@@ -82,8 +82,8 @@
font-weight : 800; font-weight : 800;
line-height : 13px; line-height : 13px;
color : white; color : white;
text-decoration : none;
text-transform : uppercase; text-transform : uppercase;
text-decoration : none;
cursor : pointer; cursor : pointer;
background-color : #333333; background-color : #333333;
i { i {
@@ -106,11 +106,11 @@
display : block; display : block;
width : 100%; width : 100%;
overflow : hidden; overflow : hidden;
text-overflow : ellipsis;
font-size : 12px; font-size : 12px;
font-weight : 800; font-weight : 800;
color : white; color : white;
text-align : center; text-align : center;
text-overflow : ellipsis;
text-transform : initial; text-transform : initial;
white-space : nowrap; white-space : nowrap;
background-color : transparent; background-color : transparent;
@@ -170,16 +170,16 @@
h4 { h4 {
box-sizing : border-box; box-sizing : border-box;
display : block; display : block;
flex-basis : 20%;
flex-grow : 1; flex-grow : 1;
flex-basis : 20%;
min-width : 76px; min-width : 76px;
padding : 5px 0; padding : 5px 0;
color : #BBBBBB; color : #BBBBBB;
text-align : center; text-align : center;
} }
p { p {
flex-basis : 80%;
flex-grow : 1; flex-grow : 1;
flex-basis : 80%;
padding : 5px 0; padding : 5px 0;
font-family : 'Open Sans', sans-serif; font-family : 'Open Sans', sans-serif;
font-size : 10px; font-size : 10px;
@@ -215,10 +215,10 @@
z-index : 10000; z-index : 10000;
box-sizing : border-box; box-sizing : border-box;
display : block; display : block;
visibility : hidden;
width : 100%; width : 100%;
padding : 13px 5px; padding : 13px 5px;
text-align : center; text-align : center;
visibility : hidden;
background-color : #333333; background-color : #333333;
} }
} }

View File

@@ -30,11 +30,11 @@ const BrewItem = ({
} }
request.delete(`/api/${brew.googleId ?? ''}${brew.editId}`).send().end((err, res)=>{ request.delete(`/api/${brew.googleId ?? ''}${brew.editId}`).send().end((err, res)=>{
if (err) reportError(err); else window.location.reload(); if(err) reportError(err); else window.location.reload();
}); });
}, [brew, reportError]); }, [brew, reportError]);
const updateFilter = useCallback((type, term)=> updateListFilter(type, term), [updateListFilter]); const updateFilter = useCallback((type, term)=>updateListFilter(type, term), [updateListFilter]);
const renderDeleteBrewLink = ()=>{ const renderDeleteBrewLink = ()=>{
if(!brew.editId) return null; if(!brew.editId) return null;

View File

@@ -1,148 +1,129 @@
.brewItem{ .brewItem {
position : relative; position : relative;
box-sizing : border-box;
display : inline-block; display : inline-block;
vertical-align : top;
box-sizing : border-box;
box-sizing : border-box;
overflow : hidden;
width : 48%; width : 48%;
min-height : 105px; min-height : 105px;
margin-right : 15px;
margin-bottom : 15px;
padding : 5px 15px 2px 6px; padding : 5px 15px 2px 6px;
padding-right : 15px; padding-right : 15px;
border : 1px solid #c9ad6a; margin-right : 15px;
margin-bottom : 15px;
overflow : hidden;
vertical-align : top;
background-color : #CAB2802E;
border : 1px solid #C9AD6A;
border-radius : 5px; border-radius : 5px;
box-shadow : 0px 4px 5px 0px #333333;
break-inside : avoid;
-webkit-column-break-inside : avoid; -webkit-column-break-inside : avoid;
page-break-inside : avoid; page-break-inside : avoid;
break-inside : avoid; .thumbnail {
box-shadow : 0px 4px 5px 0px #333; position : absolute;
background-color : #cab2802e; top : 0;
.thumbnail { right : 0;
position: absolute; z-index : -1;
width: 150px; width : 150px;
height: 100%; height : 100%;
top: 0; background-repeat : no-repeat;
right: 0; background-position : right top;
z-index: -1; background-size : contain;
background-size: contain; opacity : 50%;
background-repeat: no-repeat; -webkit-mask-image : linear-gradient(80deg, #00000000 20%, #005500 40%);
background-position: right top; mask-image : linear-gradient(80deg, #00000000 20%, #005500 40%);
mask-image: linear-gradient(80deg, #0000 20%, #050 40%);
-webkit-mask-image: linear-gradient(80deg, #0000 20%, #050 40%);
opacity: 50%;
} }
.text { .text {
min-height : 54px; min-height : 54px;
h4{ h4 {
margin-bottom : 5px; margin-bottom : 5px;
font-size : 2.2em; font-size : 2.2em;
} }
} }
.info{ .info {
position: initial; position : initial;
bottom: 2px; bottom : 2px;
font-family : ScalySansRemake; font-family : "ScalySansRemake";
font-size : 1.2em; font-size : 1.2em;
&>span{ & > span {
margin-right : 12px; margin-right : 12px;
line-height : 1.5em; line-height : 1.5em;
a { a { color : inherit; }
color:inherit;
}
} }
} }
.brewTags span { .brewTags span {
background-color: #c8ac6e3b; display : inline-block;
margin: 2px; padding : 2px;
padding: 2px; margin : 2px;
border: 1px solid #c8ac6e; font-weight : bold;
border-radius: 4px; white-space : nowrap;
white-space: nowrap; cursor : pointer;
display: inline-block; background-color : #C8AC6E3B;
font-weight: bold; border : 1px solid #C8AC6E;
border-color: currentColor; border-color : currentColor;
cursor : pointer; border-radius : 4px;
&:before { &::before {
font-family: 'Font Awesome 5 Free'; margin-right : 3px;
font-size: 12px; font-family : 'Font Awesome 5 Free';
margin-right: 3px; font-size : 12px;
} }
&.type { &.type {
background-color: #0080003b; color : #008000;
color: #008000; background-color : #0080003B;
&:before{ &::before { content : '\f0ad'; }
content: '\f0ad';
}
} }
&.group { &.group {
background-color: #5050503b; color : #000000;
color: #000000; background-color : #5050503B;
&:before{ &::before { content : '\f500'; }
content: '\f500';
}
} }
&.meta { &.meta {
background-color: #0000803b; color : #000080;
color: #000080; background-color : #0000803B;
&:before{ &::before { content : '\f05a'; }
content: '\f05a';
}
} }
&.system { &.system {
background-color: #8000003b; color : #800000;
color: #800000; background-color : #8000003B;
&:before{ &::before { content : '\f518'; }
content: '\f518';
}
} }
} }
&:hover{ &:hover {
.links{ .links { opacity : 1; }
opacity : 1;
}
} }
&:nth-child(2n + 1){ &:nth-child(2n + 1) { margin-right : 0px; }
margin-right : 0px; .links {
}
.links{
.animate(opacity); .animate(opacity);
position : absolute; position : absolute;
top : 0px; top : 0px;
right : 0px; right : 0px;
height : 100%;
width : 2em; width : 2em;
opacity : 0; height : 100%;
background-color : fade(black, 60%);
text-align : center; text-align : center;
a{ background-color : fade(black, 60%);
opacity : 0;
a {
.animate(opacity); .animate(opacity);
display : block; display : block;
margin : 8px 0px; margin : 8px 0px;
opacity : 0.6;
font-size : 1.3em; font-size : 1.3em;
color : white; color : white;
text-decoration : unset; text-decoration : unset;
&:hover{ opacity : 0.6;
opacity : 1; &:hover { opacity : 1; }
} i { cursor : pointer; }
i{
cursor : pointer;
}
} }
} }
.googleDriveIcon { .googleDriveIcon {
height : 18px; height : 18px;
padding : 0px; padding : 0px;
margin : -5px; margin : -5px;
} }
.homebreweryIcon { .homebreweryIcon {
mix-blend-mode : darken;
height : 24px;
position : relative; position : relative;
top : 5px; top : 5px;
left : -5px; left : -5px;
height : 24px;
mix-blend-mode : darken;
} }
} }

View File

@@ -1,5 +1,5 @@
.noColumns(){ .noColumns() {
column-count : auto; column-count : auto;
column-fill : auto; column-fill : auto;
column-gap : normal; column-gap : normal;
@@ -13,177 +13,151 @@
height : auto; height : auto;
min-height : 279.4mm; min-height : 279.4mm;
margin : 20px auto; margin : 20px auto;
contain : unset; contain : unset;
} }
.listPage{ .listPage {
.content{ .content {
z-index : 1; z-index : 1;
.page{ .page {
.noColumns() !important; //Needed to override PHB Theme since this is on a lower @layer .noColumns() !important; //Needed to override PHB Theme since this is on a lower @layer
&::after{ &::after { display : none; }
display : none; .noBrews {
}
.noBrews{
margin : 10px 0px; margin : 10px 0px;
font-size : 1.3em; font-size : 1.3em;
font-style : italic; font-style : italic;
} }
.brewCollection { .brewCollection {
h1:hover{ h1:hover { cursor : pointer; }
cursor: pointer;
}
.active::before, .inactive::before { .active::before, .inactive::before {
font-family: 'Font Awesome 5 Free'; padding-right : 0.5em;
font-weight: 900; font-family : 'Font Awesome 5 Free';
font-size: 0.6cm; font-size : 0.6cm;
padding-right: 0.5em; font-weight : 900;
}
.active {
color: var(--HB_Color_HeaderText);
}
.active::before {
content: '\f107';
}
.inactive {
color: #707070;
}
.inactive::before {
content: '\f105';
} }
.active { color : var(--HB_Color_HeaderText); }
.active::before { content : '\f107'; }
.inactive { color : #707070; }
.inactive::before { content : '\f105'; }
} }
} }
} }
.sort-container { .sort-container {
font-family : 'Open Sans', sans-serif; position : sticky;
position : sticky; top : 0;
top : 0; left : 0;
left : 0; z-index : 1;
width : 100%; display : flex;
height : 30px; flex-wrap : wrap;
background-color : #555; row-gap : 5px;
border-top : 1px solid #666; column-gap : 15px;
border-bottom : 1px solid #666; align-items : baseline;
color : white; justify-content : center;
text-align : center; width : 100%;
z-index : 1; height : 30px;
display : flex; font-family : 'Open Sans', sans-serif;
justify-content : center; color : white;
align-items : baseline; text-align : center;
column-gap : 15px; background-color : #555555;
row-gap : 5px; border-top : 1px solid #666666;
flex-wrap : wrap; border-bottom : 1px solid #666666;
h6{ h6 {
text-transform : uppercase;
font-family : 'Open Sans', sans-serif; font-family : 'Open Sans', sans-serif;
font-size : 11px; font-size : 11px;
font-weight : bold; font-weight : bold;
text-transform : uppercase;
} }
.sort-option { .sort-option {
display: flex; display : flex;
align-items: center; align-items : center;
padding: 0 8px; height : 100%;
color: #ccc; padding : 0 8px;
height: 100%; color : #CCCCCC;
&:hover{ &:hover { background-color : #444444; }
background-color : #444;
}
&.active { &.active {
font-weight: bold; font-weight : bold;
color: #ddd; color : #DDDDDD;
background-color: #333; background-color : #333333;
button { button {
color: white; height : 100%;
font-weight: 800; font-weight : 800;
height: 100%; color : white;
& + .sortDir { & + .sortDir { padding-left : 5px; }
padding-left: 5px;
} }
} }
}
} }
.filter-option { .filter-option {
margin-left: 20px; margin-left : 20px;
background-color : transparent !important;
font-size : 11px; font-size : 11px;
i{ background-color : transparent !important;
padding-right : 5px; i { padding-right : 5px; }
} }
button {
padding : 0;
font-family : 'Open Sans', sans-serif;
font-size : 11px;
font-weight : normal;
color : #CCCCCC;
text-transform : uppercase;
background-color : transparent;
} }
button{
background-color : transparent;
font-family : 'Open Sans', sans-serif;
text-transform : uppercase;
font-weight : normal;
font-size : 11px;
color : #ccc;
padding : 0;
}
} }
.tags-container { .tags-container {
height : 30px;
background-color : #555;
border-top : 1px solid #666;
border-bottom : 1px solid #666;
color : white;
display : flex; display : flex;
justify-content : center;
align-items : center;
column-gap : 15px;
row-gap : 5px;
flex-wrap : wrap; flex-wrap : wrap;
row-gap : 5px;
column-gap : 15px;
align-items : center;
justify-content : center;
height : 30px;
color : white;
background-color : #555555;
border-top : 1px solid #666666;
border-bottom : 1px solid #666666;
span { span {
padding : 3px;
font-family : 'Open Sans', sans-serif; font-family : 'Open Sans', sans-serif;
font-size : 11px; font-size : 11px;
font-weight : bold; font-weight : bold;
color : #DFDFDF;
cursor : pointer;
border : 1px solid; border : 1px solid;
border-radius : 3px; border-radius : 3px;
padding : 3px; &::before {
cursor : pointer; margin-right : 3px;
color: #dfdfdf; font-family : 'Font Awesome 5 Free';
&:before { font-size : 12px;
font-family: 'Font Awesome 5 Free';
font-size: 12px;
margin-right: 3px;
} }
&:after { &::after {
content: '\f00d'; margin-left : 3px;
font-family: 'Font Awesome 5 Free'; font-family : 'Font Awesome 5 Free';
font-size: 12px; font-size : 12px;
margin-left: 3px; content : '\f00d';
} }
&.type { &.type {
background-color: #008000; background-color : #008000;
border-color: #00a000; border-color : #00A000;
&:before{ &::before { content : '\f0ad'; }
content: '\f0ad';
}
} }
&.group { &.group {
background-color: #505050; background-color : #505050;
border-color: #000000; border-color : #000000;
&:before{ &::before { content : '\f500'; }
content: '\f500';
}
} }
&.meta { &.meta {
background-color: #000080; background-color : #000080;
border-color: #0000a0; border-color : #0000A0;
&:before{ &::before { content : '\f05a'; }
content: '\f05a';
}
} }
&.system { &.system {
background-color: #800000; background-color : #800000;
border-color: #a00000; border-color : #A00000;
&:before{ &::before { content : '\f518'; }
content: '\f518';
}
} }
} }
} }

View File

@@ -1,7 +1,7 @@
.homebrew { .homebrew {
.uiPage.sitePage { .uiPage.sitePage {
.content { .content {
width : ~"min(90vw, 1000px)"; width : ~'min(90vw, 1000px)';
padding : 2% 4%; padding : 2% 4%;
margin-top : 25px; margin-top : 25px;
margin-right : auto; margin-right : auto;
@@ -17,19 +17,19 @@
border : 2px solid black; border : 2px solid black;
border-radius : 5px; border-radius : 5px;
button { button {
width : 125px;
margin-right : 5px;
color : black;
background-color : transparent; background-color : transparent;
border : 1px solid black; border : 1px solid black;
border-radius : 5px; border-radius : 5px;
width : 125px;
color : black;
margin-right : 5px;
&.active { &.active {
background-color: #0007; color : white;
color: white; background-color : #00000077;
&:before { &::before {
content: '\f00c'; margin-right : 5px;
font-family: 'FONT AWESOME 5 FREE'; font-family : 'FONT AWESOME 5 FREE';
margin-right: 5px; content : '\f00c';
} }
} }
} }

View File

@@ -102,6 +102,14 @@ const EditPage = createClass({
window.onbeforeunload = function(){}; window.onbeforeunload = function(){};
document.removeEventListener('keydown', this.handleControlKeys); document.removeEventListener('keydown', this.handleControlKeys);
}, },
componentDidUpdate : function(){
const hasChange = this.hasChanges();
if(this.state.isPending != hasChange){
this.setState({
isPending : hasChange
});
}
},
handleControlKeys : function(e){ handleControlKeys : function(e){
if(!(e.ctrlKey || e.metaKey)) return; if(!(e.ctrlKey || e.metaKey)) return;
@@ -138,15 +146,13 @@ const EditPage = createClass({
this.setState((prevState)=>({ this.setState((prevState)=>({
brew : { ...prevState.brew, text: text }, brew : { ...prevState.brew, text: text },
isPending : true,
htmlErrors : htmlErrors, htmlErrors : htmlErrors,
}), ()=>{if(this.state.autoSave) this.trySave();}); }), ()=>{if(this.state.autoSave) this.trySave();});
}, },
handleStyleChange : function(style){ handleStyleChange : function(style){
this.setState((prevState)=>({ this.setState((prevState)=>({
brew : { ...prevState.brew, style: style }, brew : { ...prevState.brew, style: style }
isPending : true
}), ()=>{if(this.state.autoSave) this.trySave();}); }), ()=>{if(this.state.autoSave) this.trySave();});
}, },
@@ -158,8 +164,7 @@ const EditPage = createClass({
brew : { brew : {
...prevState.brew, ...prevState.brew,
...metadata ...metadata
}, }
isPending : true,
}), ()=>{if(this.state.autoSave) this.trySave();}); }), ()=>{if(this.state.autoSave) this.trySave();});
}, },
@@ -247,16 +252,17 @@ const EditPage = createClass({
}); });
if(!res) return; if(!res) return;
this.savedBrew = res.body; this.savedBrew = {
...this.state.brew,
googleId : res.body.googleId ? res.body.googleId : null,
editId : res.body.editId,
shareId : res.body.shareId,
version : res.body.version
};
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
this.setState((prevState)=>({ this.setState(()=>({
brew : { ...prevState.brew, brew : this.savedBrew,
googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null,
editId : this.savedBrew.editId,
shareId : this.savedBrew.shareId,
version : this.savedBrew.version
},
isPending : false, isPending : false,
isSaving : false, isSaving : false,
unsavedTime : new Date() unsavedTime : new Date()
@@ -311,7 +317,14 @@ const EditPage = createClass({
}, },
renderSaveButton : function(){ renderSaveButton : function(){
if(this.state.autoSaveWarning && this.hasChanges()){
// #1 - Currently saving, show SAVING
if(this.state.isSaving){
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
}
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
if(this.state.isPending && this.state.autoSaveWarning){
this.setAutosaveWarning(); this.setAutosaveWarning();
const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60); const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
@@ -324,18 +337,17 @@ const EditPage = createClass({
</Nav.item>; </Nav.item>;
} }
if(this.state.isSaving){ // #3 - Unsaved changes exist, click to save, show SAVE NOW
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>; // Use trySave(true) instead of save() to use debounced save function
if(this.state.isPending){
return <Nav.item className='save' onClick={()=>this.trySave(true)} color='blue' icon='fas fa-save'>Save Now</Nav.item>;
} }
if(this.state.isPending && this.hasChanges()){ // #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
return <Nav.item className='save' onClick={this.save} color='blue' icon='fas fa-save'>Save Now</Nav.item>; if(this.state.autoSave){
}
if(!this.state.isPending && !this.state.isSaving && this.state.autoSave){
return <Nav.item className='save saved'>auto-saved.</Nav.item>; return <Nav.item className='save saved'>auto-saved.</Nav.item>;
} }
if(!this.state.isPending && !this.state.isSaving){ // DEFAULT - No unsaved changes, show SAVED
return <Nav.item className='save saved'>saved.</Nav.item>; return <Nav.item className='save saved'>saved.</Nav.item>;
}
}, },
handleAutoSave : function(){ handleAutoSave : function(){
@@ -379,7 +391,7 @@ const EditPage = createClass({
const title = `${this.props.brew.title} ${systems}`; const title = `${this.props.brew.title} ${systems}`;
const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out. const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out.
**[Homebrewery Link](${global.config.publicUrl}/share/${shareLink})**`; **[Homebrewery Link](${global.config.baseUrl}/share/${shareLink})**`;
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title.toWellFormed())}&text=${encodeURIComponent(text)}`; return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title.toWellFormed())}&text=${encodeURIComponent(text)}`;
}, },
@@ -410,7 +422,7 @@ const EditPage = createClass({
<Nav.item color='blue' href={`/share/${shareLink}`}> <Nav.item color='blue' href={`/share/${shareLink}`}>
view view
</Nav.item> </Nav.item>
<Nav.item color='blue' onClick={()=>{navigator.clipboard.writeText(`${global.config.publicUrl}/share/${shareLink}`);}}> <Nav.item color='blue' onClick={()=>{navigator.clipboard.writeText(`${global.config.baseUrl}/share/${shareLink}`);}}>
copy url copy url
</Nav.item> </Nav.item>
<Nav.item color='blue' href={this.getRedditLink()} newTab={true} rel='noopener noreferrer'> <Nav.item color='blue' href={this.getRedditLink()} newTab={true} rel='noopener noreferrer'>
@@ -443,6 +455,7 @@ const EditPage = createClass({
reportError={this.errorReported} reportError={this.errorReported}
renderer={this.state.brew.renderer} renderer={this.state.brew.renderer}
userThemes={this.props.userThemes} userThemes={this.props.userThemes}
themeBundle={this.state.themeBundle}
snippetBundle={this.state.themeBundle.snippets} snippetBundle={this.state.themeBundle.snippets}
updateBrew={this.updateBrew} updateBrew={this.updateBrew}
onCursorPageChange={this.handleEditorCursorPageChange} onCursorPageChange={this.handleEditorCursorPageChange}

View File

@@ -1,29 +1,25 @@
@keyframes glideDown { @keyframes glideDown {
0% {transform : translate(-50% + 3px, 0px); 0% {
opacity : 0;} opacity : 0;transform : translate(-50% + 3px, 0px);}
100% {transform : translate(-50% + 3px, 10px); 100% {
opacity : 1;} opacity : 1;transform : translate(-50% + 3px, 10px);}
} }
.editPage{ .editPage {
.navItem.save{ .navItem.save {
position : relative;
width : 106px; width : 106px;
text-align : center; text-align : center;
position : relative; &.saved {
&.saved{ color : #666666;
cursor : initial; cursor : initial;
color : #666;
} }
} }
.googleDriveStorage { .googleDriveStorage { position : relative; }
position : relative; .googleDriveStorage img {
} height : 18px;
.googleDriveStorage img{
height : 18px;
padding : 0px; padding : 0px;
margin : -5px; margin : -5px;
&.inactive { &.inactive { filter : grayscale(1); }
filter: grayscale(1);
}
} }
} }

View File

@@ -168,6 +168,14 @@ const errorIndex = (props)=>{
**Brew ID:** ${props.brew.brewId}`, **Brew ID:** ${props.brew.brewId}`,
// Theme Not Valid
'10' : dedent`
## The selected theme is not tagged as a theme.
The brew selected as a theme exists, but has not been marked for use as a theme with the \`theme:meta\` tag.
If the selected brew is your document, you may designate it as a theme by adding the \`theme:meta\` tag.`,
//account page when account is not defined //account page when account is not defined
'50' : dedent` '50' : dedent`
## You are not signed in ## You are not signed in

View File

@@ -100,32 +100,32 @@ const HomePage = createClass({
return <div className='homePage sitePage'> return <div className='homePage sitePage'>
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' /> <Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
{this.renderNavbar()} {this.renderNavbar()}
<div className="content"> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}> <SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
ref={this.editor} ref={this.editor}
brew={this.state.brew} brew={this.state.brew}
onTextChange={this.handleTextChange} onTextChange={this.handleTextChange}
renderer={this.state.brew.renderer} renderer={this.state.brew.renderer}
showEditButtons={false} showEditButtons={false}
snippetBundle={this.state.themeBundle.snippets} snippetBundle={this.state.themeBundle.snippets}
onCursorPageChange={this.handleEditorCursorPageChange} onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange} onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum} currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum} currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum} currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/> />
<BrewRenderer <BrewRenderer
text={this.state.brew.text} text={this.state.brew.text}
style={this.state.brew.style} style={this.state.brew.style}
renderer={this.state.brew.renderer} renderer={this.state.brew.renderer}
onPageChange={this.handleBrewRendererPageChange} onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum} currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum} currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum} currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
themeBundle={this.state.themeBundle} themeBundle={this.state.themeBundle}
/> />
</SplitPane> </SplitPane>
</div> </div>
<div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.handleSave}> <div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.handleSave}>
Save current <i className='fas fa-save' /> Save current <i className='fas fa-save' />

View File

@@ -1,50 +1,40 @@
.homePage{ .homePage {
position : relative; position : relative;
a.floatingNewButton{ a.floatingNewButton {
.animate(background-color); .animate(background-color);
position : absolute; position : absolute;
display : block;
right : 70px; right : 70px;
bottom : 50px; bottom : 50px;
z-index : 100;
z-index : 5001; z-index : 5001;
display : block;
padding : 1em; padding : 1em;
background-color : @orange;
font-size : 1.5em; font-size : 1.5em;
color : white; color : white;
text-decoration : none; text-decoration : none;
background-color : @orange;
box-shadow : 3px 3px 15px black; box-shadow : 3px 3px 15px black;
&:hover{ &:hover { background-color : darken(@orange, 20%); }
background-color : darken(@orange, 20%);
}
} }
.floatingSaveButton{ .floatingSaveButton {
.animateAll(); .animateAll();
position : absolute; position : absolute;
display : block;
right : 200px; right : 200px;
bottom : 70px; bottom : 70px;
z-index : 100;
z-index : 5000; z-index : 5000;
display : block;
padding : 0.8em; padding : 0.8em;
cursor : pointer;
background-color : @blue;
font-size : 0.8em; font-size : 0.8em;
color : white; color : white;
text-decoration : none; text-decoration : none;
cursor : pointer;
background-color : @blue;
box-shadow : 3px 3px 15px black; box-shadow : 3px 3px 15px black;
&:hover{ &:hover { background-color : darken(@blue, 20%); }
background-color : darken(@blue, 20%); &.show { right : 350px; }
}
&.show{
right : 350px;
}
} }
.navItem.save{ .navItem.save {
background-color: @orange; background-color : @orange;
&:hover{ &:hover { background-color : @green; }
background-color: @green;
}
} }
} }

View File

@@ -223,38 +223,39 @@ const NewPage = createClass({
render : function(){ render : function(){
return <div className='newPage sitePage'> return <div className='newPage sitePage'>
{this.renderNavbar()} {this.renderNavbar()}
<div className="content"> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}> <SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
ref={this.editor} ref={this.editor}
brew={this.state.brew} brew={this.state.brew}
onTextChange={this.handleTextChange} onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange} onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange} onMetaChange={this.handleMetaChange}
renderer={this.state.brew.renderer} renderer={this.state.brew.renderer}
userThemes={this.props.userThemes} userThemes={this.props.userThemes}
snippetBundle={this.state.themeBundle.snippets} themeBundle={this.state.themeBundle}
onCursorPageChange={this.handleEditorCursorPageChange} snippetBundle={this.state.themeBundle.snippets}
onViewPageChange={this.handleEditorViewPageChange} onCursorPageChange={this.handleEditorCursorPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum} onViewPageChange={this.handleEditorViewPageChange}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum} currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum} currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
/> currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
<BrewRenderer />
text={this.state.brew.text} <BrewRenderer
style={this.state.brew.style} text={this.state.brew.text}
renderer={this.state.brew.renderer} style={this.state.brew.style}
theme={this.state.brew.theme} renderer={this.state.brew.renderer}
themeBundle={this.state.themeBundle} theme={this.state.brew.theme}
errors={this.state.htmlErrors} themeBundle={this.state.themeBundle}
lang={this.state.brew.lang} errors={this.state.htmlErrors}
onPageChange={this.handleBrewRendererPageChange} lang={this.state.brew.lang}
currentEditorViewPageNum={this.state.currentEditorViewPageNum} onPageChange={this.handleBrewRendererPageChange}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum} currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum} currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
allowPrint={true} currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/> allowPrint={true}
</SplitPane> />
</SplitPane>
</div> </div>
</div>; </div>;
} }

View File

@@ -1,8 +1,6 @@
.newPage{ .newPage {
.navItem.save{ .navItem.save {
background-color: @orange; background-color : @orange;
&:hover{ &:hover { background-color : @green; }
background-color: @green;
}
} }
} }

View File

@@ -1,9 +1,7 @@
.sharePage{ .sharePage {
nav .navSection.titleSection { nav .navSection.titleSection {
flex-grow: 1; flex-grow : 1;
justify-content: center; justify-content : center;
}
.content{
overflow-y : hidden;
} }
.content { overflow-y : hidden; }
} }

View File

@@ -99,14 +99,14 @@ const VaultPage = (props)=>{
setSearching(true); setSearching(true);
setError(null); setError(null);
const title = titleRef.current.value || ''; const title = titleRef.current.value || '';
const author = authorRef.current.value || ''; const author = authorRef.current.value || '';
const count = countRef.current.value || 10; const count = countRef.current.value || 10;
const v3 = v3Ref.current.checked != false; const v3 = v3Ref.current.checked != false;
const legacy = legacyRef.current.checked != false; const legacy = legacyRef.current.checked != false;
const sortOption = sort || 'title'; const sortOption = sort || 'title';
const dirOption = dir || 'asc'; const dirOption = dir || 'asc';
const pageProp = page || 1; const pageProp = page || 1;
setSort(sortOption); setSort(sortOption);
setdir(dirOption); setdir(dirOption);
@@ -247,7 +247,7 @@ const VaultPage = (props)=>{
</li> </li>
<li> <li>
Some common words like "a", "after", "through", "itself", "here", etc., Some common words like "a", "after", "through", "itself", "here", etc.,
are ignored in searches. The full list can be found &nbsp; are ignored in searches. The full list can be found&nbsp;
<a href='https://github.com/mongodb/mongo/blob/0e3b3ca8480ddddf5d0105d11a94bd4698335312/src/mongo/db/fts/stop_words_english.txt'> <a href='https://github.com/mongodb/mongo/blob/0e3b3ca8480ddddf5d0105d11a94bd4698335312/src/mongo/db/fts/stop_words_english.txt'>
here here
</a> </a>
@@ -286,9 +286,9 @@ const VaultPage = (props)=>{
}; };
const renderPaginationControls = ()=>{ const renderPaginationControls = ()=>{
if(!totalBrews) return null; if(!totalBrews || totalBrews < 10) return null;
const countInt = parseInt(props.query.count || 20); const countInt = parseInt(brewCollection.length || 20);
const totalPages = Math.ceil(totalBrews / countInt); const totalPages = Math.ceil(totalBrews / countInt);
let startPage, endPage; let startPage, endPage;
@@ -355,7 +355,7 @@ const VaultPage = (props)=>{
}; };
const renderFoundBrews = ()=>{ const renderFoundBrews = ()=>{
if(searching) { if(searching && !brewCollection) {
return ( return (
<div className='foundBrews searching'> <div className='foundBrews searching'>
<h3 className='searchAnim'>Searching</h3> <h3 className='searchAnim'>Searching</h3>
@@ -395,6 +395,7 @@ const VaultPage = (props)=>{
{`Brews found: `} {`Brews found: `}
<span>{totalBrews}</span> <span>{totalBrews}</span>
</span> </span>
{brewCollection.length > 10 && renderPaginationControls()}
{brewCollection.map((brew, index)=>{ {brewCollection.map((brew, index)=>{
return ( return (
<BrewItem <BrewItem
@@ -415,14 +416,14 @@ const VaultPage = (props)=>{
<link href='/themes/V3/Blank/style.css' rel='stylesheet' /> <link href='/themes/V3/Blank/style.css' rel='stylesheet' />
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet' /> <link href='/themes/V3/5ePHB/style.css' rel='stylesheet' />
{renderNavItems()} {renderNavItems()}
<div className="content"> <div className='content'>
<SplitPane showDividerButtons={false}> <SplitPane showDividerButtons={false}>
<div className='form dataGroup'>{renderForm()}</div> <div className='form dataGroup'>{renderForm()}</div>
<div className='resultsContainer dataGroup'> <div className='resultsContainer dataGroup'>
{renderSortBar()} {renderSortBar()}
{renderFoundBrews()} {renderFoundBrews()}
</div> </div>
</SplitPane> </SplitPane>
</div> </div>
</div> </div>
); );

View File

@@ -5,7 +5,7 @@
*:not(input) { user-select : none; } *:not(input) { user-select : none; }
.content .dataGroup { :where(.content .dataGroup) {
width : 100%; width : 100%;
height : 100%; height : 100%;
background : white; background : white;
@@ -169,9 +169,10 @@
width : 100%; width : 100%;
height : 100%; height : 100%;
max-height : 100%; max-height : 100%;
padding : 50px 50px 70px 50px; padding : 70px 50px;
overflow-y : scroll; overflow-y : scroll;
background-color : #2C3E50; background-color : #2C3E50;
container-type : inline-size;
h3 { font-size : 25px; } h3 { font-size : 25px; }
@@ -236,6 +237,7 @@
margin-right : 40px; margin-right : 40px;
color : black; color : black;
isolation : isolate; isolation : isolate;
transition : width 0.5s;
&::after { &::after {
position : absolute; position : absolute;
@@ -269,8 +271,8 @@
.links { z-index : 2; } .links { z-index : 2; }
hr { hr {
margin : 0px;
visibility : hidden; visibility : hidden;
margin : 0px;
} }
.thumbnail { z-index : -1; } .thumbnail { z-index : -1; }
@@ -278,30 +280,37 @@
.paginationControls { .paginationControls {
position : absolute; position : absolute;
top : 35px;
left : 50%; left : 50%;
display : grid; display : grid;
grid-template-areas : 'previousPage currentPage nextPage'; grid-template-areas : 'previousPage currentPage nextPage';
grid-template-columns : 50px 1fr 50px; grid-template-columns : 50px 1fr 50px;
gap : 20px;
place-items : center; place-items : center;
width : auto; width : auto;
font-size : 15px;
translate : -50%; translate : -50%;
&:last-child { top : unset; }
.pages { .pages {
display : flex; display : flex;
grid-area : currentPage; grid-area : currentPage;
gap : 1em;
justify-content : space-evenly; justify-content : space-evenly;
width : 100%; width : 100%;
height : 100%; height : 100%;
padding : 5px 8px;
text-align : center; text-align : center;
.pageNumber { .pageNumber {
margin-inline : 1vw; place-content : center;
width : fit-content;
min-width : 2em;
font-family : 'Open Sans'; font-family : 'Open Sans';
font-weight : 900; font-weight : 900;
color : white; color : white;
text-underline-position : under;
text-wrap : nowrap; text-wrap : nowrap;
text-underline-position : under;
cursor : pointer; cursor : pointer;
&.currentPage { &.currentPage {
@@ -329,7 +338,6 @@
} }
} }
} }
} }
@keyframes trailingDots { @keyframes trailingDots {
@@ -344,8 +352,7 @@
100% { content : ' ...'; } 100% { content : ' ...'; }
} }
// media query for when the page is smaller than 1079 px in width @container (width < 670px) {
@media screen and (max-width : 1079px) {
.vaultPage { .vaultPage {
.dataGroup.form .brewLookup { padding : 1px 20px 20px 10px; } .dataGroup.form .brewLookup { padding : 1px 20px 20px 10px; }

View File

@@ -1,84 +1,34 @@
.fac { .fac {
display : inline-block; display : inline-block;
background-color : currentColor;
mask-size : contain;
mask-repeat : no-repeat;
mask-position : center;
width : 1em; width : 1em;
aspect-ratio : 1; aspect-ratio : 1;
background-color : currentColor;
mask-repeat : no-repeat;
mask-position : center;
mask-size : contain;
} }
.position-top-left { .position-top-left { mask-image : url('../icons/position-top-left.svg'); }
mask-image: url('../icons/position-top-left.svg'); .position-top-right { mask-image : url('../icons/position-top-right.svg'); }
} .position-bottom-left { mask-image : url('../icons/position-bottom-left.svg'); }
.position-top-right { .position-bottom-right { mask-image : url('../icons/position-bottom-right.svg'); }
mask-image: url('../icons/position-top-right.svg'); .position-top { mask-image : url('../icons/position-top.svg'); }
} .position-right { mask-image : url('../icons/position-right.svg'); }
.position-bottom-left { .position-bottom { mask-image : url('../icons/position-bottom.svg'); }
mask-image: url('../icons/position-bottom-left.svg'); .position-left { mask-image : url('../icons/position-left.svg'); }
} .mask-edge { mask-image : url('../icons/mask-edge.svg'); }
.position-bottom-right { .mask-corner { mask-image : url('../icons/mask-corner.svg'); }
mask-image: url('../icons/position-bottom-right.svg'); .mask-center { mask-image : url('../icons/mask-center.svg'); }
} .book-front-cover { mask-image : url('../icons/book-front-cover.svg'); }
.position-top { .book-back-cover { mask-image : url('../icons/book-back-cover.svg'); }
mask-image: url('../icons/position-top.svg'); .book-inside-cover { mask-image : url('../icons/book-inside-cover.svg'); }
} .book-part-cover { mask-image : url('../icons/book-part-cover.svg'); }
.position-right { .image-wrap-left { mask-image : url('../icons/image-wrap-left.svg'); }
mask-image: url('../icons/position-right.svg'); .image-wrap-right { mask-image : url('../icons/image-wrap-right.svg'); }
} .davek { mask-image : url('../icons/Davek.svg'); }
.position-bottom { .rellanic { mask-image : url('../icons/Rellanic.svg'); }
mask-image: url('../icons/position-bottom.svg'); .iokharic { mask-image : url('../icons/Iokharic.svg'); }
} .zoom-to-fit { mask-image : url('../icons/zoom-to-fit.svg'); }
.position-left { .fit-width { mask-image : url('../icons/fit-width.svg'); }
mask-image: url('../icons/position-left.svg'); .single-spread { mask-image : url('../icons/single-spread.svg'); }
} .facing-spread { mask-image : url('../icons/facing-spread.svg'); }
.mask-edge { .flow-spread { mask-image : url('../icons/flow-spread.svg'); }
mask-image: url('../icons/mask-edge.svg');
}
.mask-corner {
mask-image: url('../icons/mask-corner.svg');
}
.mask-center {
mask-image: url('../icons/mask-center.svg');
}
.book-front-cover {
mask-image: url('../icons/book-front-cover.svg');
}
.book-back-cover {
mask-image: url('../icons/book-back-cover.svg');
}
.book-inside-cover {
mask-image: url('../icons/book-inside-cover.svg');
}
.book-part-cover {
mask-image: url('../icons/book-part-cover.svg');
}
.image-wrap-left {
mask-image: url('../icons/image-wrap-left.svg');
}
.image-wrap-right {
mask-image: url('../icons/image-wrap-right.svg');
}
.davek {
mask-image: url('../icons/Davek.svg');
}
.rellanic {
mask-image: url('../icons/Rellanic.svg');
}
.iokharic {
mask-image: url('../icons/Iokharic.svg');
}
.zoom-to-fit {
mask-image: url('../icons/zoom-to-fit.svg');
}
.fit-width {
mask-image: url('../icons/fit-width.svg');
}
.single-spread {
mask-image: url('../icons/single-spread.svg');
}
.facing-spread {
mask-image: url('../icons/facing-spread.svg');
}
.flow-spread {
mask-image: url('../icons/flow-spread.svg');
}

551
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "homebrewery", "name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown", "description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.17.0", "version": "3.18.1",
"type": "module", "type": "module",
"engines": { "engines": {
"npm": "^10.2.x", "npm": "^10.2.x",
@@ -39,7 +39,6 @@
"test:definition-lists": "jest tests/markdown/definition-lists.test.js --verbose --noStackTrace", "test:definition-lists": "jest tests/markdown/definition-lists.test.js --verbose --noStackTrace",
"test:hard-breaks": "jest tests/markdown/hard-breaks.test.js --verbose --noStackTrace", "test:hard-breaks": "jest tests/markdown/hard-breaks.test.js --verbose --noStackTrace",
"test:non-breaking-spaces": "jest tests/markdown/non-breaking-spaces.test.js --verbose --noStackTrace", "test:non-breaking-spaces": "jest tests/markdown/non-breaking-spaces.test.js --verbose --noStackTrace",
"test:paragraph-justification": "jest tests/markdown/paragraph-justification.test.js --verbose --noStackTrace",
"test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace", "test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace",
"test:route": "jest tests/routes/static-pages.test.js --verbose", "test:route": "jest tests/routes/static-pages.test.js --verbose",
"test:safehtml": "jest tests/html/safeHTML.test.js --verbose", "test:safehtml": "jest tests/html/safeHTML.test.js --verbose",
@@ -85,20 +84,19 @@
] ]
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.26.8", "@babel/core": "^7.26.9",
"@babel/plugin-transform-runtime": "^7.26.8", "@babel/plugin-transform-runtime": "^7.26.9",
"@babel/preset-env": "^7.26.8", "@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3", "@babel/preset-react": "^7.26.3",
"@googleapis/drive": "^8.14.0", "@googleapis/drive": "^8.16.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"codemirror": "^5.65.6", "codemirror": "^5.65.6",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"core-js": "^3.40.0", "core-js": "^3.41.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"create-react-class": "^15.7.0", "create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3", "dedent-tabs": "^0.10.3",
"dompurify": "^3.2.4",
"expr-eval": "^2.0.2", "expr-eval": "^2.0.2",
"express": "^4.21.2", "express": "^4.21.2",
"express-async-handler": "^1.2.0", "express-async-handler": "^1.2.0",
@@ -109,20 +107,21 @@
"jwt-simple": "^0.5.6", "jwt-simple": "^0.5.6",
"less": "^3.13.1", "less": "^3.13.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"marked": "13.0.3", "marked": "14.0.0",
"marked-emoji": "^1.4.3", "marked-emoji": "^2.0.0",
"marked-extended-tables": "^1.1.0", "marked-extended-tables": "^2.0.1",
"marked-gfm-heading-id": "^4.0.1", "marked-gfm-heading-id": "^4.0.1",
"marked-smartypants-lite": "^1.0.3", "marked-smartypants-lite": "^1.0.3",
"marked-subsuper-text": "^1.0.3",
"markedLegacy": "npm:marked@^0.3.19", "markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1", "moment": "^2.30.1",
"mongoose": "^8.10.0", "mongoose": "^8.12.1",
"nanoid": "5.0.9", "nanoid": "5.1.3",
"nconf": "^0.12.1", "nconf": "^0.12.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-frame-component": "^4.1.3", "react-frame-component": "^4.1.3",
"react-router": "^7.1.5", "react-router": "^7.3.0",
"sanitize-filename": "1.6.3", "sanitize-filename": "1.6.3",
"superagent": "^10.1.1", "superagent": "^10.1.1",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git" "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
@@ -130,15 +129,15 @@
"devDependencies": { "devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.2", "@stylistic/stylelint-plugin": "^3.1.2",
"babel-plugin-transform-import-meta": "^2.3.2", "babel-plugin-transform-import-meta": "^2.3.2",
"eslint": "^9.20.0", "eslint": "^9.22.0",
"eslint-plugin-jest": "^28.11.0", "eslint-plugin-jest": "^28.11.0",
"eslint-plugin-react": "^7.37.4", "eslint-plugin-react": "^7.37.4",
"globals": "^15.14.0", "globals": "^16.0.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2", "jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"stylelint": "^16.14.1", "stylelint": "^16.16.0",
"stylelint-config-recess-order": "^6.0.0", "stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended": "^15.0.0", "stylelint-config-recommended": "^15.0.0",
"supertest": "^7.0.0" "supertest": "^7.0.0"

View File

@@ -10,7 +10,7 @@ import babel from '@babel/core';
import babelConfig from '../babel.config.json' with { type : 'json' }; import babelConfig from '../babel.config.json' with { type : 'json' };
import less from 'less'; import less from 'less';
const isDev = !!process.argv.find((arg) => arg === '--dev'); const isDev = !!process.argv.find((arg)=>arg === '--dev');
const babelify = async (code)=>(await babel.transformAsync(code, babelConfig)).code; const babelify = async (code)=>(await babel.transformAsync(code, babelConfig)).code;
@@ -53,7 +53,7 @@ fs.emptyDirSync('./build');
const themes = { Legacy: {}, V3: {} }; const themes = { Legacy: {}, V3: {} };
let themeFiles = fs.readdirSync('./themes/Legacy'); let themeFiles = fs.readdirSync('./themes/Legacy');
for (let dir of themeFiles) { for (const dir of themeFiles) {
const themeData = JSON.parse(fs.readFileSync(`./themes/Legacy/${dir}/settings.json`).toString()); const themeData = JSON.parse(fs.readFileSync(`./themes/Legacy/${dir}/settings.json`).toString());
themeData.path = dir; themeData.path = dir;
themes.Legacy[dir] = (themeData); themes.Legacy[dir] = (themeData);
@@ -70,7 +70,7 @@ fs.emptyDirSync('./build');
} }
themeFiles = fs.readdirSync('./themes/V3'); themeFiles = fs.readdirSync('./themes/V3');
for (let dir of themeFiles) { for (const dir of themeFiles) {
const themeData = JSON.parse(fs.readFileSync(`./themes/V3/${dir}/settings.json`).toString()); const themeData = JSON.parse(fs.readFileSync(`./themes/V3/${dir}/settings.json`).toString());
themeData.path = dir; themeData.path = dir;
themes.V3[dir] = (themeData); themes.V3[dir] = (themeData);
@@ -113,7 +113,7 @@ fs.emptyDirSync('./build');
const stream = fs.createWriteStream(editorThemeFile, { flags: 'a' }); const stream = fs.createWriteStream(editorThemeFile, { flags: 'a' });
stream.write('[\n"default"'); stream.write('[\n"default"');
for (let themeFile of editorThemeFiles) { for (const themeFile of editorThemeFiles) {
stream.write(`,\n"${themeFile.slice(0, -4)}"`); stream.write(`,\n"${themeFile.slice(0, -4)}"`);
} }
stream.write('\n]\n'); stream.write('\n]\n');

View File

@@ -93,7 +93,7 @@ router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
/* Cleans `<script` and `</script>` from the "text" field of a brew */ /* Cleans `<script` and `</script>` from the "text" field of a brew */
router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res)=>{ router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res)=>{
console.log(`[ADMIN] Cleaning script tags from ShareID ${req.params.id}`); console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Cleaning script tags from ShareID ${req.params.id}`);
function cleanText(text){return text.replaceAll(/(<\/?s)cript/gi, '');}; function cleanText(text){return text.replaceAll(/(<\/?s)cript/gi, '');};
@@ -114,6 +114,18 @@ router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin',
return await HomebrewAPI.updateBrew(req, res); return await HomebrewAPI.updateBrew(req, res);
}); });
/* Get list of a user's documents */
router.get('/admin/user/list/:user', mw.adminOnly, async (req, res)=>{
const username = req.params.user;
const fields = { _id: 0, text: 0, textBin: 0 }; // Remove unnecessary fields from document lists
console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Get brew list for ${username}`);
const brews = await HomebrewModel.getByUser(username, true, fields);
return res.json(brews);
});
/* Compresses the "text" field of a brew to binary */ /* Compresses the "text" field of a brew to binary */
router.put('/admin/compress/:id', (req, res)=>{ router.put('/admin/compress/:id', (req, res)=>{
HomebrewModel.findOne({ _id: req.params.id }) HomebrewModel.findOne({ _id: req.params.id })
@@ -135,7 +147,6 @@ router.put('/admin/compress/:id', (req, res)=>{
}); });
}); });
router.get('/admin/stats', mw.adminOnly, async (req, res)=>{ router.get('/admin/stats', mw.adminOnly, async (req, res)=>{
try { try {
const totalBrewsCount = await HomebrewModel.countDocuments({}); const totalBrewsCount = await HomebrewModel.countDocuments({});

View File

@@ -1,6 +1,6 @@
import supertest from 'supertest'; import supertest from 'supertest';
import HBApp from './app.js'; import HBApp from './app.js';
import {model as NotificationModel } from './notifications.model.js'; import { model as NotificationModel } from './notifications.model.js';
// Mimic https responses to avoid being redirected all the time // Mimic https responses to avoid being redirected all the time
@@ -16,7 +16,7 @@ describe('Tests for admin api', ()=>{
const testNotifications = ['a', 'b']; const testNotifications = ['a', 'b'];
jest.spyOn(NotificationModel, 'find') jest.spyOn(NotificationModel, 'find')
.mockImplementationOnce(() => { .mockImplementationOnce(()=>{
return { exec: jest.fn().mockResolvedValue(testNotifications) }; return { exec: jest.fn().mockResolvedValue(testNotifications) };
}); });
@@ -59,7 +59,7 @@ describe('Tests for admin api', ()=>{
expect(response.body).toEqual(savedNotification); expect(response.body).toEqual(savedNotification);
}); });
it('should handle error adding a notification without dismissKey', async () => { it('should handle error adding a notification without dismissKey', async ()=>{
const inputNotification = { const inputNotification = {
title : 'Test Notification', title : 'Test Notification',
text : 'This is a test notification', text : 'This is a test notification',
@@ -75,7 +75,7 @@ describe('Tests for admin api', ()=>{
const response = await app const response = await app
.post('/admin/notification/add') .post('/admin/notification/add')
.set('Authorization', 'Basic ' + Buffer.from('admin:password3').toString('base64')) .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.send(inputNotification); .send(inputNotification);
expect(response.status).toBe(500); expect(response.status).toBe(500);
@@ -86,14 +86,14 @@ describe('Tests for admin api', ()=>{
const dismissKey = 'testKey'; const dismissKey = 'testKey';
jest.spyOn(NotificationModel, 'findOneAndDelete') jest.spyOn(NotificationModel, 'findOneAndDelete')
.mockImplementationOnce((key) => { .mockImplementationOnce((key)=>{
return { exec: jest.fn().mockResolvedValue(key) }; return { exec: jest.fn().mockResolvedValue(key) };
}); });
const response = await app const response = await app
.delete(`/admin/notification/delete/${dismissKey}`) .delete(`/admin/notification/delete/${dismissKey}`)
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`); .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({'dismissKey': 'testKey'}); expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ 'dismissKey': 'testKey' });
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body).toEqual({ dismissKey: 'testKey' }); expect(response.body).toEqual({ dismissKey: 'testKey' });
}); });
@@ -102,14 +102,14 @@ describe('Tests for admin api', ()=>{
const dismissKey = 'testKey'; const dismissKey = 'testKey';
jest.spyOn(NotificationModel, 'findOneAndDelete') jest.spyOn(NotificationModel, 'findOneAndDelete')
.mockImplementationOnce(() => { .mockImplementationOnce(()=>{
return { exec: jest.fn().mockResolvedValue() }; return { exec: jest.fn().mockResolvedValue() };
}); });
const response = await app const response = await app
.delete(`/admin/notification/delete/${dismissKey}`) .delete(`/admin/notification/delete/${dismissKey}`)
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`); .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({'dismissKey': 'testKey'}); expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ 'dismissKey': 'testKey' });
expect(response.status).toBe(500); expect(response.status).toBe(500);
expect(response.body).toEqual({ message: 'Notification not found' }); expect(response.body).toEqual({ message: 'Notification not found' });
}); });

View File

@@ -11,7 +11,6 @@ const version = packageJSON.version;
import _ from 'lodash'; import _ from 'lodash';
import jwt from 'jwt-simple'; import jwt from 'jwt-simple';
import express from 'express'; import express from 'express';
import yaml from 'js-yaml';
import config from './config.js'; import config from './config.js';
import fs from 'fs-extra'; import fs from 'fs-extra';
@@ -70,13 +69,11 @@ const corsOptions = {
'https://homebrewery-stage.herokuapp.com', 'https://homebrewery-stage.herokuapp.com',
]; ];
if(isLocalEnvironment) { const localNetworkRegex = /^http:\/\/(localhost|127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[0-1])\.\d+\.\d+):\d+$/;
allowedOrigins.push('http://localhost:8000', 'http://localhost:8010');
}
const herokuRegex = /^https:\/\/(?:homebrewery-pr-\d+\.herokuapp\.com|naturalcrit-pr-\d+\.herokuapp\.com)$/; // Matches any Heroku app const herokuRegex = /^https:\/\/(?:homebrewery-pr-\d+\.herokuapp\.com|naturalcrit-pr-\d+\.herokuapp\.com)$/; // Matches any Heroku app
if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin)) { if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin) || (isLocalEnvironment && localNetworkRegex.test(origin))) {
callback(null, true); callback(null, true);
} else { } else {
console.log(origin, 'not allowed'); console.log(origin, 'not allowed');
@@ -552,6 +549,7 @@ const renderPage = async (req, res)=>{
const configuration = { const configuration = {
local : isLocalEnvironment, local : isLocalEnvironment,
publicUrl : config.get('publicUrl') ?? '', publicUrl : config.get('publicUrl') ?? '',
baseUrl : `${req.protocol}://${req.get('host')}`,
environment : nodeEnv, environment : nodeEnv,
deployment : config.get('heroku_app_name') ?? '' deployment : config.get('heroku_app_name') ?? ''
}; };

View File

@@ -27,12 +27,12 @@ if(!config.get('service_account')){
const defaultAuth = serviceAuth || config.get('google_api_key'); const defaultAuth = serviceAuth || config.get('google_api_key');
const retryConfig = { const retryConfig = {
retry: 3, // Number of retry attempts retry : 3, // Number of retry attempts
retryDelay: 100, // Initial delay in milliseconds retryDelay : 100, // Initial delay in milliseconds
retryDelayMultiplier: 2, // Multiplier for exponential backoff retryDelayMultiplier : 2, // Multiplier for exponential backoff
maxRetryDelay: 32000, // Maximum delay in milliseconds maxRetryDelay : 32000, // Maximum delay in milliseconds
httpMethodsToRetry: ['PATCH'], // Only retry PATCH requests httpMethodsToRetry : ['PATCH'], // Only retry PATCH requests
statusCodesToRetry: [[429, 429]], // Only retry on 429 status code statusCodesToRetry : [[429, 429]], // Only retry on 429 status code
}; };
const GoogleActions = { const GoogleActions = {
@@ -177,8 +177,8 @@ const GoogleActions = {
mimeType : 'text/plain', mimeType : 'text/plain',
body : brew.text body : brew.text
}, },
headers: { headers : {
'X-Forwarded-For': userIp, // Set the X-Forwarded-For header 'X-Forwarded-For' : userIp, // Set the X-Forwarded-For header
}, },
retryConfig retryConfig
}) })

View File

@@ -1,6 +1,6 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import _ from 'lodash'; import _ from 'lodash';
import {model as HomebrewModel} from './homebrew.model.js'; import { model as HomebrewModel } from './homebrew.model.js';
import express from 'express'; import express from 'express';
import zlib from 'zlib'; import zlib from 'zlib';
import GoogleActions from './googleActions.js'; import GoogleActions from './googleActions.js';
@@ -92,7 +92,7 @@ const api = {
const accessMap = { const accessMap = {
edit : { editId: id }, edit : { editId: id },
share : { shareId: id }, share : { shareId: id },
admin : { $or : [{ editId: id }, { shareId: id }] } admin : { $or: [{ editId: id }, { shareId: id }] }
}; };
// Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine. // Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine.
@@ -181,6 +181,7 @@ const api = {
`${text}`; `${text}`;
return text; return text;
}, },
getGoodBrewTitle : (text)=>{ getGoodBrewTitle : (text)=>{
const tokens = Markdown.marked.lexer(text); const tokens = Markdown.marked.lexer(text);
return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title') return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title')
@@ -279,6 +280,8 @@ const api = {
let currentTheme; let currentTheme;
const completeStyles = []; const completeStyles = [];
const completeSnippets = []; const completeSnippets = [];
let themeName;
let themeAuthor;
while (req.params.id) { while (req.params.id) {
//=== User Themes ===// //=== User Themes ===//
@@ -292,6 +295,10 @@ const api = {
currentTheme = req.brew; currentTheme = req.brew;
splitTextStyleAndMetadata(currentTheme); splitTextStyleAndMetadata(currentTheme);
if(!currentTheme.tags.some((tag)=>tag === 'meta:theme' || tag === 'meta:Theme'))
throw { brewId: req.params.id, name: 'Invalid Theme Selected', message: 'Selected theme does not have the meta:theme tag', status: 422, HBErrorCode: '10' };
themeName ??= currentTheme.title;
themeAuthor ??= currentTheme.authors?.[0];
// If there is anything in the snippets or style members, append them to the appropriate array // If there is anything in the snippets or style members, append them to the appropriate array
if(currentTheme?.snippets) completeSnippets.push(JSON.parse(currentTheme.snippets)); if(currentTheme?.snippets) completeSnippets.push(JSON.parse(currentTheme.snippets));
@@ -301,6 +308,7 @@ const api = {
req.params.renderer = currentTheme.renderer; req.params.renderer = currentTheme.renderer;
} else { } else {
//=== Static Themes ===// //=== Static Themes ===//
themeName ??= req.params.id;
const localSnippets = `${req.params.renderer}_${req.params.id}`; // Just log the name for loading on client const localSnippets = `${req.params.renderer}_${req.params.id}`; // Just log the name for loading on client
const localStyle = `@import url(\"/themes/${req.params.renderer}/${req.params.id}/style.css\");`; const localStyle = `@import url(\"/themes/${req.params.renderer}/${req.params.id}/style.css\");`;
completeSnippets.push(localSnippets); completeSnippets.push(localSnippets);
@@ -313,7 +321,9 @@ const api = {
const returnObj = { const returnObj = {
// Reverse the order of the arrays so they are listed oldest parent to youngest child. // Reverse the order of the arrays so they are listed oldest parent to youngest child.
styles : completeStyles.reverse(), styles : completeStyles.reverse(),
snippets : completeSnippets.reverse() snippets : completeSnippets.reverse(),
name : themeName,
author : themeAuthor
}; };
res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Type', 'application/json');

View File

@@ -576,7 +576,7 @@ brew`);
describe('Theme bundle', ()=>{ describe('Theme bundle', ()=>{
it('should return Theme Bundle for a User Theme', async ()=>{ it('should return Theme Bundle for a User Theme', async ()=>{
const brews = { const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style' } userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] }
}; };
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -587,6 +587,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200); expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({ expect(res.send).toHaveBeenCalledWith({
name : 'User Theme A',
author : 'authorName',
styles : ['/* From Brew: https://localhost/share/userThemeAID */\n\nUser Theme A Style'], styles : ['/* From Brew: https://localhost/share/userThemeAID */\n\nUser Theme A Style'],
snippets : [] snippets : []
}); });
@@ -594,9 +596,9 @@ brew`);
it('should return Theme Bundle for nested User Themes', async ()=>{ it('should return Theme Bundle for nested User Themes', async ()=>{
const brews = { const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style' }, userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style' }, userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: null, shareId: 'userThemeCID', style: 'User Theme C Style' } userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: null, shareId: 'userThemeCID', style: 'User Theme C Style', tags: ['meta:theme'], authors: ['authorName'] }
}; };
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -607,6 +609,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200); expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({ expect(res.send).toHaveBeenCalledWith({
name : 'User Theme A',
author : 'authorName',
styles : [ styles : [
'/* From Brew: https://localhost/share/userThemeCID */\n\nUser Theme C Style', '/* From Brew: https://localhost/share/userThemeCID */\n\nUser Theme C Style',
'/* From Brew: https://localhost/share/userThemeBID */\n\nUser Theme B Style', '/* From Brew: https://localhost/share/userThemeBID */\n\nUser Theme B Style',
@@ -623,6 +627,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200); expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({ expect(res.send).toHaveBeenCalledWith({
name : '5ePHB',
author : undefined,
styles : [ styles : [
`/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`, `/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`,
`/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");` `/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");`
@@ -636,9 +642,9 @@ brew`);
it('should return Theme Bundle for nested User and Static Themes together', async ()=>{ it('should return Theme Bundle for nested User and Static Themes together', async ()=>{
const brews = { const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style' }, userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style' }, userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: '5eDMG', shareId: 'userThemeCID', style: 'User Theme C Style' } userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: '5eDMG', shareId: 'userThemeCID', style: 'User Theme C Style', tags: ['meta:theme'], authors: ['authorName'] }
}; };
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -649,6 +655,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200); expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({ expect(res.send).toHaveBeenCalledWith({
name : 'User Theme A',
author : 'authorName',
styles : [ styles : [
`/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`, `/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`,
`/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");`, `/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");`,
@@ -665,9 +673,9 @@ brew`);
}); });
}); });
it('should fail for an invalid Theme in the chain', async()=>{ it('should fail for a missing Theme in the chain', async()=>{
const brews = { const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'missingTheme', shareId: 'userThemeAID', style: 'User Theme A Style' }, userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'missingTheme', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] },
}; };
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -686,6 +694,27 @@ brew`);
name : 'ThemeLoad Error', name : 'ThemeLoad Error',
status : 404 }); status : 404 });
}); });
it('should fail for a User Theme not tagged with meta:theme', async ()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style' }
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
model.get = jest.fn((getParams)=>toBrewPromise(brews[getParams.shareId]));
const req = { params: { renderer: 'V3', id: 'userThemeAID' }, get: ()=>{ return 'localhost'; }, protocol: 'https' };
let err;
await api.getThemeBundle(req, res)
.catch((e)=>err = e);
expect(err).toEqual({
HBErrorCode : '10',
brewId : 'userThemeAID',
message : 'Selected theme does not have the meta:theme tag',
name : 'Invalid Theme Selected',
status : 422 });
});
}); });
describe('deleteBrew', ()=>{ describe('deleteBrew', ()=>{

View File

@@ -1,6 +1,6 @@
import express from 'express'; import express from 'express';
import asyncHandler from 'express-async-handler'; import asyncHandler from 'express-async-handler';
import {model as HomebrewModel } from './homebrew.model.js'; import { model as HomebrewModel } from './homebrew.model.js';
const router = express.Router(); const router = express.Router();
@@ -29,7 +29,7 @@ const rendererConditions = (legacy, v3)=>{
return {}; // If all renderers selected, renderer field not needed in query for speed return {}; // If all renderers selected, renderer field not needed in query for speed
}; };
const sortConditions = (sort, dir) => { const sortConditions = (sort, dir)=>{
return { [sort]: dir === 'asc' ? 1 : -1 }; return { [sort]: dir === 'asc' ? 1 : -1 };
}; };

View File

@@ -44,13 +44,19 @@ const fetchThemeBundle = async (obj, renderer, theme)=>{
.catch((err)=>{ .catch((err)=>{
obj.setState({ error: err }); obj.setState({ error: err });
}); });
if(!res) return; if(!res) {
obj.setState((prevState)=>({
...prevState,
themeBundle : {}
}));
return;
}
const themeBundle = res.body; const themeBundle = res.body;
themeBundle.joinedStyles = themeBundle.styles.map((style)=>`<style>${style}</style>`).join('\n\n'); themeBundle.joinedStyles = themeBundle.styles.map((style)=>`<style>${style}</style>`).join('\n\n');
obj.setState((prevState)=>({ obj.setState((prevState)=>({
...prevState, ...prevState,
themeBundle : themeBundle themeBundle : themeBundle,
error : null
})); }));
}; };

View File

@@ -7,6 +7,7 @@ import MarkedExtendedTables from 'marked-extended-tables';
import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite'; import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite';
import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id'; import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id';
import { markedEmoji as MarkedEmojis } from 'marked-emoji'; import { markedEmoji as MarkedEmojis } from 'marked-emoji';
import MarkedSubSuperText from 'marked-subsuper-text';
//Icon fonts included so they can appear in emoji autosuggest dropdown //Icon fonts included so they can appear in emoji autosuggest dropdown
import diceFont from '../../themes/fonts/iconFonts/diceFont.js'; import diceFont from '../../themes/fonts/iconFonts/diceFont.js';
@@ -59,8 +60,14 @@ mathParser.functions.signed = function (a) {
return `${a}`; return `${a}`;
}; };
// Normalize variable names; trim edge spaces and shorten blocks of whitespace to 1 space
const normalizeVarNames = (label)=>{
return label.trim().replace(/\s+/g, ' ');
};
//Processes the markdown within an HTML block if it's just a class-wrapper //Processes the markdown within an HTML block if it's just a class-wrapper
renderer.html = function (html) { renderer.html = function (token) {
let html = token.text;
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){ if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
const openTag = html.substring(0, html.indexOf('>')+1); const openTag = html.substring(0, html.indexOf('>')+1);
html = html.substring(html.indexOf('>')+1); html = html.substring(html.indexOf('>')+1);
@@ -71,18 +78,21 @@ renderer.html = function (html) {
}; };
// Don't wrap {{ Spans alone on a line, or {{ Divs in <p> tags // Don't wrap {{ Spans alone on a line, or {{ Divs in <p> tags
renderer.paragraph = function(text){ renderer.paragraph = function(token){
let match; let match;
const text = this.parser.parseInline(token.tokens);
if(text.startsWith('<div') || text.startsWith('</div')) if(text.startsWith('<div') || text.startsWith('</div'))
return `${text}`; return `${text}`;
else if(match = text.match(/(^|^.*?\n)<span class="inline-block(.*?<\/span>)$/)) { else if(match = text.match(/(^|^.*?\n)<span class="inline-block(.*?<\/span>)$/))
return `${match[1].trim() ? `<p>${match[1]}</p>` : ''}<span class="inline-block${match[2]}`; return `${match[1].trim() ? `<p>${match[1]}</p>` : ''}<span class="inline-block${match[2]}`;
} else else
return `<p>${text}</p>\n`; return `<p>${text}</p>\n`;
}; };
//Fix local links in the Preview iFrame to link inside the frame //Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (href, title, text) { renderer.link = function (token) {
let { href, title, tokens } = token;
const text = this.parser.parseInline(tokens);
let self = false; let self = false;
if(href[0] == '#') { if(href[0] == '#') {
self = true; self = true;
@@ -104,8 +114,8 @@ renderer.link = function (href, title, text) {
}; };
// Expose `src` attribute as `--HB_src` to make the URL accessible via CSS // Expose `src` attribute as `--HB_src` to make the URL accessible via CSS
renderer.image = function (href, title, text) { renderer.image = function (token) {
href = cleanUrl(href); const { href, title, text } = token;
if(href === null) if(href === null)
return text; return text;
@@ -333,35 +343,6 @@ const mustacheInjectBlock = {
} }
}; };
const superSubScripts = {
name : 'superSubScript',
level : 'inline',
start(src) { return src.match(/\^/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const superRegex = /^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/m;
const subRegex = /^\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/m;
let isSuper = false;
let match = subRegex.exec(src);
if(!match){
match = superRegex.exec(src);
if(match)
isSuper = true;
}
if(match?.length) {
return {
type : 'superSubScript', // Should match "name" above
raw : match[0], // Text to consume from the source
tag : isSuper ? 'sup' : 'sub',
tokens : this.lexer.inlineTokens(match[1])
};
}
},
renderer(token) {
return `<${token.tag}>${this.parser.parseInline(token.tokens)}</${token.tag}>`;
}
};
const justifiedParagraphClasses = []; const justifiedParagraphClasses = [];
justifiedParagraphClasses[2] = 'Left'; justifiedParagraphClasses[2] = 'Left';
justifiedParagraphClasses[4] = 'Right'; justifiedParagraphClasses[4] = 'Right';
@@ -415,7 +396,7 @@ const forcedParagraphBreaks = {
} }
}, },
renderer(token) { renderer(token) {
return `<div class='blank'></div>`.repeat(token.length).concat('\n'); return `<div class='blank'></div>\n`.repeat(token.length);
} }
}; };
@@ -533,7 +514,7 @@ const replaceVar = function(input, hoist=false, allowUnresolved=false) {
const match = regex.exec(input); const match = regex.exec(input);
const prefix = match[1]; const prefix = match[1];
const label = match[2]; const label = normalizeVarNames(match[2]); // Ensure the label name is normalized as it should be in the var stack.
//v=====--------------------< HANDLE MATH >-------------------=====v// //v=====--------------------< HANDLE MATH >-------------------=====v//
const mathRegex = /[a-z]+\(|[+\-*/^(),]/g; const mathRegex = /[a-z]+\(|[+\-*/^(),]/g;
@@ -688,8 +669,8 @@ function MarkedVariables() {
}); });
} }
if(match[3]) { // Block Definition if(match[3]) { // Block Definition
const label = match[4] ? match[4].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space const label = match[4] ? normalizeVarNames(match[4]) : null;
const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Normalize text content (except newlines for block-level content)
varsQueue.push( varsQueue.push(
{ type : 'varDefBlock', { type : 'varDefBlock',
@@ -698,7 +679,7 @@ function MarkedVariables() {
}); });
} }
if(match[6]) { // Block Call if(match[6]) { // Block Call
const label = match[7] ? match[7].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space const label = match[7] ? normalizeVarNames(match[7]) : null;
varsQueue.push( varsQueue.push(
{ type : 'varCallBlock', { type : 'varCallBlock',
@@ -707,7 +688,7 @@ function MarkedVariables() {
}); });
} }
if(match[8]) { // Inline Definition if(match[8]) { // Inline Definition
const label = match[10] ? match[10].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space const label = match[10] ? normalizeVarNames(match[10]) : null;
let content = match[11] || null; let content = match[11] || null;
// In case of nested (), find the correct matching end ) // In case of nested (), find the correct matching end )
@@ -739,7 +720,7 @@ function MarkedVariables() {
}); });
} }
if(match[12]) { // Inline Call if(match[12]) { // Inline Call
const label = match[13] ? match[13].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space const label = match[13] ? normalizeVarNames(match[13]) : null;
varsQueue.push( varsQueue.push(
{ type : 'varCallInline', { type : 'varCallInline',
@@ -796,10 +777,11 @@ const tableTerminators = [
Marked.use(MarkedVariables()); Marked.use(MarkedVariables());
Marked.use({ extensions : [justifiedParagraphs, definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, Marked.use({ extensions : [justifiedParagraphs, definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
nonbreakingSpaces, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] }); nonbreakingSpaces, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(mustacheInjectBlock); Marked.use(mustacheInjectBlock);
Marked.use(MarkedSubSuperText());
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false }); Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
Marked.use(MarkedExtendedTables(tableTerminators), MarkedGFMHeadingId({ globalSlugs: true }), Marked.use(MarkedExtendedTables({ interruptPatterns: tableTerminators }), MarkedGFMHeadingId({ globalSlugs: true }),
MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions)); MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions));
function cleanUrl(href) { function cleanUrl(href) {
@@ -864,12 +846,12 @@ const processStyleTags = (string)=>{
obj[key.trim()] = value.trim(); obj[key.trim()] = value.trim();
return obj; return obj;
}, {}) || null; }, {}) || null;
const styles = tags?.length ? tags.reduce((styleObj, style) => { const styles = tags?.length ? tags.reduce((styleObj, style)=>{
const index = style.indexOf(':'); const index = style.indexOf(':');
const [key, value] = [style.substring(0, index), style.substring(index + 1)]; const [key, value] = [style.substring(0, index), style.substring(index + 1)];
styleObj[key.trim()] = value.replace(/"?([^"]*)"?/g, '$1').trim(); styleObj[key.trim()] = value.replace(/"?([^"]*)"?/g, '$1').trim();
return styleObj; return styleObj;
}, {}) : null; }, {}) : null;
return { return {
id : id, id : id,
@@ -885,8 +867,8 @@ const extractHTMLStyleTags = (htmlString)=>{
const id = firstElementOnly.match(/id="([^"]*)"/)?.[1] || null; const id = firstElementOnly.match(/id="([^"]*)"/)?.[1] || null;
const classes = firstElementOnly.match(/class="([^"]*)"/)?.[1] || null; const classes = firstElementOnly.match(/class="([^"]*)"/)?.[1] || null;
const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1] const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1]
?.split(';').reduce((styleObj, style) => { ?.split(';').reduce((styleObj, style)=>{
if (style.trim() === '') return styleObj; if(style.trim() === '') return styleObj;
const index = style.indexOf(':'); const index = style.indexOf(':');
const [key, value] = [style.substring(0, index), style.substring(index + 1)]; const [key, value] = [style.substring(0, index), style.substring(index + 1)];
styleObj[key.trim()] = value.trim(); styleObj[key.trim()] = value.trim();
@@ -896,7 +878,7 @@ const extractHTMLStyleTags = (htmlString)=>{
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="')) ?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
.reduce((obj, attr)=>{ .reduce((obj, attr)=>{
const index = attr.indexOf('='); const index = attr.indexOf('=');
let [key, value] = [attr.substring(0, index), attr.substring(index + 1)]; const [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
obj[key.trim()] = value.replace(/"/g, ''); obj[key.trim()] = value.replace(/"/g, '');
return obj; return obj;
}, {}) || null; }, {}) || null;
@@ -909,7 +891,7 @@ const extractHTMLStyleTags = (htmlString)=>{
}; };
}; };
const mergeHTMLTags = (originalTags, newTags) => { const mergeHTMLTags = (originalTags, newTags)=>{
return { return {
id : newTags.id || originalTags.id || null, id : newTags.id || originalTags.id || null,
classes : [originalTags.classes, newTags.classes].join(' ').trim() || null, classes : [originalTags.classes, newTags.classes].join(' ').trim() || null,

View File

@@ -12,8 +12,8 @@ const Nav = {
displayName : 'Nav.base', displayName : 'Nav.base',
render : function(){ render : function(){
return <nav> return <nav>
{this.props.children} {this.props.children}
</nav>; </nav>;
} }
}), }),
logo : function(){ logo : function(){

View File

@@ -29,7 +29,7 @@ const SplitPane = (props)=>{
const limitPosition = (x, min = 1, max = window.innerWidth - 13)=>Math.round(Math.min(max, Math.max(min, x))); const limitPosition = (x, min = 1, max = window.innerWidth - 13)=>Math.round(Math.min(max, Math.max(min, x)));
//when resizing, the divider should grow smaller if less space is given, then grow back if the space is restored, to the original position //when resizing, the divider should grow smaller if less space is given, then grow back if the space is restored, to the original position
const handleResize = () =>setDividerPos(limitPosition(window.localStorage.getItem(storageKey), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13))); const handleResize = ()=>setDividerPos(limitPosition(window.localStorage.getItem(storageKey), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)));
const handleUp =(e)=>{ const handleUp =(e)=>{
e.preventDefault(); e.preventDefault();

View File

@@ -21,8 +21,8 @@
background-color : #BBBBBB; background-color : #BBBBBB;
.dots { .dots {
display : table-cell; display : table-cell;
text-align : center;
vertical-align : middle; vertical-align : middle;
text-align : center;
i { i {
display : block !important; display : block !important;
margin : 10px 0px; margin : 10px 0px;

View File

@@ -3,127 +3,127 @@
@defaultEasing : ease; @defaultEasing : ease;
//Animates all properties on an element //Animates all properties on an element
.animateAll(@duration : @defaultDuration, @easing : @defaultEasing){ .animateAll(@duration : @defaultDuration, @easing : @defaultEasing) {
-webkit-transition: all @duration @easing; -webkit-transition : all @duration @easing;
-moz-transition: all @duration @easing; -moz-transition : all @duration @easing;
-o-transition: all @duration @easing; -o-transition : all @duration @easing;
transition: all @duration @easing; transition : all @duration @easing;
} }
//Animates Specific property //Animates Specific property
.animate(@prop, @duration : @defaultDuration, @easing : @defaultEasing){ .animate(@prop, @duration : @defaultDuration, @easing : @defaultEasing) {
-webkit-transition: @prop @duration @easing; -webkit-transition : @prop @duration @easing;
-moz-transition: @prop @duration @easing; -moz-transition : @prop @duration @easing;
-o-transition: @prop @duration @easing; -o-transition : @prop @duration @easing;
transition: @prop @duration @easing; transition : @prop @duration @easing;
} }
.animateMany(...){ .animateMany(...) {
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
-webkit-transition-property: @value; -webkit-transition-property : @value;
-moz-transition-property: @value; -moz-transition-property : @value;
-o-transition-property: @value; -o-transition-property : @value;
transition-property: @value; transition-property : @value;
.animateDuration(); .animateDuration();
.animateEasing(); .animateEasing();
} }
.animateDuration(@duration : @defaultDuration){ .animateDuration(@duration : @defaultDuration) {
-webkit-transition-duration: @duration; -webkit-transition-duration : @duration;
-moz-transition-duration: @duration; -moz-transition-duration : @duration;
-o-transition-duration: @duration; -o-transition-duration : @duration;
transition-duration: @duration; transition-duration : @duration;
} }
.animateEasing(@easing : @defaultEasing){ .animateEasing(@easing : @defaultEasing) {
-webkit-transition-timing-function: @easing; -webkit-transition-timing-function : @easing;
-moz-transition-timing-function: @easing; -moz-transition-timing-function : @easing;
-o-transition-timing-function: @easing; -o-transition-timing-function : @easing;
transition-timing-function: @easing; transition-timing-function : @easing;
} }
.transition (@prop, @duration: @defaultDuration) { .transition (@prop, @duration: @defaultDuration) {
-webkit-transition: @prop @duration, -webkit-transform @duration; -webkit-transition : @prop @duration, -webkit-transform @duration;
-moz-transition: @prop @duration, -moz-transform @duration; -moz-transition : @prop @duration, -moz-transform @duration;
-o-transition: @prop @duration, -o-transform @duration; -o-transition : @prop @duration, -o-transform @duration;
-ms-transition: @prop @duration, -ms-transform @duration; -ms-transition : @prop @duration, -ms-transform @duration;
transition: @prop @duration, transform @duration; transition : @prop @duration, transform @duration;
} }
.transform (@transform) { .transform (@transform) {
-webkit-transform: @transform; -webkit-transform : @transform;
-moz-transform: @transform; -moz-transform : @transform;
-o-transform: @transform; -o-transform : @transform;
-ms-transform: @transform; -ms-transform : @transform;
transform: @transform; transform : @transform;
} }
.delay(@delay){ .delay(@delay) {
animation-delay:@delay; -webkit-transition-delay : @delay;
-webkit-animation-delay:@delay; transition-delay : @delay;
transition-delay:@delay; -webkit-animation-delay : @delay;
-webkit-transition-delay:@delay; animation-delay : @delay;
} }
.keep(){ .keep() {
-webkit-animation-fill-mode:forwards; -webkit-animation-fill-mode : forwards;
-moz-animation-fill-mode:forwards; -moz-animation-fill-mode : forwards;
-ms-animation-fill-mode:forwards; -ms-animation-fill-mode : forwards;
-o-animation-fill-mode:forwards; -o-animation-fill-mode : forwards;
animation-fill-mode:forwards; animation-fill-mode : forwards;
} }
.sequentialDelay(@delayInc : 0.2s, @initialDelay : 0s){ .sequentialDelay(@delayInc : 0.2s, @initialDelay : 0s) {
&:nth-child(1){.delay(0*@delayInc + @initialDelay)} &:nth-child(1) {.delay(0*@delayInc + @initialDelay); }
&:nth-child(2){.delay(1*@delayInc + @initialDelay)} &:nth-child(2) {.delay(1*@delayInc + @initialDelay); }
&:nth-child(3){.delay(2*@delayInc + @initialDelay)} &:nth-child(3) {.delay(2*@delayInc + @initialDelay); }
&:nth-child(4){.delay(3*@delayInc + @initialDelay)} &:nth-child(4) {.delay(3*@delayInc + @initialDelay); }
&:nth-child(5){.delay(4*@delayInc + @initialDelay)} &:nth-child(5) {.delay(4*@delayInc + @initialDelay); }
&:nth-child(6){.delay(5*@delayInc + @initialDelay)} &:nth-child(6) {.delay(5*@delayInc + @initialDelay); }
&:nth-child(7){.delay(6*@delayInc + @initialDelay)} &:nth-child(7) {.delay(6*@delayInc + @initialDelay); }
&:nth-child(8){.delay(7*@delayInc + @initialDelay)} &:nth-child(8) {.delay(7*@delayInc + @initialDelay); }
&:nth-child(9){.delay(8*@delayInc + @initialDelay)} &:nth-child(9) {.delay(8*@delayInc + @initialDelay); }
&:nth-child(10){.delay(9*@delayInc + @initialDelay)} &:nth-child(10) {.delay(9*@delayInc + @initialDelay); }
&:nth-child(11){.delay(10*@delayInc + @initialDelay)} &:nth-child(11) {.delay(10*@delayInc + @initialDelay); }
&:nth-child(12){.delay(11*@delayInc + @initialDelay)} &:nth-child(12) {.delay(11*@delayInc + @initialDelay); }
&:nth-child(13){.delay(12*@delayInc + @initialDelay)} &:nth-child(13) {.delay(12*@delayInc + @initialDelay); }
&:nth-child(14){.delay(13*@delayInc + @initialDelay)} &:nth-child(14) {.delay(13*@delayInc + @initialDelay); }
&:nth-child(15){.delay(14*@delayInc + @initialDelay)} &:nth-child(15) {.delay(14*@delayInc + @initialDelay); }
&:nth-child(16){.delay(15*@delayInc + @initialDelay)} &:nth-child(16) {.delay(15*@delayInc + @initialDelay); }
&:nth-child(17){.delay(16*@delayInc + @initialDelay)} &:nth-child(17) {.delay(16*@delayInc + @initialDelay); }
&:nth-child(18){.delay(17*@delayInc + @initialDelay)} &:nth-child(18) {.delay(17*@delayInc + @initialDelay); }
&:nth-child(19){.delay(18*@delayInc + @initialDelay)} &:nth-child(19) {.delay(18*@delayInc + @initialDelay); }
&:nth-child(20){.delay(19*@delayInc + @initialDelay)} &:nth-child(20) {.delay(19*@delayInc + @initialDelay); }
} }
.createFrames(@name, @from, @to){ .createFrames(@name, @from, @to) {
@frames: { @frames: {
from { @from(); } from { @from(); }
to { @to(); } to { @to(); }
}; };
@-webkit-keyframes @name {@frames();} @-webkit-keyframes @name {@frames();}
@-moz-keyframes @name {@frames();} @-moz-keyframes @name {@frames();}
@-ms-keyframes @name {@frames();} @-ms-keyframes @name {@frames();}
@-o-keyframes @name {@frames();} @-o-keyframes @name {@frames();}
@keyframes @name {@frames();} @keyframes @name {@frames();}
} }
.createAnimation(@name, @duration : @defaultDuration, @easing : @defaultEasing){ .createAnimation(@name, @duration : @defaultDuration, @easing : @defaultEasing) {
-webkit-animation-name: @name; -webkit-animation-name : @name;
-moz-animation-name: @name; -moz-animation-name : @name;
-ms-animation-name: @name; -ms-animation-name : @name;
animation-name: @name; animation-name : @name;
-webkit-animation-duration: @duration; -webkit-animation-duration : @duration;
-moz-animation-duration: @duration; -moz-animation-duration : @duration;
-ms-animation-duration: @duration; -ms-animation-duration : @duration;
animation-duration: @duration; animation-duration : @duration;
-webkit-animation-timing-function: @easing; -webkit-animation-timing-function : @easing;
-moz-animation-timing-function: @easing; -moz-animation-timing-function : @easing;
-ms-animation-timing-function: @easing; -ms-animation-timing-function : @easing;
animation-timing-function: @easing; animation-timing-function : @easing;
} }
@@ -132,82 +132,82 @@
Standard Animations Standard Animations
****************************/ ****************************/
.fadeIn(@duration : @defaultDuration, @easing : @defaultEasing){ .fadeIn(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeIn; @duration; @easing); .createAnimation(fadeIn; @duration; @easing);
.createFrames(fadeIn, .createFrames(fadeIn,
{ opacity : 0; }, { opacity : 0; },
{ opacity : 1; } { opacity : 1; }
); );
} }
.fadeInDown(@duration : @defaultDuration, @easing : @defaultEasing){ .fadeInDown(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeInDown; @duration; @easing); .createAnimation(fadeInDown; @duration; @easing);
.createFrames(fadeInDown, .createFrames(fadeInDown,
{ opacity : 0; .transform(translateY(20px));}, { opacity : 0; .transform(translateY(20px));},
{ opacity : 1; .transform(translateY(0px));} { opacity : 1; .transform(translateY(0px));}
); );
} }
.fadeInTop(@duration : @defaultDuration, @easing : @defaultEasing){ .fadeInTop(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeInTop; @duration; @easing); .createAnimation(fadeInTop; @duration; @easing);
.createFrames(fadeInTop, .createFrames(fadeInTop,
{ opacity : 0; .transform(translateY(-20px)); }, { opacity : 0; .transform(translateY(-20px)); },
{ opacity : 1; .transform(translateY(0px));} { opacity : 1; .transform(translateY(0px));}
); );
} }
.fadeInLeft(@duration : @defaultDuration, @easing : @defaultEasing){ .fadeInLeft(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeInLeft; @duration; @easing); .createAnimation(fadeInLeft; @duration; @easing);
.createFrames(fadeInLeft, .createFrames(fadeInLeft,
{ opacity: 0; .transform(translateX(-20px));}, { opacity: 0; .transform(translateX(-20px));},
{ opacity: 1; .transform(translateX(0));} { opacity: 1; .transform(translateX(0));}
); );
} }
.fadeInRight(@duration : @defaultDuration, @easing : @defaultEasing){ .fadeInRight(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeInRight; @duration; @easing); .createAnimation(fadeInRight; @duration; @easing);
.createFrames(fadeInRight, .createFrames(fadeInRight,
{ opacity: 0; .transform(translateX(20px));}, { opacity: 0; .transform(translateX(20px));},
{ opacity: 1; .transform(translateX(0));} { opacity: 1; .transform(translateX(0));}
); );
} }
.fadeOut(@duration : @defaultDuration, @easing : @defaultEasing){ .fadeOut(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeOut; @duration; @easing); .createAnimation(fadeOut; @duration; @easing);
.createFrames(fadeOut, .createFrames(fadeOut,
{ opacity : 1; }, { opacity : 1; },
{ opacity : 0; } { opacity : 0; }
); );
} }
.fadeOutDown(@duration : @defaultDuration, @easing : @defaultEasing){ .fadeOutDown(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeOutDown; @duration; @easing); .createAnimation(fadeOutDown; @duration; @easing);
.createFrames(fadeOutDown, .createFrames(fadeOutDown,
{ opacity : 1; .transform(translateY(0)); visibility: visible;}, { opacity : 1; .transform(translateY(0)); visibility: visible;},
{ opacity : 0; .transform(translateY(20px)); visibility: hidden;} { opacity : 0; .transform(translateY(20px)); visibility: hidden;}
); );
} }
.fadeOutTop(@duration : @defaultDuration, @easing : @defaultEasing){ .fadeOutTop(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeOutTop; @duration; @easing); .createAnimation(fadeOutTop; @duration; @easing);
.createFrames(fadeOutTop, .createFrames(fadeOutTop,
{ opacity : 1; .transform(translateY(0)); }, { opacity : 1; .transform(translateY(0)); },
{ opacity : 0; .transform(translateY(-20px)); } { opacity : 0; .transform(translateY(-20px)); }
); );
} }
.fadeOutLeft(@duration : @defaultDuration, @easing : @defaultEasing){ .fadeOutLeft(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeOutLeft; @duration; @easing); .createAnimation(fadeOutLeft; @duration; @easing);
.createFrames(fadeOutLeft, .createFrames(fadeOutLeft,
{ opacity : 1; .transform(translateX(0));}, { opacity : 1; .transform(translateX(0));},
{ opacity : 0; .transform(translateX(-20px));} { opacity : 0; .transform(translateX(-20px));}
); );
} }
.fadeOutRight(@duration : @defaultDuration, @easing : @defaultEasing){ .fadeOutRight(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeOutRight; @duration; @easing); .createAnimation(fadeOutRight; @duration; @easing);
.createFrames(fadeOutRight, .createFrames(fadeOutRight,
{ opacity : 1; .transform(translateX(0));}, { opacity : 1; .transform(translateX(0));},
{ opacity : 0; .transform(translateX(20px));} { opacity : 0; .transform(translateX(20px));}
); );
} }
@@ -219,50 +219,50 @@
Fun Animations Fun Animations
****************************/ ****************************/
.spin(@duration : @defaultDuration, @easing : @defaultEasing){ .spin(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(spin, @duration, @easing); .createAnimation(spin, @duration, @easing);
.spinKeyFrames(){ .spinKeyFrames() {
from { .transform(rotate(0deg)); } from { .transform(rotate(0deg)); }
to { .transform(rotate(360deg)); } to { .transform(rotate(360deg)); }
} }
@-webkit-keyframes spin {.spinKeyFrames();} @-webkit-keyframes spin {.spinKeyFrames();}
@-moz-keyframes spin {.spinKeyFrames();} @-moz-keyframes spin {.spinKeyFrames();}
@-ms-keyframes spin {.spinKeyFrames();} @-ms-keyframes spin {.spinKeyFrames();}
@-o-keyframes spin {.spinKeyFrames();} @-o-keyframes spin {.spinKeyFrames();}
@keyframes spin {.spinKeyFrames();} @keyframes spin {.spinKeyFrames();}
} }
.bounce(@duration : @defaultDuration, @easing : @defaultEasing){ .bounce(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(bounce, @duration, @easing); .createAnimation(bounce, @duration, @easing);
.bounceKeyFrames(){ .bounceKeyFrames() {
0%, 20%, 50%, 80%, 100% { .transform(translateY(0));} 0%, 20%, 50%, 80%, 100% { .transform(translateY(0));}
40% { .transform(translateY(-30px));} 40% { .transform(translateY(-30px));}
60% { .transform(translateY(-15px));} 60% { .transform(translateY(-15px));}
} }
@-webkit-keyframes bounce {.bounceKeyFrames();} @-webkit-keyframes bounce {.bounceKeyFrames();}
@-moz-keyframes bounce {.bounceKeyFrames();} @-moz-keyframes bounce {.bounceKeyFrames();}
@-ms-keyframes bounce {.bounceKeyFrames();} @-ms-keyframes bounce {.bounceKeyFrames();}
@-o-keyframes bounce {.bounceKeyFrames();} @-o-keyframes bounce {.bounceKeyFrames();}
@keyframes bounce {.bounceKeyFrames();} @keyframes bounce {.bounceKeyFrames();}
} }
.pulse(@duration : @defaultDuration, @easing : @defaultEasing){ .pulse(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(pulse, @duration, @easing); .createAnimation(pulse, @duration, @easing);
.pulseKeyFrames(){ .pulseKeyFrames() {
0% { .transform(scale(1));} 0% { .transform(scale(1));}
50% { .transform(scale(1.4));} 50% { .transform(scale(1.4));}
100% { .transform(scale(1));} 100% { .transform(scale(1));}
} }
@-webkit-keyframes pulse {.pulseKeyFrames();} @-webkit-keyframes pulse {.pulseKeyFrames();}
@-moz-keyframes pulse {.pulseKeyFrames();} @-moz-keyframes pulse {.pulseKeyFrames();}
@-ms-keyframes pulse {.pulseKeyFrames();} @-ms-keyframes pulse {.pulseKeyFrames();}
@-o-keyframes pulse {.pulseKeyFrames();} @-o-keyframes pulse {.pulseKeyFrames();}
@keyframes pulse {.pulseKeyFrames();} @keyframes pulse {.pulseKeyFrames();}
} }
.rubberBand(@duration : @defaultDuration, @easing : @defaultEasing){ .rubberBand(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(rubberBand, @duration, @easing); .createAnimation(rubberBand, @duration, @easing);
.rubberBandKeyFrames(){ .rubberBandKeyFrames() {
0% {.transform(scale(1));} 0% {.transform(scale(1));}
30% {.transform(scaleX(1.25) scaleY(0.75));} 30% {.transform(scaleX(1.25) scaleY(0.75));}
40% {.transform(scaleX(0.75) scaleY(1.25));} 40% {.transform(scaleX(0.75) scaleY(1.25));}
@@ -270,32 +270,32 @@
100% {.transform(scale(1));} 100% {.transform(scale(1));}
} }
@-webkit-keyframes rubberBand {.rubberBandKeyFrames();} @-webkit-keyframes rubberBand {.rubberBandKeyFrames();}
@-moz-keyframes rubberBand {.rubberBandKeyFrames();} @-moz-keyframes rubberBand {.rubberBandKeyFrames();}
@-ms-keyframes rubberBand {.rubberBandKeyFrames();} @-ms-keyframes rubberBand {.rubberBandKeyFrames();}
@-o-keyframes rubberBand {.rubberBandKeyFrames();} @-o-keyframes rubberBand {.rubberBandKeyFrames();}
@keyframes rubberBand {.rubberBandKeyFrames();} @keyframes rubberBand {.rubberBandKeyFrames();}
} }
.shake(@duration : @defaultDuration, @easing : @defaultEasing){ .shake(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(shake, @duration, @easing); .createAnimation(shake, @duration, @easing);
.shakeKeyFrames(){ .shakeKeyFrames() {
0%, 100% {.transform( translateX(0));} 0%, 100% {.transform( translateX(0));}
10%, 30%, 50%, 70%, 90% {.transform( translateX(-10px));} 10%, 30%, 50%, 70%, 90% {.transform( translateX(-10px));}
20%, 40%, 60%, 80% {.transform( translateX(10px));} 20%, 40%, 60%, 80% {.transform( translateX(10px));}
} }
@-webkit-keyframes shake {.shakeKeyFrames();} @-webkit-keyframes shake {.shakeKeyFrames();}
@-moz-keyframes shake {.shakeKeyFrames();} @-moz-keyframes shake {.shakeKeyFrames();}
@-ms-keyframes shake {.shakeKeyFrames();} @-ms-keyframes shake {.shakeKeyFrames();}
@-o-keyframes shake {.shakeKeyFrames();} @-o-keyframes shake {.shakeKeyFrames();}
@keyframes shake {.shakeKeyFrames();} @keyframes shake {.shakeKeyFrames();}
} }
.swing(@duration : @defaultDuration, @easing : @defaultEasing){ .swing(@duration : @defaultDuration, @easing : @defaultEasing) {
-webkit-transform-origin: top center; -webkit-transform-origin : top center;
-ms-transform-origin: top center; -ms-transform-origin : top center;
transform-origin: top center; transform-origin : top center;
.createAnimation(swing, @duration, @easing); .createAnimation(swing, @duration, @easing);
.swingKeyFrames(){ .swingKeyFrames() {
20% {.transform(rotate(15deg));} 20% {.transform(rotate(15deg));}
40% {.transform(rotate(-10deg));} 40% {.transform(rotate(-10deg));}
60% {.transform(rotate(5deg));} 60% {.transform(rotate(5deg));}
@@ -303,18 +303,18 @@
100% {.transform(rotate(0deg));} 100% {.transform(rotate(0deg));}
} }
@-webkit-keyframes swing {.swingKeyFrames();} @-webkit-keyframes swing {.swingKeyFrames();}
@-moz-keyframes swing {.swingKeyFrames();} @-moz-keyframes swing {.swingKeyFrames();}
@-ms-keyframes swing {.swingKeyFrames();} @-ms-keyframes swing {.swingKeyFrames();}
@-o-keyframes swing {.swingKeyFrames();} @-o-keyframes swing {.swingKeyFrames();}
@keyframes swing {.swingKeyFrames();} @keyframes swing {.swingKeyFrames();}
} }
.twist(@duration : @defaultDuration, @easing : @defaultEasing){ .twist(@duration : @defaultDuration, @easing : @defaultEasing) {
-webkit-transform-origin: center center; -webkit-transform-origin : center center;
-ms-transform-origin: center center; -ms-transform-origin : center center;
transform-origin: center center; transform-origin : center center;
.createAnimation(swing, @duration, @easing); .createAnimation(swing, @duration, @easing);
.swingKeyFrames(){ .swingKeyFrames() {
20% {.transform(rotate(15deg));} 20% {.transform(rotate(15deg));}
40% {.transform(rotate(-10deg));} 40% {.transform(rotate(-10deg));}
60% {.transform(rotate(5deg));} 60% {.transform(rotate(5deg));}
@@ -322,15 +322,15 @@
100% {.transform(rotate(0deg));} 100% {.transform(rotate(0deg));}
} }
@-webkit-keyframes swing {.swingKeyFrames();} @-webkit-keyframes swing {.swingKeyFrames();}
@-moz-keyframes swing {.swingKeyFrames();} @-moz-keyframes swing {.swingKeyFrames();}
@-ms-keyframes swing {.swingKeyFrames();} @-ms-keyframes swing {.swingKeyFrames();}
@-o-keyframes swing {.swingKeyFrames();} @-o-keyframes swing {.swingKeyFrames();}
@keyframes swing {.swingKeyFrames();} @keyframes swing {.swingKeyFrames();}
} }
.wobble(@duration : @defaultDuration, @easing : @defaultEasing){ .wobble(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(wobble, @duration, @easing); .createAnimation(wobble, @duration, @easing);
.wobbleKeyFrames(){ .wobbleKeyFrames() {
0% {.transform(translateX(0%));} 0% {.transform(translateX(0%));}
15% {.transform(translateX(-25%) rotate(-5deg));} 15% {.transform(translateX(-25%) rotate(-5deg));}
30% {.transform(translateX(20%) rotate(3deg));} 30% {.transform(translateX(20%) rotate(3deg));}
@@ -340,22 +340,22 @@
100% {.transform(translateX(0%));} 100% {.transform(translateX(0%));}
} }
@-webkit-keyframes wobble {.wobbleKeyFrames();} @-webkit-keyframes wobble {.wobbleKeyFrames();}
@-moz-keyframes wobble {.wobbleKeyFrames();} @-moz-keyframes wobble {.wobbleKeyFrames();}
@-ms-keyframes wobble {.wobbleKeyFrames();} @-ms-keyframes wobble {.wobbleKeyFrames();}
@-o-keyframes wobble {.wobbleKeyFrames();} @-o-keyframes wobble {.wobbleKeyFrames();}
@keyframes wobble {.wobbleKeyFrames();} @keyframes wobble {.wobbleKeyFrames();}
} }
.popIn(@duration : @defaultDuration, @easing : @defaultEasing){ .popIn(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(popIn, @duration, @easing); .createAnimation(popIn, @duration, @easing);
.popInKeyFrames(){ .popInKeyFrames() {
0% { .transform(scale(0));} 0% { .transform(scale(0));}
70% { .transform(scale(1.4));} 70% { .transform(scale(1.4));}
100% { .transform(scale(1));} 100% { .transform(scale(1));}
} }
@-webkit-keyframes popIn {.popInKeyFrames();} @-webkit-keyframes popIn {.popInKeyFrames();}
@-moz-keyframes popIn {.popInKeyFrames();} @-moz-keyframes popIn {.popInKeyFrames();}
@-ms-keyframes popIn {.popInKeyFrames();} @-ms-keyframes popIn {.popInKeyFrames();}
@-o-keyframes popIn {.popInKeyFrames();} @-o-keyframes popIn {.popInKeyFrames();}
@keyframes popIn {.popInKeyFrames();} @keyframes popIn {.popInKeyFrames();}
} }

View File

@@ -23,47 +23,47 @@
@grey : #7F8C8D; @grey : #7F8C8D;
#backgroundColors { #backgroundColors {
&.tealLight{ background-color : @tealLight }; &.tealLight { background-color : @tealLight; };
&.teal{ background-color : @teal }; &.teal { background-color : @teal; };
&.greenLight{ background-color : @greenLight }; &.greenLight { background-color : @greenLight; };
&.green{ background-color : @green }; &.green { background-color : @green; };
&.blueLight{ background-color : @blueLight }; &.blueLight { background-color : @blueLight; };
&.blue{ background-color : @blue }; &.blue { background-color : @blue; };
&.purpleLight{ background-color : @purpleLight }; &.purpleLight { background-color : @purpleLight; };
&.purple{ background-color : @purple }; &.purple { background-color : @purple; };
&.steelLight{ background-color : @steelLight }; &.steelLight { background-color : @steelLight; };
&.steel{ background-color : @steel }; &.steel { background-color : @steel; };
&.yellowLight{ background-color : @yellowLight }; &.yellowLight { background-color : @yellowLight; };
&.yellow{ background-color : @yellow }; &.yellow { background-color : @yellow; };
&.orangeLight{ background-color : @orangeLight }; &.orangeLight { background-color : @orangeLight; };
&.orange{ background-color : @orange }; &.orange { background-color : @orange; };
&.redLight{ background-color : @redLight }; &.redLight { background-color : @redLight; };
&.red{ background-color : @red }; &.red { background-color : @red; };
&.silverLight{ background-color : @silverLight }; &.silverLight { background-color : @silverLight; };
&.silver{ background-color : @silver }; &.silver { background-color : @silver; };
&.greyLight{ background-color : @greyLight }; &.greyLight { background-color : @greyLight; };
&.grey{ background-color : @grey }; &.grey { background-color : @grey; };
} }
#backgroundColorsHover { #backgroundColorsHover {
&.tealLight:hover{ background-color : @tealLight }; &.tealLight:hover { background-color : @tealLight; };
&.teal:hover{ background-color : @teal }; &.teal:hover { background-color : @teal; };
&.greenLight:hover{ background-color : @greenLight }; &.greenLight:hover { background-color : @greenLight; };
&.green:hover{ background-color : @green }; &.green:hover { background-color : @green; };
&.blueLight:hover{ background-color : @blueLight }; &.blueLight:hover { background-color : @blueLight; };
&.blue:hover{ background-color : @blue }; &.blue:hover { background-color : @blue; };
&.purpleLight:hover{ background-color : @purpleLight }; &.purpleLight:hover { background-color : @purpleLight; };
&.purple:hover{ background-color : @purple }; &.purple:hover { background-color : @purple; };
&.steelLight:hover{ background-color : @steelLight }; &.steelLight:hover { background-color : @steelLight; };
&.steel:hover{ background-color : @steel }; &.steel:hover { background-color : @steel; };
&.yellowLight:hover{ background-color : @yellowLight }; &.yellowLight:hover { background-color : @yellowLight; };
&.yellow:hover{ background-color : @yellow }; &.yellow:hover { background-color : @yellow; };
&.orangeLight:hover{ background-color : @orangeLight }; &.orangeLight:hover { background-color : @orangeLight; };
&.orange:hover{ background-color : @orange }; &.orange:hover { background-color : @orange; };
&.redLight:hover{ background-color : @redLight }; &.redLight:hover { background-color : @redLight; };
&.red:hover{ background-color : @red }; &.red:hover { background-color : @red; };
&.silverLight:hover{ background-color : @silverLight }; &.silverLight:hover { background-color : @silverLight; };
&.silver:hover{ background-color : @silver }; &.silver:hover { background-color : @silver; };
&.greyLight:hover{ background-color : @greyLight }; &.greyLight:hover { background-color : @greyLight; };
&.grey:hover{ background-color : @grey }; &.grey:hover { background-color : @grey; };
} }

View File

@@ -12,37 +12,31 @@
font-family : 'CodeBold'; font-family : 'CodeBold';
src : data-uri('naturalcrit/styles/CODE Bold.otf') format('opentype'); src : data-uri('naturalcrit/styles/CODE Bold.otf') format('opentype');
} }
html,body, #reactRoot{ html,body, #reactRoot {
height : 100vh; height : 100vh;
min-height : 100vh; min-height : 100vh;
margin : 0; margin : 0;
font-family : 'Open Sans', sans-serif; font-family : 'Open Sans', sans-serif;
} }
*{ * { box-sizing : border-box; }
box-sizing : border-box; .colorButton(@backgroundColor : @green) {
}
.colorButton(@backgroundColor : @green){
.animate(background-color); .animate(background-color);
display : inline-block; display : inline-block;
padding : 0.6em 1.2em; padding : 0.6em 1.2em;
cursor : pointer;
background-color : @backgroundColor;
font-family : 'Open Sans', sans-serif; font-family : 'Open Sans', sans-serif;
font-size : 0.8em; font-size : 0.8em;
font-weight : 800; font-weight : 800;
color : white; color : white;
text-decoration : none;
text-transform : uppercase; text-transform : uppercase;
border : none; text-decoration : none;
cursor : pointer;
outline : none; outline : none;
&:hover{ background-color : @backgroundColor;
background-color : darken(@backgroundColor, 5%); border : none;
} &:hover { background-color : darken(@backgroundColor, 5%); }
&:active{ &:active { background-color : darken(@backgroundColor, 10%); }
background-color : darken(@backgroundColor, 10%); &:disabled {
} cursor : not-allowed;
&:disabled{
background-color : @silver !important; background-color : @silver !important;
cursor:not-allowed;
} }
} }

View File

@@ -1,86 +1,76 @@
@containerWidth : 1000px; @containerWidth : 1000px;
html, body{ html, body {
position : relative; position : relative;
height : 100%; height : 100%;
min-height : 100%; min-height : 100%;
background-color : #eee;
font-family : 'Lato', sans-serif; font-family : 'Lato', sans-serif;
color : @copyGrey; color : @copyGrey;
background-color : #EEEEEE;
} }
.container{ .container {
position : relative; position : relative;
max-width : @containerWidth; max-width : @containerWidth;
margin : 0 auto;
padding-right : 20px; padding-right : 20px;
padding-left : 20px; padding-left : 20px;
margin : 0 auto;
} }
h1{ h1 {
margin-top : 10px; margin-top : 10px;
margin-bottom : 15px; margin-bottom : 15px;
font-size : 2em; font-size : 2em;
} }
h2{ h2 {
margin-top : 10px; margin-top : 10px;
margin-bottom : 15px; margin-bottom : 15px;
font-size : 1.5em; font-size : 1.5em;
font-weight : 900; font-weight : 900;
} }
h3{ h3 {
margin-top : 5px; margin-top : 5px;
margin-bottom : 7px; margin-bottom : 7px;
font-size : 1em; font-size : 1em;
font-weight : 900; font-weight : 900;
} }
p{ p {
margin-bottom : 1em; margin-bottom : 1em;
font-size : 16px; font-size : 16px;
color : @copyGrey;
line-height : 1.5em; line-height : 1.5em;
color : @copyGrey;
} }
code{ code {
background-color : #F8F8F8; font-family : 'Courier', "mono";
font-family : 'Courier', mono;
color : black; color : black;
white-space : pre; white-space : pre;
background-color : #F8F8F8;
} }
a{ a { color : inherit; }
color : inherit; strong { font-weight : bold; }
} button {
strong{
font-weight : bold;
}
button{
.button(); .button();
} }
.button(@backgroundColor : @green){ .button(@backgroundColor : @green) {
.animate(background-color); .animate(background-color);
display : inline-block; display : inline-block;
padding : 0.6em 1.2em; padding : 0.6em 1.2em;
cursor : pointer; font-family : 'Lato', "Helvetica", "Arial", sans-serif;
background-color : @backgroundColor;
font-family : "Lato", Helvetica, Arial, sans-serif;
font-size : 15px; font-size : 15px;
color : white; color : white;
text-decoration : none; text-decoration : none;
border : none;
outline : none;
&:hover{
background-color : darken(@backgroundColor, 5%);
}
&:active{
background-color : darken(@backgroundColor, 10%);
}
&:disabled{
background-color : @silver !important;
}
}
.iconButton(@backgroundColor : @green){
padding : 0.6em;
cursor : pointer; cursor : pointer;
outline : none;
background-color : @backgroundColor; background-color : @backgroundColor;
border : none;
&:hover { background-color : darken(@backgroundColor, 5%); }
&:active { background-color : darken(@backgroundColor, 10%); }
&:disabled { background-color : @silver !important; }
}
.iconButton(@backgroundColor : @green) {
padding : 0.6em;
font-size : 14px; font-size : 14px;
color : white; color : white;
text-align : center; text-align : center;
cursor : pointer;
background-color : @backgroundColor;
} }

View File

@@ -1,33 +1,23 @@
:where(html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,button,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video){ :where(html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,button,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video) {padding : 0;margin : 0;font : inherit;font-size : 100%;vertical-align : baseline;
border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0 border : 0;
} }
:where(article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section){ :where(article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section) { display : block; }
display:block
}
:where(body){ :where(body) { line-height : 1; }
line-height:1
}
:where(ol,ul){ :where(ol,ul) { list-style : none; }
list-style:none
}
:where(blockquote,q){ :where(blockquote,q) { quotes : none; }
quotes:none
}
:where(blockquote:before,blockquote:after,q:before,q:after){ :where(blockquote::before,blockquote::after,q::before,q::after) { content : none; }
content:none
}
:where(table){ :where(table) {border-spacing : 0;
border-collapse:collapse;border-spacing:0 border-collapse : collapse;
} }
:where(button) { :where(button) {
background-color: unset; color : unset;
text-transform: unset; text-transform : unset;
color: unset; background-color : unset;
} }

View File

@@ -2,116 +2,115 @@
@tooltipColor : #383838; @tooltipColor : #383838;
@arrowSize : 6px; @arrowSize : 6px;
@arrowPosition : 18px; @arrowPosition : 18px;
[data-tooltip]{ [data-tooltip] {
.tooltip(attr(data-tooltip)); .tooltip(attr(data-tooltip));
} }
[data-tooltip-top]{ [data-tooltip-top] {
.tooltipTop(attr(data-tooltip-top)); .tooltipTop(attr(data-tooltip-top));
} }
[data-tooltip-bottom]{ [data-tooltip-bottom] {
.tooltipBottom(attr(data-tooltip-bottom)); .tooltipBottom(attr(data-tooltip-bottom));
} }
[data-tooltip-left]{ [data-tooltip-left] {
.tooltipLeft(attr(data-tooltip-left)); .tooltipLeft(attr(data-tooltip-left));
} }
[data-tooltip-right]{ [data-tooltip-right] {
.tooltipRight(attr(data-tooltip-right)); .tooltipRight(attr(data-tooltip-right));
} }
.tooltip(@content){ .tooltip(@content) {
.tooltipBottom(@content); .tooltipBottom(@content);
} }
.tooltipTop(@content){ .tooltipTop(@content) {
.tooltipBase(@content); .tooltipBase(@content);
&:before { &::before {
margin-bottom : -@arrowSize * 2; margin-bottom : -@arrowSize * 2;
border-top-color : @tooltipColor; border-top-color : @tooltipColor;
} }
&:after{ margin-left: -18px; } &::after { margin-left : -18px; }
&:before, &:after{ &::before, &::after {
bottom : 100%; bottom : 100%;
left : 50%; left : 50%;
} }
&:hover:after, &:hover:before, &:focus:after, &:focus:before { &:hover::after, &:hover::before, &:focus::after, &:focus::before {
.transform(translateY(-(@arrowSize + 2))); .transform(translateY(-(@arrowSize + 2)));
} }
} }
.tooltipBottom(@content){ .tooltipBottom(@content) {
.tooltipBase(@content); .tooltipBase(@content);
&:before { &::before {
margin-top : -@arrowSize * 2; margin-top : -@arrowSize * 2;
border-bottom-color : @tooltipColor; border-bottom-color : @tooltipColor;
} }
&:after{ margin-left: -18px; } &::after { margin-left : -18px; }
&:before, &:after{ &::before, &::after {
top : 100%; top : 100%;
left : 50%; left : 50%;
} }
&:hover:after, &:hover:before, &:focus:after, &:focus:before { &:hover::after, &:hover::before, &:focus::after, &:focus::before {
.transform(translateY(@arrowSize + 2)); .transform(translateY(@arrowSize + 2));
} }
} }
.tooltipLeft(@content){ .tooltipLeft(@content) {
.tooltipBase(@content); .tooltipBase(@content);
&:before { &::before {
margin-right : -@arrowSize * 2; margin-right : -@arrowSize * 2;
margin-bottom : -@arrowSize; margin-bottom : -@arrowSize;
border-left-color : @tooltipColor; border-left-color : @tooltipColor;
} }
&:after{ margin-bottom: -14px;} &::after { margin-bottom : -14px;}
&:before, &:after { &::before, &::after {
right : 100%; right : 100%;
bottom : 50%; bottom : 50%;
} }
&:hover:after, &:hover:before, &:focus:after, &:focus:before { &:hover::after, &:hover::before, &:focus::after, &:focus::before {
.transform(translateX(-(@arrowSize + 2))); .transform(translateX(-(@arrowSize + 2)));
} }
} }
.tooltipRight(@content){ .tooltipRight(@content) {
.tooltipBase(@content); .tooltipBase(@content);
&:before { &::before {
margin-bottom : -@arrowSize; margin-bottom : -@arrowSize;
margin-left : -@arrowSize * 2; margin-left : -@arrowSize * 2;
border-right-color : @tooltipColor; border-right-color : @tooltipColor;
} }
&:after{ margin-bottom: -14px;} &::after { margin-bottom : -14px;}
&:before, &:after { &::before, &::after {
bottom : 50%; bottom : 50%;
left : 100%; left : 100%;
} }
&:hover:after, &:hover:before, &:focus:after, &:focus:before { &:hover::after, &:hover::before, &:focus::after, &:focus::before {
.transform(translateX(@arrowSize + 2)); .transform(translateX(@arrowSize + 2));
} }
} }
.tooltipShow(){ .tooltipShow(){ }
} .tooltipBase(@content) {
.tooltipBase(@content){
//position: relative; //position: relative;
&:before, &:after{ &::before, &::after {
.animateAll(); .animateAll();
position : absolute; position : absolute;
z-index : 1000000; z-index : 1000000;
opacity : 0;
pointer-events : none; pointer-events : none;
opacity : 0;
} }
//Arrow //Arrow
&:before{ &::before {
content : '';
z-index : 1000001; z-index : 1000001;
content : '';
background : transparent; background : transparent;
border : @arrowSize solid transparent; border : @arrowSize solid transparent;
} }
//Box //Box
&:after{ &::after {
content : @content;
visibility : hidden; visibility : hidden;
padding : 8px 10px; padding : 8px 10px;
background : @tooltipColor;
font-size : 12px; font-size : 12px;
color : white;
line-height : 12px; line-height : 12px;
color : white;
white-space : nowrap; white-space : nowrap;
content : @content;
background : @tooltipColor;
} }
&:hover:before, &:hover:after { &:hover::before, &:hover::after {
visibility : visible; visibility : visible;
opacity : 1; opacity : 1;
} }

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js'; import Markdown from 'naturalcrit/markdown.js';

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js'; import Markdown from 'naturalcrit/markdown.js';
@@ -92,12 +92,12 @@ describe('Multiline Definition Lists', ()=>{
test('Multiline Definition Term must have at least one non-empty Definition', function() { test('Multiline Definition Term must have at least one non-empty Definition', function() {
const source = 'Term 1\n::'; const source = 'Term 1\n::';
const rendered = Markdown.render(source).trim(); const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div><div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>`);
}); });
test('Multiline Definition List must have at least one non-newline character after ::', function() { test('Multiline Definition List must have at least one non-newline character after ::', function() {
const source = 'Term 1\n::\nDefinition 1\n\n'; const source = 'Term 1\n::\nDefinition 1\n\n';
const rendered = Markdown.render(source).trim(); const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div><div class='blank'></div>\n<p>Definition 1</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Definition 1</p>`);
}); });
}); });

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js'; import Markdown from 'naturalcrit/markdown.js';
@@ -12,31 +12,31 @@ describe('Hard Breaks', ()=>{
test('Double Break', function() { test('Double Break', function() {
const source = '::\n\n'; const source = '::\n\n';
const rendered = Markdown.render(source).trim(); const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div><div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>`);
}); });
test('Triple Break', function() { test('Triple Break', function() {
const source = ':::\n\n'; const source = ':::\n\n';
const rendered = Markdown.render(source).trim(); const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div><div class='blank'></div><div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
}); });
test('Many Break', function() { test('Many Break', function() {
const source = '::::::::::\n\n'; const source = '::::::::::\n\n';
const rendered = Markdown.render(source).trim(); const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div><div class='blank'></div><div class='blank'></div><div class='blank'></div><div class='blank'></div><div class='blank'></div><div class='blank'></div><div class='blank'></div><div class='blank'></div><div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
}); });
test('Multiple sets of Breaks', function() { test('Multiple sets of Breaks', function() {
const source = ':::\n:::\n:::'; const source = ':::\n:::\n:::';
const rendered = Markdown.render(source).trim(); const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div><div class='blank'></div><div class='blank'></div>\n<div class='blank'></div><div class='blank'></div><div class='blank'></div>\n<div class='blank'></div><div class='blank'></div><div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
}); });
test('Break directly between two paragraphs', function() { test('Break directly between two paragraphs', function() {
const source = 'Line 1\n::\nLine 2'; const source = 'Line 1\n::\nLine 2';
const rendered = Markdown.render(source).trim(); const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Line 1</p>\n<div class='blank'></div><div class='blank'></div>\n<p>Line 2</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Line 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Line 2</p>`);
}); });
test('Ignored inside a code block', function() { test('Ignored inside a code block', function() {

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js'; import Markdown from 'naturalcrit/markdown.js';

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js'; import Markdown from 'naturalcrit/markdown.js';

View File

@@ -410,4 +410,29 @@ describe('Regression Tests', ()=>{
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<table><thead><tr><th>title 1</th><th>title 2</th><th>title 3</th><th>title 4</th></tr></thead><tbody><tr><td><a href=\"bar\">foo</a></td><td>Ipsum</td><td>)</td><td>)</td></tr></tbody></table>'); expect(rendered).toBe('<table><thead><tr><th>title 1</th><th>title 2</th><th>title 3</th><th>title 4</th></tr></thead><tbody><tr><td><a href=\"bar\">foo</a></td><td>Ipsum</td><td>)</td><td>)</td></tr></tbody></table>');
}); });
it('Handle Extra spaces in image alt-text 1', function(){
const source='![ where is my image??](http://i.imgur.com/hMna6G0.png)';
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p><img src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
});
it('Handle Extra spaces in image alt-text 2', function(){
const source='![where is my image??](http://i.imgur.com/hMna6G0.png)';
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p><img src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
});
it('Handle Extra spaces in image alt-text 3', function(){
const source='![where is my image?? ](http://i.imgur.com/hMna6G0.png)';
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p><img src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
});
it('Handle Extra spaces in image alt-text 4', function(){
const source='![where is my image??](http://i.imgur.com/hMna6G0.png){height=20%,width=20%}';
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p><img style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" height=\"20%\" width=\"20%\"></p>');
});
}); });

View File

@@ -9,26 +9,22 @@
@headerText : #58180D; // Dark maroon @headerText : #58180D; // Dark maroon
@monsterStatBackground : #FDF1DC; // Lighter parchment @monsterStatBackground : #FDF1DC; // Lighter parchment
@captionText : #766649; // Brown @captionText : #766649; // Brown
@page { margin: 0; } @page { margin : 0; }
body { body { counter-reset : phb-page-numbers; }
counter-reset : phb-page-numbers; * { -webkit-print-color-adjust : exact; }
} .useSansSerif() {
*{ font-family : "ScalySans";
-webkit-print-color-adjust : exact; em {
} font-family : "ScalySans";
.useSansSerif(){
font-family : ScalySans;
em{
font-family : ScalySans;
font-style : italic; font-style : italic;
} }
strong{ strong {
font-family : ScalySans; font-family : "ScalySans";
font-weight : 800; font-weight : 800;
letter-spacing : -0.02em; letter-spacing : -0.02em;
} }
} }
.useColumns(@multiplier : 1){ .useColumns(@multiplier : 1) {
column-count : 2; column-count : 2;
column-fill : auto; column-fill : auto;
column-gap : 1cm; column-gap : 1cm;
@@ -40,21 +36,21 @@ body {
-webkit-column-gap : 1cm; -webkit-column-gap : 1cm;
-moz-column-gap : 1cm; -moz-column-gap : 1cm;
} }
.phb, .page{ .phb, .page {
.useColumns(); .useColumns();
counter-increment : phb-page-numbers;
position : relative; position : relative;
z-index : 15; z-index : 15;
box-sizing : border-box; box-sizing : border-box;
overflow : hidden;
height : 279.4mm;
width : 215.9mm; width : 215.9mm;
height : 279.4mm;
padding : 1.0cm 1.7cm; padding : 1.0cm 1.7cm;
padding-bottom : 1.5cm; padding-bottom : 1.5cm;
overflow : hidden;
font-family : "BookSanity";
font-size : 0.317cm;
counter-increment : phb-page-numbers;
background-color : @background; background-color : @background;
background-image : @backgroundImage; background-image : @backgroundImage;
font-family : BookSanity;
font-size : 0.317cm;
text-rendering : optimizeLegibility; text-rendering : optimizeLegibility;
page-break-before : always; page-break-before : always;
page-break-after : always; page-break-after : always;
@@ -63,199 +59,175 @@ body {
contain-intrinsic-size : auto none; contain-intrinsic-size : auto none;
} }
.phb{ .phb {
//***************************** //*****************************
// * BASE // * BASE
// *****************************/ // *****************************/
p{ p {
padding-bottom : 0.8em; padding-bottom : 0.8em;
line-height : 1.269em; line-height : 1.269em;
&+p{ & + p { margin-top : -0.8em; }
margin-top : -0.8em;
}
} }
ul{ ul {
margin-bottom : 0.8em;
padding-left : 1.4em; padding-left : 1.4em;
margin-bottom : 0.8em;
line-height : 1.269em; line-height : 1.269em;
list-style-position : outside; list-style-position : outside;
list-style-type : disc; list-style-type : disc;
} }
ol{ ol {
margin-bottom : 0.8em;
padding-left : 1.4em; padding-left : 1.4em;
margin-bottom : 0.8em;
line-height : 1.269em; line-height : 1.269em;
list-style-position : outside; list-style-position : outside;
list-style-type : decimal; list-style-type : decimal;
} }
//Indents after p or lists //Indents after p or lists
p+p, ul+p, ol+p{ p + p, ul + p, ol + p { text-indent : 1em; }
text-indent : 1em; img { z-index : -1; }
} strong {
img{
z-index : -1;
}
strong{
font-weight : bold; font-weight : bold;
letter-spacing : 0.03em; letter-spacing : 0.03em;
} }
em{ em { font-style : italic; }
font-style : italic; sup {
} font-size : smaller;
sup{ line-height : 0;
vertical-align : super; vertical-align : super;
font-size : smaller;
line-height : 0;
} }
sub{ sub {
vertical-align : sub;
font-size : smaller; font-size : smaller;
line-height : 0; line-height : 0;
vertical-align : sub;
} }
//***************************** //*****************************
// * HEADERS // * HEADERS
// *****************************/ // *****************************/
h1,h2,h3,h4{ h1,h2,h3,h4 {
margin-top : 0.2em; margin-top : 0.2em;
margin-bottom : 0.2em; margin-bottom : 0.2em;
font-family : MrJeeves; font-family : "MrJeeves";
font-weight : 800; font-weight : 800;
color : @headerText; color : @headerText;
} }
h1{ h1 {
column-span : all; column-span : all;
font-size : 0.987cm; font-size : 0.987cm;
-webkit-column-span : all; -webkit-column-span : all;
-moz-column-span : all; -moz-column-span : all;
&+p::first-letter{ & + p::first-letter {
float : left; float : left;
font-family : Solberry; font-family : "Solberry";
font-size : 10em; font-size : 10em;
color : #222;
line-height : 0.795em; line-height : 0.795em;
color : #222222;
} }
} }
h2{ h2 { font-size : 0.705cm; }
font-size : 0.705cm; h3 {
}
h3{
font-size : 0.529cm; font-size : 0.529cm;
border-bottom : 2px solid @headerUnderline; border-bottom : 2px solid @headerUnderline;
} }
h4{ h4 {
margin-bottom : 0.00em; margin-bottom : 0.00em;
font-size : 0.458cm; font-size : 0.458cm;
} }
h5{ h5 {
margin-bottom : 0.2em; margin-bottom : 0.2em;
font-family : ScalySansSmallCaps; font-family : "ScalySansSmallCaps";
font-size : 0.423cm; font-size : 0.423cm;
font-weight : 900; font-weight : 900;
} }
//***************************** //*****************************
// * TABLE // * TABLE
// *****************************/ // *****************************/
table{ table {
.useSansSerif(); .useSansSerif();
width : 100%; width : 100%;
margin-bottom : 1em; margin-bottom : 1em;
font-size : 10pt; font-size : 10pt;
thead{ thead {
display: table-row-group; display : table-row-group;
font-weight : 800; font-weight : 800;
th{ th {
vertical-align : bottom;
padding-bottom : 0.3em;
padding-right : 0.1em; padding-right : 0.1em;
padding-bottom : 0.3em;
padding-left : 0.1em; padding-left : 0.1em;
vertical-align : bottom;
} }
} }
tbody{ tbody {
tr{ tr {
td{ td { padding : 0.3em 0.1em; }
padding : 0.3em 0.1em; &:nth-child(odd) { background-color : @noteGreen; }
}
&:nth-child(odd){
background-color : @noteGreen;
}
} }
} }
} }
//***************************** //*****************************
// * NOTE // * NOTE
// *****************************/ // *****************************/
blockquote{ blockquote {
.useSansSerif(); .useSansSerif();
box-sizing : border-box; box-sizing : border-box;
margin-bottom : 1em;
padding : 5px 10px; padding : 5px 10px;
margin-bottom : 1em;
background-color : @noteGreen; background-color : @noteGreen;
border-style : solid; border-style : solid;
border-width : 11px; border-width : 11px;
border-image : @noteBorderImage 11; border-image : @noteBorderImage 11;
border-image-outset : 9px 0px; border-image-outset : 9px 0px;
box-shadow : 1px 4px 14px #888; box-shadow : 1px 4px 14px #888888;
p, ul{ p, ul {
font-size : 0.352cm; font-size : 0.352cm;
line-height : 1.083em; line-height : 1.083em;
} }
} }
//If a note starts a column, give it space at the top to render border //If a note starts a column, give it space at the top to render border
pre+blockquote, h2+blockquote, h3+blockquote, h4+blockquote, h5+blockquote { pre + blockquote, h2 + blockquote, h3 + blockquote, h4 + blockquote, h5 + blockquote { margin-top : 13px; }
margin-top : 13px;
}
//***************************** //*****************************
// * MONSTER STAT BLOCK // * MONSTER STAT BLOCK
// *****************************/ // *****************************/
hr+blockquote{ hr+blockquote {
position : relative; position : relative;
padding-top : 15px; padding-top : 15px;
background-color : @monsterStatBackground; background-color : @monsterStatBackground;
border-style : solid; border-style : solid;
border-width : 10px; border-width : 10px;
border-image : @monsterBorderImageLegacy 10; border-image : @monsterBorderImageLegacy 10;
h2{ h2 {
margin-top : -8px; margin-top : -8px;
margin-bottom : 0px; margin-bottom : 0px;
&+p{ & + p { padding-bottom : 0px; }
padding-bottom : 0px;
}
} }
h3{ h3 {
font-family : ScalySans; font-family : "ScalySans";
font-weight : 400; font-weight : normal;
border-bottom : 1px solid @headerText; border-bottom : 1px solid @headerText;
} }
hr+ul{ hr + ul { color : @headerText; }
color : @headerText; ul {
}
ul{
.useSansSerif(); .useSansSerif();
padding-left : 1em; padding-left : 1em;
font-size : 0.352cm; font-size : 0.352cm;
} }
// Monster Ability table // Monster Ability table
hr+table{ hr + table {
margin : 0; margin : 0;
background-color : transparent; background-color : transparent;
border-style : none; border-style : none;
border-image : none; border-image : none;
tbody{ tbody {
tr:nth-child(odd), tr:nth-child(even){ tr:nth-child(odd), tr:nth-child(even) { background-color : transparent; }
background-color : transparent;
}
} }
} }
table{ table { color : @headerText; }
color : @headerText; p + p {
}
p+p{
margin-top : 0em;
padding-bottom : 0.5em; padding-bottom : 0.5em;
margin-top : 0em;
text-indent : 0em; text-indent : 0em;
} }
//Triangle dividers //Triangle dividers
hr{ hr {
visibility : visible; visibility : visible;
height : 6px; height : 6px;
margin : 4px 0px; margin : 4px 0px;
@@ -265,100 +237,90 @@ body {
} }
} }
//Full Width //Full Width
hr+hr+blockquote{ hr + hr + blockquote {
.useColumns(0.96); .useColumns(0.96);
column-fill : balance; column-fill : balance;
} }
//***************************** //*****************************
// * FOOTER // * FOOTER
// *****************************/ // *****************************/
&:after{ &:after {
content : "";
position : absolute; position : absolute;
bottom : 0px; bottom : 0px;
left : 0px; left : 0px;
z-index : 100; z-index : 100;
height : 50px;
width : 100%; width : 100%;
height : 50px;
content : '';
background-image : @footerAccentImage; background-image : @footerAccentImage;
background-size : cover; background-size : cover;
} }
&:nth-child(even){ &:nth-child(even) {
&:after{ &::after { transform : scaleX(-1); }
transform : scaleX(-1); .pageNumber { left : 2px; }
} .footnote {
.pageNumber{
left : 2px;
}
.footnote{
left : 80px; left : 80px;
text-align : left; text-align : left;
} }
} }
.pageNumber{ .pageNumber {
position : absolute; position : absolute;
right : 2px; right : 2px;
bottom : 22px; bottom : 22px;
width : 50px; width : 50px;
font-size : 0.9em; font-size : 0.9em;
color : #c9ad6a; color : #C9AD6A;
text-align : center; text-align : center;
&.auto::after { &.auto::after { content : counter(phb-page-numbers); }
content : counter(phb-page-numbers);
}
} }
.footnote{ .footnote {
position : absolute; position : absolute;
right : 80px; right : 80px;
bottom : 32px; bottom : 32px;
z-index : 150; z-index : 150;
width : 200px; width : 200px;
font-size : 0.8em; font-size : 0.8em;
color : #c9ad6a; color : #C9AD6A;
text-align : right; text-align : right;
} }
//***************************** //*****************************
// * EXTRAS // * EXTRAS
// *****************************/ // *****************************/
hr{ hr {
visibility : hidden; visibility : hidden;
margin : 0px; margin : 0px;
} }
//Modified unorder list, used in spells //Modified unorder list, used in spells
hr+ul{ hr + ul {
margin-bottom : 0.5em;
padding-left : 1em; padding-left : 1em;
margin-bottom : 0.5em;
text-indent : -1em; text-indent : -1em;
list-style-type : none; list-style-type : none;
} }
//Column Break //Column Break
pre, code{ pre, code {
visibility : hidden; visibility : hidden;
-webkit-column-break-after : always; -webkit-column-break-after : always;
break-after : always; break-after : always;
-moz-column-break-after : always; -moz-column-break-after : always;
} }
//Avoid breaking up //Avoid breaking up
p,blockquote,table{ p,blockquote,table {
z-index : 15; z-index : 15;
-webkit-column-break-inside : avoid; -webkit-column-break-inside : avoid;
page-break-inside : avoid; page-break-inside : avoid;
break-inside : avoid; break-inside : avoid;
} }
//Better spacing for spell blocks //Better spacing for spell blocks
h4+p+hr+ul{ h4 + p + hr + ul { margin-top : -0.5em; }
margin-top : -0.5em
}
//Text indent right after table //Text indent right after table
table+p{ table + p { text-indent : 1em; }
text-indent : 1em;
}
// Nested lists // Nested lists
ul ul,ol ol,ul ol,ol ul{ ul ul,ol ol,ul ol,ol ul {
margin-bottom : 0px; margin-bottom : 0px;
margin-left : 1.5em; margin-left : 1.5em;
} }
li{ li {
-webkit-column-break-inside : avoid; -webkit-column-break-inside : avoid;
page-break-inside : avoid; page-break-inside : avoid;
break-inside : avoid; break-inside : avoid;
@@ -367,89 +329,81 @@ body {
//***************************** //*****************************
// * SPELL LIST // * SPELL LIST
// *****************************/ // *****************************/
.phb .spellList{ .phb .spellList {
.useSansSerif(); .useSansSerif();
column-count : 4; column-count : 4;
column-span : all;
-webkit-column-span : all; -webkit-column-span : all;
-moz-column-span : all; -moz-column-span : all;
ul+h5{ column-span : all;
margin-top : 15px; ul + h5 { margin-top : 15px; }
} p, ul {
p, ul{
font-size : 0.352cm; font-size : 0.352cm;
line-height : 1.263em; line-height : 1.263em;
} }
ul{ ul {
margin-bottom : 0.5em;
padding-left : 1em; padding-left : 1em;
margin-bottom : 0.5em;
text-indent : -1em; text-indent : -1em;
list-style-type : none; list-style-type : none;
break-inside : auto;
-webkit-column-break-inside : auto; -webkit-column-break-inside : auto;
page-break-inside : auto; page-break-inside : auto;
break-inside : auto;
} }
} }
//***************************** //*****************************
// * WIDE // * WIDE
// *****************************/ // *****************************/
.phb .wide{ .phb .wide {
column-span : all;
-webkit-column-span : all; -webkit-column-span : all;
-moz-column-span : all; -moz-column-span : all;
column-span : all;
} }
//***************************** //*****************************
// * CLASS TABLE // * CLASS TABLE
// *****************************/ // *****************************/
.phb .classTable{ .phb .classTable {
margin-top : 25px; margin-top : 25px;
margin-bottom : 40px; margin-bottom : 40px;
border-collapse : separate; border-collapse : separate;
background-color : white; background-color : white;
border : initial; border : initial;
border-style : solid; border-style : solid;
border-image-source : @frameBorderImage;
border-image-slice : 150 200 150 200;
border-image-width : 47px;
border-image-outset : 25px 17px; border-image-outset : 25px 17px;
border-image-repeat : stretch; border-image-repeat : stretch;
border-image-slice : 150 200 150 200; h5 { margin-bottom : 10px; }
border-image-source : @frameBorderImage;
border-image-width : 47px;
h5{
margin-bottom : 10px;
}
} }
//************************************ //************************************
// * DESCRIPTIVE TEXT BOX // * DESCRIPTIVE TEXT BOX
// ************************************/ // ************************************/
.phb .descriptive{ .phb .descriptive {
margin-bottom : 1em; margin-bottom : 1em;
background-color : #faf7ea; font-family : "ScalySans";
font-family : ScalySans; background-color : #FAF7EA;
border-style : solid; border-style : solid;
border-width : 7px; border-width : 7px;
border-image : @descriptiveBoxImage 12 stretch; border-image : @descriptiveBoxImage 12 stretch;
border-image-outset : 4px; border-image-outset : 4px;
box-shadow : 0px 0px 6px #faf7ea; box-shadow : 0px 0px 6px #FAF7EA;
p{ p {
display : block; display : block;
padding-bottom : 0px; padding-bottom : 0px;
line-height : 1.47em; line-height : 1.47em;
} }
p + p { p + p { padding-top : 0.8em; }
padding-top : .8em;
}
em { em {
font-family : ScalySans; font-family : "ScalySans";
font-style : italic; font-style : italic;
} }
strong { strong {
font-family : ScalySans; font-family : "ScalySans";
font-weight : 800; font-weight : 800;
letter-spacing : -0.02em; letter-spacing : -0.02em;
} }
} }
.phb pre+.descriptive{ .phb pre + .descriptive { margin-top : 8px; }
margin-top : 8px;
}
//***************************** //*****************************
// * ARTIST CREDIT BLOCK // * ARTIST CREDIT BLOCK
@@ -457,47 +411,41 @@ body {
.phb { .phb {
.artist { .artist {
position : absolute; position : absolute;
text-align : center; font-family : "WalterTurncoat";
font-family : WalterTurncoat;
font-size : 0.27cm; font-size : 0.27cm;
color : @captionText; color : @captionText;
text-align : center;
p, p + p { p, p + p {
margin : unset; margin : unset;
text-indent : unset;
line-height : 0.941em; line-height : 0.941em;
text-indent : unset;
} }
h5 { h5 {
font-family : "WalterTurncoat";
font-size : 1.3em; font-size : 1.3em;
font-family : WalterTurncoat;
} }
a{ a {
color : inherit; color : inherit;
text-decoration : unset; text-decoration : unset;
&:hover { &:hover { text-decoration : underline; }
text-decoration : underline;
}
} }
} }
} }
//***************************** //*****************************
// * TABLE OF CONTENTS // * TABLE OF CONTENTS
// *****************************/ // *****************************/
.phb .toc{ .phb .toc {
-webkit-column-break-inside : avoid; -webkit-column-break-inside : avoid;
page-break-inside : avoid; page-break-inside : avoid;
break-inside : avoid; break-inside : avoid;
a{ a {
color : black; color : black;
text-decoration : none; text-decoration : none;
&:hover{ &:hover { text-decoration : underline; }
text-decoration : underline;
}
} }
ul{ ul {
padding-left : 0; padding-left : 0;
list-style-type : none; list-style-type : none;
} }
&>ul>li{ & > ul > li { margin-bottom : 10px; }
margin-bottom : 10px;
}
} }

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
module.exports = [ module.exports = [
]; ];

View File

@@ -7,37 +7,29 @@
} }
.page { .page {
background-image : url(/assets/DMG_background.png); background-image : url("/assets/DMG_background.png");
background-size : cover; background-size : cover;
/*TABLES WITHIN NOTES*/ /* TABLES WITHIN NOTES */
.note table tbody tr:nth-child(odd) { .note table tbody tr:nth-child(odd) { background : #FFFFFF; }
background:#fff;
}
/*DROP CAP*/ /* DROP CAP */
h1 + p::first-letter { h1 + p::first-letter {
background-image: unset; color : black;
color:black; background-image : unset;
} }
.quote p:first-child::first-line { .quote p:first-child::first-line { all : unset; }
all: unset;
&::after {
height : 58px;
background-image : url("/assets/DMG_footerAccent.png");
} }
&:after { .footnote { bottom : 40px; }
background-image : url(/assets/DMG_footerAccent.png);
height: 58px;
}
.footnote {
bottom : 40px;
}
} }
.page:has(.partCover) { .page:has(.partCover) {
.partCover { .partCover { background-image : @partCoverHeaderDMG; }
background-image: @partCoverHeaderDMG;
}
} }

View File

@@ -148,7 +148,7 @@ const genAction = function(){
'Turnbuckle Roll' 'Turnbuckle Roll'
]); ]);
return `***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) `; return `***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5 ft., one target. *Hit:* 5 (1d6 + 2) `;
}; };
@@ -161,8 +161,8 @@ module.exports = {
*${getType()}, ${getAlignment()}* *${getType()}, ${getAlignment()}*
___ ___
**Armor Class** :: ${_.random(10, 20)} (chain mail, shield) **Armor Class** :: ${_.random(10, 20)} (chain mail, shield)
**Hit Points** :: ${_.random(1, 150)}(1d4 + 5) **Hit Points** :: ${_.random(1, 150)} (1d4 + 5)
**Speed** :: ${_.random(0, 50)}ft. **Speed** :: ${_.random(0, 50)} ft.
___ ___
| STR | DEX | CON | INT | WIS | CHA | | STR | DEX | CON | INT | WIS | CHA |
|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| |:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|

View File

@@ -17,7 +17,6 @@
.useSansSerif() { .useSansSerif() {
font-family : 'ScalySansRemake'; font-family : 'ScalySansRemake';
font-size : 0.318cm; font-size : 0.318cm;
line-height : 1.2em;
p,dl,ul,ol { line-height : 1.2em; } p,dl,ul,ol { line-height : 1.2em; }
ul, ol { padding-left : 1em; } ul, ol { padding-left : 1em; }
em { font-style : italic; } em { font-style : italic; }
@@ -306,12 +305,12 @@
margin-left : -0.16cm; margin-left : -0.16cm;
background-color : var(--HB_Color_MonsterStatBackground); background-color : var(--HB_Color_MonsterStatBackground);
background-image : @monsterBlockBackground; background-image : @monsterBlockBackground;
background-blend-mode : overlay;
border-style : solid; border-style : solid;
border-width : 7px 6px; border-width : 7px 6px;
border-image : @monsterBorderImage 14 round; border-image : @monsterBorderImage 14 round;
border-image-outset : 0px 2px; border-image-outset : 0px 2px;
box-shadow : 1px 4px 14px #888888; box-shadow : 1px 4px 14px #888888;
background-blend-mode : overlay;
} }
position : relative; position : relative;
@@ -336,9 +335,9 @@
//Triangle dividers //Triangle dividers
hr { hr {
visibility : visible;
height : 6px; height : 6px;
margin : 0.12cm 0cm; margin : 0.12cm 0cm;
visibility : visible;
background-image : @redTriangleImage; background-image : @redTriangleImage;
background-size : 100% 100%; background-size : 100% 100%;
border : none; border : none;
@@ -356,8 +355,8 @@
} }
.bonus { .bonus {
float: right; float : right;
padding-right: 0.5em; padding-right : 0.5em;
} }
// Monster Ability table // Monster Ability table
@@ -457,8 +456,8 @@
// * EXTRAS // * EXTRAS
// *****************************/ // *****************************/
hr { hr {
margin : 0px;
visibility : hidden; visibility : hidden;
margin : 0px;
} }
//Text indent right after table //Text indent right after table
table + p { text-indent : 1em; } table + p { text-indent : 1em; }
@@ -526,10 +525,10 @@
content : ''; content : '';
background-image : @classTableDecoration, background-image : @classTableDecoration,
@classTableDecoration; @classTableDecoration;
filter : drop-shadow(0px 0px 1px #C8C5C080);
background-repeat : no-repeat, no-repeat; background-repeat : no-repeat, no-repeat;
background-position : top, bottom; background-position : top, bottom;
background-size : contain, contain; background-size : contain, contain;
filter : drop-shadow(0px 0px 1px #C8C5C080);
transform : translateY(-50%) translateX(-50%); transform : translateY(-50%) translateX(-50%);
} }
&.decoration.wide::before { &.decoration.wide::before {
@@ -546,39 +545,40 @@
columns : 1; columns : 1;
text-align : center; text-align : center;
&::after { display : none; } &::after { display : none; }
.frontCover { position : absolute; }
h1 { h1 {
margin-top : 1.2cm; margin-top : 1.55cm;
margin-bottom : 0; margin-bottom : 0;
font-family : 'NodestoCapsCondensed'; font-family : 'NodestoCapsCondensed';
font-size : 2.245cm; font-size : 2.245cm;
font-weight : normal; font-weight : normal;
line-height : 1.9cm; line-height : 1.9cm;
color : white; color : white;
text-shadow : unset; text-transform : uppercase;
text-transform : uppercase; text-shadow : unset;
-webkit-text-stroke: 0.2cm black; -webkit-text-stroke : 0.2cm black;
paint-order:stroke; paint-order : stroke;
} }
h2 { h2 {
font-family : 'NodestoCapsCondensed'; font-family : 'NodestoCapsCondensed';
font-size : 0.85cm; font-size : 0.85cm;
font-weight : normal; font-weight : normal;
color : white; color : white;
letter-spacing : 0.1cm; letter-spacing : 0.1cm;
-webkit-text-stroke: 0.14cm black; -webkit-text-stroke : 0.14cm black;
paint-order:stroke; paint-order : stroke;
} }
hr { hr {
position : relative; position : relative;
display : block; display : block;
visibility : visible;
width : 12cm; width : 12cm;
height : 0.5cm; height : 0.5cm;
margin : auto; margin : auto;
visibility : visible;
background-image : @horizontalRule; background-image : @horizontalRule;
filter : drop-shadow(0 0 3px black);
background-size : 100% 100%; background-size : 100% 100%;
border : none; border : none;
filter : drop-shadow(0 0 3px black);
} }
.banner { .banner {
position : absolute; position : absolute;
@@ -601,19 +601,19 @@
filter : drop-shadow(2px 2px 2px black); filter : drop-shadow(2px 2px 2px black);
} }
.footnote { .footnote {
position : absolute; position : absolute;
right : 0; right : 0;
bottom : 1.3cm; bottom : 1.3cm;
left : 0; left : 0;
width : 70%; width : 70%;
margin-right : auto; margin-right : auto;
margin-left : auto; margin-left : auto;
font-family : 'Overpass'; font-family : 'Overpass';
font-size : 0.496cm; font-size : 0.496cm;
color : white; color : white;
text-align : center; text-align : center;
-webkit-text-stroke: 0.1cm black; -webkit-text-stroke : 0.1cm black;
paint-order:stroke; paint-order : stroke;
} }
.logo { .logo {
position : absolute; position : absolute;
@@ -621,10 +621,7 @@
right : 0; right : 0;
left : 0; left : 0;
filter : drop-shadow(0 0 0.075cm black); filter : drop-shadow(0 0 0.075cm black);
img { img { height : 2cm; }
width : 100%;
height : 2cm;
}
} }
} }
// ***************************** // *****************************
@@ -634,8 +631,9 @@
columns : 1; columns : 1;
text-align : center; text-align : center;
&::after { display : none; } &::after { display : none; }
.insideCover { position : absolute; }
h1 { h1 {
margin-top : 1.2cm; margin-top : 1.55cm;
margin-bottom : 0; margin-bottom : 0;
font-family : 'NodestoCapsCondensed'; font-family : 'NodestoCapsCondensed';
font-size : 2.1cm; font-size : 2.1cm;
@@ -652,10 +650,10 @@
hr { hr {
position : relative; position : relative;
display : block; display : block;
visibility : visible;
width : 12cm; width : 12cm;
height : 0.5cm; height : 0.5cm;
margin : auto; margin : auto;
visibility : visible;
background-image : @horizontalRule; background-image : @horizontalRule;
background-size : 100% 100%; background-size : 100% 100%;
border : none; border : none;
@@ -666,26 +664,23 @@
bottom : 1cm; bottom : 1cm;
left : 0; left : 0;
height : 2cm; height : 2cm;
img { img { height : 2cm; }
width : 100%;
height : 2cm;
}
} }
} }
// ***************************** // *****************************
// * BACK COVER // * BACK COVER
// *****************************/ // *****************************/
.page:has(.backCover) { .page:has(.backCover) {
padding : 2.25cm 1.3cm 2cm 1.3cm; padding : 2.25cm 1.3cm 2cm 1.3cm;
color : #FFFFFF; line-height : 1.4em;
columns : 1; color : #FFFFFF;
columns : 1;
&::after { display : none; } &::after { display : none; }
.columnWrapper { width : 7.6cm; } .columnWrapper { width : 7.6cm; }
.backCover { .backCover {
position : absolute; position : absolute;
inset : 0; inset : 0;
z-index : -1; z-index : -1;
width : 11cm;
background-image : @backCover; background-image : @backCover;
background-repeat : no-repeat; background-repeat : no-repeat;
background-size : contain; background-size : contain;
@@ -708,12 +703,12 @@
height : 100%; height : 100%;
} }
hr { hr {
visibility : visible;
width : 4.5cm; width : 4.5cm;
height : 0.53cm; height : 0.53cm;
margin-top : 1.1cm; margin-top : 1.1cm;
margin-right : auto; margin-right : auto;
margin-left : auto; margin-left : auto;
visibility : visible;
background-image : @horizontalRule; background-image : @horizontalRule;
background-size : 100% 100%; background-size : 100% 100%;
border : none; border : none;
@@ -737,7 +732,6 @@
img { img {
position : relative; position : relative;
z-index : 0; z-index : 0;
width : 100%;
height : 1.5cm; height : 1.5cm;
} }
p { p {
@@ -801,11 +795,9 @@
.monster { --TOC: exclude; } .monster { --TOC: exclude; }
.page:has(.partCover) { .page:has(.partCover) {
--TOC: exclude; --TOC : exclude;
& h1 { & h1 { --TOC : include; }
--TOC: include; }
}
}
.page { .page {
&:has(.toc)::after { display : none; } &:has(.toc)::after { display : none; }
@@ -871,9 +863,7 @@
.useColumns(0.96, @fillMode: balance); .useColumns(0.96, @fillMode: balance);
} }
} }
.toc.wide li { .toc.wide li { break-inside : auto; }
break-inside: auto;
}
} }
// ***************************** // *****************************
@@ -898,9 +888,7 @@
.page h1 + * { margin-top : 0; } .page h1 + * { margin-top : 0; }
.page .descriptive.wide + * { .page .descriptive.wide + * { margin-top : 0; }
margin-top: 0;
}
//***************************** //*****************************
// * RUNE TABLE // * RUNE TABLE
@@ -915,8 +903,8 @@
width : 1.3cm; width : 1.3cm;
height : 1.3cm; height : 1.3cm;
font-weight : normal; font-weight : normal;
text-transform : uppercase;
vertical-align : middle; vertical-align : middle;
text-transform : uppercase;
outline : 1px solid #000000; outline : 1px solid #000000;
} }
th { th {

View File

@@ -486,6 +486,15 @@ module.exports = [
icon : 'fas fa-print', icon : 'fas fa-print',
view : 'style', view : 'style',
snippets : [ snippets : [
{
name : 'US Letter Page Size',
icon : 'far fa-file',
gen : dedent`/* US Letter Page Size */
.page {
width : 215.9mm; /* 8.5in */
height : 279.4mm; /* 11in */
}\n\n`,
},
{ {
name : 'A3 Page Size', name : 'A3 Page Size',
icon : 'far fa-file', icon : 'far fa-file',

View File

@@ -22,9 +22,9 @@ body { counter-reset : page-numbers 0; }
// *****************************/ // *****************************/
.page { .page {
.block { .block {
break-inside : avoid;
display : inline-block; display : inline-block;
width : 100%; width : 100%;
break-inside : avoid;
img { z-index : 0; } img { z-index : 0; }
} }
.inline-block { .inline-block {
@@ -59,8 +59,8 @@ body { counter-reset : page-numbers 0; }
content-visibility : auto; content-visibility : auto;
contain-intrinsic-size : auto none; contain-intrinsic-size : auto none;
} }
//***************************** //*****************************
// * BASE // * BASE
// *****************************/ // *****************************/
.page { .page {
p { p {
@@ -121,7 +121,7 @@ body { counter-reset : page-numbers 0; }
// * CODE BLOCKS // * CODE BLOCKS
// ************************************/ // ************************************/
code { code {
font-family : 'Courier New', "Courier", monospace; font-family : 'Courier New', 'Courier', monospace;
overflow-wrap : break-word; overflow-wrap : break-word;
white-space : pre-wrap; white-space : pre-wrap;
} }
@@ -134,10 +134,10 @@ body { counter-reset : page-numbers 0; }
// * EXTRAS // * EXTRAS
// *****************************/ // *****************************/
.columnSplit { .columnSplit {
margin-top : 0;
visibility : hidden; visibility : hidden;
-webkit-column-break-after : always; margin-top : 0;
break-after : always; break-after : always;
-webkit-column-break-after : always;
-moz-column-break-after : always; -moz-column-break-after : always;
& + * { margin-top : 0; } & + * { margin-top : 0; }
} }
@@ -200,11 +200,11 @@ body { counter-reset : page-numbers 0; }
background-color : var(--HB_Color_WatercolorStain); /* default color */ background-color : var(--HB_Color_WatercolorStain); /* default color */
background-size : cover; background-size : cover;
-webkit-mask-image : var(--wc); -webkit-mask-image : var(--wc);
-webkit-mask-size : contain;
-webkit-mask-repeat : no-repeat;
mask-image : var(--wc); mask-image : var(--wc);
mask-size : contain; -webkit-mask-repeat : no-repeat;
mask-repeat : no-repeat; mask-repeat : no-repeat;
-webkit-mask-size : contain;
mask-size : contain;
--wc : @watercolor1; /* default image */ --wc : @watercolor1; /* default image */
} }
@@ -232,15 +232,15 @@ body { counter-reset : page-numbers 0; }
height : 200%; height : 200%;
background-image : var(--checkerboard); background-image : var(--checkerboard);
background-size : 20px; 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-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
-webkit-mask-position : 50% calc(50% - var(--offset));
mask-image : var(--wc); mask-image : var(--wc);
-webkit-mask-repeat : repeat-x;
mask-repeat : repeat-x; mask-repeat : repeat-x;
mask-size : 50%; -webkit-mask-position : 50% calc(50% - var(--offset));
mask-position : 50% calc(50% - var(--offset)); mask-position : 50% calc(50% - var(--offset));
-webkit-mask-size : 50%; //Scale only X to fit page width, leave height at aspect ratio, designed to hang off the edge
mask-size : 50%;
transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
--rotation : 0; --rotation : 0;
--revealer : none; --revealer : none;
--checkerboard : none; --checkerboard : none;
@@ -277,19 +277,19 @@ body { counter-reset : page-numbers 0; }
} }
&.revealImage { &.revealImage {
--revealer : linear-gradient(0deg, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.2)); --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 --checkerboard : url('/assets/waterColorMasks/missingImage.png'); //shows any masked regions not filled by image
} }
} }
.imageMaskEdge { .imageMaskEdge {
&1 { --wc : url("/assets/waterColorMasks/edge/0001.webp"); } &1 { --wc : url('/assets/waterColorMasks/edge/0001.webp'); }
&2 { --wc : url("/assets/waterColorMasks/edge/0002.webp"); } &2 { --wc : url('/assets/waterColorMasks/edge/0002.webp'); }
&3 { --wc : url("/assets/waterColorMasks/edge/0003.webp"); } &3 { --wc : url('/assets/waterColorMasks/edge/0003.webp'); }
&4 { --wc : url("/assets/waterColorMasks/edge/0004.webp"); } &4 { --wc : url('/assets/waterColorMasks/edge/0004.webp'); }
&5 { --wc : url("/assets/waterColorMasks/edge/0005.webp"); } &5 { --wc : url('/assets/waterColorMasks/edge/0005.webp'); }
&6 { --wc : url("/assets/waterColorMasks/edge/0006.webp"); } &6 { --wc : url('/assets/waterColorMasks/edge/0006.webp'); }
&7 { --wc : url("/assets/waterColorMasks/edge/0007.webp"); } &7 { --wc : url('/assets/waterColorMasks/edge/0007.webp'); }
&8 { --wc : url("/assets/waterColorMasks/edge/0008.webp"); } &8 { --wc : url('/assets/waterColorMasks/edge/0008.webp'); }
} }
[class*='imageMaskCenter'] { [class*='imageMaskCenter'] {
@@ -297,15 +297,15 @@ body { counter-reset : page-numbers 0; }
left : calc(var(--offsetX)); left : calc(var(--offsetX));
width : 100%; width : 100%;
height : 100%; height : 100%;
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
-webkit-mask-image : var(--wc), var(--revealer); -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-image : var(--wc), var(--revealer);
-webkit-mask-repeat : no-repeat;
mask-repeat : no-repeat; mask-repeat : no-repeat;
mask-size : 100% 100%; //Scale both dimensions to fit page size -webkit-mask-position : 0% 0%;
mask-position : 50% 50%; mask-position : 50% 50%;
-webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
mask-size : 100% 100%; //Scale both dimensions to fit page size
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
& > p:has(img) { & > p:has(img) {
position : absolute; position : absolute;
@@ -322,23 +322,23 @@ body { counter-reset : page-numbers 0; }
} }
.imageMaskCenter { .imageMaskCenter {
&1 { --wc : url("/assets/waterColorMasks/center/0001.webp"); } &1 { --wc : url('/assets/waterColorMasks/center/0001.webp'); }
&2 { --wc : url("/assets/waterColorMasks/center/0002.webp"); } &2 { --wc : url('/assets/waterColorMasks/center/0002.webp'); }
&3 { --wc : url("/assets/waterColorMasks/center/0003.webp"); } &3 { --wc : url('/assets/waterColorMasks/center/0003.webp'); }
&4 { --wc : url("/assets/waterColorMasks/center/0004.webp"); } &4 { --wc : url('/assets/waterColorMasks/center/0004.webp'); }
&5 { --wc : url("/assets/waterColorMasks/center/0005.webp"); } &5 { --wc : url('/assets/waterColorMasks/center/0005.webp'); }
&6 { --wc : url("/assets/waterColorMasks/center/0006.webp"); } &6 { --wc : url('/assets/waterColorMasks/center/0006.webp'); }
&7 { --wc : url("/assets/waterColorMasks/center/0007.webp"); } &7 { --wc : url('/assets/waterColorMasks/center/0007.webp'); }
&8 { --wc : url("/assets/waterColorMasks/center/0008.webp"); } &8 { --wc : url('/assets/waterColorMasks/center/0008.webp'); }
&9 { --wc : url("/assets/waterColorMasks/center/0009.webp"); } &9 { --wc : url('/assets/waterColorMasks/center/0009.webp'); }
&10 { --wc : url("/assets/waterColorMasks/center/0010.webp"); } &10 { --wc : url('/assets/waterColorMasks/center/0010.webp'); }
&11 { --wc : url("/assets/waterColorMasks/center/0011.webp"); } &11 { --wc : url('/assets/waterColorMasks/center/0011.webp'); }
&12 { --wc : url("/assets/waterColorMasks/center/0012.webp"); } &12 { --wc : url('/assets/waterColorMasks/center/0012.webp'); }
&13 { --wc : url("/assets/waterColorMasks/center/0013.webp"); } &13 { --wc : url('/assets/waterColorMasks/center/0013.webp'); }
&14 { --wc : url("/assets/waterColorMasks/center/0014.webp"); } &14 { --wc : url('/assets/waterColorMasks/center/0014.webp'); }
&15 { --wc : url("/assets/waterColorMasks/center/0015.webp"); } &15 { --wc : url('/assets/waterColorMasks/center/0015.webp'); }
&16 { --wc : url("/assets/waterColorMasks/center/0016.webp"); } &16 { --wc : url('/assets/waterColorMasks/center/0016.webp'); }
&special { --wc : url("/assets/waterColorMasks/center/special.webp"); } &special { --wc : url('/assets/waterColorMasks/center/special.webp'); }
} }
@@ -347,15 +347,15 @@ body { counter-reset : page-numbers 0; }
left : calc(-50% + var(--offsetX)); left : calc(-50% + var(--offsetX));
width : 200%; width : 200%;
height : 200%; height : 200%;
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
-webkit-mask-image : var(--wc), var(--revealer); -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 : 50% 50%;
mask-image : var(--wc), var(--revealer); mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : no-repeat;
mask-repeat : no-repeat; mask-repeat : no-repeat;
mask-size : 100% 100%; //Scale both dimensions to fit page size -webkit-mask-position : 50% 50%;
mask-position : 50% 50%; mask-position : 50% 50%;
-webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
mask-size : 100% 100%; //Scale both dimensions to fit page size
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
& > p:has(img) { & > p:has(img) {
bottom : 25%; bottom : 25%;
left : 25%; left : 25%;
@@ -368,43 +368,43 @@ body { counter-reset : page-numbers 0; }
} }
} }
.imageMaskCorner { .imageMaskCorner {
&1 { --wc : url("/assets/waterColorMasks/corner/0001.webp"); } &1 { --wc : url('/assets/waterColorMasks/corner/0001.webp'); }
&2 { --wc : url("/assets/waterColorMasks/corner/0002.webp"); } &2 { --wc : url('/assets/waterColorMasks/corner/0002.webp'); }
&3 { --wc : url("/assets/waterColorMasks/corner/0003.webp"); } &3 { --wc : url('/assets/waterColorMasks/corner/0003.webp'); }
&4 { --wc : url("/assets/waterColorMasks/corner/0004.webp"); } &4 { --wc : url('/assets/waterColorMasks/corner/0004.webp'); }
&5 { --wc : url("/assets/waterColorMasks/corner/0005.webp"); } &5 { --wc : url('/assets/waterColorMasks/corner/0005.webp'); }
&6 { --wc : url("/assets/waterColorMasks/corner/0006.webp"); } &6 { --wc : url('/assets/waterColorMasks/corner/0006.webp'); }
&7 { --wc : url("/assets/waterColorMasks/corner/0007.webp"); } &7 { --wc : url('/assets/waterColorMasks/corner/0007.webp'); }
&8 { --wc : url("/assets/waterColorMasks/corner/0008.webp"); } &8 { --wc : url('/assets/waterColorMasks/corner/0008.webp'); }
&9 { --wc : url("/assets/waterColorMasks/corner/0009.webp"); } &9 { --wc : url('/assets/waterColorMasks/corner/0009.webp'); }
&10 { --wc : url("/assets/waterColorMasks/corner/0010.webp"); } &10 { --wc : url('/assets/waterColorMasks/corner/0010.webp'); }
&11 { --wc : url("/assets/waterColorMasks/corner/0011.webp"); } &11 { --wc : url('/assets/waterColorMasks/corner/0011.webp'); }
&12 { --wc : url("/assets/waterColorMasks/corner/0012.webp"); } &12 { --wc : url('/assets/waterColorMasks/corner/0012.webp'); }
&13 { --wc : url("/assets/waterColorMasks/corner/0013.webp"); } &13 { --wc : url('/assets/waterColorMasks/corner/0013.webp'); }
&14 { --wc : url("/assets/waterColorMasks/corner/0014.webp"); } &14 { --wc : url('/assets/waterColorMasks/corner/0014.webp'); }
&15 { --wc : url("/assets/waterColorMasks/corner/0015.webp"); } &15 { --wc : url('/assets/waterColorMasks/corner/0015.webp'); }
&16 { --wc : url("/assets/waterColorMasks/corner/0016.webp"); } &16 { --wc : url('/assets/waterColorMasks/corner/0016.webp'); }
&17 { --wc : url("/assets/waterColorMasks/corner/0017.webp"); } &17 { --wc : url('/assets/waterColorMasks/corner/0017.webp'); }
&18 { --wc : url("/assets/waterColorMasks/corner/0018.webp"); } &18 { --wc : url('/assets/waterColorMasks/corner/0018.webp'); }
&19 { --wc : url("/assets/waterColorMasks/corner/0019.webp"); } &19 { --wc : url('/assets/waterColorMasks/corner/0019.webp'); }
&20 { --wc : url("/assets/waterColorMasks/corner/0020.webp"); } &20 { --wc : url('/assets/waterColorMasks/corner/0020.webp'); }
&21 { --wc : url("/assets/waterColorMasks/corner/0021.webp"); } &21 { --wc : url('/assets/waterColorMasks/corner/0021.webp'); }
&22 { --wc : url("/assets/waterColorMasks/corner/0022.webp"); } &22 { --wc : url('/assets/waterColorMasks/corner/0022.webp'); }
&23 { --wc : url("/assets/waterColorMasks/corner/0023.webp"); } &23 { --wc : url('/assets/waterColorMasks/corner/0023.webp'); }
&24 { --wc : url("/assets/waterColorMasks/corner/0024.webp"); } &24 { --wc : url('/assets/waterColorMasks/corner/0024.webp'); }
&25 { --wc : url("/assets/waterColorMasks/corner/0025.webp"); } &25 { --wc : url('/assets/waterColorMasks/corner/0025.webp'); }
&26 { --wc : url("/assets/waterColorMasks/corner/0026.webp"); } &26 { --wc : url('/assets/waterColorMasks/corner/0026.webp'); }
&27 { --wc : url("/assets/waterColorMasks/corner/0027.webp"); } &27 { --wc : url('/assets/waterColorMasks/corner/0027.webp'); }
&28 { --wc : url("/assets/waterColorMasks/corner/0028.webp"); } &28 { --wc : url('/assets/waterColorMasks/corner/0028.webp'); }
&29 { --wc : url("/assets/waterColorMasks/corner/0029.webp"); } &29 { --wc : url('/assets/waterColorMasks/corner/0029.webp'); }
&30 { --wc : url("/assets/waterColorMasks/corner/0030.webp"); } &30 { --wc : url('/assets/waterColorMasks/corner/0030.webp'); }
&31 { --wc : url("/assets/waterColorMasks/corner/0031.webp"); } &31 { --wc : url('/assets/waterColorMasks/corner/0031.webp'); }
&32 { --wc : url("/assets/waterColorMasks/corner/0032.webp"); } &32 { --wc : url('/assets/waterColorMasks/corner/0032.webp'); }
&33 { --wc : url("/assets/waterColorMasks/corner/0033.webp"); } &33 { --wc : url('/assets/waterColorMasks/corner/0033.webp'); }
&34 { --wc : url("/assets/waterColorMasks/corner/0034.webp"); } &34 { --wc : url('/assets/waterColorMasks/corner/0034.webp'); }
&35 { --wc : url("/assets/waterColorMasks/corner/0035.webp"); } &35 { --wc : url('/assets/waterColorMasks/corner/0035.webp'); }
&36 { --wc : url("/assets/waterColorMasks/corner/0036.webp"); } &36 { --wc : url('/assets/waterColorMasks/corner/0036.webp'); }
&37 { --wc : url("/assets/waterColorMasks/corner/0037.webp"); } &37 { --wc : url('/assets/waterColorMasks/corner/0037.webp'); }
} }
} }
@@ -428,17 +428,6 @@ body { counter-reset : page-numbers 0; }
} }
} }
//*****************************
// * BLANK LINE
// *****************************/
.page {
.blank {
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}
//***************************** //*****************************
// * WIDE // * WIDE
// *****************************/ // *****************************/
@@ -449,6 +438,11 @@ body { counter-reset : page-numbers 0; }
margin-bottom : 1em; margin-bottom : 1em;
& + * { margin-top : 0; } & + * { margin-top : 0; }
} }
.blank {
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
} }
//***************************** //*****************************
@@ -473,8 +467,8 @@ body { counter-reset : page-numbers 0; }
height : 1.5cm; height : 1.5cm;
margin : 0 auto; margin : 0 auto;
background-color : black; background-color : black;
-webkit-mask : url("/assets/naturalCritLogoWhite.svg") center / contain no-repeat; -webkit-mask : url('/assets/naturalCritLogoWhite.svg') center / contain no-repeat;
mask : url("/assets/naturalCritLogoWhite.svg") center / contain no-repeat; mask : url('/assets/naturalCritLogoWhite.svg') center / contain no-repeat;
} }
.homebreweryIcon.red { background-color : red; } .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%); } .homebreweryIcon.gold { background-image : linear-gradient(to top left, brown 22.5%, gold 40%, white 60%, gold 67.5%, brown 82.5%); }
@@ -498,13 +492,9 @@ body { counter-reset : page-numbers 0; }
.pageNumber { left : 30px; } .pageNumber { left : 30px; }
} }
.resetCounting { .resetCounting { counter-set : page-numbers 1; }
counter-set : page-numbers 1;
}
&:not(:has(.skipCounting)) { &:not(:has(.skipCounting)) { counter-increment : page-numbers; }
counter-increment : page-numbers;
}
} }

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
module.exports = [ module.exports = [
]; ];

View File

@@ -11,47 +11,35 @@
--HB_Color_WatercolorStain : #BBAD82; // Light brown --HB_Color_WatercolorStain : #BBAD82; // Light brown
} }
.useSansSerif(){ .useSansSerif() {
font-family : PermanentMarker; font-family : "PermanentMarker";
font-size : 0.3cm; font-size : 0.3cm;
line-height : 1.2em; line-height : 1.2em;
color : var(--HB_Color_Text2); color : var(--HB_Color_Text2);
p,dl,ul,ol { p,dl,ul,ol { line-height : 1.2em; }
line-height : 1.2em; ul, ol { padding-left : 1em; }
} em { font-style : italic; }
ul, ol { strong {
padding-left : 1em;
}
em{
font-style : italic;
}
strong{
font-weight : 800;
font-size : 1.1em; font-size : 1.1em;
font-weight : 800;
} }
h5 + * { h5 + * { margin-top : 0.1cm; }
margin-top : 0.1cm;
}
}
.useColumns(@multiplier : 1, @fillMode: balance){
column-gap : 0.5cm;
} }
.useColumns(@multiplier : 1, @fillMode: balance) { column-gap : 0.5cm; }
.page{ .page {
background-size : 200% 100%;
background-repeat : no-repeat;
filter : drop-shadow(1px 4px 14px black);
background-image : url(/assets/Journal/Background1.webp);
padding : 2.1cm 1.9cm 1.7cm 3.8cm; padding : 2.1cm 1.9cm 1.7cm 3.8cm;
&:nth-of-type(2n + 1) { background-image : url("/assets/Journal/Background1.webp");
background-position : left; background-repeat : no-repeat;
} background-size : 200% 100%;
filter : drop-shadow(1px 4px 14px black);
&:nth-of-type(2n + 1) { background-position : left; }
&:nth-of-type(2n) { &:nth-of-type(2n) {
background-position : right;
padding : 2.1cm 3.9cm 1.7cm 1.8cm; padding : 2.1cm 3.9cm 1.7cm 1.8cm;
background-position : right;
} }
&:nth-of-type(2) { &:nth-of-type(2) {
background-image : url(/assets/Journal/Background2.webp); //Only first page should show ribbon background-image : url("/assets/Journal/Background2.webp"); //Only first page should show ribbon
} }
& .columnWrapper { & .columnWrapper {
@@ -59,167 +47,137 @@
} }
} }
//***************************** //*****************************
// * BASE // * BASE
// *****************************/ // *****************************/
.page{ .page {
color : var(--HB_Color_Text); font-family : "ReenieBeanie";
font-family : ReenieBeanie;
font-size : 0.53cm; font-size : 0.53cm;
line-height : 0.8em; line-height : 0.8em;
p + * { color : var(--HB_Color_Text);
margin-top : 0.325cm; p + * { margin-top : 0.325cm; }
} p + p { margin-top : 0; }
p + p{ ul { margin-bottom : 0.8em; }
margin-top : 0; ol { margin-bottom : 0.8em; }
} em {
ul{ font-style : unset;
margin-bottom : 0.8em;
}
ol{
margin-bottom : 0.8em;
}
em{
text-decoration : underline; text-decoration : underline;
font-style : unset;
}
del{
text-decoration-style: double;
} }
del { text-decoration-style : double; }
//Indents after p or lists //Indents after p or lists
p+p, ul+p, ol+p{ p + p, ul + p, ol + p { text-indent : 1em; }
text-indent : 1em;
}
//***************************** //*****************************
// * HEADERS // * HEADERS
// *****************************/ // *****************************/
h1,h2,h3,h4,h5{ h1,h2,h3,h4,h5 {
font-family : FrederickaTheGreat; font-family : "FrederickaTheGreat";
font-weight : unset; font-weight : unset;
color : var(--HB_Color_HeaderText); color : var(--HB_Color_HeaderText);
} }
h1{ h1 {
margin-bottom : 0.18cm; //Margin-bottom only because this is WIDE margin-bottom : 0.18cm; //Margin-bottom only because this is WIDE
font-size : 0.89cm; font-size : 0.89cm;
line-height : 1em;
font-variant : small-caps; font-variant : small-caps;
&+p::first-letter{ line-height : 1em;
& + p::first-letter {
float : left; float : left;
font-family : FrederickaTheGreat;
line-height : 1em;
font-size : 1.9em;
padding-left : 40px; //Allow background color to extend into margins
margin-top : -0.3cm;
margin-bottom : -20px;
margin-left : -40px;
margin-right : 0.1em;
padding-top : 0.3em; padding-top : 0.3em;
padding-bottom : 2px; padding-bottom : 2px;
padding-left : 40px; //Allow background color to extend into margins
margin-top : -0.3cm;
margin-right : 0.1em;
margin-bottom : -20px;
margin-left : -40px;
font-family : "FrederickaTheGreat";
font-size : 1.9em;
line-height : 1em;
} }
&+p::first-line{ & + p::first-line { font-variant : small-caps; }
font-variant : small-caps;
}
} }
h2{ h2 {
font-size : 0.62cm; font-size : 0.62cm;
line-height : 0.988em; //Font is misaligned. Shift up slightly line-height : 0.988em; //Font is misaligned. Shift up slightly
} }
h3{ h3 {
margin-left : -0.9em;
font-size : 0.575cm; font-size : 0.575cm;
line-height : 0.995em; //Font is misaligned. Shift up slightly line-height : 0.995em; //Font is misaligned. Shift up slightly
margin-left : -0.9em;
} }
h4{ h4 {
padding-bottom : 5px;
font-size : 0.55cm; font-size : 0.55cm;
line-height : 0.971em; //Font is misaligned. Shift up slightly line-height : 0.971em; //Font is misaligned. Shift up slightly
color : var(--HB_Color_Text); color : var(--HB_Color_Text);
padding-bottom : 5px; transform : rotate(0deg);
transform:rotate(0deg); &:nth-of-type(2n) { transform : rotate(1deg); }
&:nth-of-type(2n) { &:nth-of-type(3n) { transform : rotate(-1.5deg); }
transform:rotate(1deg);
}
&:nth-of-type(3n) {
transform:rotate(-1.5deg);
}
} }
h5{ h5 {
font-family : PermanentMarker; font-family : "PermanentMarker";
font-size : 0.4cm; font-size : 0.4cm;
color : var(--HB_Color_Text2);
font-weight : bold; font-weight : bold;
line-height : 0.951em; //Font is misaligned. Shift up slightly line-height : 0.951em; //Font is misaligned. Shift up slightly
& + * { color : var(--HB_Color_Text2);
margin-top : 0.2cm; & + * { margin-top : 0.2cm; }
}
} }
//***************************** //*****************************
// * TABLE // * TABLE
// *****************************/ // *****************************/
table{ table {
.useSansSerif(); .useSansSerif();
& + * { & + * { margin-top : 0.325cm; }
margin-top : 0.325cm; thead {
} th {
thead{
th{
vertical-align : bottom;
padding : 0.14em 0; padding : 0.14em 0;
vertical-align : bottom;
} }
} }
tbody{ tbody {
tr{ tr {
td{ td { padding : 0.14em 0; }
padding : 0.14em 0; &:nth-child(odd) { background-image : linear-gradient(to left, #41212100, #41212122, #41212100); }
}
&:nth-child(odd){
background-image : linear-gradient(to left, #41212100, #41212122, #41212100);
}
} }
} }
} }
//***************************** //*****************************
// * NOTE // * NOTE
// *****************************/ // *****************************/
.note{ .note {
.useSansSerif(); .useSansSerif();
padding : 0.2cm;
background-image : url("/assets/Journal/HashMarks.png"),
linear-gradient(to bottom right, #FF000000, #A36A4E14, #41212100);
background-repeat : no-repeat;
background-position : center;
background-size : 120% 120%;
border-style : solid; border-style : solid;
border-width : 1px; border-width : 1px;
border-image-source : url(/assets/Journal/Border1.png); border-image-source : url("/assets/Journal/Border1.png");
border-image-slice : 18 18 18 18; border-image-slice : 18 18 18 18;
border-image-width : 6px 6px 6px 6px; border-image-width : 6px 6px 6px 6px;
border-image-outset : 5px 5px 5px 5px; border-image-outset : 5px 5px 5px 5px;
border-image-repeat : stretch stretch; border-image-repeat : stretch stretch;
background-image : url(/assets/Journal/HashMarks.png),
linear-gradient(to bottom right, #ff000000, #a36a4e14, #41212100);
background-size : 120% 120%;
background-repeat : no-repeat;
background-position : center;
padding : 0.2cm;
:where(&) { :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; }
margin-top : 0.45cm; h5 { font-size : 0.375cm; }
} p { padding-bottom : 0px; }
h5 { :last-child { margin-bottom : 0; }
font-size : 0.375cm;
}
p{
padding-bottom : 0px;
}
:last-child {
margin-bottom : 0;
}
} }
//************************************ //************************************
// * DESCRIPTIVE TEXT BOX // * DESCRIPTIVE TEXT BOX
// ************************************/ // ************************************/
* + .descriptive { * + .descriptive { margin-top : 0.6cm; }
margin-top : 0.6cm; .descriptive {
}
.descriptive{
.useSansSerif(); .useSansSerif();
padding : 0.2cm;
background-image : url("/assets/Journal/HashMarks.png"),
linear-gradient(to bottom right, #FF000000, #41212114, #41212100);
background-repeat : no-repeat;
background-position : center;
background-size : 120% 120%;
border-style : solid; border-style : solid;
border-width : 1px; border-width : 1px;
border-image-source : url('/assets/Journal/Border2.png'); border-image-source : url('/assets/Journal/Border2.png');
@@ -227,27 +185,13 @@
border-image-width : 20px; border-image-width : 20px;
border-image-outset : 16px 20px 16px 20px; border-image-outset : 16px 20px 16px 20px;
border-image-repeat : stretch stretch; border-image-repeat : stretch stretch;
background-image : url(/assets/Journal/HashMarks.png),
linear-gradient(to bottom right, #ff000000, #41212114, #41212100);
background-size : 120% 120%;
background-repeat : no-repeat;
background-position : center;
padding : 0.2cm;
:where(&) { :where(&) {
margin-top : 4px; //Prevent top border getting cut off on colbreak margin-top : 4px; //Prevent top border getting cut off on colbreak
} }
& + * { & + * { margin-top : 0.45cm; }
margin-top : 0.45cm; h5 { font-size : 0.375cm; }
} p { padding-bottom : 0px; }
h5 { :last-child { margin-bottom : 0; }
font-size : 0.375cm;
}
p{
padding-bottom : 0px;
}
:last-child {
margin-bottom : 0;
}
} }
//***************************** //*****************************
// * Images Snippets // * Images Snippets
@@ -257,25 +201,23 @@
.artist { .artist {
position : absolute; position : absolute;
width : auto; width : auto;
text-align : center; font-family : "WalterTurncoat";
font-family : WalterTurncoat;
font-size : 0.27cm; font-size : 0.27cm;
color : var(--HB_Color_CaptionText); color : var(--HB_Color_CaptionText);
text-align : center;
p, p + p { p, p + p {
margin : unset; margin : unset;
text-indent : unset;
line-height : 1em; line-height : 1em;
text-indent : unset;
} }
h5 { h5 {
font-family : "WalterTurncoat";
font-size : 1.3em; font-size : 1.3em;
font-family : WalterTurncoat;
} }
a{ a {
color : inherit; color : inherit;
text-decoration : unset; text-decoration : unset;
&:hover { &:hover { text-decoration : underline; }
text-decoration : underline;
}
} }
} }
@@ -285,6 +227,10 @@
.monster { .monster {
.useSansSerif(); .useSansSerif();
&.frame { &.frame {
padding : 0.2cm;
background-image : url('/assets/Journal/HashMarks.png'),
linear-gradient(to bottom right, #FF000000, #A36A4E14, #41212100);
background-size : 100%;
border-style : solid; border-style : solid;
border-width : 7px 6px; border-width : 7px 6px;
border-image-source : url('/assets/Journal/Border3.png'); border-image-source : url('/assets/Journal/Border3.png');
@@ -292,33 +238,29 @@
border-image-width : 15px 20px 15px 20px; border-image-width : 15px 20px 15px 20px;
border-image-outset : 12px 12px 12px 12px; border-image-outset : 12px 12px 12px 12px;
border-image-repeat : stretch round; border-image-repeat : stretch round;
background-image : url('/assets/Journal/HashMarks.png'),
linear-gradient(to bottom right, #ff000000, #a36a4e14, #41212100);
background-blend-mode : screen multiply; background-blend-mode : screen multiply;
background-size : 100%;
padding : 0.2cm;
} }
color: var(--HB_Color_Text);
position : relative; position : relative;
padding : 0px; padding : 0px;
margin-bottom : 0.325cm; margin-bottom : 0.325cm;
color : var(--HB_Color_Text);
//Headers //Headers
h2{ h2 {
margin : 0;
font-size : 0.62cm; font-size : 0.62cm;
line-height : 1em; line-height : 1em;
margin : 0; & + p {
&+p {
margin-bottom : 0; //Monster size and type subtext margin-bottom : 0; //Monster size and type subtext
} }
} }
h3{ h3 {
padding-bottom : 0.05cm;
margin-left : 0; margin-left : 0;
font-variant : small-caps; font-variant : small-caps;
padding-bottom : 0.05cm;
} }
hr{ hr {
visibility : visible; visibility : visible;
height : 6px; height : 6px;
margin : 0.12cm 0cm; margin : 0.12cm 0cm;
@@ -330,24 +272,18 @@
} }
// Monster Ability table // Monster Ability table
hr + table:first-of-type{ hr + table:first-of-type {
margin : 0; margin : 0;
column-span : none; color : inherit;
background-image : none; background-image : none;
border-style : none; border-style : none;
border-image : none; border-image : none;
color : inherit; column-span : none;
tr { tr { background-image : none; }
background-image : none; td,th { padding : 0px; }
}
td,th {
padding: 0px;
}
} }
:last-child { :last-child { margin-bottom : 0; }
margin-bottom : 0;
}
strong, em { strong, em {
font-style : normal; font-style : normal;
@@ -356,29 +292,27 @@
} }
//Full Width //Full Width
.monster.wide{ .monster.wide {
.useColumns(0.96, @fillMode: balance); .useColumns(0.96, @fillMode: balance);
} }
//***************************** //*****************************
// * FOOTER // * FOOTER
// *****************************/ // *****************************/
&:nth-child(odd){ &:nth-child(odd) {
.pageNumber{ .pageNumber { left : 3cm; }
left : 3cm; .footnote {
}
.footnote{
left : 4.5cm; left : 4.5cm;
text-align : left; text-align : left;
} }
} }
.pageNumber{ .pageNumber {
font-family : FrederickaTheGreat;
right : 3cm; right : 3cm;
bottom : 1.25cm; bottom : 1.25cm;
font-family : "FrederickaTheGreat";
color : var(--HB_Color_HeaderText); color : var(--HB_Color_HeaderText);
} }
.footnote{ .footnote {
position : absolute; position : absolute;
right : 4.5cm; right : 4.5cm;
bottom : 1.25cm; bottom : 1.25cm;
@@ -391,154 +325,134 @@
//************************************ //************************************
// * CODE BLOCKS // * CODE BLOCKS
// ************************************/ // ************************************/
code{ code {
font-size : 0.3cm;
padding : 0px 4px; padding : 0px 4px;
color : var(--HB_Color_Text); font-size : 0.3cm;
vertical-align : middle; vertical-align : middle;
background-color : #faf7ea; color : var(--HB_Color_Text);
background-color : #FAF7EA;
border-radius : 4px; border-radius : 4px;
} }
pre code{ pre code {
padding : 0.15cm;
margin-bottom : 2px;
border-style : solid; border-style : solid;
border-width : 1px; border-width : 1px;
border-radius : 12px;
border-image : @codeBorderImage 26 stretch; border-image : @codeBorderImage 26 stretch;
border-image-width : 10px; border-image-width : 10px;
border-image-outset : 2px; border-image-outset : 2px;
border-radius : 12px;
margin-bottom : 2px;
padding : 0.15cm;
.page :where(&) { .page :where(&) {
margin-top : 2px; //Prevent top border getting cut off on colbreak margin-top : 2px; //Prevent top border getting cut off on colbreak
} }
& + * { & + * { margin-top : 0.325cm; }
margin-top : 0.325cm;
}
} }
//***************************** //*****************************
// * EXTRAS // * EXTRAS
// *****************************/ // *****************************/
hr{ hr {
visibility : hidden; visibility : hidden;
border : none;
margin : 0px; margin : 0px;
border : none;
} }
//Text indent right after table //Text indent right after table
table+p{ table + p { text-indent : 1em; }
text-indent : 1em;
}
a, a:visited, a:hover { a, a:visited, a:hover {
color: var(--HB_Color_Text); color : var(--HB_Color_Text);
transition:all 1s ease; transition : all 1s ease;
}
a:hover {
color:red;
} }
a:hover { color : red; }
} }
//***************************** //*****************************
// * SPELL LIST // * SPELL LIST
// *****************************/ // *****************************/
.page .spellList{ .page .spellList {
.useSansSerif(); .useSansSerif();
font-family : PermanentMarker; font-family : "PermanentMarker";
column-count : 2; column-count : 2;
ul+h5{ ul + h5 { margin-top : 15px; }
margin-top : 15px; ul {
}
ul{
margin-bottom : 0.5em;
padding-left : 1em; padding-left : 1em;
margin-bottom : 0.5em;
text-indent : -1em; text-indent : -1em;
list-style-type : none; list-style-type : none;
break-inside : auto;
-webkit-column-break-inside : auto; -webkit-column-break-inside : auto;
page-break-inside : auto; page-break-inside : auto;
break-inside : auto;
}
&.wide{
column-count : 4;
} }
&.wide { column-count : 4; }
} }
//***************************** //*****************************
// * CLASS TABLE // * CLASS TABLE
// *****************************/ // *****************************/
.page .classTable{ .page .classTable {
th[colspan]:not([rowspan]) { th[colspan]:not([rowspan]) { white-space : nowrap; }
white-space : nowrap; h5 + table { margin-top : 0.2cm; }
}
h5 + table{
margin-top : 0.2cm;
}
} }
//***************************** //*****************************
// * TABLE OF CONTENTS // * TABLE OF CONTENTS
// *****************************/ // *****************************/
.page .toc{ .page .toc {
-webkit-column-break-inside : avoid; -webkit-column-break-inside : avoid;
page-break-inside : avoid; page-break-inside : avoid;
break-inside : avoid; break-inside : avoid;
h1 { h1 {
text-align : center;
margin-bottom : 0.3cm; margin-bottom : 0.3cm;
text-align : center;
} }
a{ a {
display : inline; display : inline;
color : inherit; color : inherit;
text-decoration : none; text-decoration : none;
&:hover{ &:hover { text-decoration : underline; }
text-decoration : underline;
}
} }
h4 { h4 {
margin-top : 0.2cm; margin-top : 0.2cm;
line-height : 0.4cm; line-height : 0.4cm;
& + ul li { & + ul li { line-height : 1.2em; }
line-height: 1.2em;
}
} }
ul{ ul {
padding-left : 0; padding-left : 0;
list-style-type : none; list-style-type : none;
li + li h3 { li + li h3 {
margin-top : 0.26cm; margin-top : 0.26cm;
line-height : 1em line-height : 1em;
}
h3 span:first-child::after {
border : none;
} }
h3 span:first-child::after { border : none; }
span { span {
display : table-cell; display : table-cell;
&:first-child { &:first-child {
position : relative; position : relative;
overflow : hidden; overflow : hidden;
&::after { &::after {
content : "";
position : absolute; position : absolute;
bottom : 0.08cm; bottom : 0.08cm;
margin-left : 0.06cm; /* Spacing before dot leaders */
width : 100%; width : 100%;
border-bottom : 0.05cm dotted #000; margin-left : 0.06cm; /* Spacing before dot leaders */
content : '';
border-bottom : 0.05cm dotted #000000;
} }
} }
&:last-child { &:last-child {
font-family : ReenieBeanie;
font-size : 0.34cm;
font-weight : normal;
color : black;
text-align : right;
vertical-align : bottom; /* Keep page number bottom-aligned */
width : 1%; width : 1%;
padding-left : 0.06cm; /* Spacing after dot leaders */ padding-left : 0.06cm; /* Spacing after dot leaders */
/*white-space : nowrap; /* Uncomment if needed */ font-family : "ReenieBeanie";
font-size : 0.34cm;
font-weight : normal;
vertical-align : bottom; /* Keep page number bottom-aligned */
color : black;
text-align : right;
/* white-space : nowrap; /* Uncomment if needed */
} }
} }
ul { /*List indent*/ ul { /* List indent */
margin-left : 1em; margin-left : 1em;
} }
} }
&.wide{ &.wide {
.useColumns(0.96, @fillMode: balance); .useColumns(0.96, @fillMode: balance);
} }
} }
@@ -546,6 +460,4 @@
//***************************** //*****************************
// * WIDE // * WIDE
// *****************************/ // *****************************/
.page .wide { .page .wide { margin-bottom : 0.45cm; }
margin-bottom : 0.45cm;
}

View File

@@ -1,88 +1,83 @@
.editor .codeEditor .CodeMirror { .editor .codeEditor .CodeMirror {
// Themes with dark backgrounds // Themes with dark backgrounds
&.cm-s-3024-night, &.cm-s-3024-night,
&.cm-s-abbott, &.cm-s-abbott,
&.cm-s-abcdef, &.cm-s-abcdef,
&.cm-s-ambiance, &.cm-s-ambiance,
&.cm-s-ayu-dark, &.cm-s-ayu-dark,
&.cm-s-ayu-mirage, &.cm-s-ayu-mirage,
&.cm-s-base16-dark, &.cm-s-base16-dark,
&.cm-s-bespin, &.cm-s-bespin,
&.cm-s-blackboard, &.cm-s-blackboard,
&.cm-s-cobalt, &.cm-s-cobalt,
&.cm-s-colorforth, &.cm-s-colorforth,
&.cm-s-darcula, &.cm-s-darcula,
&.cm-s-dracula, &.cm-s-dracula,
&.cm-s-duotone-dark, &.cm-s-duotone-dark,
&.cm-s-erlang-dark, &.cm-s-erlang-dark,
&.cm-s-gruvbox-dark, &.cm-s-gruvbox-dark,
&.cm-s-hopscotch, &.cm-s-hopscotch,
&.cm-s-icecoder, &.cm-s-icecoder,
&.cm-s-isotope, &.cm-s-isotope,
&.cm-s-lesser-dark, &.cm-s-lesser-dark,
&.cm-s-liquibyte, &.cm-s-liquibyte,
&.cm-s-lucario, &.cm-s-lucario,
&.cm-s-material, &.cm-s-material,
&.cm-s-material-darker, &.cm-s-material-darker,
&.cm-s-material-ocean, &.cm-s-material-ocean,
&.cm-s-material-palenight, &.cm-s-material-palenight,
&.cm-s-mbo, &.cm-s-mbo,
&.cm-s-midnight, &.cm-s-midnight,
&.cm-s-monokai, &.cm-s-monokai,
&.cm-s-moxer, &.cm-s-moxer,
&.cm-s-night, &.cm-s-night,
&.cm-s-nord, &.cm-s-nord,
&.cm-s-oceanic-next, &.cm-s-oceanic-next,
&.cm-s-panda-syntax, &.cm-s-panda-syntax,
&.cm-s-paraiso-dark, &.cm-s-paraiso-dark,
&.cm-s-pastel-on-dark, &.cm-s-pastel-on-dark,
&.cm-s-railscasts, &.cm-s-railscasts,
&.cm-s-rubyblue, &.cm-s-rubyblue,
&.cm-s-seti, &.cm-s-seti,
&.cm-s-shadowfox, &.cm-s-shadowfox,
&.cm-s-the-matrix, &.cm-s-the-matrix,
&.cm-s-tomorrow-night-bright, &.cm-s-tomorrow-night-bright,
&.cm-s-tomorrow-night-eighties, &.cm-s-tomorrow-night-eighties,
&.cm-s-twilight, &.cm-s-twilight,
&.cm-s-vibrant-ink, &.cm-s-vibrant-ink,
&.cm-s-xq-dark, &.cm-s-xq-dark,
&.cm-s-yonce, &.cm-s-yonce,
&.cm-s-zenburn &.cm-s-zenburn {
{ .CodeMirror-code {
.CodeMirror-code { .block:not(.cm-comment) { color : magenta; }
.block:not(.cm-comment) { .columnSplit {
color: magenta; color : black;
} background-color : rgba(35,153,153,0.5);
.columnSplit { }
color: black; .pageLine {
background-color: rgba(35,153,153,0.5); background-color : rgba(255,255,255,0.5);
} & ~ pre.CodeMirror-line { color : black; }
.pageLine { }
background-color: rgba(255,255,255,0.5); }
& ~ pre.CodeMirror-line { }
color: black; // Themes with light backgrounds
} &.cm-s-default,
} &.cm-s-3024-day,
} &.cm-s-ambiance-mobile,
} &.cm-s-base16-light,
// Themes with light backgrounds &.cm-s-duotone-light,
&.cm-s-default, &.cm-s-eclipse,
&.cm-s-3024-day, &.cm-s-elegant,
&.cm-s-ambiance-mobile, &.cm-s-juejin,
&.cm-s-base16-light, &.cm-s-neat,
&.cm-s-duotone-light, &.cm-s-neo,
&.cm-s-eclipse, &.cm-s-paraiso-lightm
&.cm-s-elegant, &.cm-s-solarized,
&.cm-s-juejin, &.cm-s-ssms,
&.cm-s-neat, &.cm-s-ttcn,
&.cm-s-neo, &.cm-s-xq-light,
&.cm-s-paraiso-lightm &.cm-s-yeti {
&.cm-s-solarized, // Future styling for themes with light backgrounds
&.cm-s-ssms, --dummyVar : 'currently unused';
&.cm-s-ttcn, }
&.cm-s-xq-light,
&.cm-s-yeti {
// Future styling for themes with light backgrounds
--dummyVar: 'currently unused';
}
} }

View File

@@ -1,61 +1,61 @@
/* Main Font, serif */ /* Main Font, serif */
@font-face { @font-face {
font-family: BookSanity; font-family : "BookSanity";
src: url('../../../fonts/5e legacy/Bookinsanity.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e legacy/Bookinsanity.woff2');
} }
@font-face { @font-face {
font-family: BookSanity; font-family : "BookSanity";
src: url('../../../fonts/5e legacy/Bookinsanity Bold.woff2'); font-style : normal;
font-weight: bold; font-weight : bold;
font-style: normal; src : url('../../../fonts/5e legacy/Bookinsanity Bold.woff2');
} }
@font-face { @font-face {
font-family: BookSanity; font-family : "BookSanity";
src: url('../../../fonts/5e legacy/Bookinsanity Italic.woff2'); font-style : italic;
font-weight: normal; font-weight : normal;
font-style: italic; src : url('../../../fonts/5e legacy/Bookinsanity Italic.woff2');
} }
@font-face { @font-face {
font-family: BookSanity; font-family : "BookSanity";
src: url('../../../fonts/5e legacy/Bookinsanity Bold Italic.woff2'); font-style : italic;
font-weight: bold; font-weight : bold;
font-style: italic; src : url('../../../fonts/5e legacy/Bookinsanity Bold Italic.woff2');
} }
/* Notes and Tables, sans-serif */ /* Notes and Tables, sans-serif */
@font-face { @font-face {
font-family: ScalySans; font-family : "ScalySans";
src: url('../../../fonts/5e legacy/Scaly Sans.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e legacy/Scaly Sans.woff2');
} }
@font-face { @font-face {
font-family: ScalySansSmallCaps; font-family : "ScalySansSmallCaps";
src: url('../../../fonts/5e legacy/Scaly Sans Caps.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e legacy/Scaly Sans Caps.woff2');
} }
@font-face { @font-face {
font-family: WalterTurncoat; font-family : "WalterTurncoat";
src: url('../../../fonts/5e legacy/WalterTurncoat-Regular.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e legacy/WalterTurncoat-Regular.woff2');
} }
/* Headers */ /* Headers */
@font-face { @font-face {
font-family: MrJeeves; font-family : "MrJeeves";
src: url('../../../fonts/5e legacy/Mr Eaves Small Caps.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e legacy/Mr Eaves Small Caps.woff2');
} }
/* Fancy Drop Cap */ /* Fancy Drop Cap */
@font-face { @font-face {
font-family: Solberry; font-family : "Solberry";
src: url('../../../fonts/5e legacy/Solbera Imitation.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e legacy/Solbera Imitation.woff2');
} }

View File

@@ -1,143 +1,143 @@
/* Main Font, serif */ /* Main Font, serif */
@font-face { @font-face {
font-family: BookInsanityRemake; font-family : "BookInsanityRemake";
src: url('../../../fonts/5e/Bookinsanity.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e/Bookinsanity.woff2');
} }
@font-face { @font-face {
font-family: BookInsanityRemake; font-family : "BookInsanityRemake";
src: url('../../../fonts/5e/Bookinsanity Bold.woff2'); font-style : normal;
font-weight: bold; font-weight : bold;
font-style: normal; src : url('../../../fonts/5e/Bookinsanity Bold.woff2');
} }
@font-face { @font-face {
font-family: BookInsanityRemake; font-family : "BookInsanityRemake";
src: url('../../../fonts/5e/Bookinsanity Italic.woff2'); font-style : italic;
font-weight: normal; font-weight : normal;
font-style: italic; src : url('../../../fonts/5e/Bookinsanity Italic.woff2');
} }
@font-face { @font-face {
font-family: BookInsanityRemake; font-family : "BookInsanityRemake";
src: url('../../../fonts/5e/Bookinsanity Bold Italic.woff2'); font-style : italic;
font-weight: bold; font-weight : bold;
font-style: italic; src : url('../../../fonts/5e/Bookinsanity Bold Italic.woff2');
} }
/* Notes and Tables, sans-serif */ /* Notes and Tables, sans-serif */
@font-face { @font-face {
font-family: ScalySansRemake; font-family : "ScalySansRemake";
src: url('../../../fonts/5e/Scaly Sans.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e/Scaly Sans.woff2');
} }
@font-face { @font-face {
font-family: ScalySansRemake; font-family : "ScalySansRemake";
src: url('../../../fonts/5e/Scaly Sans Bold.woff2'); font-style : normal;
font-weight: bold; font-weight : bold;
font-style: normal; src : url('../../../fonts/5e/Scaly Sans Bold.woff2');
} }
@font-face { @font-face {
font-family: ScalySansRemake; font-family : "ScalySansRemake";
src: url('../../../fonts/5e/Scaly Sans Italic.woff2'); font-style : italic;
font-weight: normal; font-weight : normal;
font-style: italic; src : url('../../../fonts/5e/Scaly Sans Italic.woff2');
} }
@font-face { @font-face {
font-family: ScalySansRemake; font-family : "ScalySansRemake";
src: url('../../../fonts/5e/Scaly Sans Bold Italic.woff2'); font-style : italic;
font-weight: bold; font-weight : bold;
font-style: italic; src : url('../../../fonts/5e/Scaly Sans Bold Italic.woff2');
} }
@font-face { @font-face {
font-family: ScalySansSmallCapsRemake; font-family : "ScalySansSmallCapsRemake";
src: url('../../../fonts/5e/Scaly Sans Caps.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e/Scaly Sans Caps.woff2');
} }
@font-face { @font-face {
font-family: WalterTurncoat; font-family : "WalterTurncoat";
src: url('../../../fonts/5e/WalterTurncoat-Regular.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e/WalterTurncoat-Regular.woff2');
} }
/* Headers */ /* Headers */
@font-face { @font-face {
font-family: MrEavesRemake; font-family : "MrEavesRemake";
src: url('../../../fonts/5e/Mr Eaves Small Caps.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e/Mr Eaves Small Caps.woff2');
} }
/* Fancy Drop Cap */ /* Fancy Drop Cap */
@font-face { @font-face {
font-family: SolberaImitationRemake; //Tweaked 5e version font-family : "SolberaImitationRemake"; //Tweaked 5e version
src: url('../../../fonts/5e/Solbera Imitation Tweak.woff2'); font-style : normal;
font-weight: 100 1000; font-style : italic;
font-style: normal; font-weight : 100 1000;
font-style: italic; src : url('../../../fonts/5e/Solbera Imitation Tweak.woff2');
} }
/* Cover Page */ /* Cover Page */
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family : "NodestoCapsCondensed";
src: url('../../../fonts/5e/Nodesto Caps Condensed.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e/Nodesto Caps Condensed.woff2');
} }
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family : "NodestoCapsCondensed";
src: url('../../../fonts/5e/Nodesto Caps Condensed Bold.woff2'); font-style : normal;
font-weight: bold; font-weight : bold;
font-style: normal; src : url('../../../fonts/5e/Nodesto Caps Condensed Bold.woff2');
} }
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family : "NodestoCapsCondensed";
src: url('../../../fonts/5e/Nodesto Caps Condensed Italic.woff2'); font-style : italic;
font-weight: normal; font-weight : normal;
font-style: italic; src : url('../../../fonts/5e/Nodesto Caps Condensed Italic.woff2');
} }
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family : "NodestoCapsCondensed";
src: url('../../../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2'); font-style : italic;
font-weight: bold; font-weight : bold;
font-style: italic; src : url('../../../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
} }
@font-face { @font-face {
font-family: NodestoCapsWide; font-family : "NodestoCapsWide";
src: url('../../../fonts/5e/Nodesto Caps Wide.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal src : url('../../../fonts/5e/Nodesto Caps Wide.woff2');
} }
@font-face { @font-face {
font-family: Overpass; font-family : "Overpass";
src: url('../../../fonts/5e/Overpass Medium.woff2'); font-style : normal;
font-weight: 500; font-weight : 500;
font-style: normal; src : url('../../../fonts/5e/Overpass Medium.woff2');
} }
@font-face { @font-face {
font-family: Davek; font-family : "Davek";
src: url('../../../fonts/5e/Davek.woff2'); font-style : normal;
font-weight: 500; font-weight : 500;
font-style: normal; src : url('../../../fonts/5e/Davek.woff2');
} }
@font-face { @font-face {
font-family: Iokharic; font-family : "Iokharic";
src: url('../../../fonts/5e/Iokharic.woff2'); font-style : normal;
font-weight: 500; font-weight : 500;
font-style: normal; src : url('../../../fonts/5e/Iokharic.woff2');
} }
@font-face { @font-face {
font-family: Rellanic; font-family : "Rellanic";
src: url('../../../fonts/5e/Rellanic.woff2'); font-style : normal;
font-weight: 500; font-weight : 500;
font-style: normal; src : url('../../../fonts/5e/Rellanic.woff2');
} }

View File

@@ -18,29 +18,29 @@ License:
*/ */
@font-face { @font-face {
font-family: Pagella; font-family : "Pagella";
src: url('../../../fonts/Blank/texgyrepagella-regular.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/Blank/texgyrepagella-regular.woff2');
} }
@font-face { @font-face {
font-family: Pagella; font-family : "Pagella";
src: url('../../../fonts/Blank/texgyrepagella-bold.woff2'); font-style : normal;
font-weight: bold; font-weight : bold;
font-style: normal; src : url('../../../fonts/Blank/texgyrepagella-bold.woff2');
} }
@font-face { @font-face {
font-family: Pagella; font-family : "Pagella";
src: url('../../../fonts/Blank/texgyrepagella-italic.woff2'); font-style : italic;
font-weight: normal; font-weight : normal;
font-style: italic; src : url('../../../fonts/Blank/texgyrepagella-italic.woff2');
} }
@font-face { @font-face {
font-family: Pagella; font-family : "Pagella";
src: url('../../../fonts/Blank/texgyrepagella-bolditalic.woff2'); font-style : italic;
font-weight: bold; font-weight : bold;
font-style: italic; src : url('../../../fonts/Blank/texgyrepagella-bolditalic.woff2');
} }

View File

@@ -1,58 +1,58 @@
/* Main Font, serif */ /* Main Font, serif */
@font-face { @font-face {
font-family: ReenieBeanie; font-family : "ReenieBeanie";
src: url('../../../fonts/Journal/ReenieBeanie-Regular.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/Journal/ReenieBeanie-Regular.woff2');
} }
/* Notes and Tables, sans-serif */ /* Notes and Tables, sans-serif */
@font-face { @font-face {
font-family: PermanentMarker; font-family : "PermanentMarker";
src: url('../../../fonts/Journal/PermanentMarker-Regular.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/Journal/PermanentMarker-Regular.woff2');
} }
@font-face { @font-face {
font-family: WalterTurncoat; font-family : "WalterTurncoat";
src: url('../../../fonts/5e/WalterTurncoat-Regular.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/5e/WalterTurncoat-Regular.woff2');
} }
/* Headers */ /* Headers */
@font-face { @font-face {
font-family: FrederickaTheGreat; font-family : "FrederickaTheGreat";
src: url('../../../fonts/Journal/FrederickaTheGreat-Regular.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../../../fonts/Journal/FrederickaTheGreat-Regular.woff2');
} }
/* Cover Page */ /* Cover Page */
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family : "NodestoCapsCondensed";
src: url('../fonts/5e/Nodesto Caps Condensed.woff2'); font-style : normal;
font-weight: normal; font-weight : normal;
font-style: normal; src : url('../fonts/5e/Nodesto Caps Condensed.woff2');
} }
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family : "NodestoCapsCondensed";
src: url('../fonts/5e/Nodesto Caps Condensed Bold.woff2'); font-style : normal;
font-weight: bold; font-weight : bold;
font-style: normal; src : url('../fonts/5e/Nodesto Caps Condensed Bold.woff2');
} }
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family : "NodestoCapsCondensed";
src: url('../fonts/5e/Nodesto Caps Condensed Italic.woff2'); font-style : italic;
font-weight: normal; font-weight : normal;
font-style: italic; src : url('../fonts/5e/Nodesto Caps Condensed Italic.woff2');
} }
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family : "NodestoCapsCondensed";
src: url('../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2'); font-style : italic;
font-weight: bold; font-weight : bold;
font-style: italic; src : url('../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
} }

View File

@@ -13,8 +13,8 @@
font-weight : normal; font-weight : normal;
font-variant : normal; font-variant : normal;
line-height : 1; line-height : 1;
text-decoration : inherit;
text-transform : none; text-transform : none;
text-decoration : inherit;
text-rendering : optimizeLegibility; text-rendering : optimizeLegibility;
/* Better Font Rendering =========== */ /* Better Font Rendering =========== */

View File

@@ -1,3 +1,5 @@
/* eslint-disable max-lines */
const fontAwesome = { const fontAwesome = {
// FONT-AWESOME SOLID // FONT-AWESOME SOLID
'fas_0' : 'fas fa-0', 'fas_0' : 'fas fa-0',

View File

@@ -1,31 +1,31 @@
.phb{ .phb {
//Double hr for full width elements //Double hr for full width elements
hr+hr+blockquote{ hr + hr + blockquote {
column-span : all;
-webkit-column-span : all; -webkit-column-span : all;
-moz-column-span : all; -moz-column-span : all;
column-span : all;
} }
//***************************** //*****************************
// * CLASS TABLE // * CLASS TABLE
// *****************************/ // *****************************/
hr+table{ hr+table {
padding-top : 10px;
margin-top : -5px; margin-top : -5px;
margin-bottom : 50px; margin-bottom : 50px;
padding-top : 10px;
border-collapse : separate; border-collapse : separate;
background-color : white; background-color : white;
border : initial; border : initial;
border-style : solid; border-style : solid;
border-image-source : @frameBorderImage;
border-image-slice : 150 200 150 200;
border-image-width : 47px;
border-image-outset : 37px 17px; border-image-outset : 37px 17px;
border-image-repeat : round; border-image-repeat : round;
border-image-slice : 150 200 150 200;
border-image-source : @frameBorderImage;
border-image-width : 47px;
} }
h5+hr+table{ h5 + hr + table {
column-span : all;
-webkit-column-span : all; -webkit-column-span : all;
-moz-column-span : all; -moz-column-span : all;
column-span : all;
} }
} }