/* ═══════════════════════════════════════════════════════════════
   BLOG — index grid, the cover, and the post page.

   Every colour, space and radius here is a design-system token. The
   cover is the one exception worth naming: its geometry is in px on a
   1080-wide canvas, copied from _linkedin/post-builder.html, because
   the card is a fixed-ratio composition rather than a flowing layout.
   It scales by container query, so the numbers stay comparable to the
   builder's and only one variable changes.
   ═══════════════════════════════════════════════════════════════ */

/* ═══ THE COVER ══════════════════════════════════════════════════
   An ordinary card: a picture at the top, the words underneath it.
   Nothing is set over the photograph.

   It carries .ds-card, so it gets the same pointer spotlight and lit
   ring as every other card on the site, and .ds-card--flush so the
   image can reach the card's own edges — the padding moves onto the
   text block instead. That is the whole reason to use the primitive
   rather than restyle a box here: the lens mechanic is the site's,
   not this page's, and a card that opted out of it would read as
   foreign the moment the pointer crossed it.

   The card sizes to its content. There is no aspect-ratio and no
   container query any more: with the words in normal flow, a longer
   title simply makes a taller card, and forcing a fixed 4:5 would
   either clip that title or strand air under a short one. */
.cover {
  /* Named inline-size container, so the title can size off the card's
     own width (cqi) rather than the viewport's. */
  container: cover / inline-size;
  /* NOT overflow:hidden. ds-card's lit ring is a ::before inset at the
     card's own edge, and clipping the card clips the ring away
     entirely — the hover looked like it had simply stopped working.
     The photo rounds its own top corners instead (see .cover__photo),
     which is the only thing that needed clipping. */
  display: flex;
  flex-direction: column;
  /* Korean and English both set here, and a headline broken mid-word
     reads as a rendering fault in Korean. */
  word-break: keep-all;
  overflow-wrap: break-word;
}

/* 3:2, the aspect the photographs are shot at, so nothing is cropped
   in the common case. object-fit still covers, because the next post's
   picture may not be 3:2 and a letterboxed image inside a card reads
   as a mistake. */
.cover__photo {
  position: relative;
  aspect-ratio: 3 / 2;
  /* Clips itself rather than relying on the card to clip it, so the
     card can stay unclipped for its ring. Top corners only: the bottom
     of the photo meets the text block, not the card's edge. */
  border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
  overflow: hidden;
  background-color: var(--color-bg-sunk);
  background-size: cover;
  background-position: 50% calc(var(--py, 50) * 1%);
}
/* A cover can exist before its photograph does, and a blank slab reads
   as a broken image rather than as a slot waiting to be filled. */
.cover__photo:not(.is-set)::after {
  content: attr(data-placeholder);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 12%;
  text-align: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--type-small);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-fg-subtle);
  line-height: 1.9;
}

/* The text block carries the padding .ds-card--flush gave up.
   Title, then standfirst, then one byline row — the reading order of
   an article opening, which is what this is. */
.cover__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
}

/* The title leads. It is the biggest thing in the card by a clear
   step, because on a page of covers the title is what is being
   scanned — everything else is confirmation. Not ds-lit: the gradient
   is display type only, and a gradient headline inside every card in
   a grid stops being an accent. */
.cover__title {
  margin: 0;
  /* Between h3 (1.25rem) and h2, and sized off the CARD rather than the
     viewport: this type has to lead inside a 300-400px column, and a
     vw-based clamp would shrink it on a narrow window while the column
     it sits in stayed the same width. cqi is that column. */
  font-size: clamp(1.35rem, 5.6cqi, 1.75rem);
  font-weight: 700;
  line-height: 1.16;
  letter-spacing: var(--tracking-tight);
  color: var(--color-fg);
  text-wrap: balance;
}

/* The standfirst, one step down and muted, so the pair reads as
   headline and subhead rather than as two competing lines. */
.cover__excerpt {
  margin: 0;
  font-size: var(--type-caption);
  line-height: var(--leading-base);
  color: var(--color-fg-muted);
}

/* Everything about the piece's provenance on ONE line: who, when, how
   long. Three separate rows for three short facts is what made the
   earlier version feel like a form rather than a byline. */
