/* Blog — listing, category archive and article. Built only from the tokens in
   site.css (--ink, --muted, --hairline, --blue, --radius, --shadow-*), so the
   blog inherits the light/dark themes and any future palette change for free.
   Layout classes the marketing pages already own (.wrap, .page-hero, .crumb,
   .page-title, .page-lede, .doc-meta, .section-tight) are reused as-is; only
   the pieces that have no equivalent live here. */

/* ---------- hero eyebrow ----------
   usecases.css wraps .uc-eyebrow in a bordered, shadowed capsule. That works on
   a product page, where the trailing text is two or three words; on the blog it
   holds a whole sentence ("notes from the people running the network") and the
   capsule stretches into something that reads as a disabled input.
   Blog pages get a kicker instead: the chip keeps the section colour, the rest
   is quiet text, and the mono uppercase matches the labels already used for the
   breadcrumb, PUBLISHED/READ and RELATED READING. This stylesheet loads only on
   blog pages, so the product heroes keep their capsule. */
.uc-eyebrow{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  margin-bottom:20px;padding:0;
  border:0;background:none;box-shadow:none;border-radius:0;
  font-size:14px;font-weight:450;color:var(--muted);line-height:1.5;
}
/* Sentence case, as the value is stored: category names are CMS data and
   "Knowledge Base" is how a human wrote it. Mixed case needs less tracking and
   a touch more size than the uppercase micro-labels elsewhere, or the lowercase
   letters look starved next to the caps. */
.uc-eyebrow .tag{
  padding:4px 10px;border-radius:7px;
  background:var(--blue);color:#fff;
  font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.03em;
  font-weight:500;white-space:nowrap;
}
html[data-theme="dark"] .uc-eyebrow .tag{background:#e6ecff;color:#0a0f26}
@media (max-width:600px){
  /* The chip and its sentence each get a line rather than sharing a cramped
     one; gap keeps them from touching. */
  .uc-eyebrow{gap:9px;font-size:13.5px}
}

/* ---------- listing / archive grid ---------- */
.blog-list{padding:8px 0 72px}
/* auto-fill (not auto-fit) so a single post keeps card width instead of
   stretching one card across the full 1240px wrap. */
.blog-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:22px}
.blog-card{
  display:flex;flex-direction:column;background:var(--bg-card);
  border:1px solid var(--hairline);border-radius:var(--radius-lg);overflow:hidden;
  box-shadow:var(--shadow-sm);transition:box-shadow .18s ease,border-color .18s ease,transform .18s ease;
}
.blog-card:hover{box-shadow:var(--shadow-md);border-color:var(--blue-100);transform:translateY(-2px)}
/* Fixed ratio so a missing or oddly-sized hero can't shift the grid rows. */
.blog-card-media{aspect-ratio:16/9;background:var(--bg-tint);overflow:hidden}
.blog-card-media img{width:100%;height:100%;object-fit:cover;display:block}
.blog-card-body{padding:20px 22px 22px;display:flex;flex-direction:column;gap:10px;flex:1}
.blog-card h2{
  margin:0;font-size:19px;line-height:1.35;letter-spacing:-0.015em;color:var(--ink);
  font-weight:600;
}
.blog-card h2 a{color:inherit;text-decoration:none}
.blog-card h2 a:hover{color:var(--blue)}
.blog-card p{margin:0;color:var(--muted);font-size:14.5px;line-height:1.62}
.blog-card-foot{
  margin-top:auto;padding-top:14px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.05em;color:var(--muted-2);
  text-transform:uppercase;
}

