/* ========================================================================== 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); } .json-null { opacity: 50%; } /* -- 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); } /* -- Tables --------------------------------------------------------------- */ table { inline-size: 100%; border-collapse: collapse; } :is(th, td) { text-align: start; padding: var(--s-2) var(--s-1); padding-inline-start: 0; border-block-end: var(--border-width) solid var(--border); vertical-align: baseline; } tbody tr:last-child :is(th, td) { border-block-end: none; } /* A table with more columns than the page has room for scrolls inside its own box rather than pushing the whole page sideways. */ .table-scroll { overflow-x: auto; } /* -- rvrb stats ----------------------------------------------------------- */ .status-up { color: var(--green); } .status-idle { color: var(--info); } .status-down { color: var(--error); } .muted { opacity: 50%; } .now-playing { display: flex; flex-wrap: wrap; gap: var(--s0); align-items: start; } /* Overrides the block margin and auto-centring the base `img` rule gives every image, which would otherwise push the art away from the track it belongs to. */ .now-playing .album-art { margin: 0; flex: none; inline-size: 8em; block-size: 8em; border-radius: var(--radius); } .now-playing > div { flex: 1 1 20ch; } progress { inline-size: 100%; block-size: var(--s-1); } /* Counts big enough to read at a glance, laid out as many per row as fit. */ .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(10ch, 1fr)); gap: var(--s-1); margin-block: 0; } .stats dt { opacity: 60%; } .stats dd { margin-inline-start: 0; font-size: calc(1em * var(--ratio)); } /* Plays per day. One
  • per day, so a quiet day is an empty column rather than a gap - see Rvrb.Stats.plays_per_day/1. A grid rather than a flex row, because a bar's height is a percentage and can only resolve against a track with a height of its own. Sharing one 6em box with the labels made the two compete for it: flex shrank whichever bar was tall enough to overflow *and* the labels under it, so the tallest bars ended up hanging below the baseline the rest stood on. */ .bars { /* A container, so the labels below can react to how much room each column actually got rather than to the width of the viewport. */ container-type: inline-size; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; /* the bar track, then a row each for the count and the weekday */ grid-template-rows: 6em auto auto; /* Column gap only: the labels belong against the bar they describe. */ gap: 0 var(--border-width); margin-block: 0; padding-inline: 0; list-style: none; } /* subgrid, so every column's count and weekday line up on the same two rows however tall the bar above them is. */ .bars li { display: grid; grid-row: span 3; grid-template-rows: subgrid; align-items: end; justify-items: center; } .bars .bar { inline-size: 100%; background-color: var(--color); } .bars :is(.bar-label, .bar-day) { font-size: 0.7em; opacity: 60%; } /* Two weeks of columns in one text column: the day has to give way before the count does, since the count is the number and the day is the axis. */ .bars .bar-day { overflow: hidden; max-inline-size: 100%; /* Clipped rather than wrapped: three letters over two lines would push the bars up and misalign every column. */ white-space: nowrap; } .bars .day-initial { display: none; } /* "Thu" wants about 3.2ch, so a fortnight of them needs roughly this much chart before the columns stop being wide enough to hold three letters. On a phone they don't, and the day falls back to its initial rather than being clipped mid-letter. */ @container (width < 21rem) { .bars .day-abbrev { display: none; } .bars .day-initial { display: inline; } } /* -- 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; }