.cover__who {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* A lit divider closes the text block off from the byline.
     --rule-top as a BACKGROUND, never a border: a 1px border cannot
     hold the gradient, which is why the element sets no background of
     its own. */
  margin-top: var(--space-2);
  padding-top: var(--space-4);
  background: var(--rule-top);
  font-size: var(--type-small);
  color: var(--color-fg-subtle);
  /* The facts wrap as a group at narrow widths instead of the name
     splitting away from the avatar. */
  flex-wrap: wrap;
}
.cover__av {
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  border-radius: var(--radius-full);
  background: var(--color-bg-raised) center / cover no-repeat;
}
.cover__nm { font-weight: 600; color: var(--color-fg-muted); }
.cover__who .dot { color: var(--color-fg-faint); }

/* The series line left the card: it names the whole shelf, and
   repeating it on every cover said the same thing once per post. It is
   the page's section headline now — see .blog-series below. */


/* ── The upcoming card ────────────────────────────────────────
   A card with no picture and nothing to click. It has to read as a
   place being held, not as a post that failed to load, so it states
   what it is rather than miming a real card with the parts missing.

   A dashed outline and NO surface fill: filled, it competed with the
   published card beside it for the same attention; outlined, it reads
   as an empty frame on the same shelf. It also takes no ds-card
   spotlight — the light answers the pointer, and there is nothing
   here to point at. */
.cover--soon {
  background: none;
  border: 1px dashed var(--color-line-strong);
  /* Matches the published card's height without hard-coding one, so
     the row stays level as the real card's title wraps. */
  display: flex;
  align-items: center;
  min-height: 260px;
}
.cover--soon::before,
.cover--soon::after { content: none; }
.cover--soon:hover { background: none; }
/* The text sits on the card's own optical centre. `align-items:center`
   on the card centres the block as a whole; without a matching centre
   here the short copy stacked at the top and left the lower half
   empty, which read as content that had failed to load. */
.cover--soon .cover__head {
  gap: var(--space-3);
  padding: var(--space-6) var(--space-5);
  justify-content: center;
  flex: 1;
}
/* Muted a step below the published card's title: it is a label for
   something that does not exist yet, and should not pull the eye off
   the post that does. */
.cover--soon .cover__title { color: var(--color-fg-muted); }
.cover--soon .cover__excerpt { color: var(--color-fg-subtle); }
/* Icon tile, matching .cap-ico on the landing. */
.cover__soon-ico {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-lg);
  background: var(--color-bg-raised);
  display: grid;
  place-items: center;
  margin-bottom: var(--space-2);
  color: rgb(var(--light-1));
}

.cover__soon-label {
  margin: 0;
  font-size: var(--type-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--color-fg-faint);
}


/* ═══ THE INDEX ══════════════════════════════════════════════════
   The page has no heading block, so the grid itself has to clear the
   sticky nav — that top pad was the header's job and does not
   disappear with it. */
/* auto-fill, not auto-fit: with one post published, auto-fit would
   stretch that single card across the full 1240px measure and the
   cover would tower over the page. auto-fill holds the column width
   and lets the row stay short. */
/* The section headline. It carries the top clearance the grid used to
   hold, so the two cannot drift apart. */
.blog-series {
  margin: 0;
  /* The nav is sticky and already occupies its height in the flow;
     adding --nav-h here counted it twice and left a screenful of air
     above the heading on a phone. */
  padding-top: clamp(40px, 6vw, 60px);
  margin-bottom: clamp(22px, 3vw, 34px);
  font-size: var(--type-h2);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: var(--tracking-tight);
  color: var(--color-fg);
  text-wrap: balance;
  max-width: 22ch;
}

.post-grid {
  display: grid;
  /* Wider than the old 340 cap: the title is the card's largest
     element now, and at 340 a five-word one broke over four lines. */
  grid-template-columns: repeat(auto-fill, minmax(300px, 400px));
  gap: clamp(26px, 3.2vw, 44px);
  padding: 0 0 var(--space-section);
}

/* A plain container. The link is the Read button inside it, which
   stretches to cover the card — so the whole card is clickable while
   the accessibility tree sees exactly one link with a real name. */
.post-card { border-radius: var(--radius-2xl); }

