About Croqstyle
Croqstyle is a bunch of miscellaneous CSS tweaks and utilities that I,
Croquembouche, use a lot and want to share.
This component:
- Makes a few behind-the-scenes changes that improve the reading or writing experience e.g. monospaced text in the editor
- Adds a few utilities for documenting components and themes e.g. custom styling for code blocks
- Adds a few opt-in features for regular pages e.g. special image blocks
Croqstyle is basically compatible with any theme as far as I know.
Usage
On any wiki:
[[include :scp-wiki:component:croqstyle]]
For normal pages: Croqstyle should be included before any other component or theme, so that they can override it.
For components/themes: Croqstyle can be used on other components for their documentation — include it inside the component's [[iftags]] block, so that your component's users are not forced to use Croqstyle.
You don't have to include it like this — feel free to look through and use my CSS at a starting point for new stuff, or steal just the bits you like.
Source code
Here's the source CSS for Croqstyle:
/* CROQSTYLE CSS */
/* Source: https://scp-wiki.wikidot.com/component:croqstyle */
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700');
/* Little improvements */
/* 1. Reasonably-sized footnotes */
.hovertip { max-width: 400px; }
/* 2. No more big avatar hover thingies */
.avatar-hover { display: none !important; }
/* 3. Monospace editor + code blocks */
: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; }
.change-textarea-size { padding-right: 3em; }
/* 4. Better image block positioning */
@media (max-width: 550px), (min-width: 768px) and (max-width: 850px) {
.scp-image-block:is(.block-right, .block-left) {
float: none;
margin-inline: auto;
}
}
/* Documentation helpers */
/* 1. Teletype backgrounds */
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;
}
/* 2. Code colours */
:root {
--croqstyle-terminal-light-bg: #ffffff;
--croqstyle-terminal-light-syntax: #000000;
--croqstyle-terminal-light-comment: #008000;
--croqstyle-terminal-light-error: #e51602;
--croqstyle-terminal-light-value: #267f99;
--croqstyle-terminal-light-symbol: #795e26;
--croqstyle-terminal-light-string: #a31515;
--croqstyle-terminal-light-operator: #0000ff;
--croqstyle-terminal-light-builtin: #14238a;
--croqstyle-terminal-light-keyword: #af00db;
--croqstyle-terminal-light-border: 0.1rem solid #444444;
--croqstyle-terminal-dark-bg: #1f1f1f;
--croqstyle-terminal-dark-syntax: #d4d4d4;
--croqstyle-terminal-dark-comment: #6a9955;
--croqstyle-terminal-dark-error: #cd4343;
--croqstyle-terminal-dark-value: #4dc8af;
--croqstyle-terminal-dark-symbol: #d9d9a7;
--croqstyle-terminal-dark-string: #ce9178;
--croqstyle-terminal-dark-operator: #5294ca;
--croqstyle-terminal-dark-builtin: #9adafb;
--croqstyle-terminal-dark-keyword: #c384be;
--croqstyle-terminal-dark-border: 0.3rem solid #bbbbbb;
}
.code.terminal, .terminal > .code {
color: var(--c-syntax);
background-color: var(--c-bg);
border: var(--croqstyle-terminal-border);
border-radius: 0.3rem;
margin-inline: 1em;
}
:root, .terminal--light {
--c-bg: var(--croqstyle-terminal-light-bg);
--c-syntax: var(--croqstyle-terminal-light-syntax);
--c-comment: var(--croqstyle-terminal-light-comment);
--c-error: var(--croqstyle-terminal-light-error);
--c-value: var(--croqstyle-terminal-light-value);
--c-symbol: var(--croqstyle-terminal-light-symbol);
--c-string: var(--croqstyle-terminal-light-string);
--c-operator: var(--croqstyle-terminal-light-operator);
--c-builtin: var(--croqstyle-terminal-light-builtin);
--c-keyword: var(--croqstyle-terminal-light-keyword);
--croqstyle-terminal-border: var(--croqstyle-terminal-light-border);
}
.terminal--dark {
--c-bg: var(--croqstyle-terminal-dark-bg);
--c-syntax: var(--croqstyle-terminal-dark-syntax);
--c-comment: var(--croqstyle-terminal-dark-comment);
--c-error: var(--croqstyle-terminal-dark-error);
--c-value: var(--croqstyle-terminal-dark-value);
--c-symbol: var(--croqstyle-terminal-dark-symbol);
--c-string: var(--croqstyle-terminal-dark-string);
--c-operator: var(--croqstyle-terminal-dark-operator);
--c-builtin: var(--croqstyle-terminal-dark-builtin);
--c-keyword: var(--croqstyle-terminal-dark-keyword);
--croqstyle-terminal-border: var(--croqstyle-terminal-dark-border);
}
/* 3. Debug mode */
.debug-mode, .debug-mode *, .debug-mode *::before, .debug-mode *::after {
outline: thin 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; }
/* Opt-in features */
/* 1. Improved image block */
/* Custom image block with alt text, hover zoom, high res swap and WEBP support
* Define low res (~300px wide) image via --image-url CSS var
* Define high res (~1000px wide) image via --image-large-url CSS var (optional)
* Define aspect ratio (as 'H / W') via --aspect-ratio CSS var
*/
.croqstyle-image-block .scp-image-alt {
position: relative;
font-size: 0;
aspect-ratio: var(--aspect-ratio);
}
.croqstyle-image-block .scp-image-alt::before {
content: ""; position: absolute; inset: 0;
pointer-events: none;
background-image: var(--image-url);
background-size: cover, 0.6rem, cover;
background-position: center, left 0.3rem bottom 0.3rem, center;
background-repeat: no-repeat;
transition: transform 0.3s ease-in-out;
}
@media (prefers-reduced-motion) {
.croqstyle-image-block .scp-image-alt::before {
transition-duration: 0s;
}
}
@media (min-width: 670px) and (max-width: 767px), (min-width: 940px) {
.croqstyle-image-block:not(.croqstyle-image-block--no-hover) .scp-image-alt:hover::before,
.croqstyle-image-block--no-hover .scp-image-alt::before {
background-image: var(--image-large-url, var(--image-url)), url(https://scp-wiki.wdfiles.com/local--files/component%3Acroqstyle/loading.gif), var(--image-url);
}
.croqstyle-image-block:not(.croqstyle-image-block--no-hover) .scp-image-alt:hover::before {
transform: scale3d(1.6, 1.6, 1);
z-index: 1;
}
}
.croqstyle-image-block.block-right .scp-image-alt::before { transform-origin: 90% center }
.croqstyle-image-block.block-left .scp-image-alt::before { transform-origin: 10% center }
.croqstyle-image-block .scp-image-alt img {
width: 100%;
height: 100%;
object-fit: center;
}
/* 2. Image footnotes */
/* Custom footnote that uses the same vars as the image block above */
.croqstyle-img-footnote {
position: relative;
--croqstyle-img-footnote-link-color: rgb(var(--link-color, 187,0,17));
text-decoration: var(--croqstyle-img-footnote-link-color) dashed underline;
cursor: help;
}
.croqstyle-img-footnote::after {
content: "";
display: inline-block;
/* https://icon-sets.iconify.design/material-symbols/image-outline/ */
--croqstyle-img-footnote-icon-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zm1-2h12l-3.75-5l-3 4L9 13zm-1 2V5z'/%3E%3C/svg%3E");
-webkit-mask-image: var(--croqstyle-img-footnote-icon-url);
mask-image: var(--croqstyle-img-footnote-icon-url);
-webkit-mask-size: cover;
mask-size: cover;
-webkit-mask-position: center;
mask-position: center;
background-color: var(--croqstyle-img-footnote-link-color);
width: 1em; height: 1em;
vertical-align: super;
font-size: smaller;
line-height: normal;
}
.croqstyle-img-footnote::before {
content: "";
display: block;
position: absolute;
aspect-ratio: var(--aspect-ratio);
height: 16rem;
border: thin solid grey;
background-color: white;
background-image: var(--image-url);
background-size: 92.5%;
background-position: center;
background-repeat: no-repeat;
bottom: 0; left: 50%;
--croqstyle-img-footnote-icon-offset: 0px;
transform: translate(-50%, calc(100% + var(--croqstyle-img-footnote-icon-offset)));
z-index: 20;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
}
.croqstyle-img-footnote:hover { --croqstyle-img-footnote-link-color: purple; }
.croqstyle-img-footnote:hover::before {
opacity: 1;
pointer-events: initial;
--croqstyle-img-footnote-icon-offset: 0.5em;
}
@media (max-width: 1200px) {
.croqstyle-img-footnote { position: static; }
.croqstyle-img-footnote::before {
bottom: auto;
left: 50%;
transform: translate(-50%, calc(0px + var(--croqstyle-img-footnote-icon-offset)));
}
}
/* 3. Simple responsive div */
.croqstyle-responsive-div {
container-type: inline-size;
}
.croqstyle-responsive-div__desktop {
display: none;
}
.croqstyle-responsive-div__mobile table {
margin-left: 0; /* Left align for consistent header row placement */
}
@container (min-width: 550px) {
.croqstyle-responsive-div__desktop { display: revert !important }
.croqstyle-responsive-div__mobile { display: none }
}
/* 4. Reset tabview */
/* Undoes base Wikidot tab styling. These reset styles are derived from Wikidot's base theme. They override and undo all the tab styles in the base theme, matching the selectors exactly to cancel them out.
* It might look like these selectors can be simplified, merged, reduced in complexity - they can't, because otherwise they wouldn't cancel out the base theme styling.
*/
/* Entire tabview */
:where(.croqstyle-reset-tabs) .yui-navset { padding: initial; position: initial }
/* List of tabs */
:where(.croqstyle-reset-tabs) .yui-nav { margin: initial; padding: initial; list-style: initial }
:where(.croqstyle-reset-tabs) .yui-navset .yui-nav { width: initial; position: initial; border: initial }
/* Internal wrapper for each tab */
:where(.croqstyle-reset-tabs) .yui-nav li { margin: initial; padding: initial; list-style: initial }
:where(.croqstyle-reset-tabs) .yui-navset .yui-nav li { display: initial; vertical-align: initial; cursor: initial; margin: initial; padding: initial }
:where(.croqstyle-reset-tabs) .yui-navset .yui-nav .selected { margin: initial }
/* Each tab */
:where(.croqstyle-reset-tabs) .yui-navset .yui-nav li a { background: initial; border: initial; color: initial; position: initial; text-decoration: initial; display: initial; vertical-align: initial }
/* Selected tab */
:where(.croqstyle-reset-tabs) .yui-navset .yui-nav .selected a { border-color: initial }
/* Hovered tab */
:where(.croqstyle-reset-tabs) .yui-navset .yui-nav a:is(:hover, :focus) { background: initial; outline: initial }
/* Selected + hovered tab */
:where(.croqstyle-reset-tabs) .yui-navset .yui-nav .selected :is(a, a:focus, a:hover) { background: initial; color: initial }
/* Text in each tab */
:where(.croqstyle-reset-tabs) .yui-navset .yui-nav li a em { font-style: initial; display: initial; border: initial; cursor: initial; padding: initial; left: initial; top: initial; position: initial }
/* Text in the selected tab */
:where(.croqstyle-reset-tabs) .yui-navset .yui-nav .selected a em { padding: initial; border-color: initial }
/* Content panel */
:where(.croqstyle-reset-tabs) .yui-navset .yui-content { background: initial; border: initial; padding: initial }
Little improvements
These are all passive changes that improve™ either the reading or editing experience.
Reasonably-sized footnotes
Stops footnotes from being a million miles wide, so that you can actually read them.
No more big avatar hover thingies
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.
Hover me and watch nothing happen:
Croquembouche
Monospace editor + code blocks
Makes the edit textbox monospace, and also changes all monospace text to Fira Code, the obviously superior monospace font.
Better image block positioning
On mobile-sized screens, image blocks can no longer overflow the width of the page.
On slightly larger screens, image blocks can no longer take up like 80% of the page leaving only a tiny strip of space for text to render one-word-per-line. At those widths, images will be centred and text won't be able to appear next to it.
Documentation helpers
These are utilities intended for making it easier to document themes and components.
Teletype backgrounds
Adds a light grey background to <tt> elements ({{text}}), so code snippets stand out more.
Code colours
Adds some syntax highlighting colours as CSS variables. The syntax colours are taken from VSCode's default light and dark themes.
You can add the .terminal class to a fake code block as [[div class="code terminal"]] to give it a pseudo-terminal look. 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. Enable dark mode with .terminal--dark:
[[div class="code terminal"]]
**##var(--c-keyword)|[[####var(--c-builtin)|div## ##var(--c-symbol)|class##=##var(--c-string)|"code terminal"####var(--c-keyword)|]]##**
**##var(--c-keyword)|[[/####var(--c-builtin)|div####var(--c-keyword)|]]##**
[[/div]]
[[div class="code terminal terminal--dark"]]
**##var(--c-keyword)|[[####var(--c-builtin)|div## ##var(--c-symbol)|class##=##var(--c-string)|"code terminal terminal--dark"####var(--c-keyword)|]]##**
**##var(--c-keyword)|[[/####var(--c-builtin)|div####var(--c-keyword)|]]##**
[[/div]]
Tools to auto-highlight: component includes · divs and spans
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.
Opt-in features
These are new features and widgets that you can add to your page if you like. All of these opt-in features have CSS classes starting with 'croqstyle', so there's zero risk of contaminating anything on your page if you're not using them.
Improved image block
"Low-res image" and the image resolution (300x300) written on a pixelated background. When hovered the image changes to say "High-res image" and the new resolution of (1200x1200).
Provides an improved image block with more features, including:
- Hover zoom is built-in, similar to the 'Hover Enlarge' style provided by
Woedenaz' Image Features component
- Built-in alt text.
- Supports showing a higher-resolution image when you hover it (unlike Image Features, which shows the same image but bigger), and even shows a loading icon while the larger image is downloaded.
Please don't make the hover image different to the unhovered image as a jumpscare, that's very cringe. This is an accessibility tool.
To use, insert the following syntax (which closely matches the source of the standard image block):
[[div class="croqstyle-image-block scp-image-block block-right" style="--aspect-ratio: 1 / 1; --image-url: url(lowres.png); --image-large-url: url(highres.webp);"]]
[[div class="scp-image-alt"]]
Alt text goes here, which describes the content of the image for non-sighted users et al - e.g. if the image contains information, write it in plaintext here; or if the image invokes a particular feeling, describe that feeling here.
[[/div]]
[[div class="scp-image-caption"]]
Image caption
[[/div]]
[[/div]]
Replace block-right with block-left or block-center for left and centre alignment respectively. You can put a fallback image inside .scp-image-alt using standard [[image]] syntax if you like.
If you don't want to provide a different image for the large version, you can omit the --image-large-url CSS variable — the image in --image-url will be used both when zoomed and not zoomed.
You can also add class croqstyle-image-block--no-hover to disable the hover zoom feature and show the high res image all the time, but that's for a very niche use case — probably just use the standard image block instead.
For advanced CSS users:
Note that the aspect ratio and image URLs are set via CSS variables. In this example they're set by the style attribute, but you can set them however you like (e.g. via a class defined in a CSS module).
For advanced image editors:
If the larger image is a WEBP, make sure that it has the transparency feature enabled - otherwise, when it starts loading, the rest of the smaller image will get blanked out until it's finished loading.
To enable the transparency feature, before you convert the image to WEBP, just make sure it has at least one pixel that's at least partially transparent.
You can do that in image editing software, alternatively here's an ImageMagick command that does it:
convert -alpha on -channel A -fx 'j==h-1 && i==w-1 ? A*0.99 : A' image_original.png image_original_with_transparency.png
cwebp -q 80 image_original_with_transparency.png -o image.webp
Image footnotes
Provides a class that adds to stuff. They're like regular footnotes, but for images.
[[span class="croqstyle-img-footnote" style="--aspect-ratio: 1 /1; --image-url: url(lowres.png);"]]Example.[[/span]]
It uses the same CSS variables as the 'improved image block' feature above, so it's handy to define a CSS class per image, and then you can use that for both features.
You can actually already put images in footnotes using standard Wikidot syntax:
[[footnote]][[image imagegoeshere.png]][[/footnote]]
That looks like this.
The croqstyle-img-footnote class provided by Croqstyle is a little bit different to regular footnotes:
- It highlights a word or phrase as the context, not just a number
- It doesn't add the footnote to the footnote list, giving you the freedom to repeat image footnotes as often as you like without clogging up the bottom of the page.
They work well as 'reminder footnotes' to help the reader recall what e.g. an SCP looks like, without needing to scroll back up to the original picture.
Simple responsive div
Provides some classes for a simple responsive div that shows one set of contents for desktop users and another for mobile users.
Here's the markup:
[[div class="croqstyle-responsive-div"]]
[[div class="croqstyle-responsive-div__desktop" style="display: none"]]
Desktop content goes here
[[/div]]
[[div class="croqstyle-responsive-div__mobile"]]
Mobile content goes here
[[/div]]
[[/div]]
(That display: none style is there so that if this get copied onto a page without croqstyle, or if croqstyle gets removed from the page, it gracefully falls back to showing the mobile version without breaking anything)
The 'mobile' style will be activated any time the page content area is less than 550 pixels wide. That takes into account not only the screen width but also themes and whether or not the sidebar is present (e.g. you know that one breakpoint at about ~800px where the desktop sidebar appears and there's only a bit of space for the content?) so it should be pretty resilient to anything you throw at it.
Do note that .croqstyle-responsive-div must be a div as wide as the full page that wraps any responsive content, and you might even be able to wrap your entire page in it. However, __desktop and __mobile can be e.g. spans, if you need them to be; and you can include as many of them as you like.
Here's a way you can use .croqstyle-responsive-div to make responsive tables.
Consider a wide table (either with lots of columns, or a few columns with a lot of text). It's impossible to display this on smaller screens because the table is just wider than your average mobile device.
In this situation, you want to keep that table layout on desktop, but on mobile it would be better to present that data as one smaller table per row of the original table.
This is very similar to
Woedenaz' responsive-tables, with the following differences:
- The markup is way, way, way less complicated and verbose - it's just normal tables and a few wrapping divs
- You need to put the CSS on the page as well
- It's just tables, not totally-custom from-scratch grid styling, so they look identical to normal tables and all pre-existing table CSS automatically applies
- (Biggest downside) The table content is repeated for the desktop and mobile version, so if you change one, you need to remember to change the other
The following JavaScript snippet will convert a simple Wikidot table with the headers at the top, e.g. ->
to several smaller tables, one per row of the original table, e.g. ->
Here's the JavaScript snippet. To convert the desktop table to the mobile tables, copy this, replace PASTE TABLE HERE with the table source (leave the backticks in place), paste that into your JS console and hit go; the output will be copied to your clipboard, paste that into your page (replacing the original table).
(() => {
const desktopTable = `PASTE TABLE HERE`
const rowToCells = row => row.split(/\s*\|\|~?\s*/).slice(1, -1)
const [header, ...rows] = desktopTable
.replace(/ _\n/g, "\\n")
.split(/[\r\n]+/)
.map(rowToCells)
.map(row => row.map(cell => cell.replace(/\\n/g, " _\n")))
const mobileTables = rows.map(row => {
return row.reduce((table, cell, cellIndex) => {
return `${table}\n||~ ${header[cellIndex]} || ${cell} ||`
}, "")
})
const output = [
'[[div class="croqstyle-responsive-div"]]',
'[[div class="croqstyle-responsive-div__desktop" style="display: none"]]',
desktopTable,
'[[/div]]',
'[[div class="croqstyle-responsive-div__mobile"]]',
...mobileTables,
'[[/div]]',
'[[/div]]',
].join("\n")
console.log(output)
copy(output)
})()
To switch out a single word/phrase on desktop or mobile:
[[div class="croqstyle-responsive-div"]]
You are currently browsing on [[span class="croqstyle-responsive-div__desktop" style="display: none"]]desktop[[/span]][[span class="croqstyle-responsive-div__mobile"]]mobile[[/span]], probably!
[[/div]]
You are currently browsing on desktopmobile, probably!
Reset tabview
This utility CSS-resets a tabview — i.e., it removes all CSS styling from it. I always find it a pain to dig through the way Wikidot styles tabs and undo bits of it every time I want to style them. Using this, that work is already done.
To reset a tabview, wrap it in .croqstyle-reset-tabs:
[[div class="croqstyle-reset-tabs"]]
[[tabview]]
[[tab First tab]]
Content 1
[[/tab]]
[[tab Second tab]]
Content 2
[[/tab]]
[[/tabview]]
[[/div]]
This doesn't undo the CSS that hides/shows each the tab contents, but you could reset it yourself if you wanted to replace it with your own animation:
[id^=wiki-tab] { display: initial !important }
Minimal selectors for each tabview part:
Part |
Selector |
The entire tabview |
.yui-navset |
List of tabs |
.yui-navset .yui-nav |
Tab |
.yui-navset .yui-nav li a |
Tab (selected) |
.yui-navset .yui-nav .selected a |
Tab (hovered) |
.yui-navset .yui-nav a:is(:hover, :focus) |
Tab (selected + hovered) |
.yui-navset .yui-nav .selected :is(a, a:focus, a:hover) |
Text in each tab |
.yui-navset .yui-nav li a em |
Text in the selected tab |
.yui-navset .yui-nav .selected a em |
Tab content display area |
.yui-navset .yui-content |
Content of the selected tab |
[id^=wiki-tab][style*=block] |
Content of unselected tabs |
[id^=wiki-tab][style*=none] |
To style only tabviews that have been reset, prepend :where(.croqstyle-reset-tabs) to each selector, e.g. :where(.croqstyle-reset-tabs) .yui-navset
To style a specific tabview, or if providing a custom opt-in tabview for a theme, do the same thing but with a different class, e.g. :where(.special-fancy-tabs) .yui-navset
Here's some tabs:
Here's those same tabs with the reset class applied: