/* ═══════════════════════════════════════════════════════════════════════════
   DRIIF — BLOG: the index listing and the article layout.

   Two page shapes share this sheet because they are one thing seen twice: the
   index is a list of posts, a post is one of those entries opened. Keeping
   them together is what stops the card's title and the article's title
   drifting into two different ideas of what a post looks like.

   WHY NOTHING HERE IS A CARD. The obvious move was .card-notch, the diagonal
   cut card the Mission Layer and Edge Autonomy use. It is the wrong component:
   that card is a feature tile with an icon and three lines of copy, built to
   sit in a row of equals. A post list is editorial — it is read top to bottom,
   the entries are different lengths, and the thing that matters is the title,
   not the frame. A hairline between rows carries that without inventing a
   second card. If the blog ever needs a featured post with artwork, that is a
   new component and a row in SECTION-TYPES.md, not a modifier bolted here.

   Everything below is scoped to .post-list / .article so nothing leaks into a
   page that merely links to a post. Per CLAUDE.md rule 3, if a second page
   ever wants the post list — a "latest writing" strip on the homepage, say —
   it moves to components.css rather than being copied.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ── INDEX: THE POST GRID ─────────────────────────────────────────────────
   Same grid idiom as .pillars in components.css — one column, two at 640,
   three at 1600 — rather than a new set of breakpoints. The site has three
   breakpoints and only three (guidelines), and a blog grid is not a reason
   for a fourth. --gutter is the one grid gutter token.

   This started as a vertical list with hairline rules. It reads well for
   long-form, but with a thumbnail per post the card is the right shape: the
   image wants to sit above its own title, not beside a full-width row. */
/* Index hero: t-h1 and t-lead both carry margin:0 (base.css), so without this the
   lead sits flush under the heading. Same --space-6 gap as every .split section
   and the legal pages' #top. Scoped to the index's #top so articles are unaffected.
   The extra .12em of --text-h1 cancels .intro-line's margin-bottom:-.12em
   (motion.css), which would otherwise pull the lead up by that much and leave
   the gap shorter than the home page's .split. */
#top .t-lead{ margin-top:calc(var(--space-6) + .12 * var(--text-h1)); }

.post-grid{
  display:grid; grid-template-columns:1fr; gap:var(--gutter);
  margin-top:var(--section-y-sm);
}
@media (min-width:640px){  .post-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (min-width:1600px){ .post-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); } }

/* The whole card is the link. An anchor wrapping block content is valid
   HTML5 and gives a pointer target the size of the card rather than the size
   of the title — which matters most on a phone, where the title may be the
   only thing small enough to miss.
   grid, not block: it makes the card a flex-like column so the thumbnail
   keeps its own box and the copy below it can take the remaining height,
   which is what lets every card in a row end level. */
a.post-item{
  display:grid; grid-template-rows:auto 1fr;
  color:inherit; text-decoration:none;
  border:var(--hair) solid var(--color-border-subtle);
  background:var(--color-bg);
  transition:border-color var(--dur-fast) var(--ease-out);
}
a.post-item:hover{ border-color:var(--color-border-strong); }
a.post-item:focus-visible{ outline:2px solid var(--color-accent); outline-offset:3px; }
a.post-item:hover .post-title{ color:var(--color-accent); }

/* 16:10 holds the row level regardless of each source image's own
   proportions, and object-fit:cover crops rather than letterboxes — the
   three images that ship with these posts are not all the same shape.
   aspect-ratio on the BOX, not the img, so the box is sized before the image
   decodes and the grid never reflows as they arrive. */
.post-thumb{
  position:relative; aspect-ratio:16/10; overflow:hidden;
  background:var(--color-surface);
  border-bottom:var(--hair) solid var(--color-border-subtle);
}
.post-thumb img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  transition:transform var(--dur-enter) var(--ease-out);
}
a.post-item:hover .post-thumb img{ transform:scale(1.03); }

.post-card-body{ padding:var(--space-6); display:flex; flex-direction:column; }

/* Meta line: category pill, date, reading time. */
.post-meta{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-3);
  margin-bottom:var(--space-4);
}
.post-meta-sep{ color:var(--color-border); }
/* Date and reading time travel together. Apart, the line wrapped between the
   separator and the reading time on a 375px screen and left a dangling "/"
   at the end of the first line, which reads as a typo rather than as a
   divider. As one nowrap unit the whole group moves to the next line
   instead — the pill above it is already doing the separating. */
.post-meta-when{
  display:inline-flex; align-items:center; gap:var(--space-3);
  white-space:nowrap;
}

/* THE CATEGORY IS A PILL, not loose eyebrow text. As an eyebrow it read as a
   stray label floating above the title with nothing marking it as a
   category — "off rather than highlighting", as reported.
   .pill is the existing component (components.css), so this is a reuse, not
   a fourth label style: hairline border, --ui-xs, uppercase with the
   tracking already compensated on the right side.
   It is deliberately NOT accent-filled. Guidelines rule 7 allows one accent
   moment per viewport and a grid puts two to six of these on screen at once;
   the page spends its accent on the heading. The hairline box is what makes
   it read as a tag — the highlight is the frame, not the colour. */
