/* ==========================================================================
   JakeScalesAI — the blog.

   ONE stylesheet, TWO places:
     1. the generated pages at /blog and /blog/<slug> (scripts/build-blog.mjs),
        which load it as their only stylesheet;
     2. the #blog view inside index.html, which loads it alongside index.css.

   That is deliberate. The list rows are built by two different things — the
   generator writes them into a file, index.js builds them from posts.json —
   and if each carried its own CSS the two would drift the first time one of
   them was touched. Everything under .blog-list and .post is therefore defined
   HERE and nowhere else; index.css owns only where the #blog VIEW sits on the
   page, which is a landing-page concern that has no meaning on /blog.

   Shape is the jakethinks.co thoughts tab: hairline-separated rows carrying a
   title, a "date · N min read" line and a two-line dek; the article is a title,
   the same meta line, then one reading column. Rendered in this site's own
   palette rather than that one's white-on-glass — that treatment works there
   because it floats over a video, and there is no video behind this.
   ========================================================================== */

:root{
  /* palette, copied from index.css exactly as resources.css and legal.css copy
     it, so the blog stays independent of edits to the site core */
  --cream:#fdfaf0;
  --ink:#221c10;
  --ink-hover:#33301f;
  --muted:#6e6450;
  --terracotta:#b45f2a;
  --terracotta-hover:#9c4f20;
  --line:#e8dfc6;
  --white:#ffffff;
  --faint:#a39a78;

  /* a row's hover wash: the same terracotta, at the weight the landing page
     uses for a selection tint */
  --row-hover:rgba(180,95,42,.05);

  /* EXACTLY index.css's stack, character for character, and that is not
     pedantry: this sheet is linked AFTER index.css on the landing page, so
     its :root wins there. A longer fallback chain here (the one legal.css
     uses) silently restyled the WHOLE landing page on any machine without
     SF Pro Display, which is every Windows machine. scripts/check-blog.mjs
     now fails if these two drift apart again. */
  --font:'SF Pro Display',-apple-system,BlinkMacSystemFont,sans-serif;
  --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace;

  --t-press:90ms;
  --t-feedback:180ms;
  --ease-out:cubic-bezier(.16,1,.3,1);

  /* The placeholder cross-fade. blog-search.js reads this value off the element
     rather than carrying its own copy, so changing it here changes both halves
     and they cannot fall out of step. */
  --ph-fade:320ms;

  /* The reading column. 68ch is the measure the package pages settled on and
     this matches them, so a post and a package read at the same width. */
  --measure:68ch;

  /* The frame the top bar and the content BOTH sit in: the measure plus the
     side padding, so the back pill's left edge and the first word of the page
     land on the same vertical. They were 26px apart until 09-03, because the
     bar was 760px wide while the column was 68ch centred inside it, and a
     control hanging off the edge of the text it belongs to reads as a mistake.
     Deriving it from --measure rather than hardcoding a pixel width means the
     two stay aligned even where `ch` resolves differently, e.g. when SF Pro
     is not installed and the fallback face has different metrics. */
  --pad:16px;
  --frame:calc(var(--measure) + var(--pad) * 2);
}

*,*::before,*::after{box-sizing:border-box}

/* The blog pages hide their scrollbar, exactly as the landing page does
   (index.css: `html{scrollbar-width:none}`, "the page scrolls without ever
   showing a bar", Jake's call). Until 2026-09-07 they RESERVED a gutter
   instead, which kept the column still between /blog and a post but put every
   blog page 7px left of the landing page at 1440, so the top bar moved the
   moment a reader crossed from the #blog view to a post. Jake: "the
   positioning of the top header needs to be the exact same". One convention,
   the landing page's, and the bar stays put. The cost is the reading-position
   indicator on a long post; the README records the trade. */
html.blog-html{scrollbar-width:none}
html.blog-html::-webkit-scrollbar{display:none}

