Papers, Pages and Post-it Notes
NOTICE:
This is a component page used internally by the SCP Wiki. It is intended to be used and included on other pages.
What this is
A bunch of miscellaneous CSS 'improvements' that I, Croquembouche, use on a bunch of pages because I think it makes them easier to deal with.
The changes this component makes are bunch of really trivial modifications to ease the writing experience and to make documenting components/themes a bit easier (which I do a lot). It doesn't change anything about the page visually for the reader — the changes are for the writer.
I wouldn't expect translations of articles that use this component to also use this component, unless the translator likes it and would want to use it anyway.
This component probably won't conflict with other components or themes, and even if it does, it probably won't matter too much.
Usage
On any wiki:
[[include :scp-wiki:component:croqstyle]]
This component is designed to be used on other components. When using on another component, be sure to add this inside the component's [[iftags]] block, so that users of your component are not forced into also using Croqstyle.
Related components
Other personal styling components (which change just a couple things):
Personal styling themes (which are visual overhauls):
CSS changes
Reasonably-sized footnotes
Stops footnotes from being a million miles wide, so that you can actually read them.
.hovertip { max-width: 400px; }
Monospace edit/code
Makes the edit textbox monospace, and also changes all monospace text to Fira Code, the obviously superior monospace font.
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap');
:root { --mono-font: "Fira Code", Cousine, monospace; }
#edit-page-textarea, .code pre, .code p, .code, tt, .page-source { font-family: var(--mono-font); }
.code pre * { white-space: pre; }
.code *, .pre * { font-feature-settings: unset; }
Teletype backgrounds
Adds a light grey background to <tt> elements ({{text}}), so code snippets stand out more.
tt {
background-color: var(--swatch-something-bhl-idk-will-fix-later, #f4f4f4);
font-size: 85%;
padding: 0.2em 0.4em;
margin: 0;
border-radius: 6px;
}
No more bigfaces
Stops big pictures from appearing when you hover over someone's avatar image, because they're stupid and really annoying and you can just click on them if you want to see the big version.
.avatar-hover { display: none !important; }
Breaky breaky
Any text inside a div with class nobreak has line-wrapping happen between every letter.
.nobreak { word-break: break-all; }
Code colours
Add my terminal's code colours as variables. Maybe I'll change this to a more common terminal theme like Monokai or something at some point, but for now it's just my personal theme, which is derived from Tomorrow Night Eighties.
Also, adding the .terminal class to a fake code block as [[div class="code terminal"]] gives it a sort of pseudo-terminal look with a dark background. Doesn't work with [[code]], because Wikidot inserts a bunch of syntax highlighting that you can't change yourself without a bunch of CSS. Use it for non-[[code]] code snippets only.
Quick tool to colourise a 'standard' Wikidot component usage example with the above vars: link
:root {
--c-bg: #393939;
--c-syntax: #e0e0e0;
--c-comment: #999999;
--c-error: #f2777a;
--c-value: #f99157;
--c-symbol: #ffcc66;
--c-string: #99cc99;
--c-operator: #66cccc;
--c-builtin: #70a7df;
--c-keyword: #cc99cc;
}
.terminal, .terminal > .code {
color: var(--c-syntax);
background: var(--c-bg);
border: 0.4rem solid var(--c-comment);
border-radius: 1rem;
}
Debug mode
Draw lines around anything inside .debug-mode. The colour of the lines is red but defers to CSS variable --debug-colour.
You can also add div.debug-info.over and div.debug-info.under inside an element to annotate the debug boxes — though you'll need to make sure to leave enough vertical space that the annotation doesn't overlap the thing above or below it.
.debug-mode, .debug-mode *, .debug-mode *::before, .debug-mode *::after {
outline: 1px solid var(--debug-colour, red);
position: relative;
}
.debug-info {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-family: 'Fira Code', monospace;
font-size: 1rem;
white-space: nowrap;
}
.debug-info.over { top: -2.5rem; }
.debug-info.under { bottom: -2.5rem; }
.debug-info p { margin: 0; }
What this is
A component that displays your text on separate pages, for example the pages of a book.
Made by Croquembouche.
Other components that simulate pagination:
Usage
On any wiki:
[[include :scp-wiki:component:pages
| medium=vintage-book
| contents=
<h1>Hello, welcome to my book.</h1>
I hope you <em>really enjoy</em> it so that the pain it took to write it was worth it.
]]
There are two parameters for this component.
medium |
What medium should your text be presented in? This determines the background image of each page, the font, etc. I'm open to adding more options — please contact me (Croquembouche) if you have an idea.
These are documented in detail at the bottom of this page. |
contents |
The contents of your pages. Can include very simple formatting and images. |
style |
CSS styling to inject into the document. |
The contents parameter can spread across as many lines as you want.
The contents may not contain the following characters:
- | (pipe symbol)
- ` (backtick), unless it's preceded by a backslash (\‘)
- ]] (two closing brackets), but only if it’s at the end of a line or followed by whitespace
Text formatting
The contents parameter won't be rendered by Wikidot. It goes straight through to a custom parser that I've written myself that spits out HTML.
All that lovely Wikidot syntax that you're used to goes out of the window. Instead, you'll have to write inline HTML.
For example:
- To make bold text, instead of **text**, write <b>text</b>.
- To make an em dash, instead of --, write a literal em dash (—).
- To make a horizontal rule, instead of -----, write <hr>.
Unlike HTML, you do not need to write <p>…</p> around each line.
Images
Include images using standard HTML for an image:
<img src="http://scp-wiki.wdfiles.com/local--files/your-page/your-image.png">
You can fix the image to the top or bottom of a page by adding either position="top" or position="bottom". This will fix the image to either the top or the bottom of whatever page it would appear on. This only works for images that are on a line by themselves, not in the middle of text:
<img position="top" src="URL">
To horizontally centre an image, add this CSS to its style attribute directly:
<img style="display: block; margin-left: auto; margin-right: auto;" src="URL">
If an image is too tall to fit on a single page, the bottom of it will be chopped off.
Page breaks
Sometimes you want to say "okay, stop there. Start the next bit on a new page. The rest of this page can be empty."
To do that, on a line by itself, add a page break:
The thing that comes next after a page break will be on the next page. If there's an image with position="bottom" just before the page break, it will still be at the bottom of that page, as expected.
Advanced usage
You can include CSS via the style parameter, and embedded Javascript in the contents parameter.
Using CSS
Any CSS you include in the style parameter will be injected into the pages. Use this to tweak things, add classes that you can use in your content, and change the font (for example).
For example:
[[include :scp-wiki:component:pages
| medium=vintage-book
| style=
@import url('https://fonts.googleapis.com/css2?family=Architects+Daughter');
.page {
font-family: 'Architects Daughter', cursive;
margin: 2rem;
}
.page .red {
color: red;
}
| contents=
This text will appear <span class="red">handwritten</span>.
]]
You can't style this component with a CSS module elsewhere on your article — this is the only way.
Tip! You can add parameters in any order you like, but it's a good idea to put the style parameter above the contents parameters. Your contents parameter will probably be really, really long — you might find it easier to have all the settings and CSS at the top.
Using Javascript
The contents parameter will be parsed as a Javascript template literal, which is why it can't contain a backtick.
Because of that, you can include arbitrary Javascript in the contents parameter inside an embedded expression (${expression}), which will be executed and substituted back into the string. For example:
[[include :scp-wiki:component:pages
| medium=vintage-book
| contents=
The day today is ${new Date().toLocaleString("default", { weekday: "long" })}.
]]
This book will tell the reader the name of the current day.
Should you do this? Probably not. But you can.
Please note that while you can include arbitrary Javascript, the OR operator (||) is unavailable because it's made of pipe symbols, which are forbidden. However, using AND and NOT, you can create an OR yourself: NOT((NOT x) AND (NOT y)) is equivalent to (x OR y). In Javascript, that's !((!x) && (!y)).
Medium Options
The following are your options for the 'medium' parameter.
I am actively soliciting requests for new medium options. I mean, the title of this component is 'Papers, Pages and Post-it Notes', but right now it can only do a vintage book. This component is designed to be as versatile as possible, but right now, it can only do one thing.
If you have any requests for paginated formats, please let me know (Croquembouche).
Each medium has a fixed aspect ratio that determines its height for any browser width.
Medium |
Background |
Font |
y:x |
Source |
plain (default) |
|
Browser default |
4:7 |
|
vintage-book |
|
Libre Baskerville |
10:9 |
Public Domain |
Source code
HTML
Check the bottom of the page source for the [[html]] module.
CSS
/* Imports */
/* Note that fonts MUST be imported synchronously using the link element,
* otherwise the page structure may not render properly */
/* Styling */
.pages-container {
font-size: min(4vw, 1rem);
position: relative;
}
/* A page without a medium needs some default styling. */
.aspect-ratio {
margin: 1rem 0;
/* The padding-top should be the ratio of the bg image's width / height */
padding-top: calc(400 / 700 * 100%);
position: relative;
}
.page {
height: 0;
padding: 2rem 2rem 3rem;
position: absolute;
height: 100%; width: 100%; top: 0; left: 0;
box-sizing: border-box;
}
.page::before {
content: "";
position: absolute;
height: 100%; width: 100%; top: 0; left: 0;
pointer-events: none;
background-image: linear-gradient(beige, antiquewhite);
background-size: 100% 100%;
background-repeat: no-repeat;
z-index: -1;
}
.content {
position: relative;
display: flex;
flex-direction: column;
}
.content > * {
margin-block-start: 0.5em;
margin-block-end: 0.5em;
}
p, img, div { max-width: 100%; }
h1, h2, h3, h4, h5, h6 { line-height: 1em; }
/* Each medium gets its own style. */
.vintage-book {
font-family: 'Libre Baskerville', serif;
color: #514132;
line-height: 1.5em;
text-align: justify;
}
.vintage-book .aspect-ratio {
padding-top: calc(2500 / 2250 * 100%);
}
.vintage-book .page {
padding: 8vw 17vw;
}
.vintage-book .page::before {
background-image:
url(http://topia.wikidot.com/local--files/cqb:book/book.1000px.webp);
}
.vintage-book > :nth-child(2n+1) .page::before {
transform: scaleX(-1);
}
.vintage-book .content {
transform: skewY(-0.5deg);
}
.vintage-book > :nth-child(2n+1) .content {
transform: skewY(0.5deg);
}
.vintage-book .split-paragraph-start {
text-align-last: justify;
}
.vintage-book .content > p:not(.split-paragraph-end):not(.is-image) {
text-indent: 2em;
}
.letter .page {}
Javascript
let pageCount = 1
let resetCount = 0
contents = contents
.split("\n")
.filter(line => line.length > 0)
.map(line => {
return $(`<p>${line}<${"/p"}>`)
})
contents = $($.map(contents, el => el.get()))
contents = contents.filter(":not(:empty)")
contents.map((index, element) => {
element = $(element)
if (
element.is("p") &&
element.text().length === 0 &&
element.children().length === 1 &&
element.children().first().is("img")
) {
element.addClass("is-image")
if (
["top", "bottom"].includes(element.children().first().attr("position"))
) {
element.css(
"order",
{ top: -1, bottom: 1 }[element.children().first().attr("position")]
)
}
}
})
$(window).on("load", () => {
resetCount = 0
let thisDocument = $(".pages-container")
resetPage(thisDocument)
setTimeout(() => resetPage(thisDocument), 2000)
let windowWidth = $(window).width()
$(window).on(
"resize",
debounce(() => {
if ($(window).width() !== windowWidth) {
windowWidth = $(window).width()
resetPage(thisDocument)
}
}, 200)
)
})
https://davidwalsh.name/javascript-debounce-function
function debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
function resetPage(thisDocument) {
thisDocument.empty()
pageCount = 1
thisPage = preparePage(thisDocument)
setTimeout(() => {
makePage(contents.clone(), thisPage, thisDocument)
checkPage(thisDocument)
}, 1)
}
function preparePage(thisDocument) {
const aspectBox = $(`<div class='aspect-ratio'></div>`).appendTo(thisDocument)
return $(`<div class='page'></div>`).appendTo(aspectBox)
}
function makePage(contents, thisPage, thisDocument) {
console.log(`Making page ${pageCount++}`)
let nextPageContents = []
thisPageContents = $("<div class='content'></div>").appendTo(thisPage)
thisPageContents.empty().append(contents)
const pageHeight = thisPage.height()
const contentHeight = thisPageContents.outerHeight()
if (
contentHeight > pageHeight ||
thisPageContents.has("br.page-break").length > 0
) {
console.log(`Split needed (${contentHeight} > ${pageHeight})`)
const children = thisPageContents.children()
let lastParagraphIndex = -1
for (let index = 0; index < children.length; index++) {
const child = $(children.eq(index))
if (child.has("br.page-break").length > 0) {
console.log("Splitting on page break")
lastParagraphIndex = index
break
}
const childBottom =
children
.slice(0, index)
.toArray()
.reduce((height, child) => {
return height + $(child).outerHeight(true)
}, 0) + child.outerHeight(true)
console.log(index, childBottom, thisPage.height())
if (childBottom > thisPage.height()) {
lastParagraphIndex = index
break
}
}
if (lastParagraphIndex === -1) {
console.log("No elements can fit on this page")
} else {
console.log(`Splitting element with index ${lastParagraphIndex}`)
nextPageContents = contents.splice(lastParagraphIndex)
thisPageContents.empty().append(contents)
let partialElement = $(nextPageContents[0]).clone()
if (partialElement.is("p") && partialElement.text() != "") {
partialElement.appendTo(thisPageContents)
const partialText = partialElement.text()
let keepWords = partialText.split(" ")
let moveWords = []
for (let index = keepWords.length; index > 0; index--) {
if (thisPageContents.outerHeight(true) <= thisPage.height()) {
console.log(
`Split successful after ${moveWords.length} words removed`
)
if (moveWords.length > 0) {
partialElement.addClass("split-paragraph-start")
}
break
}
let word = keepWords.pop()
moveWords.unshift(word)
partialElement.text(keepWords.join(" "))
}
if (keepWords.length === 0) {
console.log("Split unsuccessful; partial element removed")
partialElement.remove()
} else {
nextPageContents[0].textContent = moveWords.join(" ")
}
} else {
if (lastParagraphIndex == 0) {
console.log("Something couldn't be split, so it was dumped")
partialElement.appendTo(thisPageContents)
partialElement.css("height", pageHeight)
partialElement.css("overflow", "hidden")
nextPageContents.shift()
}
}
}
if (
thisPageContents.children().last().is("p.split-paragraph-start") &&
$(nextPageContents[0]).is("p")
) {
nextPageContents[0].classList.add("split-paragraph-end")
}
if ($(nextPageContents[0]).has("br.page-break").length > 0) {
console.log("Scrapping a page break")
nextPageContents.shift()
}
if (pageCount < 1000) {
const nextPage = preparePage(thisDocument)
makePage(nextPageContents, nextPage, thisDocument)
} else {
console.log("Pages stopped after 1000")
}
} else {
console.log("No split needed")
}
}
function checkPage(thisDocument) {
if (resetCount > 5) {
console.log("Overflow reset limit reached")
return
}
let shouldReset = 0
thisDocument.children().each((index, aspectBox) => {
aspectBox = $(aspectBox)
pageBox = $(aspectBox.children().first())
contentBox = $(pageBox.children().first())
if (contentBox.outerHeight(true) > pageBox.height()) {
shouldReset++
}
if (shouldReset > 1) {
console.log(`${shouldReset} pages overflowing, resetting`)
resetCount++
resetPage(thisDocument)
checkPage(thisDocument)
} else {
console.log("No overflow reset needed")
}
})
}