/* The site's language: the wireframe field (js/spheres.js) — the orrery on the landing page, the sky
   alone on every other page — with the design's hero rectangle kept whole on top of it, the account
   corner, the reading view for post.html and the sheet the other four pages wear. */

@font-face {
  font-family: "Bethany Elingston";
  src: url("../fonts/Bethany%20Elingston.otf") format("opentype");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

/* WHITE IS THE DEFAULT SCHEME (2026-09-25, by request): the variables live here as the white phosphor,
   and amber — which used to be what an untouched page painted — is one of the overrides below. js/theme.js
   writes data-phosphor before the first paint, so a reader with a saved choice sees that scheme; with no
   script at all, or before it runs, the page is white. */
:root {
  --bg: #080808;
  --fg: #e6e6e6;
  --dim: #9a9a9a;
  --glow: rgba(230, 230, 230, 0.3);
  --danger: #ff5555;
  /* The accent: what a WRITEUP ITSELF is written in — the reading page's own title, and the headings
     its Markdown steps down through (2026-09-25, by request: "post titles on the planets should remain
     white. Only WITHIN the blog post the titles should be red", and "the same counts for the smaller
     titles in markdown indicated with multi hashtags"). Nothing on the field wears it: a post's title
     over a planet is a label like any other and keeps the field's colour. It is one red in every scheme
     on purpose: a title is the same thing whichever phosphor the reader chose, and the colour is what
     says "this is the writeup" against a page that is otherwise one hue. Red and --danger (a refusal, a
     Delete) are neighbours and deliberately not the same value. */
  --accent: #ff4d4d;
  --accent-glow: rgba(255, 77, 77, 0.34);
  --edge: color-mix(in srgb, var(--fg) 40%, transparent);
  --edge-dim: color-mix(in srgb, var(--fg) 22%, transparent);
  --wire: color-mix(in srgb, var(--fg) 55%, transparent);
  --logo-filter: grayscale(1) contrast(1.25) brightness(1.6);
  /* Syntax highlighting for the code a post shows (js/highlight.js puts these classes on the tokens).
     The block's own text stays --fg, so code that is not a word the highlighter knows still reads in
     the site's voice; everything it DOES know steps out of that voice, which is the whole point of
     them being here: a keyword must not look like the prose it sits in.
     THE PALETTE IS THE ACCENT'S OWN FAMILY (2026-09-25, by request: "the syntax highlighting should be
     more in accordance with the red color of the titles"). A fence is the writeup in its own voice, so
     it is written in the writeup's colour: one arc from the accent's red through crimson and rose, with
     the tokens told apart by DEPTH — how bright, how saturated — rather than by hue. The comment is the
     drained end, the keyword the accent itself, and a string is still recognisable as a string by its
     quotes and a number by its digits, so a fence reads as one colour that means "code" instead of a
     second palette arguing with the page. The set is the same in every scheme, for the same reason
     --accent is: the writeup is the same thing whichever phosphor the reader chose. That also pins the
     arc: the tokens have to hold against all three prose colours at once, and going any further round
     the wheel than rose would collide with one of them — an orange type would read as prose on amber,
     and a green one as prose on green. */
  --syn-com: #8a6169; /* comments: the writeup's colour, drained — the dimmest thing on the plate */
  --syn-kw: #ff4d4d; /* keywords and instructions: the accent itself, the loudest word in a line */
  --syn-typ: #ff5c8a; /* types, Windows typedefs, registers: the accent turned toward crimson rose */
  --syn-str: #ff9ec2; /* strings and character literals: the accent turned toward rose */
  --syn-num: #ffc2c2; /* numbers: the accent, palest of the set — a number is a number by its digits */
  --syn-pre: #c987a0; /* preprocessor lines: dusty rose, half the keyword's voice */
  --syn-fn: #ff8a8a; /* a name that calls something: the accent, one step lighter */
}
html[data-phosphor="amber"] {
  --bg: #0c0800;
  --fg: #ffb000;
  --dim: #c07d1a;
  --glow: rgba(255, 176, 0, 0.38);
}
html[data-phosphor="green"] {
  --bg: #030a05;
  --fg: #33ff66;
  --dim: #27b851;
  --glow: rgba(51, 255, 102, 0.4);
}

html {
  background: var(--bg);
}

body {
  /* The page owns its own box: the UA's 8px margin would sit outside every fixed layer's alignment
     and, with the column's 100vh floor, hand the site a strip of empty scroll. */
  margin: 0;
  /* The site's type: phosphor monospace, with Bethany Elingston for the marks and page titles. */
  font-family: "Lucida Console", "Consolas", "Courier New", monospace;
  font-size: 14px;
}

/* Keyboard focus, the tube's own: a dashed rectangle rather than the browser's ring. */
:focus-visible {
  outline: 1px dashed var(--fg);
  outline-offset: 2px;
}

/* A SCROLL BAR OF THE HOUSE'S OWN (2026-09-27, by request — "make a custom scroll bar" — and then "put
   the scroll bar in a nice box, and center it better"): the platform's grey slab is replaced wherever a
   scroll can happen — the window itself, the panel's body over the field, a table wider than its column,
   a sheet's own fields — by one of the house's own plates: the TRACK is a box, the same
   color-mix(--bg 60%) ground and 1px --edge-dim rule a fence or a table sits on, so the bar reads as
   something the site drew rather than something the platform did; the THUMB is the house's dashed rule
   drawn in --edge, which goes bright --fg under the house's own --glow while the pointer is on it (NOT
   --accent: that red is what a writeup is written in, and the bar is chrome). The thumb is centered in
   the box, not just in the bar: a 14px bar with the track's 1px rule leaves 12px of ground, and the
   thumb's 4px transparent border (with background-clip holding the paint inside it) puts a 6px dashed
   line 3px from either side of that ground. The dashes run along the bar's own axis — a
   repeating-linear-gradient to bottom on a vertical bar, to right on a horizontal one — and
   ::-webkit-scrollbar-button is display: none, so Windows's end arrows are gone: a scroll bar here is a
   rule you drag on a plate, not a control panel. Firefox has no pseudo-elements for this, so it takes
   the same two colours and thinness through scrollbar-width / scrollbar-color, gated behind @supports
   (-moz-appearance: none) — the one test only Firefox passes, because Chrome supports both properties
   now and setting them there would switch its own dashed thumb off. The dashes and the box are the two
   things that cannot cross to it. */
::-webkit-scrollbar {
  width: 14px;
  height: 14px;
}
::-webkit-scrollbar-track {
  box-sizing: border-box;
  background: color-mix(in srgb, var(--bg) 60%, transparent);
  border: 1px solid var(--edge-dim);
}
::-webkit-scrollbar-corner {
  background: color-mix(in srgb, var(--bg) 60%, transparent);
}
::-webkit-scrollbar-button {
  display: none;
}
::-webkit-scrollbar-thumb {
  border: 4px solid transparent;
  background-clip: content-box;
  background-image: repeating-linear-gradient(to bottom, var(--edge) 0 6px, transparent 6px 10px);
}
::-webkit-scrollbar-thumb:horizontal {
  background-image: repeating-linear-gradient(to right, var(--edge) 0 6px, transparent 6px 10px);
}
::-webkit-scrollbar-thumb:hover {
  background-image: repeating-linear-gradient(to bottom, var(--fg) 0 6px, transparent 6px 10px);
  box-shadow: 0 0 8px var(--glow);
}
::-webkit-scrollbar-thumb:horizontal:hover {
  background-image: repeating-linear-gradient(to right, var(--fg) 0 6px, transparent 6px 10px);
}
@supports (-moz-appearance: none) {
  html {
    scrollbar-width: thin;
    scrollbar-color: var(--edge) color-mix(in srgb, var(--bg) 60%, transparent);
  }
}

/* The fixed field: full viewport, over the page background and under the
   vignette, never in the way of the pointer. */
#sphere-field {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  display: block;
  pointer-events: none;
}

