/* ==========================================================================
   ShopFront — Blog
   --------------------------------------------------------------------------
   Shared by the two documents of the blog family: blog.html (the index) and
   post.html (a single article). They are one family for the same reason
   about / faq / contact are one: the index links straight into the article
   and the article ends by linking back into the index, so a card, a topic
   chip and a byline have to look identical on both. Splitting them into
   blog.css + post.css would mean maintaining two copies of the card, the
   chip and the meta row and watching them drift.

   Load order is `tokens.css → styles.css → page.css → blog.css`. Both
   documents load all four: page.css carries the breadcrumb, the gradient
   page hero, the media placeholders and the closing CTA band, and nothing
   below re-implements any of them.

   The house rules from styles.css apply unchanged, and are not negotiable:
     · The design is FLAT. Surfaces separate by border and tone, never by
       shade. The --shadow-* ramp resolves to none — the calls are kept so
       that turning elevation back on stays a one-line edit in tokens.css.
     · Logical properties only (inline/block): the document is RTL and
       every one of these blocks has to mirror.
     · No raw colour, no raw shadow, no off-grid spacing. Tokens only.

   The listing card is NOT a second card component. `.blog-card` already
   exists in styles.css section 24, drawn for the three-card strip on the
   home page; the index needs the same card carrying two extra rows, so
   what follows adds `.blog-card__topic`, `__excerpt` and `__meta` to it
   rather than authoring a `.post-card` that would immediately drift from
   the home page's copy of the same object.

   Contents
     01 · HERO SEARCH
     02 · TOPIC RAIL
     03 · FEATURED POST
     04 · LISTING LAYOUT
     05 · TOOLBAR
     06 · CARD EXTENSIONS
     07 · EMPTY STATE & LOAD MORE
     08 · SIDEBAR MODULES
     09 · STAND-IN NOTE
     10 · READING PROGRESS
     11 · ARTICLE HEAD
     12 · ARTICLE LAYOUT & CONTENTS
     13 · ARTICLE BODY
     14 · ARTICLE FURNITURE
     15 · TAGS, SHARE, VOTE, AUTHOR
     16 · POST NAV & RELATED
     17 · COMMENTS
     18 · RESPONSIVE
     19 · MOTION, CONTRAST, PRINT
   ========================================================================== */


/* ==========================================================================
   01 · HERO SEARCH
   --------------------------------------------------------------------------
   Deliberately not the header's .search combobox. That one searches the
   catalogue and returns products; this one filters the articles on this
   page and returns nothing at all if nothing was written about the term.
   Two different jobs, so two different controls — the same split faq.css
   makes for the same reason.

   It sits inside .page-hero, i.e. on the brand gradient, so its chrome is
   drawn against --fg-on-brand rather than against the page surface.
   ========================================================================== */

.blog-search {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-inline-md);
  inline-size: 100%;
  max-inline-size: 34rem;
}

.blog-search__field {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--gap-inline-sm);
  flex: 1 1 18rem;
  min-inline-size: 0;
  padding-inline: var(--input-pad-inline);
  border: var(--border-thin) solid var(--border-inverse);
  border-radius: var(--radius-pill);
  /* A black wash, not a white one: white at any opacity lifts the panel
     toward the 4.5:1 floor the white text above it depends on. Same
     reasoning as .btn--ghost-onbrand in page.css. */
  background: oklch(0 0 0 / 0.16);
  color: var(--fg-on-brand);
  transition: var(--transition-colors);
}
.blog-search__field:focus-within {
  border-color: oklch(1 0 0 / 0.55);
  background: oklch(0 0 0 / 0.26);
}

.blog-search__field > .icon {
  flex: none;
  font-size: var(--icon-md);
  opacity: 0.85;
}

.blog-search__input {
  flex: 1 1 auto;
  min-inline-size: 0;
  min-block-size: var(--input-height);
  border: 0;
  background: none;
  color: inherit;
  font-size: var(--text-lg);
}
.blog-search__input::placeholder {
  color: inherit;
  opacity: 0.7;
}
/* The UA's own clear affordance would sit beside ours and do half the job:
   it empties the field without re-running the filter. */
.blog-search__input::-webkit-search-cancel-button {
  display: none;
}
.blog-search__input:focus-visible {
  outline: none;
}

.blog-search__clear {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: var(--space-8);
  block-size: var(--space-8);
  border-radius: var(--radius-circle);
  background: oklch(0 0 0 / 0.24);
  color: inherit;
  font-size: var(--icon-sm);
  transition: var(--transition-colors);
}
.blog-search__clear:hover {
  background: oklch(0 0 0 / 0.38);
}
.blog-search__clear[hidden] {
  display: none;
}

/* Ready-made queries. They write into the field rather than filtering
   directly, so the visitor can see and edit what was searched. */
.blog-search__hints {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap-inline-sm);
  inline-size: 100%;
  color: inherit;
  font-size: var(--text-sm);
}
.blog-search__hints > span {
  opacity: 0.8;
}

.blog-search__hint {
  padding: var(--space-1) var(--space-3);
  border: var(--border-hairline) solid var(--border-inverse);
  border-radius: var(--radius-pill);
  background: oklch(0 0 0 / 0.14);
  color: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  transition: var(--transition-colors);
}
.blog-search__hint:hover {
  background: oklch(0 0 0 / 0.28);
}


