/* layout and typography for the reading pages.
   the terminal and the darkroom bring their own; they only load base.css. */

body{
  max-width:760px;
  margin:0 auto;
  padding:clamp(32px,7vw,64px) clamp(16px,5vw,20px) clamp(28px,6vw,40px);
  padding-left:max(clamp(16px,5vw,20px),env(safe-area-inset-left));
  padding-right:max(clamp(16px,5vw,20px),env(safe-area-inset-right));
  background:var(--bg);
  color:var(--text);
  font-family:ui-monospace,'Cascadia Code',Menlo,Consolas,monospace;
  font-size:clamp(15px,14.2px + .25vw,16px);
  line-height:1.7;
  overflow-wrap:break-word;

  /* short page: <main> stretches and the footer rests on the bottom of the
     screen. long page: <main> is taller than the spare room, nothing
     stretches, and the footer simply follows the text off-screen.
     one rule covers both — no measuring, no JS, no overlap. */
  min-height:100vh;
  display:flex;
  flex-direction:column;
}
@supports (min-height:100dvh){
  /* dvh tracks mobile browser chrome as it hides and shows */
  body{min-height:100dvh}
}
main{flex:1 0 auto}
footer{flex:0 0 auto}
/* browsers without view transitions get a plain cross-fade instead of the wipe */
@supports not (view-transition-name:none){
  body{transition:background .25s ease,color .25s ease}
}

h1{font-size:clamp(20px,17.8px + .6vw,23px);font-weight:600;color:var(--bright);margin:0;line-height:1.35}
/* the wordmark links home but stays visually put — no colour shift on hover */
h1 a{color:inherit;border-bottom:none}
h1 a:hover,h1 a:focus-visible{color:inherit;background:none;border-bottom:none}
/* a muted line that hangs off the line directly above it: the tagline under
   the wordmark, and the same shape in body copy. it stays tight to the line it
   belongs to, so whatever comes next carries the gap — which is what an empty
   <br> used to be doing on the contact page. */
.prompt{color:var(--muted);margin:0}
main .prompt + *{margin-top:28px}

/* nav as a shell prompt; the current "directory" is inverted like a pager
   status bar. the theme button rides at the right end. */
nav{
  display:flex;align-items:center;gap:16px;
  margin:28px 0 0;color:var(--muted);
}
/* one line, always. when the links don't fit — which is most phones — the
   strip slides sideways instead of wrapping into ragged rows.
   · min-width:0 lets the flex item shrink below its content, or it would
     never overflow and never scroll
   · overscroll-behavior stops a sideways swipe becoming a browser "back"
   · the 4px padding (cancelled by the matching negative margin) keeps focus
     outlines from being clipped by the scroll container */
.navlinks{
  display:flex;flex-wrap:nowrap;align-items:center;
  gap:2px;flex:1 1 auto;min-width:0;
  overflow-x:auto;
  overscroll-behavior-x:contain;
  -webkit-overflow-scrolling:touch;
  /* the negative margin cancels the padding, so the strip still starts flush
     with the text column: -10 + 5 puts the first link's own padding at -5,
     and its text lands back on 0 */
  padding:4px 4px 4px 5px;
  margin:-4px -4px -4px -10px;
  scrollbar-width:none;
}
.navlinks::-webkit-scrollbar{display:none}
/* every link carries the padding all the time — the current one differs only
   in colour. if the padding arrived with aria-current, that link would widen
   by 10px on arrival and shove every link after it sideways. */
nav a{flex:0 0 auto;border-bottom:none;white-space:nowrap;padding:1px 5px}
nav a[aria-current="page"],
nav a[aria-current="page"]:hover{background:var(--inv-bg);color:var(--inv-text)}

@media (max-width:480px){
  nav{gap:12px}
  .navlinks{gap:0}
}

h2{
  font-size:1em;font-weight:400;color:var(--muted);
  margin:clamp(34px,8vw,52px) 0 14px;
}
h2::before{content:"// "}
/* the gap under the nav belongs to <main>, so pages that open with a
   paragraph sit exactly where pages that open with a heading do */