/* ---- the hero rectangle, kept whole and anchored in the top left corner ----
   Every value below is the design's own, at the same 2.6rem em base, so this is
   the mark the hero showed rather than a redraw. The only change is where it
   sits: the corner instead of the centre of the page. */
#hero {
  position: fixed;
  top: 16px;
  left: 16px;
  /* 100px WIDER SINCE 2026-09-27 (by request: "You can make the blog post previews like 100px wider,
     along with the banner") — the banner is the column the field's panel opens in, so the two widen
     together: the panel takes its width from this box (js/spheres.js's placeWindow), and the type inside
     is left exactly as it was, the design's own 2.6rem, so the plate has more room to the right than the
     name needs. What is measured below about the scaling still holds below 652px, where the box is the
     window's own width either way. */
  width: min(620px, calc(100vw - 32px));
  /* THE BANNER SCALES WITH ITSELF (2026-09-25, by request — "The skeletal computing banner does not
     scale when the window is smaller, please make sure it scales well"): the box was always
     min(520px, 100vw - 32px) and the type inside it was pinned at the design's 2.6rem, so below 552px
     the box shrank and the title did not — it ran out of room and wrapped. The size is the box's own
     now: one custom property, the design's 2.6rem wherever there is room for it and the same fraction
     of the box below that. 8.6vw - 6px is what keeps the title's width proportional to the space it is
     written in (the box is 100vw - 32px with 18px of padding either side, and the title fills about
     0.62 of what is left; at 552px this is 41.5px, which is the 2.6rem the design asks for). The floor
     is where a phone stops being able to read it — unreadably small is worse than a second line. */
  --mark-size: clamp(1.25rem, calc(8.6vw - 6px), 2.6rem);
  /* The site is content-box, so a 100vw - 32px width PLUS 18px of padding either side and a border came
     to 100vw + 6px: measured at 552px and below, the banner's right edge sat six pixels outside the
     window. Border-box is what the width was always meant to be. */
  box-sizing: border-box;
  border: 1px solid var(--edge);
  box-shadow: 0 0 16px var(--glow);
  padding: 14px 18px 16px;
  text-align: left;
  /* The banner is type, not a control: it takes no pointer, so a label that has drifted under it
     can still be pressed — its own links keep their pointer-events and answer the click, and a
     click on the banner's empty area falls through to the field, which listens on the window. */
  pointer-events: none;
}

#mark {
  position: relative;
  display: flex;
  align-items: flex-end;
  /* Reference size: the cross geometry, the shelf and the title's own indent are all in em of this, so
     the whole mark — crucifix included — scales with the banner (see --mark-size). */
  font-size: var(--mark-size);
  padding: 0 0 calc(0.072em + 1px) 0.865em;
  margin: 2px 0 0.4rem;
}

/* Crossbar: solid shelf under the title. */
#mark::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: calc(0.072em + 1px);
  background: var(--fg);
  box-shadow: 0 0 8px color-mix(in srgb, var(--fg) 55%, transparent);
}

/* Upright beam of the crucifix: up-stick above the title, foot below the
   crossbar. It is also the one part of the banner that answers the pointer — the rest of the hero
   is pointer-transparent on purpose, so a label drifting behind it stays pressable — and three
   clicks on it show a verse (js/verse.js). The beam is three pixels wide, so ::after widens the
   hit area either side rather than asking anyone to hit a hairline. */
#mark-cross {
  position: absolute;
  left: 0.29em;
  top: 0.79em;
  bottom: calc(-0.72em + 2px);
  width: calc(0.072em + 1px);
  background: var(--fg);
  box-shadow: 0 0 8px color-mix(in srgb, var(--fg) 55%, transparent);
  pointer-events: auto;
  cursor: pointer;
}

#mark-cross::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -16px;
  right: -16px;
}