/* ==========================================================================
   02 · TOPIC RAIL
   --------------------------------------------------------------------------
   Real links, not buttons: every topic is its own address, so it has to be
   shareable, openable in a new tab and crawlable. blog.js intercepts the
   click and filters in place — the href is the no-JavaScript path, not
   decoration.
   ========================================================================== */

.topic-rail {
  display: flex;
  gap: var(--space-3);
  margin-block-start: var(--space-8);
  padding-block-end: var(--space-2);
}

.topic-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-inline-sm);
  flex: none;
  padding: var(--space-3) var(--space-5);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  color: var(--fg-body);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  white-space: nowrap;
  transition: var(--transition-colors), var(--transition-elevate);
}
.topic-chip .icon {
  color: var(--brand);
  font-size: var(--icon-md);
  transition: var(--transition-colors);
}
.topic-chip:hover {
  transform: translateY(-2px);
  border-color: var(--border-brand);
  color: var(--brand-text);
}

.topic-chip.is-active {
  border-color: transparent;
  background: var(--brand);
  color: var(--fg-on-brand);
}
.topic-chip.is-active .icon {
  color: inherit;
}

/* The count is a fact about the topic, not a badge demanding attention, so
   it takes the quietest weight on the chip. */
.topic-chip__count {
  padding: var(--space-0-5) var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--bg-inset);
  color: var(--fg-muted);
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold);
}
.topic-chip.is-active .topic-chip__count {
  background: oklch(0 0 0 / 0.22);
  color: inherit;
}


/* ==========================================================================
   03 · FEATURED POST
   --------------------------------------------------------------------------
   One article gets the wide slot. It is a different shape from the card,
   not a bigger one: the card leads with its picture and the feature leads
   with its copy, because at this size the excerpt is what decides whether
   the piece is worth opening.
   ========================================================================== */

.post-feature {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  align-items: stretch;
  gap: 0;
  margin-block-start: var(--space-8);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-3xl);
  background: var(--bg-surface);
  overflow: hidden;
  transition: var(--transition-colors);
}
.post-feature:hover,
.post-feature:focus-within {
  border-color: var(--border-brand);
}

/* `display: grid` above outranks the UA's `[hidden] { display: none }`, and
   blog.js hides the featured post whenever a search or a topic filter no
   longer matches it. */
.post-feature[hidden] {
  display: none;
}

.post-feature__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  padding: var(--space-10);
  min-inline-size: 0;
}

.post-feature__title {
  font-size: var(--text-4xl);
  font-weight: var(--weight-black);
  line-height: var(--leading-tight);
}
.post-feature__title a {
  transition: var(--transition-colors);
}
.post-feature__title a:hover {
  color: var(--brand-text);
}

.post-feature__excerpt {
  max-inline-size: 54ch;
  color: var(--fg-muted);
  font-size: var(--text-lg);
  line-height: var(--leading-relaxed);
}

.post-feature__cta {
  margin-block-start: var(--space-2);
}

.post-feature__media {
  min-inline-size: 0;
  overflow: hidden;
}
.post-feature__media img,
.post-feature__media .ph__frame {
  inline-size: 100%;
  block-size: 100%;
  min-block-size: 20rem;
  border-radius: 0;
  object-fit: cover;
  transition: transform var(--duration-slower) var(--ease-out);
}
.post-feature:hover .post-feature__media img {
  transform: scale(1.03);
}

/* The eyebrow row shared by the feature, the card and the article head:
   topic pill, date, reading time. One rule for all three so a byline never
   reads differently depending on where it is standing. */
.post-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap-inline-md);
  color: var(--fg-subtle);
  font-size: var(--text-sm);
}

.post-meta > * {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-inline-xs);
}

.post-meta .icon {
  font-size: var(--icon-sm);
  color: var(--fg-subtle);
}

/* Hairline divider between meta items, drawn rather than typed: a bullet
   character picks up the paragraph's direction and lands on the wrong side
   of the pair in RTL. */
.post-meta > * + *::before {
  content: "";
  inline-size: var(--border-hairline);
  block-size: 1em;
  margin-inline-end: var(--gap-inline-xs);
  background: var(--border-default);
}

.post-topic {
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--bg-brand-subtle);
  color: var(--brand-text);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
}
.post-topic .icon {
  color: inherit;
  font-size: var(--icon-sm);
}
/* On the article head the pill is a link back to its topic; in a card it is
   a label. Styling the interactive state rather than the element keeps one
   rule for both. */
a.post-topic {
  transition: var(--transition-colors);
}
a.post-topic:hover {
  background: var(--brand);
  color: var(--fg-on-brand);
}


/* ==========================================================================
   04 · LISTING LAYOUT
   --------------------------------------------------------------------------
   Articles on the inline-start, a column of standing modules on the other.
   The aside is sticky and scrolls with the list rather than sitting at the
   top of a page that is three screens long.
   ========================================================================== */

.blog-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 20rem;
  align-items: start;
  gap: var(--space-10);
  margin-block-start: var(--space-10);
}

.blog-main {
  min-inline-size: 0;
}

.blog-side {
  position: sticky;
  inset-block-start: calc(var(--header-height-compact) + var(--space-4));
  display: grid;
  gap: var(--space-5);
  min-inline-size: 0;
}


/* ==========================================================================
   05 · TOOLBAR
   ========================================================================== */

.blog-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-inline-md);
  padding-block-end: var(--space-5);
  border-block-end: var(--border-hairline) solid var(--border-subtle);
}

