/* ==========================================================================
bes.is — a small terminal-flavoured stylesheet.
1. Tokens design decisions, in one place
2. Reset & base the same starting point in every browser
3. Typography headings, text, lists, links
4. Layout page frame and layout utilities
5. Controls buttons, fields, everything the browser styles for us
6. Components header, footer, log, panels, json, dialogs
7. Media logo, images, video
========================================================================== */
/* 1. Tokens ================================================================ */
:root {
/* Tells the browser to theme native UI (scrollbars, select popups, focus
rings, spinners) to match the page. Without it those stay light in a
dark page, which is the main reason controls look different per
browser. It also enables light-dark() below. */
color-scheme: light dark;
--black: #050505;
--white: #fafafa;
--background: light-dark(var(--white), var(--black));
--color: light-dark(var(--black), var(--white));
--border: var(--color);
--error: light-dark(#b00000, #ff6b6b);
--info: light-dark(#3d6f8e, #86b6d3);
--green: light-dark(#2f7a2d, #6fce6d);
/* Modular scale. Every gap, margin and padding comes from here so
spacing stays proportional to the fluid type size. */
--ratio: 1.5;
--s-2: calc(var(--s-1) / var(--ratio));
--s-1: calc(var(--s0) / var(--ratio));
--s0: 1rem;
--s1: calc(var(--s0) * var(--ratio));
--s2: calc(var(--s1) * var(--ratio));
/* A concrete stack instead of bare `monospace`: browsers disagree both on
which font that is and on its default size. */
--font: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--measure: 80ch;
--radius: 0.5em;
--border-width: 1px;
--rule: 2px dotted var(--border);
}
/* 2. Reset & base ========================================================== */
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: var(--font);
font-size: calc(1rem + 0.4vw);
line-height: 1.5;
-webkit-text-size-adjust: 100%;
tab-size: 4;
}
body {
margin: 0;
min-block-size: 100dvh;
display: flex;
flex-direction: column;
gap: var(--s0);
padding-block: var(--s0);
background-color: var(--background);
color: var(--color);
}
/* One focus style for every focusable thing, instead of six browser defaults.
`:focus:not(:focus-visible)` keeps programmatic focus (FocusOnNavigate
lands on the
after every navigation) from flashing a ring. */
:focus-visible {
outline: 2px solid var(--color);
outline-offset: 2px;
}
:focus:not(:focus-visible) {
outline: none;
}
::selection {
background-color: var(--color);
color: var(--background);
}
/* 3. Typography ============================================================ */
h1,
h2,
h3,
h4,
h5,
h6 {
margin-block: 0 var(--s-1);
font-weight: 700;
line-height: 1.2;
text-wrap: balance;
}
h1 {
font-size: calc(1em * var(--ratio));
}
h2 {
font-size: calc(1em * 1.25);
}
h3,
h4,
h5,
h6 {
font-size: 1em;
}
p {
margin-block: var(--s-1);
text-wrap: pretty;
}
ul,
ol {
margin-block: var(--s-1);
padding-inline-start: var(--s1);
}
pre {
margin-block: var(--s-1);
/* pre-line, so examples can stay indented in the .razor source */
white-space: pre-line;
overflow-wrap: anywhere;
}
small {
font-size: 0.75em;
}
a {
color: inherit;
text-decoration-line: underline;
text-decoration-thickness: var(--border-width);
text-underline-offset: 0.2em;
}
a:hover,
a:focus-visible {
background-color: var(--color);
color: var(--background);
text-decoration: none;
}
/* 4. Layout ================================================================ */
.center {
box-sizing: content-box;
max-inline-size: var(--measure);
margin-inline: auto;
padding-inline: var(--s1);
}
main {
flex: 1;
border-block-start: var(--rule);
border-block-end: var(--rule);
padding-block: var(--s0);
}
.flex-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--s0);
}
/* A field shares the row instead of claiming a whole one to itself. */
.flex-row > :is(input[type="text"], textarea) {
flex: 1;
}
.flex-spread {
display: flex;
flex-wrap: wrap;
gap: var(--s0);
justify-content: space-between;
}
.row + .row {
margin-block-start: var(--s0);
}
/* Two columns, but only while they stay wide enough to read. */
.two-column {
columns: 2 24ch;
gap: var(--s1);
}
.noselect {
user-select: none;
-webkit-user-select: none;
}
/* 5. Controls ============================================================== */
/* Controls do not inherit the page font on their own — this is what keeps
fields and buttons from turning into system UI in each browser. */
button,
input,
select,
textarea {
font: inherit;
color: inherit;
background-color: var(--background);
border: var(--border-width) solid var(--border);
border-radius: var(--radius);
padding: 0.1em 0.5em;
max-inline-size: 100%;
}
label {
display: block;
}
input[type="text"] {
/* min-inline-size lets it shrink inside a .flex-row instead of overflowing */
inline-size: 100%;
min-inline-size: 0;
}
textarea {
display: block;
inline-size: 100%;
min-block-size: 4lh;
resize: vertical;
white-space: pre-wrap;
}
/* A block textarea leaves its button hard against it otherwise. */
textarea + button,
textarea + input[type="submit"] {
margin-block-start: var(--s-1);
}
/* Editors sized against the viewport rather than a row count. */
.editor {
block-size: 20vh;
}
.editor-tall {
block-size: 60vh;
}
select {
cursor: pointer;
}
button,
input[type="button"],
input[type="submit"],
input[type="reset"] {
min-inline-size: 8ch;
padding-inline: var(--s0);
text-align: center;
cursor: pointer;
}
button:hover,
input[type="button"]:hover,
input[type="submit"]:hover,
input[type="reset"]:hover {
background-color: var(--color);
color: var(--background);
}
button:active,
input[type="button"]:active,
input[type="submit"]:active,
input[type="reset"]:active {
translate: 0 1px;
}
/* The file input itself is a bare label for its button. */
input[type="file"] {
border: none;
background: none;
padding: 0;
cursor: pointer;
}
/* Kept in its own rule: an unsupported pseudo-element would invalidate the
whole selector list if it shared one with the buttons above. */
::file-selector-button {
font: inherit;
color: inherit;
background-color: var(--background);
border: var(--border-width) solid var(--border);
border-radius: var(--radius);
min-inline-size: 8ch;
padding: 0.1em var(--s0);
margin-inline-end: var(--s-1);
cursor: pointer;
}
::file-selector-button:hover {
background-color: var(--color);
color: var(--background);
}
input[type="checkbox"] {
appearance: none;
inline-size: 3ch;
padding: 0;
border: none;
background: none;
text-align: center;
cursor: pointer;
}
input[type="checkbox"]::after {
content: "[ ]";
}
input[type="checkbox"]:checked::after {
content: "[x]";
}
fieldset {
/* min-inline-size overrides the min-content floor browsers give fieldsets,
which otherwise makes them overflow flex and grid containers. */
min-inline-size: 0;
margin: 0;
border: var(--border-width) solid var(--border);
border-radius: var(--radius);
padding: var(--s-1) var(--s0);
}
legend {
padding-inline: var(--s-2);
}
legend::before {
content: "( ";
}
legend::after {
content: " )";
}
summary {
cursor: help;
}
summary::marker {
content: "> ";
}
details[open] > summary::marker {
content: "x ";
}
/* A heading is allowed to be a summary without breaking the line. */
summary > :is(h1, h2, h3, h4, h5, h6) {
display: inline;
}
/* 6. Components ============================================================ */
/* -- Site header --------------------------------------------------------- */
header {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--s0);
}
header > h1 {
margin-block: 0;
}
/* -- Site footer --------------------------------------------------------- */
/* Markers stay outside the text column, in a gutter wide enough for the
longest of them ("==> "). That way the arrow slides right on hover and
focus while the link itself never moves. The column gap is measured in the
same unit, so the second column's markers cannot reach the first. */
footer ul {
columns: 2 20ch;
padding-inline-start: 4ch;
column-gap: 5ch;
}
footer li::marker {
content: "> ";
}
footer li:has(a:hover)::marker {
content: " >";
}
footer li:has(a:focus)::marker {
content: "==> ";
}
footer li:has(a:hover:focus)::marker {
content: "==>";
}
/* NavLink marks the current page for us. */
footer li:has(a.active)::marker {
content: "* ";
}
/* -- Panels (bordered boxes with a title) -------------------------------- */
.panel {
margin-block: var(--s1);
border: 2px dashed var(--border);
padding: var(--s-1) var(--s0);
}
/* -- Log ------------------------------------------------------------------ */
#log {
margin-block-start: var(--s0);
}
.error::before,
.info::before,
.debug::before {
/* not selectable, so copying a log line copies the message only */
user-select: none;
-webkit-user-select: none;
}
.error::before {
content: "ERR: ";
color: var(--error);
}
.info::before {
content: "INF: ";
color: var(--info);
}
.debug::before {
content: "DBG: ";
color: var(--info);
}
/* Debug lines stay hidden until the checkbox next to the log is ticked. */
label:has(> #debug:not(:checked)) + #log .debug {
display: none;
}
/* Earlier runs, folded away and dimmed. */
.history {
opacity: 30%;
}
* + .history {
margin-block-start: var(--s1);
}
/* -- Rendered JSON -------------------------------------------------------- */
li.dash {
list-style-type: "- ";
}
span.name {
opacity: 50%;
}
.json-true {
color: var(--green);
}
.json-false {
color: var(--error);
}
.json-number {
color: var(--info);
}
/* -- Dialogs -------------------------------------------------------------- */
dialog {
background-color: transparent;
color: var(--color);
border: none;
padding: 0;
max-inline-size: min(100% - var(--s2), 60ch);
}
dialog::backdrop {
backdrop-filter: blur(calc(var(--s1) / 20));
}
/* The panel inside supplies the surface, so the dialog itself can stay bare. */
dialog > .panel {
margin-block: 0;
background-color: var(--background);
}
dialog > .panel > legend {
background-color: var(--background);
border-radius: var(--radius);
}
/* -- Storage ------------------------------------------------------------- */
.grid-stores {
display: grid;
grid-template-columns: 1fr auto auto;
align-items: center;
gap: var(--s-1) var(--s0);
}
/* -- Scroll shortcuts ------------------------------------------------------ */
/* Docked to the viewport edge rather than the page flow, so it stays
reachable no matter how far down a long page (e.g. Query's history and
favorites) you've scrolled. */
.scroll-nav {
position: fixed;
inset-inline-end: var(--s0);
inset-block-start: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: var(--s-1);
z-index: 100;
}
.scroll-nav button {
line-height: 1;
padding: var(--s-1);
}
/* 7. Media ================================================================= */
#logo {
block-size: 5em;
flex: none;
}
img {
display: block;
max-inline-size: 100%;
block-size: auto;
margin-inline: auto;
margin-block: var(--s0);
}
video {
display: block;
inline-size: 100%;
block-size: auto;
border-radius: var(--radius);
}
#qrcode {
margin-block: var(--s0);
text-align: center;
}
#qrcode :is(canvas, table) {
margin-inline: auto;
}