/* Short upper crossbar near the top of the beam. */
#mark-cross::before {
  content: "";
  position: absolute;
  left: 50%;
  top: calc(0.19em + 2px);
  width: 0.29em;
  height: 0.096em;
  transform: translateX(-50%);
  background: var(--fg);
  box-shadow: 0 0 8px color-mix(in srgb, var(--fg) 55%, transparent);
}

.mark-title {
  position: relative;
  z-index: 1;
  font-family: "Bethany Elingston", cursive;
  font-weight: 400;
  color: var(--fg);
  /* The mark's own size, whatever the window is: the title IS the mark it is drawn in. */
  font-size: 1em;
  line-height: 1.2;
  margin: 0;
  text-shadow: 0 0 12px var(--glow);
}

.mark-subtitle {
  margin: 0;
  /* The design's 36px indent, which is 0.865em of the mark — written in the mark's own size so it stays
     under the title's first letter as the banner scales. This element is a SIBLING of #mark, not a
     child, so it cannot inherit that size: it borrows the same property. */
  padding-left: calc(0.865 * var(--mark-size));
  font-weight: bold;
  color: var(--dim);
}

/* The verse the cross keeps: hidden until the mark has been clicked three times (js/verse.js), then
   the verse and its reference, in the banner's own voice and on the subtitle's left edge. The banner
   grows to hold it, and the post panel measures the banner when it opens, so the column follows. */
.mark-verse {
  margin: 10px 0 0;
  padding-left: calc(0.865 * var(--mark-size));
  max-width: 46ch;
  color: var(--fg);
  line-height: 1.5;
  animation: verse-in 0.55s ease-out;
}

.mark-verse[hidden] {
  display: none;
}

.verse-ref {
  color: var(--dim);
}

@keyframes verse-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .mark-verse {
    animation: none;
  }
}

/* The banner's own type sits on a plate of the page's colour, the way the design's masthead name
   did, and the plate covers the WHOLE hero rectangle: the field's labels pass behind the banner —
   they are drawn under the page's content on purpose — and without this a post title reads
   straight through the site name, the subtitle, or the empty space between them. The rectangle
   stays a plate over the field, not a filled box: it is the page colour at 86%, so the field is
   still there behind it, just dimmed out of the way. */
#hero {
  background: color-mix(in srgb, var(--bg) 86%, transparent);
}

/* ---- the account corner, top right ----
   The hero's surface (border plus glow, no fill) holding the one control the landing page has. Signed
   out it is the Log in link the markup carries; signed in (js/user-bar.js) it is the account's own name
   and a DROPDOWN under it (2026-09-26, by request) — the corner used to be a row of six controls, which
   is what pushed it off the edge of a narrow window. Everything is scoped to #user-bar so the sheets,
   which use the same ids, keep the same control. */
#user-bar {
  position: fixed;
  top: 16px;
  right: 16px;
  display: flex;
  align-items: center;
  padding: 10px 12px;
  border: 1px solid var(--edge);
  box-shadow: 0 0 16px var(--glow);
  font-size: 13px;
}

#user-bar #nav-user {
  display: flex;
  align-items: center;
}

/* The logged-in container holds the trigger, so the hidden attribute needs the explicit rule. */
#user-bar #nav-user[hidden] {
  display: none;
}

#user-bar a {
  color: var(--fg);
  text-decoration: none;
}

/* The trigger is the account's name in the site's bracketed idiom, with the caret a menu is read by. */
#user-bar .nav-account {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--fg);
  cursor: pointer;
}

#user-bar .nav-account::before {
  content: "[ ";
}

/* The caret is the character itself, not an escape: CSS reads \u25be as an escaped "u" followed by
   "25be", which is what the trigger read for a minute. */
#user-bar .nav-account::after {
  content: " ▾ ]";
}

#user-bar a:hover,
#user-bar .nav-account:hover {
  text-shadow: 0 0 8px var(--glow);
}

#user-bar #nav-login::before {
  content: "[ ";
}

#user-bar #nav-login::after {
  content: " ]";
}

/* ---- the account dropdown ----
   The panel the picker on the people sheet already uses: the page's own ground, one --edge border and a
   drop shadow, fixed so it is never clipped by the corner's box. js/user-bar.js places it against the
   trigger's rectangle and turns it upward when the corner sits at the bottom of the window. */
.nav-menu {
  position: fixed;
  z-index: 5;
  box-sizing: border-box;
  min-width: 13em;
  /* THE MENU KEEPS ITS OWN BOUNDS (2026-09-27, by request — "the dropdown menu scrollbar is out of the
     menu bounds on the profile dropdown menu"): the menu had no ceiling, so a long one — the people
     sheet's account picker, or the account corner on a short window — ran past the bottom of the window
     with no way to reach the rows below the fold; it now takes the room between two 16px margins and
     scrolls inside itself, which is what puts the house's boxed scroll bar inside the menu's own edge
     rather than hanging outside it. */
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  padding: 4px;
  background: var(--bg);
  border: 1px solid var(--edge);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.65);
  font-size: 13px;
}

.nav-menu[hidden] {
  display: none;
}

.nav-menu-item {
  display: block;
  /* The rows were content-box: width: 100% plus their own 9px of side padding made every row 18px wider
     than the menu's content box, so each one painted out past the menu's right border — the rows are
     border-box now and stop at the menu's own edge, a scroll bar or not (2026-09-27, with the bounds
     above). */
  box-sizing: border-box;
  width: 100%;
  padding: 5px 9px;
  border: 0;
  background: none;
  font: inherit;
  color: var(--dim);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.nav-menu-item:hover,
.nav-menu-item:focus-visible {
  background: color-mix(in srgb, var(--fg) 14%, transparent);
  color: var(--fg);
}

@media (max-width: 860px) {
  body.landing #user-bar {
    top: auto;
    bottom: 16px;
  }
}