.blog-toolbar__count {
  color: var(--fg-muted);
  font-size: var(--text-md);
}
.blog-toolbar__count b {
  color: var(--fg-default);
  font-size: var(--text-xl);
  font-weight: var(--weight-black);
}

/* Segmented control rather than a <select>: three mutually exclusive
   orders, all of them one word, is exactly the case where a menu costs a
   click and shows nothing the row could not show already. */
.sort-toggle {
  display: flex;
  gap: var(--space-1);
  padding: var(--space-1);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
}

.sort-toggle button {
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  color: var(--fg-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  white-space: nowrap;
  transition: var(--transition-colors);
}
.sort-toggle button:hover {
  color: var(--brand-text);
}
.sort-toggle button[aria-pressed="true"] {
  background: var(--bg-brand-subtle);
  color: var(--brand-text);
}


/* ==========================================================================
   06 · CARD EXTENSIONS
   --------------------------------------------------------------------------
   Added to `.blog-card` from styles.css section 24, never replacing it.
   The home page uses the same object with the topic, excerpt and meta rows
   simply absent, and both stay in step because there is only one card.
   ========================================================================== */

.post-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap-grid-lg);
  margin-block-start: var(--space-6);
}

/* Hidden by the filter, by the sort, or by the pager. `display: none` and
   not `visibility`, so the grid closes the gap the card left. */
.blog-card[hidden] {
  display: none;
}

/* The topic sits over the picture: inside the body it would push the title
   down a line on every card and cost the grid a row of height. */
.blog-card__media {
  position: relative;
}

.blog-card__topic {
  position: absolute;
  inset-block-start: var(--space-3);
  inset-inline-start: var(--space-3);
  display: inline-flex;
  align-items: center;
  gap: var(--gap-inline-xs);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  /* Reads against a photograph of unknown brightness, so it carries its own
     dark plate rather than a tint of the page surface. */
  background: oklch(0 0 0 / 0.62);
  color: var(--fg-on-brand);
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold);
}
.blog-card__topic .icon {
  font-size: var(--icon-sm);
}

@supports (backdrop-filter: blur(1px)) {
  .blog-card__topic {
    background: oklch(0 0 0 / 0.45);
    backdrop-filter: blur(var(--header-blur));
  }
}

.blog-card__excerpt {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  padding: var(--space-2-5) var(--space-5) 0;
  color: var(--fg-muted);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
}

/* Second meta line, under the excerpt: reading time and author. The date
   stays in `.blog-card__foot` where styles.css put it. */
.blog-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap-inline-md);
  padding: var(--space-3) var(--space-5) 0;
  color: var(--fg-subtle);
  font-size: var(--text-xs);
}
.blog-card__meta > * {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-inline-xs);
}
.blog-card__meta .icon {
  font-size: var(--icon-sm);
}

/* A placeholder standing in for a photograph that has not been shot yet.
   It has to hold exactly the box the <img> will hold, or every card in the
   row changes height the day the art lands. */
.blog-card__media .ph {
  block-size: 100%;
}
.blog-card__media .ph__frame {
  min-block-size: 100%;
  border-radius: 0;
  padding: var(--space-4);
}


/* ==========================================================================
   07 · EMPTY STATE & LOAD MORE
   ========================================================================== */

.blog-empty {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  margin-block-start: var(--space-10);
  padding: var(--space-12) var(--space-6);
  border: var(--border-thin) dashed var(--border-strong);
  border-radius: var(--radius-3xl);
  background: var(--bg-surface);
  color: var(--fg-muted);
  text-align: center;
}
.blog-empty[hidden] {
  display: none;
}
.blog-empty b {
  color: var(--fg-default);
  font-size: var(--text-2xl);
  font-weight: var(--weight-extrabold);
}
.blog-empty span {
  max-inline-size: 46ch;
}
.blog-empty .btn {
  margin-block-start: var(--space-2);
}

.blog-more {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  margin-block-start: var(--space-10);
}
.blog-more[hidden] {
  display: none;
}

.blog-more__status {
  color: var(--fg-muted);
  font-size: var(--text-sm);
}

/* How much of the list has been seen. A bare "load more" says nothing about
   whether the next press is the last one. */
.blog-more__track {
  inline-size: min(18rem, 100%);
  block-size: var(--space-1-5);
  border-radius: var(--radius-pill);
  background: var(--bg-inset);
  overflow: hidden;
}

.blog-more__fill {
  display: block;
  block-size: 100%;
  border-radius: inherit;
  background: var(--gradient-brand);
  transition: inline-size var(--duration-slow) var(--ease-out);
}


/* ==========================================================================
   08 · SIDEBAR MODULES
   ========================================================================== */

.side-card {
  padding: var(--space-6);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-2xl);
  background: var(--bg-surface);
}

.side-card__title {
  display: flex;
  align-items: center;
  gap: var(--gap-inline-sm);
  margin-block-end: var(--space-4);
  font-size: var(--text-lg);
  font-weight: var(--weight-extrabold);
}
.side-card__title .icon {
  color: var(--brand);
  font-size: var(--icon-md);
}

/* ---- Topic list ------------------------------------------------------- */

.topic-list {
  display: grid;
  gap: var(--space-1);
  list-style: none;
  padding: 0;
}