/* The hit area covers the whole card, including the photograph.

   It is a real element on .post-card, not a pseudo-element on the
   button: .ds-card is position:relative and lifts every direct child
   to z-index 1, so anything inside .cover is sealed into that
   stacking context and cannot rise over the sibling photo. Clicking
   the picture did nothing until this moved out here. */
.post-card { position: relative; }
.post-card__hit {
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: var(--radius-2xl);
  cursor: pointer;
}

/* The card lights when the pointer is anywhere over it, which now
   means anywhere over the stretched link. */
.post-card:hover .cover::before,
.post-card:hover .cover::after,
.post-card:focus-within .cover::before { opacity: 1; }
/* Focus lands on the button but belongs to the card, so the ring is
   drawn around the card rather than around two words of text. */
.post-card:focus-within { outline: 2px solid var(--color-focus); outline-offset: 6px; }
.post-card__link:focus-visible { outline: none; }
/* Surface lift only — no translate and no shadow. The card answers the
   pointer by lighting up (the ring and the wash above), which is the
   site's own mechanic; moving it as well made the grid feel springy
   without saying anything the light had not already said. */
.post-card:hover .cover { background: var(--color-bg-raised); }
.cover { transition: background-color var(--duration-medium) var(--ease-standard); }

/* The empty state is all the grid needs when nothing is published. */
.blog-empty { padding: calc(var(--nav-h) + clamp(40px, 6vw, 76px)) 0 var(--space-section); color: var(--color-fg-muted); }




/* ═══ THE POST PAGE ══════════════════════════════════════════════
   One column, not two. The opening reads top to bottom — series,
   headline, standfirst, byline — and the photograph comes after it,
   full width, as the thing the reader passes through on the way into
   the piece. The two-column version put the picture beside the words
   and made them compete for the same first fixation. */
.post-hero {
  /* padding-block only: .ds-wrap owns the inline gutter, and a
     shorthand here would zero it and pull the copy off the header. */
  padding-block: 0 clamp(34px, 4.4vw, 56px);
  display: grid;
  /* A single gap made the kicker, headline, standfirst and byline read
     as one dense stack. Each element sets the space it needs below it
     instead, so the opening has room to breathe. */
  gap: 0;
  justify-items: start;
}
.post-hero .ds-h1 { margin-bottom: clamp(18px, 2.2vw, 26px); }
.post-standfirst { margin-bottom: clamp(10px, 1.4vw, 18px); }

/* Series and episode on one line. The number is separated by a rule
   rather than a bullet: a bullet is a list separator, and this is one
   fact with two halves. */
.post-kicker {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 clamp(18px, 2.2vw, 28px);
  font-size: var(--type-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--color-fg-subtle);
}
/* The kicker is the one lit element on this page: the headline is plain
   white, so the light marks WHICH piece this is rather than competing
   with what it says.

   The gradient is set on each HALF rather than on the flex parent.
   background-clip:text paints across the element's own box, and a flex
   child sits outside its parent's painted range — with the rule on
   .post-kicker the episode number rendered as transparent text on
   nothing and simply vanished. */
.post-kicker > span {
  background: linear-gradient(96deg, rgb(var(--light-1)), rgb(var(--light-2)));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.post-kicker__ep {
  position: relative;
  padding-left: var(--space-3);
  font-weight: 700;
}
.post-kicker__ep::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 1px;
  height: 1em;
  transform: translateY(-50%);
  /* Its own colour, not the clipped gradient above: background-clip
     text on the parent would otherwise make this rule invisible. */
  background: rgba(var(--light-1), 0.55);
}
.post-kicker__ep:empty { display: none; }

@media (max-width: 560px) {
  /* The series name wraps here, and the number was left stranded at
     the end of the first line. Stacking puts it under the name as its
     own mark instead. */
  .post-kicker { flex-direction: column; align-items: flex-start; gap: var(--space-1); }
  .post-kicker__ep { padding-left: 0; }
  .post-kicker__ep::before { content: none; }
}

.post-hero .ds-h1 { max-width: 22ch; line-height: 1.1; }
.post-standfirst {
  margin: 0;
  max-width: 58ch;
  font-size: var(--type-body);
  line-height: var(--leading-base);
  color: var(--color-fg-muted);
}