/* ---- the site's column: the reading view (post.html) and the sheet (about / contact / log in /
   new post) ----
   One column of the site's own type, centred across the window and, when it is shorter than the
   screen, down it too — a taller page simply grows the column and the page scrolls from the top
   padding. box-sizing: border-box keeps the 100vh floor around the padding as well: with
   content-box the padding sits outside the floor and hands every page a strip of empty scroll. */
body.reading,
body.sheet {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 56px 20px 48px;
}

/* ---- the reading view (post.html) ----
   The writeup alone, centred on the sky. The column is capped and the type is sized with clamp(), so
   the same page reads on a phone and on a wide monitor, and the sky behind it is the field's stars
   with nothing else drawn (data-field="stars"). */
:is(body.reading, .preview) main {
  width: min(720px, 100%);
}

:is(body.reading, .preview) .post {
  width: 100%;
}

:is(body.reading, .preview) .post-header {
  margin-bottom: 26px;
}

/* The second selector is not redundant: inside a preview a writeup's h1 sits on a SHEET page, whose own
   page-title rule is body.sheet main > h1 — specificity 0-2-2. This one is 0-3-2 (.post and
   .post-header are both on the article in either place), so the writeup's title wins where the two
   meet, while the first selector keeps covering a reading page's h1s that are not in a post header
   (the "not found" block). */
:is(body.reading, .preview) h1,
:is(body.reading, .preview) .post .post-header h1 {
  margin: 0 0 8px;
  font-family: "Bethany Elingston", cursive;
  font-weight: 400;
  font-size: clamp(26px, 4.6vw, 40px);
  line-height: 1.15;
  /* The writeup's own name, in the accent — the writeup is the one place the site carries red
     (see --accent). */
  color: var(--accent);
  text-shadow: 0 0 14px var(--accent-glow);
}

:is(body.reading, .preview) .post-meta {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--dim);
}

:is(body.reading, .preview) .tags {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
  padding: 0 0 12px;
  border-bottom: 1px dashed var(--edge);
  list-style: none;
  font-size: 13px;
}

:is(body.reading, .preview) .tag {
  color: var(--dim);
}

:is(body.reading, .preview) .tag::before {
  content: "[ ";
}

:is(body.reading, .preview) .tag::after {
  content: " ]";
}

/* A WORD TOO LONG FOR THE MEASURE WRAPS (2026-09-27, with the fences — "please make sure it fits in the
   preview window"): a pasted URL or a long identifier in a paragraph used to push the column wider than
   itself, which is the same "the text does not fit" the fences had. overflow-wrap is inherited, so one
   declaration on the reading view's post and on the panel's body covers everything inside them; a fence's
   own code carries overflow-wrap: anywhere on top of it, because a column of code has no spaces to break
   at. */
:is(body.reading, .preview) .post-content,
.post-window-body {
  overflow-wrap: break-word;
}

:is(body.reading, .preview) .post-content p,
body.sheet .sheet-copy p {
  margin: 0 0 1.1rem;
  font-size: clamp(14px, 1.5vw, 16px);
  line-height: 1.75;
  color: color-mix(in srgb, var(--fg) 88%, transparent);
}

/* The body's own blocks (js/wasm reads the Markdown): a heading steps down from the post's title,
   code and quotes sit on plates of the page's own colour, and an image is framed the way the hero
   is. Body text is one measure, so a heading is the only thing that changes size. A heading is a
   title too, so it is written in the accent with the post's own title above it — the `#` a writer
   types is the writeup's own voice, whatever its depth (2026-09-25, by request). */
:is(body.reading, .preview) .post-content h2,
:is(body.reading, .preview) .post-content h3,
:is(body.reading, .preview) .post-content h4,
:is(body.reading, .preview) .post-content h5,
:is(body.reading, .preview) .post-content h6 {
  margin: 2rem 0 0.8rem;
  font-family: "Bethany Elingston", cursive;
  font-weight: 400;
  line-height: 1.2;
  color: var(--accent);
  text-shadow: 0 0 10px var(--accent-glow);
}

:is(body.reading, .preview) .post-content h2 {
  font-size: clamp(22px, 3.4vw, 30px);
}

:is(body.reading, .preview) .post-content h3 {
  font-size: clamp(19px, 2.6vw, 24px);
}

:is(body.reading, .preview) .post-content h4,
:is(body.reading, .preview) .post-content h5,
:is(body.reading, .preview) .post-content h6 {
  font-size: clamp(16px, 2vw, 19px);
}

/* An image a post carries is its own frame — the picture is the content, and the page draws no box
   around it (2026-09-27, by request: "Remove borders from embedded images in markdown"). It keeps the
   glow the rest of the writeup wears, so it still sits in the page's light rather than on it. */
:is(body.reading, .preview) .post-content img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 1.6rem auto;
  box-shadow: 0 0 16px var(--glow);
}

/* A link inside the body is prose, not a control: the page's own colour, underlined, glow on hover —
   the same rule a sheet's copy follows. */
:is(body.reading, .preview) .post-content a {
  color: var(--fg);
  text-underline-offset: 3px;
}

:is(body.reading, .preview) .post-content a:hover {
  text-shadow: 0 0 10px var(--glow);
}

:is(body.reading, .preview) .post-content ul,
:is(body.reading, .preview) .post-content ol {
  margin: 0 0 1.1rem;
  padding-left: 1.6em;
  font-size: clamp(14px, 1.5vw, 16px);
  line-height: 1.75;
  color: color-mix(in srgb, var(--fg) 88%, transparent);
}

:is(body.reading, .preview) .post-content li {
  margin: 0.25rem 0;
}

/* A GFM task list (2026-09-26): the box in front of the line IS the marker, so the bullet goes and
   the list gives back the indent that was holding the bullet's place. The renderer always writes the
   box disabled — a post is read, not filled in — so it keeps full strength instead of taking the
   browser's greyed-out look, and it is coloured from the page's own palette. */