.post-title{
  margin:0; text-wrap:balance;
  transition:color var(--dur-fast) var(--ease-out);
}
/* --measure-card, the token for copy inside a card, rather than the body
   measure a full-width row would take. */
.post-excerpt{ margin:var(--space-4) 0 0; max-width:var(--measure-card); }

.post-more{
  display:inline-flex; align-items:center; gap:var(--space-2);
  margin-top:auto; padding-top:var(--space-6);
}
.post-more::after{
  content:""; width:7px; height:7px;
  border-right:2px solid currentColor; border-bottom:2px solid currentColor;
  transform:rotate(-45deg) translate(-1px,-1px);
  transition:transform var(--dur-fast) var(--ease-out);
}
a.post-item:hover .post-more::after{ transform:rotate(-45deg) translate(1px,-3px); }


/* ── ARTICLE ──────────────────────────────────────────────────────────────
   A TWO-PART GRID: a sticky table of contents on the left at one third, the
   article on the right at two thirds.

   THE COLUMN CAP IS DERIVED FROM A TOKEN, NOT A LITERAL. The grid is capped
   at calc(var(--measure-body) * 1.5) and centred. That factor is chosen so
   the 2/3 content column lands on --measure-body exactly — the measure the
   body type already wanted — rather than picking a container width and
   hoping the line length falls out of it. Rule 1 bans literal values and
   rule 2 bans a max-width that caps the LAYOUT; this caps the reading
   column, which is what the measure tokens are for, and it stays arithmetic
   on a token rather than a new number.

   This supersedes the two earlier notes that stood here — first "no width on
   the container at all", then "one centred column". Both are gone: a 1/3
   sidebar has to come out of a defined width, and the content column has to
   keep its measure while it does.

   Head and cover stay OUTSIDE the grid, at full column width, so the title
   and banner read across the whole measure and only the body is narrowed by
   the sidebar. */
article.section > .bleed{
  max-width:calc(var(--measure-body) * 1.5);
  margin-inline:auto;
}
.article-grid{ display:grid; grid-template-columns:1fr; gap:var(--gutter); }
@media (min-width:1024px){
  .article-grid{ grid-template-columns:minmax(0,1fr) minmax(0,2fr); }
}

/* ── TABLE OF CONTENTS ────────────────────────────────────────────────────
   align-self:start is the whole trick. A grid item stretches to the row
   height by default, which would make the sticky box exactly as tall as the
   article — so it has nowhere to travel and never sticks. Starting it means
   the box is only as tall as its own content, and the rest of the column
   height is the distance it can stick through. That is what "sticky to the
   long parent container" needs.

   top reuses calc(--space-8 + --space-4), the site's effective-nav-height
   expression — the same one html uses for scroll-padding-top (base.css:53)
   and the sticky tab bar and VIPER's sticky media already use. Because
   scroll-padding-top uses it too, a TOC link lands its heading below the
   fixed nav with no extra rule here. */
.article-toc{
  position:sticky; top:calc(var(--space-8) + var(--space-4));
  align-self:start;
}
.toc-h{ margin:0 0 var(--space-4); }
.toc-list{
  margin:0; padding:0; list-style:none;
  border-left:var(--hair) solid var(--color-border-subtle);
}
.toc-list li + li{ margin-top:var(--space-3); }
.toc-list a{
  display:block; padding-left:var(--space-5);
  margin-left:calc(var(--hair) * -1);
  color:var(--color-text-muted); text-decoration:none;
  border-left:2px solid transparent;
  transition:color var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out);
}
.toc-list a:hover{ color:var(--color-text); }
.toc-list a:focus-visible{ outline:2px solid var(--color-accent); outline-offset:3px; }
/* The one accent moment this component spends: the section you are actually
   in. Exactly one link carries .is-current at a time (toc.js enforces it), so
   a scrolling page never shows two. */
.toc-list a.is-current{ color:var(--color-text); border-left-color:var(--color-accent); }

@media (max-width:1023px){
  /* Below the two-column breakpoint the TOC is a plain list at the top of the
     article rather than a rail. Not hidden: on a phone a long read still
     benefits from knowing what is in it, and hiding it would make the ids and
     anchors dead weight. */
  .article-toc{ position:static; padding-bottom:var(--space-6); }
}

/* ── RELATED ──────────────────────────────────────────────────────────────
   Full column width under the grid, reusing .post-grid / .post-item from the
   index wholesale — same card, same thumbnail box, same hover. A second card
   idiom for "related" would be exactly the drift SECTION-TYPES.md exists to
   stop. */
.article-related{
  margin-top:var(--section-y);
  padding-top:var(--space-7);
  border-top:var(--hair) solid var(--color-border-subtle);
}
.article-related .post-grid{ margin-top:var(--space-6); }