.topic-list a {
  display: flex;
  align-items: center;
  gap: var(--gap-inline-sm);
  padding: var(--space-2-5) var(--space-3);
  border-radius: var(--radius-lg);
  color: var(--fg-body);
  font-size: var(--text-md);
  transition: var(--transition-colors);
}
.topic-list a:hover,
.topic-list a.is-active {
  background: var(--bg-brand-subtle);
  color: var(--brand-text);
}
.topic-list a .icon {
  color: var(--brand);
  font-size: var(--icon-md);
}
.topic-list b {
  margin-inline-start: auto;
  color: var(--fg-subtle);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
}
.topic-list a:hover b,
.topic-list a.is-active b {
  color: inherit;
}

/* ---- Most read -------------------------------------------------------- */

.mini-post {
  display: flex;
  align-items: flex-start;
  gap: var(--gap-inline-md);
  padding-block: var(--space-3-5);
}
.mini-post + .mini-post {
  border-block-start: var(--border-hairline) solid var(--border-subtle);
}

/* The rank is the picture. A 56px thumbnail of a photograph is a smear;
   a numeral at the same size is legible and says something the thumbnail
   does not. */
.mini-post__rank {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: var(--space-9);
  block-size: var(--space-9);
  border-radius: var(--radius-md);
  background: var(--bg-brand-subtle);
  color: var(--brand-text);
  font-size: var(--text-lg);
  font-weight: var(--weight-black);
  font-variant-numeric: tabular-nums;
}

.mini-post__body {
  min-inline-size: 0;
}

.mini-post__title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--fg-default);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  transition: var(--transition-colors);
}
.mini-post__title:hover {
  color: var(--brand-text);
}

.mini-post__meta {
  margin-block-start: var(--space-1-5);
  color: var(--fg-subtle);
  font-size: var(--text-xs);
}

/* ---- Tag cloud -------------------------------------------------------- */

.tag-cloud {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.tag {
  padding: var(--space-1-5) var(--space-3);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-pill);
  background: var(--bg-surface-2);
  color: var(--fg-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  transition: var(--transition-colors);
}
.tag:hover {
  border-color: var(--border-brand);
  background: var(--bg-brand-subtle);
  color: var(--brand-text);
}

/* ---- Newsletter ------------------------------------------------------- */

.subscribe {
  padding: var(--space-6);
  border-radius: var(--radius-2xl);
  background: var(--gradient-brand);
  color: var(--fg-on-brand);
}
.subscribe__title {
  color: inherit;
  font-size: var(--text-xl);
  font-weight: var(--weight-extrabold);
}
.subscribe__text {
  margin-block-start: var(--space-2);
  color: inherit;
  opacity: 0.9;
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
}

.subscribe__form {
  display: grid;
  gap: var(--space-3);
  margin-block-start: var(--space-5);
}

.subscribe__input {
  min-block-size: var(--input-height);
  padding-inline: var(--input-pad-inline);
  border: var(--border-thin) solid var(--border-inverse);
  border-radius: var(--radius-pill);
  background: oklch(0 0 0 / 0.18);
  color: var(--fg-on-brand);
  font-size: var(--text-md);
  /* An address is Latin inside an RTL form; without this the caret starts
     on the wrong side and the domain reads back-to-front while typing. */
  direction: ltr;
  text-align: start;
  transition: var(--transition-colors);
}
.subscribe__input::placeholder {
  color: var(--fg-on-brand);
  opacity: 0.62;
}
.subscribe__input:focus {
  border-color: oklch(1 0 0 / 0.55);
  background: oklch(0 0 0 / 0.28);
  outline: none;
}

.subscribe__note {
  color: inherit;
  opacity: 0.78;
  font-size: var(--text-2xs);
}


/* ==========================================================================
   09 · STAND-IN NOTE
   --------------------------------------------------------------------------
   post.html is one written article standing in for the whole index, exactly
   as product.html stands in for the catalogue. When the ?p= in the address
   bar names a different piece, saying so is better than serving this one as
   though it were whatever was clicked.
   ========================================================================== */

.post-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap-inline-md);
  margin-block-start: var(--space-4);
  padding: var(--space-3) var(--space-5);
  border: var(--border-hairline) solid var(--warning);
  border-radius: var(--radius-lg);
  background: var(--warning-subtle);
  color: var(--warning-text);
  font-size: var(--text-md);
}
.post-note[hidden] {
  display: none;
}
.post-note .icon {
  flex: none;
}
.post-note b {
  font-weight: var(--weight-extrabold);
}
.post-note a {
  margin-inline-start: auto;
  font-weight: var(--weight-bold);
  text-decoration: underline;
  text-underline-offset: 0.25em;
}


/* ==========================================================================
   10 · READING PROGRESS
   --------------------------------------------------------------------------
   A hairline across the top of the viewport. Not the site's only progress
   signal — the contents list marks the section too — so it is decorative,
   carries aria-hidden, and disappears entirely under reduced motion.
   ========================================================================== */

.read-progress {
  position: fixed;
  z-index: var(--z-sticky);
  inset-block-start: 0;
  inset-inline: 0;
  block-size: var(--space-1);
  background: transparent;
  pointer-events: none;
}

.read-progress__fill {
  display: block;
  inline-size: 0;
  block-size: 100%;
  /* Physical transform-origin: the bar grows from the inline-start edge,
     which the writing direction already mirrors for us via `inline-size`. */
  background: var(--gradient-brand-vivid);
  transition: inline-size var(--duration-fast) var(--ease-out);
}


/* ==========================================================================
   11 · ARTICLE HEAD
   ========================================================================== */