:is(body.reading, .preview) .post-content .contains-task-list,
.post-window-body .contains-task-list {
  padding-left: 0;
}

:is(body.reading, .preview) .post-content .task-list-item,
.post-window-body .task-list-item {
  list-style: none;
}

:is(body.reading, .preview) .post-content .task-list-item-checkbox,
.post-window-body .task-list-item-checkbox {
  width: 13px;
  height: 13px;
  margin: 0 6px 0 0;
  vertical-align: baseline;
  opacity: 1;
  accent-color: var(--accent);
}

:is(body.reading, .preview) .post-content blockquote {
  margin: 0 0 1.1rem;
  padding: 0 0 0 16px;
  border-left: 2px solid var(--edge);
  font-size: clamp(14px, 1.5vw, 16px);
  line-height: 1.75;
  color: var(--dim);
}

:is(body.reading, .preview) .post-content blockquote p {
  margin: 0 0 0.6rem;
}

:is(body.reading, .preview) .post-content code {
  font-family: inherit;
  font-size: 0.95em;
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  padding: 1px 5px;
}

:is(body.reading, .preview) .post-content pre {
  margin: 0 0 1.1rem;
  padding: 14px 16px;
  border: 1px solid var(--edge-dim);
  background: color-mix(in srgb, var(--bg) 60%, transparent);
}

/* A FENCE WRAPS RATHER THAN SCROLLS (2026-09-27, by request — "please make sure it fits in the preview
   window"): the block used to be overflow-x: auto, so a long line was cut at its edge and, once the block
   was scrolled to read the end, the text at its left was out of view. The block and its code carry no
   horizontal scrollbar now: the source's own line breaks are kept, a too-long line continues on the next,
   and overflow-wrap breaks a token that still cannot fit (a long identifier, a pasted URL). The writing
   pages' preview renders through these same rules (js/preview.js), so a draft and its published page fit
   alike — which is the point of that preview. */
:is(body.reading, .preview) .post-content pre code {
  display: block;
  padding: 0;
  background: none;
  font-size: clamp(13px, 1.4vw, 15px);
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The tokens js/highlight.js puts inside a fenced block. The block's plain text keeps --fg; these are
   the words that have a meaning of their own, and each kind carries its own colour (see the --syn-*
   variables for what each one is and why the palette is not the page's). A fence is coloured wherever
   a post's own Markdown is read — the reading page and the panel over the field both show one. */
:is(body.reading, .preview) .post-content pre code .tok-c,
.post-window-body pre code .tok-c {
  color: var(--syn-com);
}
:is(body.reading, .preview) .post-content pre code .tok-k,
.post-window-body pre code .tok-k {
  color: var(--syn-kw);
}
:is(body.reading, .preview) .post-content pre code .tok-t,
.post-window-body pre code .tok-t {
  color: var(--syn-typ);
}
:is(body.reading, .preview) .post-content pre code .tok-s,
.post-window-body pre code .tok-s {
  color: var(--syn-str);
}
:is(body.reading, .preview) .post-content pre code .tok-n,
.post-window-body pre code .tok-n {
  color: var(--syn-num);
}
:is(body.reading, .preview) .post-content pre code .tok-p,
.post-window-body pre code .tok-p {
  color: var(--syn-pre);
}
:is(body.reading, .preview) .post-content pre code .tok-f,
.post-window-body pre code .tok-f {
  color: var(--syn-fn);
}

/* A GFM table (2026-09-26): ruled in --edge-dim so the grid reads as structure rather than as prose,
   with the head written in the accent, the way every other title on the page is. The element is a
   block that scrolls: a table is the one block a writer can make wider than the column, so it takes
   the column's own edge as its boundary and a wheel to the rest, and the reading measure stays
   min(720px, 100%) in both scopes. */
:is(body.reading, .preview) .post-content table,
.post-window-body table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  margin: 0 0 1.1rem;
  font-size: clamp(14px, 1.5vw, 16px);
  color: var(--fg);
}

/* The panel's own measure, a step down like the rest of its prose. */
.post-window-body table {
  font-size: 13px;
  color: var(--fg);
}

:is(body.reading, .preview) .post-content th,
:is(body.reading, .preview) .post-content td,
.post-window-body th,
.post-window-body td {
  padding: 6px 10px;
  border: 1px solid var(--edge-dim);
  text-align: left;
}

/* The renderer's align attribute is the writeup's own column alignment, and an author rule outranks a
   presentational hint — so left is the default above (a header without an attribute reads like the
   body under it, not centred) and the two other columns are written out. */
:is(body.reading, .preview) .post-content th[align="center"],
:is(body.reading, .preview) .post-content td[align="center"],
.post-window-body th[align="center"],
.post-window-body td[align="center"] {
  text-align: center;
}

:is(body.reading, .preview) .post-content th[align="right"],
:is(body.reading, .preview) .post-content td[align="right"],
.post-window-body th[align="right"],
.post-window-body td[align="right"] {
  text-align: right;
}

:is(body.reading, .preview) .post-content th,
.post-window-body th {
  color: var(--accent);
  font-weight: 700;
}

/* Struck text is a correction rather than prose: the line through it is the browser's own, the colour
   is the page's dimmed voice. */
:is(body.reading, .preview) .post-content del,
.post-window-body del {
  color: var(--dim);
}

/* Footnotes (2026-09-26): the reference wears the accent because it is a link into the writeup's own
   apparatus, and the section is set a step below the prose it belongs to and parted from it by the
   house's dashed rule. The backref is a way back, not a link somewhere new, so it stays dim until the
   pointer is on it. */
:is(body.reading, .preview) .post-content sup.footnote-ref,
.post-window-body sup.footnote-ref {
  color: var(--accent);
  font-size: 0.75em;
}

:is(body.reading, .preview) .post-content sup.footnote-ref a,
.post-window-body sup.footnote-ref a {
  color: var(--accent);
  text-decoration: none;
}

:is(body.reading, .preview) .post-content .footnotes,
.post-window-body .footnotes {
  margin-top: 2rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--edge);
}