/* Category chips — same visual language as the .ribbon pills. */
.blog-cats{display:flex;flex-wrap:wrap;gap:8px;margin-top:26px}
.blog-cat{
  display:inline-flex;align-items:center;padding:6px 13px;border-radius:20px;
  border:1px solid var(--hairline);color:var(--muted);text-decoration:none;
  font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  transition:color .15s ease,border-color .15s ease,background-color .15s ease;
}
.blog-cat:hover{color:var(--blue);border-color:var(--blue)}
.blog-cat.on{color:#fff;background:var(--blue);border-color:var(--blue)}

/* ---------- pager ---------- */
.blog-pager{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:44px;flex-wrap:wrap}
.blog-pager a,.blog-pager span{
  min-width:38px;height:38px;padding:0 12px;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--hairline);border-radius:10px;text-decoration:none;color:var(--ink-2);
  font-family:'JetBrains Mono',monospace;font-size:12.5px;
}
.blog-pager a:hover{border-color:var(--blue);color:var(--blue)}
.blog-pager .on{background:var(--blue);border-color:var(--blue);color:#fff}
.blog-pager .gap{border:0;color:var(--muted-2)}

/* ---------- empty state ---------- */
.blog-empty{
  border:1px dashed var(--hairline);border-radius:var(--radius-lg);
  padding:56px 28px;text-align:center;color:var(--muted);background:var(--bg-2);
}
.blog-empty b{display:block;color:var(--ink);font-size:17px;margin-bottom:8px;font-weight:600}

/* ---------- article ---------- */
/* ONE column for the whole article: title, lede, meta row, body, tables, code,
   footer and related posts all share --prose and all centre on the page. An
   article that changes width between its parts reads as broken — a table wider
   than its own paragraphs makes the text look truncated mid-screen, and a
   two-column table stretched to 1176px is mostly empty cell.
   In px, not ch: `ch` resolves against each element's own font-size, and the
   hero, the body (16.5px) and the footer do not share one. */
/* On :root rather than .blog-article because the hero is a SIBLING of the
   article section and needs the same measure. This stylesheet ships only with
   blog-shell*.html, so nothing on the marketing pages can see it. */
:root{--prose:760px;--toc:236px;--toc-gap:60px}
.blog-article{padding:8px 0 80px}
/* Article hero: the same block as the body underneath it, so the breadcrumb,
   title, lede and meta row start on the body's left edge instead of floating
   inside it. `prose` for a plain article, `block` for one with a contents rail —
   whose content starts at the rail, not at the prose. Listing heroes carry
   neither class and keep the full wrap, because their card grid uses it. */
.page-hero-prose .page-hero-inner,
.page-hero-block .page-hero-inner{max-width:var(--prose);margin:0 auto}
@media (min-width:1100px){
  .page-hero-block .page-hero-inner{max-width:calc(var(--toc) + var(--toc-gap) + var(--prose))}
}

/* ---------- contents rail ----------
   A sticky index of the article's own h2s, in the left gutter. The prose keeps
   its measure — the rail is not an excuse to stretch the text — so the pair is
   centred as one block and the page still has air on both sides.
   Below 1100px there is no gutter to put it in, and it is dropped rather than
   stacked: the hero already carries a breadcrumb and a reading time, and a
   collapsed list on top of that is noise on a phone. */
@media (min-width:1100px){
  .blog-article.has-toc .article-grid{
    display:grid;
    grid-template-columns:var(--toc) minmax(0,var(--prose));
    gap:var(--toc-gap);
    justify-content:center;
  }
  /* Inside its own track the column no longer needs to centre itself. */
  .blog-article.has-toc .blog-prose,
  .blog-article.has-toc .blog-foot{margin-left:0;margin-right:0}
}

.blog-toc{display:none}
@media (min-width:1100px){
  .blog-toc{display:block}
  /* header.nav is sticky with a 76px inner row; the rail parks below it and
     scrolls internally when an article has more headings than fit the screen. */
  .blog-toc .toc-inner{position:sticky;top:104px;max-height:calc(100vh - 150px);overflow-y:auto}
  .blog-toc h2{
    font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.14em;
    text-transform:uppercase;color:var(--muted-2);margin:0 0 16px;font-weight:500;
  }
  .blog-toc ul{list-style:none;margin:0;padding:0}
  .blog-toc li{margin:0}
  .blog-toc a{
    display:grid;grid-template-columns:auto 1fr;align-items:start;gap:11px;
    padding:8px 0;color:var(--muted);text-decoration:none;
    font-size:13.5px;line-height:1.45;transition:color .12s;
  }
  /* The marker is a pseudo-element so the link text stays a clean flex item and
     the dot can grow a halo on the active entry without shifting the row. */
  .blog-toc a::before{
    content:"";width:6px;height:6px;margin-top:6px;border-radius:50%;
    background:var(--hairline-2);transition:background .12s, box-shadow .12s;
  }
  .blog-toc a:hover{color:var(--ink)}
  .blog-toc a[aria-current="true"]{color:var(--ink);font-weight:600}
  .blog-toc a[aria-current="true"]::before{
    background:var(--accent);
    box-shadow:0 0 0 4px color-mix(in oklab, var(--accent) 24%, transparent);
  }
}
/* ~78 characters at the body size — the top of the comfortable range for body
   text, past which the eye starts losing the beginning of the next line. */
.blog-prose{max-width:var(--prose);margin:0 auto;color:var(--ink-2);font-size:16.5px;line-height:1.75}
.blog-hero-img{
  max-width:var(--prose);margin:0 auto 42px;border-radius:var(--radius-lg);overflow:hidden;
  border:1px solid var(--hairline);box-shadow:var(--shadow-md);
}
.blog-hero-img img{width:100%;height:auto;display:block}
.blog-prose>*:first-child{margin-top:0}
.blog-prose h2{
  font-size:26px;line-height:1.25;letter-spacing:-0.02em;color:var(--ink);
  margin:44px 0 14px;font-weight:600;
  /* Contents-rail targets: without this the sticky header covers the heading
     the reader just jumped to. */
  scroll-margin-top:100px;
}
.blog-prose h3{
  font-size:19.5px;line-height:1.3;letter-spacing:-0.01em;color:var(--ink);
  margin:32px 0 10px;font-weight:600;
}
.blog-prose p{margin:0 0 18px}
.blog-prose a{color:var(--blue);text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px}
.blog-prose a:hover{color:var(--blue-400)}
.blog-prose ul,.blog-prose ol{margin:0 0 18px;padding-left:24px}
.blog-prose li{margin:0 0 8px}
.blog-prose img{max-width:100%;height:auto;border-radius:var(--radius);border:1px solid var(--hairline)}
.blog-prose blockquote{
  margin:24px 0;padding:4px 0 4px 20px;border-left:3px solid var(--blue-100);
  color:var(--muted);font-style:italic;
}
.blog-prose code{
  font-family:'JetBrains Mono',monospace;font-size:.88em;background:var(--bg-tint);
  border:1px solid var(--hairline-2);border-radius:6px;padding:2px 6px;
}
.blog-prose pre{
  background:var(--ink);color:#dbe4f6;border-radius:var(--radius);padding:18px 20px;
  overflow-x:auto;margin:0 0 22px;font-size:13.5px;line-height:1.6;
}
.blog-prose pre code{background:none;border:0;padding:0;color:inherit;font-size:inherit}
/* Tables stay inside the column like every other block. When one genuinely needs
   more room than the measure it scrolls inside this wrapper — the page itself
   never scrolls sideways. */
.blog-prose .table-wrap{overflow-x:auto;margin:0 0 22px}
.blog-prose table{border-collapse:collapse;width:100%;font-size:14.5px}
.blog-prose th,.blog-prose td{border:1px solid var(--hairline);padding:10px 13px;text-align:left;vertical-align:top}
.blog-prose th{background:var(--bg-tint);color:var(--ink);font-weight:600}
.blog-prose hr{border:0;border-top:1px solid var(--hairline);margin:36px 0}

/* ---------- article footer + related ---------- */
.blog-foot{
  max-width:var(--prose);
  margin:44px auto 0;padding-top:24px;border-top:1px solid var(--hairline);
}
/* Related posts stay in the column too, so the article ends on the same edges it
   started on. One card per row at this width would be a lonely list, so the grid
   drops to two tracks rather than the listing page's auto-fill. */
.blog-related{max-width:var(--prose);margin:64px auto 0}
/* One row, whatever the count: `related` returns at most three, and a 2+1 wrap
   leaves a hole where the fourth card would be. Column flow sizes the tracks
   from the number of cards instead of a fixed template, so one or two related
   posts fill the row rather than lining up short. */
.blog-related .blog-grid{grid-auto-flow:column;grid-auto-columns:1fr;grid-template-columns:none}
/* Three cards across the prose measure alone are 239px wide and seven excerpt
   lines tall. On an article that has a rail there is a wider block to align to —
   the rail plus the prose — which is also the shape of everything above it. */
@media (min-width:1100px){
  .blog-article.has-toc .blog-related{
    max-width:calc(var(--toc) + var(--toc-gap) + var(--prose));
  }
}
.blog-related h2{
  font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted-2);margin:0 0 18px;font-weight:500;
}