.post-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-5);
  margin-block-start: var(--space-8);
}

.post-head__title {
  max-inline-size: 24ch;
  font-size: var(--text-6xl);
  font-weight: var(--weight-black);
  line-height: var(--leading-tight);
}

.post-head__lede {
  max-inline-size: 62ch;
  color: var(--fg-muted);
  font-size: var(--text-2xl);
  line-height: var(--leading-normal);
}

.post-head__byline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap-inline-lg);
  inline-size: 100%;
  padding-block: var(--space-5);
  border-block: var(--border-hairline) solid var(--border-subtle);
}

.post-head__author {
  display: flex;
  align-items: center;
  gap: var(--gap-inline-md);
}
.post-head__author .ph--avatar {
  inline-size: var(--space-12);
  block-size: var(--space-12);
}
.post-head__author b {
  display: block;
  color: var(--fg-default);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
}
.post-head__author span {
  display: block;
  color: var(--fg-subtle);
  font-size: var(--text-xs);
}

.post-head__facts {
  margin-inline-start: auto;
}

/* .post-head aligns its children to the inline-start, so a figure that did
   not claim the full width would shrink to its caption. */
.post-cover {
  inline-size: 100%;
  margin-block-start: var(--space-8);
}
.post-cover img,
.post-cover .ph__frame {
  inline-size: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-3xl);
  object-fit: cover;
}
.post-cover figcaption {
  margin-block-start: var(--space-3);
  color: var(--fg-subtle);
  font-size: var(--text-sm);
  text-align: center;
}


/* ==========================================================================
   12 · ARTICLE LAYOUT & CONTENTS
   --------------------------------------------------------------------------
   Body on the inline-start, contents list on the other. The list is the
   only sticky element on the page: it is the one thing a reader four
   screens down must never have to scroll back up to find.
   ========================================================================== */

.post-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 18rem;
  align-items: start;
  gap: var(--space-12);
  margin-block-start: var(--space-10);
}

.post-main {
  min-inline-size: 0;
}

.post-aside {
  position: sticky;
  inset-block-start: calc(var(--header-height-compact) + var(--space-4));
  display: grid;
  gap: var(--space-5);
  min-inline-size: 0;
}

.post-toc__list {
  display: grid;
  gap: var(--space-0-5);
  list-style: none;
  padding: 0;
  /* Numbered by CSS rather than by <ol>: the numeral has to sit in the
     hanging position beside a two-line label, which a list marker cannot do
     without also indenting the wrap. */
  counter-reset: toc;
}

.post-toc__list a {
  display: flex;
  align-items: baseline;
  gap: var(--gap-inline-sm);
  padding: var(--space-2) var(--space-3);
  border-inline-start: var(--border-thick) solid transparent;
  border-radius: var(--radius-sm);
  color: var(--fg-muted);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  transition: var(--transition-colors);
}
.post-toc__list a::before {
  counter-increment: toc;
  content: counter(toc, persian) ".";
  flex: none;
  color: var(--fg-subtle);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
}
.post-toc__list a:hover {
  background: var(--bg-brand-subtle);
  color: var(--brand-text);
}

/* Set by post.js as the reader passes each heading. The rule is on the
   inline-start edge so it mirrors with the writing direction. */
.post-toc__list a.is-current {
  border-inline-start-color: var(--brand);
  background: var(--bg-brand-subtle);
  color: var(--brand-text);
  font-weight: var(--weight-bold);
}
.post-toc__list a.is-current::before {
  color: inherit;
}


/* ==========================================================================
   13 · ARTICLE BODY
   --------------------------------------------------------------------------
   Long-form running text, which is a different object from product.css's
   `.prose` — that one dresses a three-paragraph description inside a tab
   panel and has no headings, no tables and no figures to place. Naming this
   `.prose` too would have meant one class with two incompatible sets of
   children, and the two sheets never load together to say so.

   Element selectors, not a class per paragraph: the body is the one place
   in this codebase where the markup is written by an author rather than by
   a component, and an author writes <p>, not <p class="post-body__p">.
   ========================================================================== */

.post-body {
  max-inline-size: 70ch;
  color: var(--fg-body);
  font-size: var(--text-lg);
  line-height: var(--leading-relaxed);
}

.post-body > * + * {
  margin-block-start: var(--space-5);
}

.post-body h2 {
  margin-block-start: var(--space-12);
  font-size: var(--text-3xl);
  font-weight: var(--weight-black);
  /* Clears the sticky header when the contents list jumps here. */
  scroll-margin-block-start: calc(var(--header-height-compact) + var(--space-8));
}

.post-body h3 {
  margin-block-start: var(--space-8);
  font-size: var(--text-2xl);
  font-weight: var(--weight-extrabold);
}

.post-body h2 + p,
.post-body h3 + p {
  margin-block-start: var(--space-4);
}

/* The opening paragraph carries the reader in, so it is set one step larger
   than the rest — the only typographic exception in the body. */
.post-body > p:first-of-type {
  color: var(--fg-default);
  font-size: var(--text-xl);
  line-height: var(--leading-relaxed);
}

.post-body a:not(.btn) {
  color: var(--brand-text);
  font-weight: var(--weight-bold);
  text-decoration: underline;
  text-underline-offset: 0.25em;
  text-decoration-thickness: var(--border-thin);
}
.post-body a:not(.btn):hover {
  text-decoration-thickness: var(--border-thick);
}