:is(body.reading, .preview) .post-content .footnotes {
  font-size: clamp(13px, 1.4vw, 15px);
}

.post-window-body .footnotes {
  font-size: 12px;
}

:is(body.reading, .preview) .post-content .footnotes ol,
.post-window-body .footnotes ol {
  padding-left: 1.4em;
  font-size: inherit;
}

:is(body.reading, .preview) .post-content .footnotes li,
.post-window-body .footnotes li {
  margin: 0.4rem 0;
}

:is(body.reading, .preview) .post-content .footnotes p,
.post-window-body .footnotes p {
  margin: 0 0 0.4rem;
  font-size: inherit;
}

:is(body.reading, .preview) .post-content .footnote-backref,
.post-window-body .footnote-backref {
  color: var(--dim);
  text-decoration: none;
}

:is(body.reading, .preview) .post-content .footnote-backref:hover,
.post-window-body .footnote-backref:hover {
  text-shadow: 0 0 10px var(--glow);
}

/* The footnote section's own name, for a reader who cannot see the rule above it. */
:is(body.reading, .preview) .post-content .sr-only,
.post-window-body .sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Alerts (2026-09-26): a blockquote a writer opened with [!NOTE] and the four like it. The page's
   accent and danger are both red, so the five are told apart by their title and by the rule down the
   left edge: note's is the accent, tip keeps the plain edge, important's title takes --fg, and the two
   reds are separated by the rule itself — warning's dashed, caution's the same colour at twice the
   thickness. */
:is(body.reading, .preview) .post-content .markdown-alert,
.post-window-body .markdown-alert {
  margin: 0 0 1.1rem;
  padding: 10px 14px;
  border-left: 2px solid var(--edge);
}

:is(body.reading, .preview) .post-content .markdown-alert > :last-child,
.post-window-body .markdown-alert > :last-child {
  margin-bottom: 0;
}

:is(body.reading, .preview) .post-content .markdown-alert-title,
.post-window-body .markdown-alert-title {
  margin: 0 0 0.5rem;
  font-size: 0.9em;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

:is(body.reading, .preview) .post-content .markdown-alert-note,
.post-window-body .markdown-alert-note {
  border-left-color: var(--accent);
}

:is(body.reading, .preview) .post-content .markdown-alert-warning,
.post-window-body .markdown-alert-warning {
  border-left-style: dashed;
  border-left-color: var(--danger);
}

:is(body.reading, .preview) .post-content .markdown-alert-caution,
.post-window-body .markdown-alert-caution {
  border-left-width: 4px;
  border-left-color: var(--danger);
}

:is(body.reading, .preview) .post-content .markdown-alert-note .markdown-alert-title,
:is(body.reading, .preview) .post-content .markdown-alert-tip .markdown-alert-title,
.post-window-body .markdown-alert-note .markdown-alert-title,
.post-window-body .markdown-alert-tip .markdown-alert-title {
  color: var(--accent);
}

:is(body.reading, .preview) .post-content .markdown-alert-important .markdown-alert-title,
.post-window-body .markdown-alert-important .markdown-alert-title {
  color: var(--fg);
}

:is(body.reading, .preview) .post-content .markdown-alert-warning .markdown-alert-title,
:is(body.reading, .preview) .post-content .markdown-alert-caution .markdown-alert-title,
.post-window-body .markdown-alert-warning .markdown-alert-title,
.post-window-body .markdown-alert-caution .markdown-alert-title {
  color: var(--danger);
}

:is(body.reading, .preview) .post-content hr {
  margin: 2rem 0;
  border: 0;
  border-top: 1px dashed var(--edge);
}

/* The way back, and the Edit and Delete controls when the reader may change the post: one row,
   bracketed like everything else the site asks you to press. The Edit control is an anchor and the
   Delete one a button, so the two share every rule but their own element's defaults. */
:is(body.reading, .preview) .post-actions,
:is(body.reading, .preview) .back-link {
  margin-top: 28px;
  font-size: 13px;
}

:is(body.reading, .preview) .post-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}

:is(body.reading, .preview) .post-actions .back-link {
  margin-top: 0;
}

:is(body.reading, .preview) .back-link,
:is(body.reading, .preview) .edit-link,
:is(body.reading, .preview) .delete-button {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--dim);
  text-decoration: none;
  cursor: pointer;
}

:is(body.reading, .preview) .back-link::before,
:is(body.reading, .preview) .edit-link::before,
:is(body.reading, .preview) .delete-button::before {
  content: "[ ";
}

:is(body.reading, .preview) .back-link::after,
:is(body.reading, .preview) .edit-link::after,
:is(body.reading, .preview) .delete-button::after {
  content: " ]";
}

:is(body.reading, .preview) .back-link:hover,
:is(body.reading, .preview) .edit-link:hover,
:is(body.reading, .preview) .delete-button:hover {
  color: var(--fg);
  text-shadow: 0 0 10px var(--glow);
}

/* ---- the sheet: about / contact / log in / new post ----
   A page of prose or a form in the site's own column (the shared layout above), over the field's
   stars — data-field="stars" tells js/spheres.js to draw the sky and stop, so no sun, planet, orbit
   or label belongs on a page like this. The page's own name is the title, under it a dashed rule and
   then either prose (.sheet-copy) or a form (.contact-form); the account corner (#user-bar) is the
   page's chrome, and the foot carries the way back and the design's status line — the phosphor
   switch, the mark and the copyright. A NARROW sheet (body.narrow) holds a form, on the column the
   design's own forms were built at; a page of prose keeps the reading view's own 720px. */
body.sheet main {
  display: flex;
  flex-direction: column;
  gap: 18px;
  width: min(720px, 100%);
}

body.sheet.narrow main {
  width: min(460px, 100%);
}