main{margin-top:clamp(34px,8vw,52px)}
main > h2:first-child{margin-top:0}
p{margin:0 0 16px}
a{color:var(--link);text-decoration:none;border-bottom:1px solid var(--link-edge)}
a:hover,a:focus-visible{color:var(--bright);border-color:var(--link);background:var(--hover-bg)}
a:focus-visible{outline:1px solid var(--link);outline-offset:3px}
ul{list-style:none;padding:0;margin:0 0 16px}
li{margin-bottom:14px;padding-left:18px;position:relative}
li::before{content:"-";position:absolute;left:0;color:var(--muted)}
code{
  background:var(--chip);border:1px solid var(--rule);border-radius:4px;
  padding:1px 6px;font-size:.875em;color:var(--link);font-family:inherit;
}
/* bold in a monospace face lands heavy and breaks the even colour of a
   paragraph. the brighter ink carries the emphasis on its own. */
strong{font-weight:inherit;color:var(--bright)}
.quiet{color:var(--muted);font-size:.93em}
.date{color:var(--muted);font-size:.875em}
.wip{color:var(--bright)}
hr{border:none;border-top:1px solid var(--rule);margin:clamp(32px,8vw,48px) 0}
.aside{
  color:var(--muted);font-size:.93em;font-style:italic;
  border-left:2px solid var(--rule);padding-left:14px;margin:20px 0;
}

/* contact channels.
   the mark replaces the list's "-" bullet and carries the channel's identity,
   which frees the address to be the whole label — writing "github —
   github.com/qomusiy" only says it twice. the whole row is the target, and
   it shrink-wraps so that target ends where the address does. */
.channels{list-style:none;padding:0;margin:0 0 16px}
.channels li{margin:0;padding:0;position:static}
.channels li::before{content:none}
.channels a{
  display:flex;align-items:center;gap:14px;
  width:max-content;max-width:100%;
  padding:10px 0;
  border-bottom:none;
  color:var(--link);
  transition:color .2s ease;
}
/* no hover block: the mark and the address both shift colour, which is
   plenty of signal. with the block gone the row needs no side padding
   either, so the marks line up flush with the paragraphs above. */
.channels a:hover,.channels a:focus-visible{background:none;color:var(--bright)}
.channels svg{
  width:21px;height:21px;flex:none;display:block;
  fill:currentColor;stroke:none;color:var(--muted);
  transition:color .2s ease;
}
.channels a:hover svg,.channels a:focus-visible svg{color:var(--bright)}
/* direct child only: the reveal wraps each word in its own span inside this
   one, and those must not inherit the anywhere-breaking */
.channels a > span{min-width:0;overflow-wrap:anywhere}

/* footer, centred.
   three registers, quietest last: the side-door pages, then the direct
   channels, then the colophon. the links keep the header's `name/` wording so
   the two navs read as one system, but sit in the muted colour the social
   icons already use — secondary, not disabled. */
.morelinks{
  display:flex;flex-wrap:wrap;justify-content:center;
  gap:6px 18px;margin:0;
}
.morelinks a{color:var(--muted);border-bottom:none;white-space:nowrap}
.morelinks a:hover,.morelinks a:focus-visible{color:var(--link);background:none}
/* no inverted block down here — it would shout in a quiet footer */
.morelinks a[aria-current="page"],
.morelinks a[aria-current="page"]:hover{color:var(--bright);background:none}

.colophon{
  color:var(--muted);font-size:.93em;text-align:center;line-height:1.9;
  margin:18px auto 0;
}

@media (pointer:coarse){
  nav a{padding-top:2px;padding-bottom:2px}
}

/* ---- how a page arrives ----------------------------------------------
   the first time you open a given page in a session its words appear one
   after another, as if being written. every time after that it just fades.

   nothing moves in either case. the script wraps each word in a span up
   front, so the finished layout — every line break in its final place —
   exists from the first frame and only opacity changes. animating real
   typing would re-wrap the paragraph on almost every character. */
main{animation:pagein .32s ease both}
@keyframes pagein{from{opacity:0}to{opacity:1}}

/* held blank until the script takes over. the long delay is a failsafe:
   if app.js never loads, the text still turns up rather than staying
   invisible for good. */
.typing main{opacity:0;animation:pagein .3s ease 1.5s both}
.typing main .w{animation:wordin .3s ease both}
@keyframes wordin{from{opacity:0}to{opacity:1}}

@media (prefers-reduced-motion:reduce){
  main,.typing main,.typing main .w{animation:none;opacity:1}
}