/* Byline left, share right, on one rule-topped row. */
.post-byline {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: clamp(14px, 1.8vw, 22px);
  padding-top: clamp(20px, 2.4vw, 28px);
  background: var(--rule-top);
}
.post-byline__who { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.post-byline__av {
  width: 40px;
  height: 40px;
  flex: 0 0 40px;
  border-radius: var(--radius-full);
  background: var(--color-bg-raised) center / cover no-repeat;
}
.post-byline__id { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.post-byline__nm { font-size: var(--type-caption); font-weight: 600; color: var(--color-fg); }
.post-byline__meta { font-size: var(--type-small); color: var(--color-fg-subtle); }
.post-byline__meta .dot { color: var(--color-fg-faint); padding: 0 4px; }

/* The hero image. It breaks the opening's reading measure on purpose —
   the words are held to 62ch, the picture is not. */
.post-figure {
  margin-block: 0 clamp(30px, 4vw, 48px);
  margin-inline: auto;
  /* The nav is sticky and so already takes its own height in the flow;
     adding --nav-h here counted it a second time. This is just the gap
     under the bar — matched to the index page's heading so both pages
     start at the same height. */
  padding-top: clamp(28px, 3.4vw, 40px);
}
.post-figure img {
  display: block;
  width: 100%;
  /* A banner rather than a portrait: at 3/2 the picture filled the
     screen and pushed the headline out of view. */
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: 50% 45%;
  border-radius: var(--radius-2xl);
  background: var(--color-bg-sunk);
}

/* No image in the manifest yet: the figure would otherwise render as
   an empty rounded slab. */
.post-figure:not(.is-set) { display: none; }

/* Full container width, like every other block on the site. The
   paragraphs inside keep a reading measure of their own — a 1240px
   line of body copy is unreadable — but the block itself is no longer
   narrower than the page. */
.post-body {
  padding-bottom: clamp(40px, 5vw, 68px);
  /* .ds-prose sets 58ch and .ds-wrap sets --container-max; the more
     specific prose rule was winning. Restate the container width so
     this block matches every other one on the site — `none` here would
     cancel .ds-wrap too and run the block the full viewport. */
  max-width: var(--container-max);
}
/* The measure lives on the paragraphs, and they start at the container's
   left edge rather than being centred inside it. */
.post-body > * { max-width: 68ch; margin-inline: 0; }

/* Section headings inside the piece. They are H2 in the markup — the
   page's H1 is the title, and skipping to H3 would leave a hole in the
   outline — but they take .ds-h3's size, because at h2 scale they would
   be louder than the title they sit under.

   The top margin is much larger than the bottom one: a heading belongs
   to the text that follows it, and equal spacing makes it float between
   two blocks instead. */
.post-body h2 {
  margin: clamp(34px, 4vw, 52px) 0 var(--space-3);
  font-size: var(--type-h3);
  font-weight: 600;
  letter-spacing: var(--tracking-snug);
  line-height: 1.3;
  color: var(--color-fg);
}
.post-body h2:first-child { margin-top: 0; }

/* Where the piece continues. A card, because it is a destination
   rather than a line of copy. */
.post-onward {
  display: grid;
  gap: var(--space-4);
  justify-items: start;
  padding: clamp(28px, 3.4vw, 44px);
  /* The card is also a .ds-wrap; its own padding replaces the wrap's
     gutter, but the wrap's centring has to be restated or the card sits
     against the viewport edge. */
  max-width: var(--container-max);
  margin: 0 auto var(--space-section);
}
.post-onward p { margin: 0; max-width: 52ch; color: var(--color-fg-muted); font-size: var(--type-caption); line-height: var(--leading-base); }

/* A way back, at the top where the reader arrives rather than only at
   the end. Tertiary, and the arrow is flipped to point back. */
.post-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.post-back::after { content: none; }
.post-back::before {
  content: "";
  width: 0.9em;
  height: 0.9em;
  flex: none;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 12H5M11 18l-6-6 6-6'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 12H5M11 18l-6-6 6-6'/%3E%3C/svg%3E") center / contain no-repeat;
  transition: transform var(--duration-short) var(--ease-standard);
}
.post-back:hover::before,
.post-back:focus-visible::before { transform: translateX(-3px); }
@media (prefers-reduced-motion: reduce) { .post-back::before { transition: none; } }

.post-nav { display: flex; justify-content: space-between; gap: var(--space-4); padding-bottom: var(--space-section); }