/* A DRAFT IS PREVIEWED IN THE COLUMN IT WILL BE READ IN (2026-09-25, by request: "Allow people to
   'preview' their posts in markdown before posting"): while the preview is up, the narrow sheet gives
   its column the reading view's own width, so the measure on screen is the measure the published post
   will have — and the form's column comes back with the form, because the class goes with it. */
body.sheet.narrow.previewing main {
  width: min(720px, 100%);
}

/* The draft itself (js/preview.js): the reading view's rules already carry the `.preview` scope, so this
   only has to be a block the reader can look at, with the reading view's own gap under it. */
body.sheet .preview {
  width: 100%;
  margin-bottom: 26px;
}

/* The two things a writer does with a draft — look at it, or post it — on one row, in the site's own
   bracketed controls; the fields they stand beside are hidden while the preview is up, and .field has a
   display of its own, so the attribute needs its rule here as well. */
body.sheet .form-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}

body.sheet .field[hidden] {
  display: none;
}

/* The page's name, in the reading view's own title face and size. The child combinator matters: a
   PREVIEW (js/preview.js) puts a writeup's own h1 inside this page, and that one is a writeup's title,
   not the page's — it must not take the sheet's title nor the dashed rule under it. */
body.sheet main > h1 {
  margin: 0;
  font-family: "Bethany Elingston", cursive;
  font-weight: 400;
  font-size: clamp(26px, 4.6vw, 40px);
  line-height: 1.15;
  color: var(--fg);
  text-shadow: 0 0 14px var(--glow);
}

/* The design's own dashed underline for a page title. */
body.sheet main > h1::after {
  content: "";
  display: block;
  margin-top: 16px;
  border-bottom: 1px dashed var(--edge-dim);
}

body.sheet .sheet-copy a {
  color: var(--fg);
  text-underline-offset: 3px;
}

body.sheet .sheet-copy a:hover {
  text-shadow: 0 0 10px var(--glow);
}

/* ---- the sheet's forms ----
   The design's own form: a column of fields, each with a dim label carrying the "> " prompt, a
   transparent input on an --edge border that lights up when it takes the caret, and a bracketed
   control under them. */
body.sheet .contact-form {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* The form column is a flex box, so the hidden attribute needs the explicit rule. */
body.sheet .contact-form[hidden] {
  display: none;
}

body.sheet .field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 13px;
}

body.sheet .field-label {
  font-weight: bold;
  color: var(--dim);
}

body.sheet .field-label::before {
  content: "> ";
  color: var(--fg);
}

body.sheet .field input,
body.sheet .field select,
body.sheet .field textarea {
  font: inherit;
  font-size: 14px;
  color: var(--fg);
  background: transparent;
  border: 1px solid var(--edge);
  padding: 6px 9px;
  width: 100%;
  /* The site is content-box (the hero's own box is measured that way), so an input carrying a border
     and padding would sit 20px proud of the column it is asked to fill. */
  box-sizing: border-box;
}

body.sheet .field input:focus,
body.sheet .field select:focus,
body.sheet .field textarea:focus {
  border-color: var(--fg);
  box-shadow: 0 0 8px var(--glow);
  outline: none;
}

body.sheet .field textarea {
  resize: vertical;
  min-height: 110px;
}

/* A role is two values, so the control is a select — and it wears the field's own clothes (the same
   transparent ground, border and caret) rather than the browser's. */
body.sheet .field select {
  cursor: pointer;
}

/* The images a post carries (js/upload.js): the picker is the sheet's own control — a bracketed
   [ Upload image ] — and every upload lands as a row that answers the writer's question. THE LINK THE
   IMAGE NOW LIVES AT is the loud part of it (--fg, and the one string on the panel worth reading off by
   eye), the file's own name sits above it in --dim, and the thumbnail is that URL served back — proof
   the image is really there. A row is ruled with the form's own dashed edge, so the panel reads as part
   of the sheet rather than a widget dropped into it. */
body.sheet .upload-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* The list is a flex box, so the hidden attribute needs its explicit rule here as well. */
body.sheet .upload-list[hidden] {
  display: none;
}

body.sheet .upload-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  padding: 8px 10px;
  border: 1px dashed var(--edge-dim);
}

/* The thumbnail is the picture's own way out: a link, so the full-size image can be opened from the
   panel, over the form's dashed edge like every other thing on the sheet. */
body.sheet .upload-shot {
  display: block;
  flex: none;
}

body.sheet .upload-thumb {
  display: block;
  width: 44px;
  height: 44px;
  object-fit: cover;
  border: 1px solid var(--edge-dim);
}

/* The link gives way before the controls do: it wraps inside the space it has rather than pushing
   [ Copy link ] and [ Insert ] off the row. */
body.sheet .upload-what {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

body.sheet .upload-name {
  font-size: 12px;
  color: var(--dim);
}

body.sheet .upload-url {
  font-size: 13px;
  color: var(--fg);
  overflow-wrap: anywhere;
}

/* A row's own controls hold their size and place (the sheet's control rules below would have the
   row's text size spread into them). */
body.sheet .upload-row .upload-copy,
body.sheet .upload-row .upload-insert,
body.sheet .upload-row .upload-delete {
  flex: none;
  font-size: 13px;
}

body.sheet .form-note {
  margin: 0;
  font-size: 13px;
  color: var(--dim);
}

body.sheet .form-note a {
  color: var(--fg);
  text-underline-offset: 3px;
}

body.sheet .form-note a:hover {
  text-shadow: 0 0 10px var(--glow);
}

body.sheet .form-error {
  margin: 0;
  font-size: 13px;
  color: var(--danger);
}

/* A control on a sheet is bracketed type with no box, the reading view's own idiom: dim while it
   waits, --fg with a glow once the pointer is on it. The form's own button is the page's action, so
   it carries --fg from the start. THE UPLOAD PANEL'S four controls are of that family — the picker,
   and each row's [ Copy link ], [ Insert ] and [ Delete ] — and wear it unchanged, the delete reading
   as quietly as the reading page's own [ Delete ] does. */
body.sheet .back-link,
body.sheet .send-button,
body.sheet .preview-toggle,
body.sheet .theme-toggle,
body.sheet .upload-browse,
body.sheet .upload-copy,
body.sheet .upload-insert,
body.sheet .upload-delete {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--dim);
  text-decoration: none;
  cursor: pointer;
}