.post-body strong,
.post-body b {
  color: var(--fg-default);
  font-weight: var(--weight-bold);
}

.post-body ul,
.post-body ol {
  display: grid;
  gap: var(--space-3);
  padding: 0;
  list-style: none;
}

.post-body li {
  position: relative;
  padding-inline-start: var(--space-7);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
}

.post-body ul > li::before {
  content: "";
  position: absolute;
  inset-block-start: 0.65em;
  inset-inline-start: var(--space-1);
  inline-size: var(--space-2);
  block-size: var(--space-2);
  border-radius: var(--radius-circle);
  background: var(--brand);
}

.post-body ol {
  counter-reset: steps;
}
.post-body ol > li::before {
  counter-increment: steps;
  content: counter(steps, persian);
  position: absolute;
  inset-block-start: 0.05em;
  inset-inline-start: 0;
  display: grid;
  place-items: center;
  inline-size: var(--space-5);
  block-size: var(--space-5);
  border-radius: var(--radius-circle);
  background: var(--bg-brand-subtle);
  color: var(--brand-text);
  font-size: var(--text-xs);
  font-weight: var(--weight-black);
}

.post-body hr {
  block-size: var(--border-hairline);
  margin-block: var(--space-10);
  border: 0;
  background: var(--border-subtle);
}


/* ==========================================================================
   14 · ARTICLE FURNITURE
   ========================================================================== */

/* ---- Pull quote ------------------------------------------------------- */

.post-quote {
  display: flex;
  align-items: flex-start;
  gap: var(--gap-inline-md);
  margin-inline: 0;
  padding: var(--space-6);
  border-inline-start: var(--border-heavy) solid var(--brand);
  border-radius: var(--radius-md);
  background: var(--bg-brand-subtle);
  color: var(--fg-default);
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
}
.post-quote .icon {
  flex: none;
  margin-block-start: 0.15em;
  color: var(--brand);
  font-size: var(--icon-lg);
}
.post-quote cite {
  display: block;
  margin-block-start: var(--space-3);
  color: var(--fg-muted);
  font-size: var(--text-sm);
  font-style: normal;
  font-weight: var(--weight-medium);
}

/* ---- Callout ---------------------------------------------------------- */

.post-callout {
  display: flex;
  align-items: flex-start;
  gap: var(--gap-inline-md);
  padding: var(--space-5);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-xl);
  background: var(--bg-surface);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
}
.post-callout .icon-tile {
  margin-block-start: var(--space-0-5);
}
.post-callout b {
  display: block;
  margin-block-end: var(--space-1);
  color: var(--fg-default);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
}

/* Warning flavour, for the one paragraph on the page that costs money if it
   is skipped. */
.post-callout--warn {
  border-color: var(--warning);
  background: var(--warning-subtle);
  color: var(--warning-text);
}
.post-callout--warn .icon-tile {
  background: oklch(0 0 0 / 0.08);
  color: inherit;
}
.post-callout--warn b {
  color: inherit;
}

/* ---- Figure ----------------------------------------------------------- */

.post-figure {
  margin-inline: 0;
}
.post-figure img,
.post-figure .ph__frame {
  inline-size: 100%;
  /* The ratio is on the frame as well as on the image, so a slot that is
     still a placeholder holds exactly the box the photograph will hold and
     the article does not reflow the day the art lands. */
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-2xl);
  object-fit: cover;
}
.post-figure figcaption {
  margin-block-start: var(--space-3);
  padding-inline-start: var(--space-3);
  border-inline-start: var(--border-thick) solid var(--border-brand);
  color: var(--fg-subtle);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}

/* ---- Table ------------------------------------------------------------ */

/* The wrapper scrolls, not the table: a table that sets its own overflow
   loses the sticky header row and cannot be given a border that follows the
   scroll. On a phone this is the one element allowed to scroll sideways. */
.post-table {
  inline-size: 100%;
  overflow-x: auto;
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-xl);
  background: var(--bg-surface);
}

.post-table table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--text-md);
}

.post-table th,
.post-table td {
  padding: var(--space-3-5) var(--space-4);
  text-align: start;
  vertical-align: top;
}

.post-table thead th {
  background: var(--bg-surface-2);
  color: var(--fg-default);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  white-space: nowrap;
}

.post-table tbody tr + tr th,
.post-table tbody tr + tr td {
  border-block-start: var(--border-hairline) solid var(--border-subtle);
}

.post-table tbody th {
  color: var(--fg-default);
  font-weight: var(--weight-bold);
}

.post-table td {
  color: var(--fg-muted);
}

/* ---- In-article product strip ----------------------------------------- */

/* The article is about things the shop sells, so it says which ones. Not a
   `.product-card`: that object carries a price, a rating and an add-to-cart
   button, and none of the three belong in the middle of a paragraph. */
.post-picks {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
  padding: var(--space-5);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-2xl);
  background: var(--bg-surface-2);
}

.post-picks__head {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--gap-inline-sm);
  color: var(--fg-default);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
}
.post-picks__head .icon {
  color: var(--brand);
  font-size: var(--icon-md);
}

.post-pick {
  display: flex;
  align-items: center;
  gap: var(--gap-inline-md);
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-xl);
  background: var(--bg-surface);
  color: var(--fg-body);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  transition: var(--transition-colors), var(--transition-elevate);
}
.post-pick:hover {
  transform: translateY(-3px);
  border-color: var(--border-brand);
  color: var(--brand-text);
}
.post-pick .icon-tile {
  flex: none;
}