/* Scoped to the standalone pages: inside index.html these are index.css's job
   and re-declaring them here would fight it. The class is emitted by
   scripts/build-blog.mjs; without it these rules never apply and the page
   falls through to the browser's own defaults, which on a device in dark mode
   is dark text on a dark ground. */
body.blog-page{
  margin:0;
  /* The rest of the site is light-only and paints its own cream ground. Saying
     so explicitly keeps the UA from darkening scrollbars and form controls
     around a page that is deliberately not going to follow them. */
  color-scheme:light;
  background:var(--cream);
  color:var(--ink);
  font-family:var(--font);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
body.blog-page::selection,
body.blog-page ::selection{background:rgba(180,95,42,.85);color:#fdfaf0}

body.blog-page :focus-visible{
  outline:2px solid var(--terracotta);
  outline-offset:3px;
  box-shadow:0 0 0 6px rgba(180,95,42,.12);
}

/* --------------------------------------------------------------------------
   Top bar. Three columns with the wordmark in the middle one, so the wordmark
   is centred on the PAGE rather than on whatever is left over beside the pill —
   the landing page's mobile bar does the same and the two must line up when a
   reader crosses between them.
   -------------------------------------------------------------------------- */
.blog-topbar{
  display:grid;
  grid-template-columns:1fr auto 1fr;
  align-items:center;
  gap:12px;
  padding:14px var(--pad);
  max-width:var(--frame);
  margin:0 auto;
}
.blog-topbar .wordmark{
  justify-self:center;
  font-size:17px;
  font-weight:800;
  letter-spacing:-.02em;
  color:var(--ink);
  text-decoration:none;
  white-space:nowrap;
}
.wordmark-ai{color:var(--terracotta)}

/* The pill, matched to the landing page's so a reader crossing between them
   sees one control rather than two similar ones. */
.blog-topbar .pill,
.post-foot .pill{
  display:inline-flex;
  align-items:center;
  gap:6px;
  padding:9px 15px;
  border:1px solid var(--line);
  border-radius:999px;
  background:var(--white);
  color:var(--ink);
  font-size:14px;
  font-weight:600;
  line-height:1;
  text-decoration:none;
  white-space:nowrap;
  transition:border-color var(--t-feedback) var(--ease-out),
             background-color var(--t-feedback) var(--ease-out),
             transform var(--t-press) var(--ease-out);
}
.blog-topbar .pill:hover,
.post-foot .pill:hover{border-color:var(--terracotta);color:var(--terracotta-hover)}
.blog-topbar .pill:active,
.post-foot .pill:active{transform:translateY(1px)}
.blog-topbar .pill-back{justify-self:start}

/* --------------------------------------------------------------------------
   Page frame
   -------------------------------------------------------------------------- */
.blog-wrap{
  max-width:var(--frame);
  margin:0 auto;
  padding:0 var(--pad) 72px;
}

/* The reading column is narrower than the frame, so it is CENTRED in it rather
   than left-aligned. Left-aligned, it sat well off to one side of a wordmark
   centred above it, which reads as a layout that came apart rather than a
   deliberate measure. */
.blog-head,.blog-list,.post,.post-foot{margin-inline:auto}

.blog-head{
  max-width:var(--measure);
  padding:24px 0 8px;
}
.blog-h1{
  margin:0;
  font-size:clamp(34px,9vw,44px);
  font-weight:800;
  line-height:1.04;
  letter-spacing:-.03em;
}
.blog-sub{
  margin:12px 0 0;
  max-width:52ch;
  color:var(--muted);
  font-size:16px;
  line-height:1.5;
}

.blog-empty{
  margin:56px 0;
  color:var(--muted);
  font-size:17px;
}

/* Visually hidden but still read aloud. index.css has its own copy; the
   generated pages never load that sheet, so this one needs it too. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}

/* --------------------------------------------------------------------------
   Search. One field, above the list, on both surfaces.
   -------------------------------------------------------------------------- */
.blog-search{
  position:relative;
  max-width:var(--measure);
  margin:26px auto 0;
}
.blog-search-ico{
  position:absolute;
  top:50%;
  left:15px;
  transform:translateY(-50%);
  width:16px;
  height:16px;
  fill:none;
  stroke:var(--faint);
  stroke-width:2;
  stroke-linecap:round;
  pointer-events:none;   /* the icon must never eat a click meant for the field */
  transition:stroke var(--t-feedback) var(--ease-out);
}
.blog-search-input{
  display:block;
  width:100%;
  height:42px;
  /* left padding clears the icon; right padding balances it by eye */
  padding:0 16px 0 42px;
  border:1px solid var(--line);
  border-radius:999px;
  background:var(--white);
  color:var(--ink);
  /* 16px, NOT the 15px the rest of this scale uses. iOS Safari zooms the whole
     page in when a focused input's font-size is under 16px, and it does not
     zoom back out on blur -- the reader is left on a magnified page with the
     layout shifted, and nothing on screen says why. This is the one control on
     the blog they can type into, so it is the one place that matters. */
  font:400 16px/42px var(--font);
  -webkit-appearance:none;
  appearance:none;
  transition:border-color var(--t-feedback) var(--ease-out),
             box-shadow var(--t-feedback) var(--ease-out);
}
/* The placeholder rotates through a few phrases (blog-search.js). It cross-
   fades rather than snapping, and the fade lives HERE rather than in the script
   so it is one declaration to change and so reduced-motion can take it away
   without the script knowing.

   ::placeholder is a pseudo-element, and opacity on it is animatable in every
   engine we care about. Where it is not, the transition is simply ignored and
   the text swaps instantly -- the swap is driven by a timer, never by
   transitionend, so nothing can hang waiting for an event that never fires. */
.blog-search-input::placeholder{
  color:var(--faint);
  opacity:1;
  transition:opacity var(--ph-fade) var(--ease-out);
}
.blog-search-input[data-ph-fading]::placeholder{opacity:0}
.blog-search-input:hover{border-color:var(--faint)}
.blog-search-input:focus{
  outline:none;
  border-color:var(--terracotta);
  box-shadow:0 0 0 4px rgba(180,95,42,.12);
}
.blog-search:focus-within .blog-search-ico{stroke:var(--terracotta)}
/* Safari draws its own clear button and magnifier inside type=search; both
   fight the icon and the pill shape, and Escape already clears the field. */
.blog-search-input::-webkit-search-decoration,
.blog-search-input::-webkit-search-cancel-button,
.blog-search-input::-webkit-search-results-button{-webkit-appearance:none;display:none}

/* --------------------------------------------------------------------------
   The list. One hairline BETWEEN rows, never above the first or below the
   last: a rule under the final row reads as a cut-off list rather than the end
   of one.
   -------------------------------------------------------------------------- */
.blog-list{
  list-style:none;
  margin:20px auto 0;
  padding:0;
  max-width:var(--measure);
}
/* The rule sits on EVERY row and is taken off whichever one is currently
   first, rather than being drawn with `.blog-row + .blog-row`. A filtered-out
   row is still a DOM sibling, so the adjacent-sibling form draws a line above
   the first result whenever the row before it is hidden, and CSS has no way to
   say "first visible sibling". blog-search.js owns data-first. */
.blog-row{border-top:1px solid var(--line)}
.blog-row[data-first]{border-top:0}

.blog-row-link{
  display:block;
  padding:18px 14px;
  margin:0 -14px;          /* the hover wash reaches past the text, the rule does not */
  border-radius:10px;
  color:inherit;
  text-decoration:none;
  transition:background-color var(--t-feedback) var(--ease-out);
}
.blog-row-link:hover{background:var(--row-hover)}
.blog-row-link:focus-visible{background:var(--row-hover)}

.blog-row-title{
  margin:0;
  font-size:19px;
  font-weight:700;
  line-height:1.25;
  letter-spacing:-.02em;
  color:var(--ink);
}
.blog-row-link:hover .blog-row-title{color:var(--terracotta-hover)}

.blog-row-meta{
  margin:5px 0 0;
  color:var(--muted);
  font-family:var(--mono);
  font-size:12.5px;
  line-height:1.4;
  letter-spacing:.01em;
}
.dot-sep{color:var(--faint)}

.blog-row-dek{
  margin:7px 0 0;
  color:var(--muted);
  font-size:15px;
  line-height:1.5;
  /* Two lines, then an ellipsis. The dek is a taste of the post, and a long one
     left to run turns the list into a wall. */
  display:-webkit-box;
  -webkit-line-clamp:2;
  line-clamp:2;
  -webkit-box-orient:vertical;
  overflow:hidden;
}

/* --------------------------------------------------------------------------
   One post
   -------------------------------------------------------------------------- */
.post{
  max-width:var(--measure);
  padding-top:24px;
}
.post-head{padding-bottom:26px;border-bottom:1px solid var(--line)}
.post-title{
  margin:0;
  font-size:clamp(30px,7.5vw,40px);
  font-weight:800;
  line-height:1.1;
  letter-spacing:-.03em;
  text-wrap:balance;
}
.post-meta{
  margin:12px 0 0;
  color:var(--muted);
  font-family:var(--mono);
  font-size:12.5px;
  line-height:1.4;
  letter-spacing:.01em;
}
.post-edited{color:var(--faint)}
.post-dek{
  margin:14px 0 0;
  color:var(--muted);
  font-size:17px;
  line-height:1.5;
}

.post-foot{
  max-width:var(--measure);
  margin:48px auto 0;
  padding-top:24px;
  border-top:1px solid var(--line);
}

/* Where the blog is posted: a small row of the accounts a post is shared to,
   at the foot of /blog, every /blog/<slug> and the #blog view (BLOG_SOCIALS in
   index.js; the generator writes it, the view builds it). Smaller than the
   home page's row on purpose (owner, 2026-10-02: "have it shrink down"):
   34px rings, radius 7 to stay concentric with the home ring's 9 at 44.
   Same brand hover as the home row: index.js / the generator set --brand. */
.social-row-blog{
  display:flex;flex-wrap:wrap;justify-content:center;gap:10px;
  max-width:var(--measure);
  margin:56px auto 0;
  padding-top:24px;
  border-top:1px solid var(--line);
}
.social-row-blog a{
  display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;
  border:1.5px solid var(--line);border-radius:7px;
  color:var(--muted);
  transition:color var(--t-feedback) ease,border-color var(--t-feedback) ease,
             background-color var(--t-feedback) ease,
             transform var(--t-feedback) var(--ease-out);
}
.social-row-blog a:hover,
.social-row-blog a:focus-visible{
  color:var(--brand,var(--terracotta));
  border-color:var(--brand,var(--terracotta));
  background-color:color-mix(in srgb,var(--brand,var(--terracotta)) 8%,transparent);
  transform:translateY(-1px);
}
.social-row-blog a:active{transform:scale(.94);transition-duration:var(--t-press)}
.social-row-blog svg{width:14px;height:14px;display:block;fill:currentColor}
/* On a post the All posts pill already sits under a hairline; a second one
   right below it reads as a stray rule, so the row joins that section. */
.post-foot + .social-row-blog{margin-top:32px;padding-top:0;border-top:0}

/* --------------------------------------------------------------------------
   Prose. This is the only part of the page whose markup we do not write: it is
   whatever the Markdown rendered into, after the sanitizer. So every element a
   post can emit is given a value here rather than left to the browser's
   defaults, which are Times on a white ground and would arrive as a visible
   seam halfway down the page.
   -------------------------------------------------------------------------- */
.prose{
  margin-top:28px;
  font-size:17px;
  line-height:1.65;
  color:var(--ink);
}
.prose > :first-child{margin-top:0}
.prose > :last-child{margin-bottom:0}

.prose p{margin:0 0 20px}

.prose h2{
  margin:38px 0 12px;
  font-size:24px;
  font-weight:700;
  line-height:1.25;
  letter-spacing:-.02em;
}
.prose h3{
  margin:30px 0 10px;
  font-size:19px;
  font-weight:700;
  line-height:1.3;
  letter-spacing:-.01em;
}
.prose h4,.prose h5,.prose h6{
  margin:24px 0 8px;
  font-size:17px;
  font-weight:700;
  line-height:1.35;
}
/* A post body starts at h2: its h1 is the page title, which the generator
   writes. A stray h1 in the Markdown is levelled here rather than allowed to
   compete with the real one. */
.prose h1{
  margin:38px 0 12px;
  font-size:24px;
  font-weight:700;
  line-height:1.25;
  letter-spacing:-.02em;
}

.prose ul,.prose ol{margin:0 0 20px;padding-left:24px}
.prose li{margin:0 0 8px}
.prose li::marker{color:var(--faint)}
.prose li > ul,.prose li > ol{margin-top:8px}

.prose a{
  color:var(--terracotta);
  text-decoration:underline;
  text-underline-offset:2px;
  text-decoration-thickness:1px;
  transition:color var(--t-feedback) var(--ease-out);
}
.prose a:hover{color:var(--terracotta-hover)}

.prose strong{font-weight:700}
.prose em{font-style:italic}
.prose del{color:var(--muted)}
.prose mark{background:rgba(180,95,42,.16);color:inherit;padding:0 2px;border-radius:2px}

.prose blockquote{
  margin:24px 0;
  padding:2px 0 2px 18px;
  border-left:3px solid var(--terracotta);
  color:var(--muted);
}
.prose blockquote p:last-child{margin-bottom:0}

.prose hr{
  margin:36px 0;
  border:0;
  border-top:1px solid var(--line);
}

.prose code{
  font-family:var(--mono);
  font-size:.88em;
  background:rgba(34,28,16,.06);
  padding:.15em .38em;
  border-radius:4px;
}
.prose pre{
  margin:0 0 24px;
  padding:16px 18px;
  background:var(--ink);
  color:#f3edda;
  border-radius:10px;
  /* A long line scrolls INSIDE the block. Without this the widest line in any
     code sample sets the width of the whole page and the body scrolls
     sideways. */
  overflow-x:auto;
  font-size:14px;
  line-height:1.55;
}
.prose pre code{background:none;padding:0;font-size:inherit;color:inherit}

.prose img{
  max-width:100%;
  height:auto;
  border-radius:10px;
  border:1px solid var(--line);
  display:block;
  margin:24px 0;
}

/* A table is the one thing here that can genuinely be wider than the column,
   so it gets its own scroller rather than being allowed to widen the page. */
.prose table{
  display:block;
  width:100%;
  overflow-x:auto;
  border-collapse:collapse;
  margin:0 0 24px;
  font-size:15px;
}
.prose th,.prose td{
  border:1px solid var(--line);
  padding:9px 12px;
  text-align:left;
  vertical-align:top;
}
.prose th{background:rgba(232,223,198,.42);font-weight:700}

/* GFM task list: the checkbox is inert (the generator pins it disabled), so it
   reads as a mark on the page rather than a control that ignores clicks. */
.prose li:has(> input[type=checkbox]){list-style:none;margin-left:-24px}
.prose input[type=checkbox]{margin-right:8px;accent-color:var(--terracotta)}

@media (min-width:700px){
  /* One value, so the bar and the column widen together and stay aligned. */
  :root{--pad:24px}
  .blog-topbar{padding:18px var(--pad)}
  .blog-wrap{padding:0 var(--pad) 96px}
  .blog-head{padding-top:34px}
  .blog-row-meta,.post-meta{font-size:13px}
}

/* The site's own reduced-motion posture: nothing here animates position, so
   only the transitions need standing down. */
@media (prefers-reduced-motion:reduce){
  .blog-topbar .pill,
  .post-foot .pill,
  .social-row-blog a,
  .blog-row-link,
  .blog-row-title,
  .prose a{transition:none}

  /* The placeholder does not rotate at all here -- blog-search.js checks the
     same query and never starts the timer, so the first phrase simply stands.
     That is the posture index.css already takes with the hero's rotating word:
     it stops rather than slows. These two lines are the belt: if the script
     ever starts it anyway, the text still changes without anything moving. */
  .blog-search-input::placeholder{transition:none}
  .blog-search-input[data-ph-fading]::placeholder{opacity:1}
}


/* --------------------------------------------------------------------------
   ONE BAR (2026-09-07). Jake: "when the blog is clicked the positioning of the
   top header needs to be the exact same". Measured at 1440x900 before this:
   the hero bar's wordmark was 19.8px at y=45 with 40px pills at y=35; the
   #blog VIEW's bar, which replaces it on that click, had a 15px wordmark at
   y=40 with 32px pills; the standalone /blog and post pages had a 17px
   wordmark at y=25 with 34px pills at y=18, and sat a further 7px left. Five
   bars, three geometries.

   These tokens are the hero bar's own numbers, mirrored from index.css
   CHARACTER FOR CHARACTER (scripts/check-blog.mjs diffs each pair, so a drift
   on either side fails the build): --bar-u is index.css's --u, --bar-top is
   .view-hero's padding-top, the pill tokens are .pill (phone) and .pill-d
   (desktop), the wordmark tokens are .wordmark-m and .wordmark-d. They live
   HERE because this is the one sheet both index.html and the generated pages
   load; index.css never reaches /blog.

   The rules below then apply them to every bar, the two landing bars
   included: the hero stops being a reference the others copy and becomes one
   of five readers of the same values. Specificity (0,2,0) beats index.css's
   .pill-d and .wordmark-d (0,1,0), and this sheet loads after index.css on
   the landing page, so equal-specificity ties go this way too.
   -------------------------------------------------------------------------- */
:root{
  --bar-u:1px;
  --bar-top:clamp(16px, calc(6.67svh - 26.67px), 28px);
  --bar-pad:24px;
  --bar-gap:10px;
  --bar-wordmark:15px;
  --bar-pill-h:32px;
  --bar-pill-pad:0 14px;
  --bar-pill-gap:6px;
  --bar-pill-font:13px;
}
@media (min-width:900px){
  :root{
    --bar-u:min(0.12195svh, 0.077vw, 1.45px);
    --bar-top:calc(32 * var(--bar-u));
    --bar-pad:var(--pad);
    --bar-gap:calc(16 * var(--bar-u));
    --bar-wordmark:calc(18 * var(--bar-u));
    --bar-pill-h:calc(36 * var(--bar-u));
    --bar-pill-pad:0 calc(16 * var(--bar-u));
    --bar-pill-gap:calc(6 * var(--bar-u));
    --bar-pill-font:calc(13.5 * var(--bar-u));
  }
}
.m-topbar,.d-topbar,.legal-topbar,.results-topbar,.blog-topbar{gap:var(--bar-gap)}
.m-topbar .pill,.d-topbar .pill,.legal-topbar .pill,.results-topbar .pill,.blog-topbar .pill{
  height:var(--bar-pill-h);
  padding:var(--bar-pill-pad);
  gap:var(--bar-pill-gap);
  font-size:var(--bar-pill-font);
  font-weight:500;
  line-height:1;
  margin-bottom:0;
}
.m-topbar .wordmark,.d-topbar .wordmark,.legal-topbar .wordmark,.results-topbar .wordmark,.blog-topbar .wordmark{
  font-size:var(--bar-wordmark);
  font-weight:700;
  letter-spacing:-.02em;
  line-height:1;
}
/* Where each bar's top edge lands. The hero's is its container's padding; the
   views' are theirs; the standalone bar carries its own. All one token. */
.view-hero,.view-legal,.view-results{padding-top:var(--bar-top)}
.blog-topbar{padding:var(--bar-top) var(--bar-pad) 0}