body.sheet .back-link::before,
body.sheet .send-button::before,
body.sheet .preview-toggle::before,
body.sheet .theme-toggle::before,
body.sheet .upload-browse::before,
body.sheet .upload-copy::before,
body.sheet .upload-insert::before,
body.sheet .upload-delete::before {
  content: "[ ";
}

body.sheet .back-link::after,
body.sheet .send-button::after,
body.sheet .preview-toggle::after,
body.sheet .theme-toggle::after,
body.sheet .upload-browse::after,
body.sheet .upload-copy::after,
body.sheet .upload-insert::after,
body.sheet .upload-delete::after {
  content: " ]";
}

/* The page's own action, and the controls that do something with the draft in front of the writer —
   the preview toggle, the upload panel's picker, and the [ Insert ] that writes an image's Markdown
   in at the caret: all in --fg, because all of them are things the writer is doing. Only the ones that
   stand alone in the form's column lean to its start; [ Insert ] belongs to an upload row, whose own
   rule centres it. */
body.sheet .send-button,
body.sheet .preview-toggle,
body.sheet .upload-browse {
  align-self: flex-start;
}

body.sheet .send-button,
body.sheet .preview-toggle,
body.sheet .upload-browse,
body.sheet .upload-insert {
  font-size: 13px;
  color: var(--fg);
}

body.sheet .back-link:hover,
body.sheet .send-button:hover,
body.sheet .preview-toggle:hover,
body.sheet .theme-toggle:hover,
body.sheet .upload-browse:hover,
body.sheet .upload-copy:hover,
body.sheet .upload-insert:hover,
body.sheet .upload-delete:hover {
  color: var(--fg);
  text-shadow: 0 0 10px var(--glow);
}

/* The foot: the way back on the left, the design's status line on the right, over a dashed rule. */
body.sheet .sheet-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin-top: 16px;
  padding-top: 10px;
  border-top: 1px dashed var(--edge-dim);
  font-size: 12px;
  color: var(--dim);
}

body.sheet .sheet-status {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

body.sheet .brand-logo {
  width: 16px;
  height: auto;
  filter: var(--logo-filter);
}

/* ---- the people sheet (people.html) ----
   The second star's subjects managed as forms: one block per person carrying the fields their planet
   is built from (name, biography, links) and — for an admin — the account the page belongs to. ONE
   BLOCK IS ON THE PAGE AT A TIME: the admin's index at the head (the person picker, with the way to
   add a person in a drawer under it) says which, so a roster of any length is a page the height of
   one form. It wears the sheet's own column, fields and controls; a block is parted from the page
   above it by the same dashed rule the page title carries, and its own name is set in the title face
   a step down from the page's. The section wrappers are flex columns of their own (js/people.js
   fills them), so the hidden attribute needs its own rule here too. */
body.sheet h2 {
  margin: 0;
  font-family: "Bethany Elingston", cursive;
  font-weight: 400;
  font-size: clamp(19px, 2.6vw, 24px);
  line-height: 1.2;
  color: var(--fg);
  text-shadow: 0 0 10px var(--glow);
}

body.sheet #admin-part,
body.sheet #add-person,
body.sheet #roster {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

body.sheet #admin-part[hidden],
body.sheet #add-person[hidden],
body.sheet #roster[hidden],
body.sheet .person[hidden] {
  display: none;
}

body.sheet .person-list {
  display: flex;
  flex-direction: column;
  gap: 26px;
}

body.sheet .person {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding-top: 18px;
  border-top: 1px dashed var(--edge-dim);
}

body.sheet .person-meta {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--dim);
}

body.sheet .person-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}

/* The account picker: the field's own plate (its input is a .field input like any other) with the
   accounts FILTERED as a name is typed, and the list on a fixed panel under the field rather than in
   the page — an account roster is longer than the sheet, and neither the page nor the column may grow
   with it, so a long list costs a few rows and a wheel and nothing else. The panel is capped and
   scrolls inside itself; the option the keys are on carries a wash of the field's own colour. */
body.sheet .account-picker {
  display: block;
}

body.sheet .account-list {
  position: fixed;
  z-index: 5;
  box-sizing: border-box; /* the site is content-box: without this the border makes the panel 2px proud */
  margin: 0;
  padding: 0;
  max-height: 13em;
  overflow-y: auto;
  list-style: none;
  background: var(--bg);
  border: 1px solid var(--edge);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.65);
}

body.sheet .account-list[hidden] {
  display: none;
}

body.sheet .account-option {
  padding: 4px 9px;
  font-size: 13px;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}

body.sheet .account-option[hidden] {
  display: none;
}

body.sheet .account-option .account-user {
  color: var(--dim);
}

body.sheet .account-option.is-active {
  background: color-mix(in srgb, var(--fg) 18%, transparent);
}

/* The sheet's own destructive control, the reading view's Delete: bracketed, dim until the pointer. */
body.sheet .delete-button {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  color: var(--dim);
  text-decoration: none;
  cursor: pointer;
}

body.sheet .delete-button::before {
  content: "[ ";
}

body.sheet .delete-button::after {
  content: " ]";
}

body.sheet .delete-button:hover {
  color: var(--fg);
  text-shadow: 0 0 10px var(--glow);
}

/* Vignette: the grade the field has always been seen through. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(ellipse at 50% 38%, transparent 55%, rgba(0, 0, 0, 0.55) 100%),
    linear-gradient(to bottom, transparent 0 70%, var(--bg) 100%);
}