.article-head{ padding-bottom:var(--section-y-sm); }
.article-title{ margin:var(--space-5) 0 0; text-wrap:balance; }
.article-lead{ margin:var(--space-6) 0 0; }

/* COVER, directly under the title block. 16:9 rather than the card's 16:10 —
   it is the widest thing on the page and a shallower crop reads as a banner
   rather than as a big version of the thumbnail. Same source image as the
   card on the index, so a reader arrives at a page that looks like the card
   they clicked.
   aspect-ratio on the box with object-fit:cover on the image, so the space is
   reserved before the image decodes and the article below it never shifts —
   the same reasoning as .post-thumb. */
.article-cover{
  margin:0 0 var(--section-y-sm);
  position:relative; aspect-ratio:16/9; overflow:hidden;
  background:var(--color-surface);
  border:var(--hair) solid var(--color-border-subtle);
}
.article-cover img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }

/* Body copy. Every block is capped by its own role's measure, so the column
   is set by the type, not by the container. The h2s are --text-h2, one step
   below the article's own --text-h1 title — rule 5, headings descend. */
.article-body > * + *{ margin-top:var(--space-6); }
.article-body h2{ margin-top:var(--section-y-sm); }
.article-body h3{ margin-top:var(--space-7); }
.article-body p{ max-width:var(--measure-body); }
.article-body ul{ margin:0; padding:0; list-style:none; max-width:var(--measure-body); }
.article-body li + li{ margin-top:var(--space-3); }
/* Accent chevron marker, matching .copy-list elsewhere on the site rather
   than introducing a second list idiom. */
.article-body li{ position:relative; padding-left:var(--space-5); }
.article-body li::before{
  content:""; position:absolute; left:0; top:calc(var(--text-body) * var(--lh-body) / 2);
  width:5px; height:5px; margin-top:-3px;
  border-right:2px solid var(--color-accent); border-bottom:2px solid var(--color-accent);
  transform:rotate(-45deg);
}

/* LINKS INSIDE THE ARTICLE. base.css sets `a{ color:inherit; text-decoration:
   none }` globally, which is right for nav and chrome and wrong here: with it
   an in-body link is indistinguishable from the sentence around it. That went
   unnoticed while the posts were short; the real articles carry ~28 links,
   inline citations among them, and a citation nobody can see is not one.
   An underline in the text colour, drawn at --hair, is enough to mark a link
   without spending accent (rule 7). Accent appears only on hover, and only
   because this page is on the dark pole — rule 11 forbids accent as text on a
   light surface, so this would have to change if posts ever sit on one. */
.article-body a{
  text-decoration:underline;
  text-decoration-thickness:var(--hair);
  text-underline-offset:var(--space-1);
  transition:color var(--dur-fast) var(--ease-out);
}
.article-body a:hover{ color:var(--color-accent); }
.article-body a:focus-visible{ outline:2px solid var(--color-accent); outline-offset:2px; }

/* An image inside the text, between paragraphs. Not the cover: the cover is
   the .article-cover banner under the title and is cropped to 16:9. This one
   keeps its own proportions (width/height are on the <img>, so the box is
   reserved before it decodes). The global figure margin is UA-default 40px
   on each inline side, which would indent it out of the measure — only the
   inline and bottom margins are reset, because `.article-body > * + *`
   owns the top one. */
.article-figure{
  margin-inline:0; margin-bottom:0;
  border:var(--hair) solid var(--color-border-subtle);
  background:var(--color-surface);
}
.article-figure img{ display:block; width:100%; height:auto; }

/* Sources: a reference list. The chevron marker the other lists use would
   read as one more point in the argument, so it is removed here, and the
   items are muted in the markup (.muted) so the list sits below the body in
   weight. Unnumbered on purpose — the source document's own list is bulleted,
   and the article body does not cite by number. */
.article-sources{ list-style:none; }
.article-sources li{ padding-left:0; }
.article-sources li::before{ content:none; }

/* A pulled line, not a blockquote with a big quote mark. The rule on the left
   is the same hairline as everywhere else; the type steps up to --text-lead
   and goes unmuted, which is enough to lift it out of the column. */
.article-pull{
  margin:var(--section-y-sm) 0;
  padding-left:var(--space-6);
  border-left:2px solid var(--color-accent);
  max-width:var(--measure-lead);
}

/* Footer of the article: back to the index, and the two product pages a post
   most often sends a reader to. Links, not buttons — the page already spends
   its button on the nav CTA. */
.article-foot{
  margin-top:var(--section-y);
  padding-top:var(--space-7);
  border-top:var(--hair) solid var(--color-border-subtle);
  display:flex; flex-wrap:wrap; gap:var(--space-6) var(--space-7);
  align-items:baseline; justify-content:space-between;
}
.article-foot-links{ display:flex; flex-wrap:wrap; gap:var(--space-6); }

@media (max-width:639px){
  .post-item{ padding-block:var(--space-6); }
  /* The arrow row is the first thing to go when the column is this narrow —
     the whole row is already the link, so it was never the only affordance. */
  .post-more{ display:none; }
}