/* ==========================================================================
   15 · TAGS, SHARE, VOTE, AUTHOR
   ========================================================================== */

.post-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-block-start: var(--space-10);
}
.post-tags > span {
  color: var(--fg-subtle);
  font-size: var(--text-sm);
}

.post-share {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap-inline-sm);
}
.post-share > span {
  color: var(--fg-subtle);
  font-size: var(--text-sm);
}

.share-btn {
  display: inline-grid;
  place-items: center;
  flex: none;
  inline-size: var(--space-10);
  block-size: var(--space-10);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-circle);
  background: var(--bg-surface);
  color: var(--fg-body);
  font-size: var(--icon-md);
  transition: var(--transition-colors), var(--transition-elevate);
}
.share-btn:hover {
  transform: translateY(-3px);
  border-color: var(--border-brand);
  background: var(--bg-brand-subtle);
  color: var(--brand-text);
}

.post-vote {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap-inline-md);
  margin-block-start: var(--space-10);
  padding: var(--space-5) var(--space-6);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-2xl);
  background: var(--bg-surface);
}
.post-vote > b {
  color: var(--fg-default);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
}
.post-vote__actions {
  display: flex;
  gap: var(--gap-inline-sm);
  margin-inline-start: auto;
}
.post-vote button {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-inline-xs);
  padding: var(--space-2) var(--space-4);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  color: var(--fg-body);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  transition: var(--transition-colors);
}
.post-vote button:hover:not(:disabled) {
  border-color: var(--border-brand);
  color: var(--brand-text);
}
.post-vote button[aria-pressed="true"] {
  border-color: transparent;
  background: var(--brand);
  color: var(--fg-on-brand);
}
.post-vote button:disabled {
  opacity: 0.55;
  cursor: default;
}
/* The chosen answer keeps full contrast; only the one not taken dims. */
.post-vote button[aria-pressed="true"]:disabled {
  opacity: 1;
}

.author-card {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-5);
  margin-block-start: var(--space-10);
  padding: var(--space-6);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-2xl);
  background: var(--bg-surface);
}
.author-card__body {
  flex: 1 1 20rem;
  min-inline-size: 0;
}
.author-card__name {
  font-size: var(--text-xl);
  font-weight: var(--weight-extrabold);
}
.author-card__role {
  margin-block-start: var(--space-1);
  color: var(--brand-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
}
.author-card__bio {
  margin-block-start: var(--space-3);
  color: var(--fg-muted);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
}


/* ==========================================================================
   16 · POST NAV & RELATED
   ========================================================================== */

.post-nav {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
  margin-block-start: var(--space-10);
}

.post-nav__link {
  display: flex;
  align-items: center;
  gap: var(--gap-inline-md);
  padding: var(--space-5);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-2xl);
  background: var(--bg-surface);
  transition: var(--transition-colors), var(--transition-elevate);
}
.post-nav__link:hover {
  transform: translateY(-3px);
  border-color: var(--border-brand);
}
.post-nav__link .icon {
  flex: none;
  color: var(--brand);
  font-size: var(--icon-lg);
}
.post-nav__link span {
  display: block;
  color: var(--fg-subtle);
  font-size: var(--text-xs);
}
.post-nav__link b {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-block-start: var(--space-1);
  color: var(--fg-default);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
}

/* "Next" points toward the inline-end, so its content packs that way and
   its label aligns with the arrow rather than away from it. */
.post-nav__link--next {
  flex-direction: row-reverse;
  text-align: end;
}


/* ==========================================================================
   17 · COMMENTS
   ========================================================================== */

.comments {
  margin-block-start: var(--space-10);
}

.comment-list {
  display: grid;
  gap: var(--space-4);
  margin-block-start: var(--space-6);
  list-style: none;
  padding: 0;
}

.comment {
  display: flex;
  align-items: flex-start;
  gap: var(--gap-inline-md);
  padding: var(--space-5);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-2xl);
  background: var(--bg-surface);
}

/* A reply is indented from the inline-start and carries a brand rule, so
   the thread reads without any nesting markup beyond one modifier. */
.comment--reply {
  margin-inline-start: var(--space-10);
  border-inline-start: var(--border-thick) solid var(--border-brand);
  background: var(--bg-surface-2);
}

.comment__body {
  min-inline-size: 0;
}

.comment__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--gap-inline-md);
}
.comment__name {
  color: var(--fg-default);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
}
.comment__badge {
  padding: var(--space-0-5) var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--bg-brand-subtle);
  color: var(--brand-text);
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold);
}
.comment__date {
  color: var(--fg-subtle);
  font-size: var(--text-xs);
}
.comment__text {
  margin-block-start: var(--space-2);
  color: var(--fg-body);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
}

.comment-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
  margin-block-start: var(--space-6);
  padding: var(--space-6);
  border: var(--border-hairline) solid var(--border-subtle);
  border-radius: var(--radius-2xl);
  background: var(--bg-surface);
}

.comment-form__title {
  grid-column: 1 / -1;
  font-size: var(--text-xl);
  font-weight: var(--weight-extrabold);
}

.field {
  display: grid;
  gap: var(--space-2);
  min-inline-size: 0;
}
.field--wide {
  grid-column: 1 / -1;
}