/* legal.css stacks .doc-meta one fact per row below 600px, because a wrapping
   flex row leaves a dangling divider whenever an item lands last on a line —
   and legal facts (addresses, links) are long enough to wrap constantly. An
   article's facts are "4 August 2026" and "4 min", so they stay a row here.
   Grid rather than flex: equal columns that never wrap, so the count can grow
   to three (a post that has been updated) without a divider dangling. */
@media (max-width:600px){
  .page-hero-prose .doc-meta,
  .page-hero-block .doc-meta{
    display:grid;grid-auto-flow:column;grid-auto-columns:1fr;margin-top:22px;
  }
  .page-hero-prose .doc-meta .dm,
  .page-hero-block .doc-meta .dm{
    width:auto;padding:13px 14px 13px 0;margin-right:14px;
    border-bottom:0;border-right:1px solid var(--hairline);
  }
  .page-hero-prose .doc-meta .dm:last-child,
  .page-hero-block .doc-meta .dm:last-child{
    border-right:0;margin-right:0;padding-right:0;padding-bottom:13px;
  }
}

@media (max-width:640px){
  .blog-grid{grid-template-columns:1fr;gap:18px}
  /* More specific than the rule above, so it has to be repeated here or the
     related cards stay in one row at ~110px each on a phone. */
  .blog-related .blog-grid{grid-auto-flow:row;grid-template-columns:1fr}
  .blog-prose{font-size:16px}
  .blog-prose h2{font-size:23px;margin-top:36px}
  .blog-article{padding-bottom:56px}
}