.field > span {
  color: var(--fg-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.field input,
.field textarea {
  inline-size: 100%;
  padding: var(--space-3) var(--input-pad-inline);
  border: var(--border-thin) solid var(--input-border);
  border-radius: var(--input-radius);
  background: var(--input-bg);
  color: var(--input-fg);
  font-size: var(--text-md);
  transition: var(--transition-colors);
}
.field input {
  min-block-size: var(--input-height);
  padding-block: 0;
}
.field textarea {
  min-block-size: 8rem;
  line-height: var(--leading-relaxed);
  resize: vertical;
}
.field input::placeholder,
.field textarea::placeholder {
  color: var(--input-placeholder);
}
.field input:focus,
.field textarea:focus {
  border-color: var(--input-border-focus);
  outline: none;
  box-shadow: var(--ring);
}

/* An address is Latin text inside an RTL form. */
.field input[type="email"] {
  direction: ltr;
  text-align: start;
}

.comment-form__foot {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-inline-md);
}
.comment-form__note {
  color: var(--fg-subtle);
  font-size: var(--text-xs);
  max-inline-size: 46ch;
}


/* ==========================================================================
   18 · RESPONSIVE
   --------------------------------------------------------------------------
   The same four breakpoints styles.css and page.css use. Nothing new is
   introduced; the blog reflows on the widths the rest of the site already
   reflows on.
   ========================================================================== */

@media (max-width: 1240px) {
  .post-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .blog-layout { gap: var(--space-8); }
  .post-layout { gap: var(--space-8); }

  .post-feature__body { padding: var(--space-8); }
  .post-feature__title { font-size: var(--text-3xl); }
}

@media (max-width: 1150px) {
  /* The sidebar stops being a column and becomes a row of standing modules
     under the list. Sticky is dropped with it: a full-width block that
     follows the scroll would cover the articles it is advertising. */
  .blog-layout,
  .post-layout {
    grid-template-columns: minmax(0, 1fr);
  }
  .blog-side,
  .post-aside {
    position: static;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
  }

  /* The contents list is a navigation aid for a long scroll; below the
     article it is a list of places the reader has already been. */
  .post-toc { display: none; }

  .post-feature { grid-template-columns: minmax(0, 1fr); }
  .post-feature__media { order: -1; }
  .post-feature__media img,
  .post-feature__media .ph__frame { min-block-size: 0; aspect-ratio: 16 / 9; }

  .post-head__title { font-size: var(--text-5xl); }
}

@media (max-width: 800px) {
  .post-grid { grid-template-columns: minmax(0, 1fr); }

  .blog-side,
  .post-aside { grid-template-columns: minmax(0, 1fr); }

  .blog-toolbar {
    align-items: flex-start;
    flex-direction: column;
  }

  .post-head__title { font-size: var(--text-4xl); }
  .post-head__lede { font-size: var(--text-xl); }
  .post-head__facts { margin-inline-start: 0; }

  .post-body { font-size: var(--text-md); }
  .post-body > p:first-of-type { font-size: var(--text-lg); }
  .post-body h2 { font-size: var(--text-2xl); }
  .post-body h3 { font-size: var(--text-xl); }

  .post-picks { grid-template-columns: minmax(0, 1fr); }

  .post-nav { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, mirroring the second card only puts its arrow on the far side
     of a row it no longer shares with anything. */
  .post-nav__link--next {
    flex-direction: row;
    text-align: start;
  }

  .comment-form { grid-template-columns: minmax(0, 1fr); }
  .comment--reply { margin-inline-start: var(--space-5); }
}

@media (max-width: 540px) {
  .post-feature__body { padding: var(--space-6); }
  .post-feature { border-radius: var(--radius-2xl); }

  .blog-search { max-inline-size: none; }

  .post-vote { flex-direction: column; align-items: flex-start; }
  .post-vote__actions { margin-inline-start: 0; }

  .post-cover img,
  .post-cover .ph__frame { border-radius: var(--radius-2xl); }

  .comment { padding: var(--space-4); }
  .comment--reply { margin-inline-start: var(--space-3); }
}


/* ==========================================================================
   19 · MOTION, CONTRAST, PRINT
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  /* The bar is pure decoration and its whole content is movement. */
  .read-progress { display: none; }

  .post-feature__media img,
  .blog-more__fill {
    transition: none;
  }
  .post-feature:hover .post-feature__media img {
    transform: none;
  }
}

@media (prefers-contrast: more) {
  .topic-chip,
  .side-card,
  .post-callout,
  .post-nav__link,
  .comment,
  .post-table {
    border-color: var(--fg-muted);
  }
  .blog-card__topic {
    background: oklch(0 0 0 / 0.85);
    backdrop-filter: none;
  }
}

@media print {
  .read-progress,
  .blog-search,
  .topic-rail,
  .blog-toolbar__sort,
  .blog-more,
  .blog-side,
  .post-aside,
  .post-share,
  .post-vote,
  .post-nav,
  .comment-form,
  .subscribe {
    display: none;
  }

  .blog-layout,
  .post-layout {
    display: block;
  }

  .post-feature,
  .side-card,
  .post-callout,
  .comment {
    border: 1px solid #000;
  }

  /* A printed article has no destinations, so the body sets for paper:
     full measure, no clamping, no hover affordances. */
  .post-body {
    max-inline-size: none;
  }
  .blog-card__excerpt,
  .mini-post__title,
  .post-nav__link b {
    -webkit-line-clamp: none;
    line-clamp: none;
    display: block;
    overflow: visible;
  }
}
