:root {
  color-scheme: dark;
  --page: #10121d;
  --deep: #0b0d17;
  --surface: #161718;
  --accent: #5ee3d3;
  --coral: #ff4d45;
  --light: #e0f2f1;
  --white: #ffffff;
  --body: #d1d5db;
  --muted: #9ca3af;
  /* #6b7280 was 4.01:1 on --deep, and every element painted with --dim is 14px
     or smaller (the footer legal row, the comment counter), so all of it sat
     under the 4.5:1 AA bar. Raised to clear 4.5 on all three blog grounds:
     5.03 on --deep, 4.84 on --page, 4.66 on --surface. The cost is that --dim
     and --muted now read closer together; the gap that used to separate them
     was illegible text. */
  --dim: #818283;
  /* Two border tokens, split by WCAG 1.4.11 rather than by taste. --border is
     decoration: hairlines that separate blocks (the rule between two comments).
     --border-strong is the boundary of a control that has no other outline --
     the share buttons and the comment textarea are nothing but their border, so
     3:1 applies there. Raising the single old token everywhere would have drawn
     bright rules across a dark reading column to no accessibility end.
     3.24:1 on --page, 3.12:1 on --surface. */
  --border: #2a2b2c;
  --border-strong: #656667;
  --container: 1280px;
  --reading: 748px;
  --gutter: clamp(1rem, 4vw, 3rem);
  /* Site container is `max-w-7xl mx-auto px-4 sm:px-6 lg:px-8` — a 1280px box with
     padding INSIDE it, so its content stops 32px short of 1280 on desktop. The blog
     used `1280px - gutter` instead, which put every page edge 32px further out than
     the React pages. --pad restores the Tailwind step scale. */
  --pad: 1rem;
  --header-height: 80px;
  /* One step for the whole lower article: the gap under the closing hairline,
     between the tags and the share row, and between the share row and the
     comments heading. Changing it moves all three together, which is the
     point -- they used to run on 2.5rem, 1.25rem and 3.25rem and read as
     three unrelated blocks. */
  --article-stack: 2rem;
}

@media (min-width: 640px) {
  :root {
    --pad: 1.5rem;
  }
}

@media (min-width: 1024px) {
  :root {
    --pad: 2rem;
  }
}

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

html {
  background: var(--page);
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  min-width: 320px;
  min-height: 100vh;
  margin: 0;
  background:
    radial-gradient(circle at 16% 8%, rgba(94, 227, 211, 0.035), transparent 30rem),
    radial-gradient(circle at 86% 28%, rgba(59, 130, 246, 0.025), transparent 34rem),
    var(--page);
  color: var(--body);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 16px;
  line-height: 1.6;
  text-rendering: optimizeLegibility;
}

body,
button,
input,
textarea,
select {
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

img {
  display: block;
  max-width: 100%;
}

/* The islands toggle elements with the `hidden` property, which relies on the
   UA rule `[hidden] { display: none }`. That rule is an attribute selector in
   the UA origin, so ANY author rule that sets `display` on a class wins over it
   regardless of source order -- `.comment__avatar--initial { display: grid }`
   did exactly that, and prod showed an empty monogram sitting next to the real
   photo (2026-09-11). This guard settles it once for every element any island
   hides now or later, instead of patching each class as it is discovered. */
[hidden] {
  display: none !important;
}

a {
  color: inherit;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  color: var(--white);
  font-family: Montserrat, Inter, ui-sans-serif, system-ui, sans-serif;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.12;
}

p {
  margin: 0;
}

::selection {
  background: rgba(94, 227, 211, 0.28);
  color: var(--white);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

.skip-link {
  position: fixed;
  z-index: 100;
  top: 0.75rem;
  left: 0.75rem;
  padding: 0.75rem 1rem;
  border-radius: 8px;
  background: var(--accent);
  color: var(--deep);
  font-weight: 700;
  transform: translateY(-160%);
  transition: transform 160ms ease;
}

.skip-link:focus {
  transform: translateY(0);
}

.blog-container {
  width: min(var(--container), 100%);
  padding-inline: var(--pad);
  margin-inline: auto;
}

/* Header mirrors src/components/Navbar.jsx in its scrolled state: `.glass-effect`
   (rgba(16,18,29,0.8) + blur(10px)) plus shadow-lg. The blog cannot swap to a
   transparent bar on scroll without JS, and glass is the state a reader sees for
   all but the first 50px anyway. */
.site-header {
  position: fixed;
  z-index: 50;
  inset: 0 0 auto;
  height: var(--header-height);
  background: rgba(16, 18, 29, 0.8);
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: min(var(--container), 100%);
  height: 100%;
  padding-inline: var(--pad);
  margin-inline: auto;
}

.site-logo {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
}

/* h-10, same as the Navbar logo. */
.site-logo img {
  width: auto;
  height: 40px;
}

.site-nav,
.site-nav__links,
.site-nav__auth {
  display: flex;
  align-items: center;
}

/* Three space-between children — logo, links, auth — exactly like the flex row in
   Navbar.jsx, so the link cluster lands in the same place on both. */
.site-nav {
  gap: 1rem;
}

/* Navbar.jsx: gap-3 lg:gap-4 xl:gap-6 (12 / 16 / 24px). */
.site-nav__links {
  gap: 1.5rem;
}

/* Navbar.jsx link: text-white hover:text-kstudio-accent transition-colors duration-200,
   font-medium, text-base. Active link is accent with a 2px accent underline. */
.site-nav__links > a,
.site-nav__products-trigger {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 0;
  border: 0;
  background: none;
  color: var(--white);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.5;
  text-decoration: none;
  white-space: nowrap;
  transition: color 200ms ease;
}

.site-nav__links > a:hover,
.site-nav__products-trigger:hover,
.site-nav__products:focus-within .site-nav__products-trigger {
  color: var(--accent);
}

.site-nav__links > a.is-active {
  color: var(--accent);
}

.site-nav__links > a.is-active::after {
  position: absolute;
  right: 0;
  bottom: -0.25rem;
  left: 0;
  height: 2px;
  background: var(--accent);
  content: "";
}

/* Products dropdown — hover parity with the Navbar mega-dropdown, plus
   focus-within so it also opens for keyboard users. */
.site-nav__products {
  position: relative;
}

.site-nav__products-trigger {
  gap: 0.125rem;
  cursor: pointer;
  font-family: inherit;
}

.site-nav__chevron {
  transition: transform 300ms ease;
}

.site-nav__products:hover .site-nav__chevron,
.site-nav__products:focus-within .site-nav__chevron {
  transform: rotate(180deg);
}

/* Offset is padding on the positioned wrapper, never margin: margin is not
   hit-tested, so it would leave a gap the pointer cannot cross without dropping
   :hover and closing the menu. Navbar.jsx solves it the same way with `pt-2`. */
.site-nav__dropdown {
  position: absolute;
  top: 100%;
  left: -0.75rem;
  display: none;
  padding-top: 0.5rem;
}

.site-nav__dropdown-panel {
  position: relative;
  min-width: 210px;
  padding: 0.375rem 0;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 0.5rem;
  background: rgba(13, 15, 26, 0.95);
  box-shadow: 0 8px 30px -8px rgba(0, 0, 0, 0.6);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
}

.site-nav__dropdown-panel::before {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(94, 227, 211, 0.3), transparent);
  content: "";
}

.site-nav__products:hover .site-nav__dropdown,
.site-nav__products:focus-within .site-nav__dropdown {
  display: block;
}

/* Escape. The panel above opens on :hover and :focus-within alone, with no
   script and therefore no state to close, so pressing Escape left it sitting
   over the page for exactly as long as the trigger kept focus. The inline
   script marks the wrapper instead, and this rule -- the same (0,3,0)
   specificity as the pair above, so only its later position settles it --
   takes the panel back down. No !important: a future state that legitimately
   needs to reopen the panel must still be able to. */
.site-nav__products[data-dismissed] .site-nav__dropdown {
  display: none;
}

.site-nav__dropdown a {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.875rem;
  color: #d1d5db;
  font-size: 14px;
  text-decoration: none;
  white-space: nowrap;
  transition: color 150ms ease, background-color 150ms ease;
}

.site-nav__dropdown a svg {
  flex: 0 0 auto;
  color: rgba(94, 227, 211, 0.7);
  transition: color 150ms ease;
}

.site-nav__dropdown a:hover {
  background: rgba(255, 255, 255, 0.04);
  color: var(--accent);
}

.site-nav__dropdown a:hover svg {
  color: var(--accent);
}

/* Auth pair — the logged-out Navbar state, which is also what every prerendered
   page bakes, since the snapshot has no session either. min-width: 0 overrides
   the flex default of min-width: auto, whose automatic minimum is the content's
   min-content size -- for "Sign In"/"Sign Up" that has a wrap opportunity at the
   space, but the signed-in pill's label is one unbreakable string, so without
   this the pill refused to shrink at all and pushed 36-49px past the header at
   1024-1072px (design review, 2026-09-11), a band the signed-out pair fits fine. */
.site-nav__auth {
  gap: 0.5rem;
  min-width: 0;
}

.site-nav__auth .button {
  min-height: 40px;
  padding: 0 1rem;
  font-size: 14px;
}

/* Swapped in by the inline auth script (shell.ts) in place of Sign In / Sign
   Up once a valid session is found. `.button` is `display: inline-flex` with
   `justify-content: center` -- text-overflow does not apply to a flex
   container's own box (the spec only defines it for block containers), which
   without this inner span rendered as a centered, un-ellipsized slice cut off
   at both ends instead of a leading "name…". The span is a normal block flex
   item, so ellipsis works on IT. On mobile it already gets width: 100% from
   `.site-nav__mobile-auth .button`, so only the desktop pair needs a cap. */
.site-nav__auth-user {
  min-width: 0;
}

.site-nav__auth-user-label {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.site-nav__auth .site-nav__auth-user-label {
  max-width: 160px;
}

/* Sign out, added by the inline script next to the name in both auth pairs. A
   text control, not a third pill: the bar already carries an outlined and an
   accent button, and a third filled shape would read as a primary action. The
   type follows .site-nav__links > a so it belongs to the same row. */
.site-nav__signout {
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.5;
  white-space: nowrap;
  /* A hairline underline at rest, because without it the button is the same
     muted grey as the non-interactive meta text on the page and nothing says it
     can be clicked until the pointer is already on it (design review, 2026-09-11). */
  text-decoration: underline;
  text-decoration-color: rgba(156, 163, 175, 0.45);
  text-underline-offset: 3px;
  transition: color 200ms ease, text-decoration-color 200ms ease;
}

.site-nav__signout:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
}

.site-nav__mobile {
  display: none;
}

/* Buttons follow the three canonical variants in src/index.css:
   .button--outline = .btn-secondary, .button--accent = .btn-primary,
   .button--coral = .button-order. Shared geometry: rounded-[20px] pill,
   py-3 px-6, text-base, transition-all duration-300. */
.button {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  padding: 0.75rem 1.5rem;
  border: 2px solid transparent;
  border-radius: 20px;
  font-size: 16px;
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  transition: border-color 300ms ease, background-color 300ms ease, box-shadow 300ms ease, color 300ms ease, transform 300ms ease;
}

.button svg {
  flex: 0 0 auto;
  transition: transform 300ms ease;
}

.button:hover svg {
  transform: translateX(3px);
}

/* .btn-secondary */
.button--outline {
  border-color: var(--accent);
  background: transparent;
  color: var(--accent);
}

.button--outline:hover {
  background: rgba(94, 227, 211, 0.1);
  color: var(--white);
}

/* .btn-primary */
.button--accent {
  background: var(--accent);
  color: var(--deep);
  font-weight: 600;
}

.button--accent:hover {
  background: rgba(94, 227, 211, 0.9);
  box-shadow: 0 0 20px rgba(94, 227, 211, 0.4), 0 0 40px rgba(94, 227, 211, 0.2);
}

/* .button-order */
.button--coral {
  background: linear-gradient(90deg, #cc344e, #e8553a);
  color: var(--white);
  font-weight: 600;
}

.button--coral:hover {
  box-shadow: 0 0 25px rgba(204, 52, 78, 0.6), 0 0 50px rgba(204, 52, 78, 0.4), 0 0 80px rgba(204, 52, 78, 0.15);
  transform: scale(1.05);
}

.button--coral:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--page), 0 0 0 4px #cc344e;
}

.blog-index,
.blog-article,
.blog-error {
  padding-top: var(--header-height);
}

.index-hero {
  display: grid;
  grid-template-columns: minmax(360px, 400px) minmax(620px, 1fr);
  gap: 0;
  align-items: center;
  min-height: 0;
  padding-block: 2rem 1.5rem;
}

.index-hero__intro h1 {
  max-width: 390px;
  font-size: clamp(2.35rem, 2.55vw, 2.375rem);
}

.index-hero__intro > p {
  max-width: 420px;
  margin-top: 1.4rem;
  color: #bdc0ca;
  font-size: 17px;
  line-height: 1.65;
}

/* Brand card: bg-kstudio-dark/50, rounded-lg, border-gray-800,
   hover:border-kstudio-accent hover:bg-kstudio-dark, duration-300. */
/* min-height, never height: the text column is the tall one and a fixed 300px
   clipped whatever did not fit — with the current copy that was the bottom
   third of the "Read article" button.

   The card's HEIGHT is what decides how badly the cover gets cropped, which is
   not obvious and is worth spelling out. The text column sets the height, the
   media column then gets `that height x its own width` as its box, and
   object-fit: cover throws away whatever does not match the image's own ratio.
   Covers here are 1200x630 (1.90) because they double as OG images. At 505px
   wide the box only reaches 1.90 when the card is ~266px tall, so every extra
   line of text in the right-hand column is paid for by a slice off both sides
   of the picture. Widening the media column is the intuitive fix and it is the
   wrong one — it makes the box taller too, and the crop got worse (22.6% of
   the width, straight through the cover's headline). Trimming the text block
   is the fix: at ~266px the box lands within a couple of percent of 1.90.

   The media column is a capped px track rather than an `fr` share for the same
   reason. As a share it kept growing with the card — on the stacked hero at
   1024 it reached 585px against the same 264px height (ratio 2.22) and started
   cropping 14% off the TOP and bottom instead, which is where this cover keeps
   its logo. Capped, the extra width goes to the text column, where it does some
   good: the excerpt stops being truncated there. */
.featured-story {
  display: grid;
  grid-template-columns: minmax(0, 500px) minmax(250px, 1fr);
  min-height: 260px;
  overflow: hidden;
  border: 1px solid #1f2937;
  border-radius: 0.5rem;
  background: rgba(11, 13, 23, 0.5);
  transition: border-color 300ms ease, background-color 300ms ease;
}

.featured-story:hover {
  border-color: var(--accent);
  background: var(--deep);
}

/* The cover is taken out of flow so it can never size the card: a portrait or
   square upload would otherwise push the grid row past the text column and
   reintroduce the clipping this rule exists to prevent. The stretched media
   box is a definite height, so the absolute image fills whatever the text
   column ends up asking for. */
.featured-story__media {
  position: relative;
  overflow: hidden;
  background: var(--deep);
}

.featured-story__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 500ms ease;
}

.featured-story:hover .featured-story__image {
  transform: scale(1.018);
}

.featured-story__content {
  display: flex;
  align-items: flex-start;
  flex-direction: column;
  justify-content: center;
  padding: 1.3rem;
}

.story-category {
  margin-bottom: 0.65rem;
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
}

.featured-story h2 {
  font-size: clamp(1.35rem, 1.55vw, 1.48rem);
}

.featured-story h2 a,
.insight-row h3 a {
  text-decoration: none;
}

.featured-story h2 a:hover,
.insight-row h3 a:hover {
  color: var(--accent);
}

/* Excerpts are author-written and uncapped in the editor, and every line they
   are allowed to take is a slice off the cover (see the note on .featured-story).
   Three lines is the teaser; the whole excerpt is on the article page. */
.featured-story__content > p:not(.story-category) {
  display: -webkit-box;
  overflow: hidden;
  margin-top: 0.75rem;
  color: #bbc0ca;
  line-height: 1.55;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
}

.featured-story .button {
  margin-top: 0.75rem;
}

.featured-story--empty {
  display: grid;
  min-height: 260px;
  height: auto;
  grid-template-columns: 1fr;
  place-items: center;
  padding: 2rem;
  color: var(--muted);
  text-align: center;
}

.latest-section {
  padding-block: 1.3rem 1rem;
}

.latest-section__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.075);
}

.latest-section__header h2 {
  flex: 0 0 auto;
  padding-bottom: 1rem;
  font-size: clamp(1.65rem, 2vw, 1.9rem);
}

.category-filters {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2.35rem);
  overflow-x: auto;
  padding: 0.4rem 0 0.75rem;
  scrollbar-width: thin;
}

.category-filters a {
  flex: 0 0 auto;
  padding: 0.4rem 0.85rem;
  border: 1px solid transparent;
  border-radius: 20px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  transition: color 200ms ease, border-color 200ms ease, background-color 200ms ease;
}

.category-filters a:hover {
  color: var(--accent);
}

.category-filters a.is-active {
  border-color: rgba(94, 227, 211, 0.65);
  background: rgba(94, 227, 211, 0.035);
  color: var(--accent);
}

/* height: 100px was 14px shorter than the row's own text, so the divider was
   drawn across the copy instead of under it and the stretched arrow column —
   with its hover tint — hung below the line. The row is sized by its content
   now; 104px is the floor that keeps the thumbnail rhythm. */
.insight-row {
  display: grid;
  grid-template-columns: minmax(230px, 370px) minmax(0, 1fr) 42px;
  min-height: 104px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.075);
}

/* Out of flow for the same reason as the featured cover: the thumbnail must
   follow the row height, never set it. */
.insight-row__media {
  position: relative;
  overflow: hidden;
  background: var(--deep);
}

.insight-row__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 400ms ease;
}

.insight-row:hover .insight-row__image {
  transform: scale(1.025);
}

.insight-row__content {
  align-self: center;
  padding: 1rem 1.65rem;
}

.insight-row__content .story-category {
  margin-bottom: 0.25rem;
  font-size: 10px;
}

.insight-row h3 {
  font-size: clamp(1rem, 1.6vw, 1.18rem);
  letter-spacing: -0.015em;
}

.insight-row__content > p:not(.story-category) {
  margin-top: 0.28rem;
  overflow: hidden;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.story-meta {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-top: 0.4rem;
  color: var(--dim);
  font-size: 11px;
}

.insight-row__arrow {
  display: grid;
  align-self: stretch;
  color: var(--accent);
  place-items: center;
  transition: background 180ms ease, color 180ms ease;
}

.insight-row__arrow:hover {
  background: rgba(94, 227, 211, 0.1);
  color: var(--accent);
}

.blog-cta {
  position: relative;
  display: flex;
  overflow: hidden;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  margin-block: 3.25rem;
  padding: clamp(1.7rem, 3.6vw, 2.75rem);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 1rem;
  background:
    radial-gradient(circle at 6% 100%, rgba(94, 227, 211, 0.14), transparent 15rem),
    radial-gradient(circle at 94% 0, rgba(255, 77, 69, 0.1), transparent 14rem),
    rgba(11, 13, 23, 0.55);
}

.blog-cta::before {
  position: absolute;
  top: 0;
  right: 20%;
  left: 20%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(94, 227, 211, 0.4), transparent);
  content: "";
}

.blog-cta h2 {
  max-width: 700px;
  font-size: clamp(1.7rem, 2.7vw, 2.25rem);
}

/* Canonical Final CTA button pair: gap-6, px-8 py-3 text-lg. */
.blog-cta__actions {
  display: flex;
  flex: 0 0 auto;
  gap: 1.5rem;
}

.blog-cta__actions .button {
  padding: 0.75rem 2rem;
  font-size: 18px;
}

.article-header {
  max-width: 1100px;
  padding-top: clamp(3rem, 7vw, 5.5rem);
  padding-bottom: 2.4rem;
}

.breadcrumbs {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: 1.4rem;
  color: var(--muted);
  font-size: 13px;
}

.breadcrumbs a {
  color: var(--accent);
  text-decoration: none;
}

.breadcrumbs a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.article-header h1 {
  max-width: 900px;
  font-size: clamp(2.45rem, 5.4vw, 4.1rem);
}

.article-excerpt {
  max-width: 880px;
  margin-top: 1.35rem;
  color: #b8bdc8;
  font-size: clamp(1.05rem, 1.8vw, 1.25rem);
  line-height: 1.55;
}

.article-byline {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.45rem;
  color: #bdc1ca;
  font-size: 13px;
}

.article-byline__avatar {
  display: grid;
  width: 34px;
  height: 34px;
  border: 1px solid rgba(94, 227, 211, 0.45);
  border-radius: 50%;
  background: linear-gradient(135deg, rgba(94, 227, 211, 0.28), rgba(255, 77, 69, 0.22));
  color: var(--white);
  font-size: 13px;
  font-weight: 700;
  place-items: center;
}

.article-cover {
  max-width: 1120px;
  margin-bottom: 2.9rem;
}

.article-cover__image {
  width: 100%;
  max-height: 570px;
  aspect-ratio: 16 / 8.5;
  border-radius: 10px;
  object-fit: cover;
}

.article-cover figcaption,
.blog-figure figcaption {
  margin-top: 0.65rem;
  color: var(--dim);
  font-size: 12px;
  line-height: 1.45;
  text-align: center;
}

.article-body {
  width: min(var(--reading), calc(100% - (var(--gutter) * 2)));
  margin-inline: auto;
  color: #d1d5dc;
  font-size: 18px;
  line-height: 1.76;
}

.article-body::after {
  display: table;
  clear: both;
  content: "";
}

.article-body > :first-child {
  margin-top: 0;
}

.article-body p {
  margin: 0 0 1.35rem;
}

.article-body h2,
.article-body h3,
.article-body h4 {
  clear: both;
  scroll-margin-top: calc(var(--header-height) + 1.5rem);
}

.article-body h2 {
  margin: 2.2rem 0 0.8rem;
  font-size: clamp(1.55rem, 3vw, 2rem);
}

.article-body h3 {
  margin: 1.9rem 0 0.7rem;
  font-size: clamp(1.3rem, 2.4vw, 1.58rem);
}

.article-body h4 {
  margin: 1.65rem 0 0.6rem;
  font-size: 1.2rem;
}

.article-body a {
  color: var(--accent);
  text-decoration-color: rgba(94, 227, 211, 0.45);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.article-body a:hover,
.article-body a:focus-visible {
  text-decoration-color: var(--accent);
}

.article-body strong {
  color: var(--white);
  font-weight: 700;
}

.article-body code {
  padding: 0.12em 0.35em;
  border: 1px solid rgba(94, 227, 211, 0.18);
  border-radius: 5px;
  background: rgba(11, 13, 23, 0.7);
  color: var(--light);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.88em;
}

.article-body ul,
.article-body ol {
  margin: 0 0 1.45rem;
  padding-left: 1.4rem;
}

.article-body li {
  margin: 0.28rem 0;
  padding-left: 0.25rem;
}

.article-body li::marker {
  color: var(--accent);
  font-weight: 700;
}

.article-body blockquote {
  margin: 1.8rem 0;
  padding: 0.15rem 0 0.15rem 1.45rem;
  border-left: 3px solid var(--accent);
  color: var(--light);
  font-size: 1.13em;
  font-style: italic;
}

.article-body blockquote p:last-child {
  margin-bottom: 0;
}

.article-body hr {
  clear: both;
  margin: 2.4rem 0;
  border: 0;
  border-top: 1px solid rgba(255, 255, 255, 0.11);
}

.blog-figure {
  margin: 2rem 0;
}

.blog-figure img {
  width: 100%;
  max-height: 680px;
  border-radius: 9px;
  object-fit: cover;
}

.blog-figure--wide {
  width: min(1060px, calc(100vw - (var(--gutter) * 2)));
  margin-left: 50%;
  transform: translateX(-50%);
}

.blog-figure--float-right {
  width: min(48%, 360px);
  margin: 0.4rem 0 1rem 1.75rem;
  float: right;
}

/* Opens the lower article: this row carries the only hairline down here, and
   everything below it steps on --article-stack. */
.article-tags {
  display: flex;
  width: min(var(--reading), calc(100% - (var(--gutter) * 2)));
  align-items: center;
  flex-wrap: wrap;
  gap: 0.65rem;
  margin: 2.5rem auto 0;
  padding-top: var(--article-stack);
  border-top: 1px solid rgba(255, 255, 255, 0.11);
}

.article-tags span {
  padding: 0.38rem 0.72rem;
  border: 1px solid rgba(94, 227, 211, 0.68);
  border-radius: 999px;
  color: var(--accent);
  font-size: 11px;
  font-weight: 600;
}

/* Share controls. Icons only: the accessible name is on aria-label, the sighted
   hint is data-tip drawn through ::after, and the copied state swaps data-tip
   rather than any text node. Buttons are 44x44 hit targets whatever the glyph
   measures. Markup order: native (hidden until the browser supports
   navigator.share), LinkedIn, X, Facebook, Copy link. Without JS the three
   links still work; native and copy simply never appear.

   Placed after the tags, as the last row of the article. The whole lower
   article -- tags, share, comments -- runs on one spacing step and one hairline
   (drawn by whichever of tags/share comes first). A second rule between the
   share row and the comments heading read as a stutter at that distance, so
   there is none: the step alone marks the section change. */
.article-share {
  display: flex;
  width: min(var(--reading), calc(100% - (var(--gutter) * 2)));
  margin: var(--article-stack) auto 0;
}

.article-share__group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem;
}

/* 12px, not the 11px an eyebrow usually gets: on a row with no button labels
   this word is the only text naming the group, so it carries more than an
   eyebrow's normal load. */
.article-share__label {
  margin-right: 0.35rem;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* 44x44 even though the glyph is 18px: the hit target is the accessibility
   requirement, the circle is only what it looks like. */
.article-share__button {
  position: relative;
  display: inline-flex;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: transparent;
  color: var(--body);
  font: inherit;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: border-color 200ms ease, color 200ms ease, background-color 200ms ease;
}

.article-share__button svg {
  flex: 0 0 auto;
}

.article-share__button:hover,
.article-share__button:focus-visible {
  border-color: var(--accent);
  background: rgba(94, 227, 211, 0.08);
  color: var(--white);
}

.article-share__button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.article-share__button[data-copied] {
  border-color: var(--accent);
  color: var(--accent);
}

/* The copy button carries both glyphs and shows one. Deliberately outside the
   tooltip's `hover: hover` block: this is the confirmation a touch reader gets,
   where no tip can ever appear.

   Selected by class, not by nth-of-type: position is not a structural signal,
   and reordering the two icon() calls or adding a third glyph would silently
   swap or break the pair. */
.article-share__glyph--check,
.article-share__button[data-copied] .article-share__glyph--link {
  display: none;
}

.article-share__button[data-copied] .article-share__glyph--check {
  display: block;
}

/* No `.article-share__button[hidden]` rule here: the global `[hidden]` guard at
   the top of this file already covers it with !important, and a second rule
   that can only ever agree with the first is an invitation to write a third. */

/* The copy button's live region: announced, never seen. */
.article-share__status {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Tooltip. Pointer devices only -- on touch there is no hover to reveal it, and
   a tip that appears on tap would fight the tap it is describing.

   It opens DOWNWARD. Above the row is the cluster gap, one --article-stack tall
   and fully occupied by the tip, which then sat flush against the tag pills and
   read as a third row of chips. Below is the doubled gap before the comments,
   which has room to spare. */
@media (hover: hover) and (pointer: fine) {
  .article-share__button::after {
    position: absolute;
    z-index: 20;
    top: calc(100% + 8px);
    left: 50%;
    padding: 0.3rem 0.55rem;
    /* Strong, and not because 1.4.11 demands it -- a tooltip is not a control.
       The bubble's fill is --page, the same ground the article text behind it
       sits on, so this 1px edge is the only thing separating a floating tip
       from the paragraph underneath it. At 1.31:1 the tip read as loose white
       words hovering over the page. */
    border: 1px solid var(--border-strong);
    border-radius: 6px;
    background: var(--page);
    color: var(--white);
    content: attr(data-tip);
    font-size: 12px;
    line-height: 1.2;
    white-space: nowrap;
    opacity: 0;
    transform: translate(-50%, -4px);
    transition: opacity 140ms ease, transform 140ms ease;
    pointer-events: none;
  }

  .article-share__button:hover::after,
  .article-share__button:focus-visible::after,
  .article-share__button[data-copied]::after {
    opacity: 1;
    transform: translate(-50%, 0);
  }

  /* Tab to one button, then reach for another with the mouse, and both the
     focused and the hovered tip were open at once -- they are wider than the
     button pitch, so they overlapped and clipped each other mid-word. While the
     pointer is in the row the pointer wins; the focus ring still marks focus. */
  .article-share__group:hover .article-share__button:focus-visible:not(:hover)::after {
    opacity: 0;
  }
}

/* An article with no tags has no rule above it either, so the share row takes
   that job over. Only one of the two ever draws it. */
.article-body + .article-share {
  padding-top: var(--article-stack);
  border-top: 1px solid rgba(255, 255, 255, 0.11);
}

@media (prefers-reduced-motion: reduce) {
  .article-share__button,
  .article-share__button::after {
    transition: none;
  }
}

.preview-banner {
  position: sticky;
  z-index: 40;
  top: var(--header-height);
  padding: 0.55rem var(--gutter);
  border-bottom: 1px solid rgba(94, 227, 211, 0.35);
  background: #17242a;
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
  text-align: center;
}

.blog-error {
  display: flex;
  min-height: 620px;
  align-items: center;
  flex-direction: column;
  justify-content: center;
  padding-block: 5rem;
  text-align: center;
}

.blog-error__code {
  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.12em;
}

.blog-error h1 {
  margin-top: 0.7rem;
  font-size: clamp(2.2rem, 5vw, 3.75rem);
}

.blog-error > p:not(.blog-error__code) {
  max-width: 520px;
  margin-top: 1rem;
  color: var(--muted);
  font-size: 17px;
}

.blog-error .button {
  margin-top: 1.8rem;
}

/* Footer mirrors src/components/Footer.jsx: bg-kstudio-dark, border-t border-gray-800,
   section-padding (py-10 lg:py-16), 5-column grid, gray-400 links going accent on hover. */
/* section-padding = py-10 lg:py-16. */
.site-footer {
  margin-top: 0;
  padding: 4rem 0;
  border-top: 1px solid #1f2937;
  background: var(--deep);
}

.site-footer__inner,
.site-footer__badges,
.site-footer__legal {
  width: min(var(--container), 100%);
  padding-inline: var(--pad);
  margin-inline: auto;
}

/* Footer.jsx: lg:grid-cols-5 gap-8 lg:gap-12 lg:justify-items-center, with the
   brand block pinned left via lg:justify-self-start. */
.site-footer__inner {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  justify-items: center;
  gap: 3rem;
  margin-bottom: 3rem;
}

.site-footer__brand {
  justify-self: start;
}

.site-footer__brand .site-logo {
  margin-bottom: 1rem;
}

.site-footer__brand > p {
  max-width: 300px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.55;
}

.site-footer__social {
  display: flex;
  gap: 1rem;
  margin-top: 1.5rem;
}

.site-footer__social a {
  display: inline-flex;
  color: var(--muted);
  transition: color 300ms ease;
}

.site-footer__title {
  margin: 0 0 1rem;
  color: var(--white);
  font-size: 16px;
  font-weight: 700;
}

.site-footer__column ul {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-footer a,
.site-footer span {
  color: var(--muted);
  font-size: 14px;
  text-decoration: none;
  transition: color 200ms ease;
}

.site-footer a:hover {
  color: var(--accent);
}

.site-footer__meeting {
  font-weight: 600;
}

.site-footer__badges {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding-top: 2rem;
  border-top: 1px solid #1f2937;
}

.site-footer__badges p {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 14px;
}

.site-footer__flag {
  flex: 0 0 auto;
}

.site-footer__badge-icon {
  flex: 0 0 auto;
  color: var(--accent);
}

.site-footer__legal {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1.5rem;
  padding-top: 2rem;
  border-top: 1px solid #1f2937;
}

.site-footer__legal > span {
  color: var(--dim);
}

.site-footer__legal > div {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.site-footer__legal a {
  color: var(--dim);
}

/* lg step. */
@media (max-width: 1279px) {
  .site-nav__links {
    gap: 1rem;
  }
}

/* The hero runs out of room before the nav does. The two-column split only
   works while the container is at its full 1280px: below that the fixed 400px
   intro column leaves the featured card too narrow, its text column drops under
   300px and the excerpt starts getting clamped away. Stacking hands the card
   the whole container instead, which is wider than the split ever gives it. */
@media (max-width: 1279px) {
  .index-hero {
    grid-template-columns: 1fr;
    gap: 2rem;
    min-height: auto;
    padding-top: 4rem;
  }

  .index-hero__intro h1,
  .index-hero__intro > p {
    max-width: 720px;
  }
}

@media (max-width: 1023px) {
  /* md step. Nine nav items plus the auth pair stop fitting before the mobile
     breakpoint, so the row also tightens its type rather than overflowing the bar. */
  .site-nav__links {
    gap: 0.75rem;
  }

  .site-nav__links > a,
  .site-nav__products-trigger {
    font-size: 15px;
  }

  .latest-section__header {
    align-items: flex-start;
    flex-direction: column;
    gap: 0.25rem;
  }

  .category-filters {
    width: 100%;
  }

  .insight-row {
    grid-template-columns: minmax(210px, 32vw) minmax(0, 1fr) 42px;
  }

  .blog-cta {
    align-items: flex-start;
    flex-direction: column;
  }

  /* Footer.jsx: md:grid-cols-2 with the brand block spanning both, gap-8,
     and no justify-items-center below lg. */
  .site-footer {
    padding-block: 2.5rem;
  }

  .site-footer__inner {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    justify-items: start;
    gap: 2rem;
  }

  .site-footer__brand {
    grid-column: 1 / -1;
  }

  .site-footer__badges {
    align-items: flex-start;
    flex-direction: column;
    gap: 1rem;
  }
}

@media (max-width: 767px) {
  :root {
    /* Navbar.jsx is h-20 at every breakpoint — the blog no longer shrinks it. */
    --gutter: 1rem;
  }

  body {
    font-size: 15px;
  }
}

/* The header switches to the hamburger at 959px, not at the md step Navbar.jsx
   uses. The blog carries nine nav items plus the auth pair, and between 768px
   and ~930px they do not fit: the auth controls were laid out past the right
   edge of a position:fixed bar, which does not extend the document scroll
   width, so they were painted nowhere and reachable by nothing. A guest could
   not sign in and a signed-in reader could not sign out on any tablet-width
   screen (design review, 2026-09-11, measured 768-929px). Tightening the type
   at 1023px was not enough; the row has to collapse earlier. */
@media (max-width: 959px) {
  .site-nav {
    gap: 0.75rem;
  }

  /* Both the link row and the auth pair go away together and everything moves
     into the hamburger panel — the auth controls must never be the half that is
     left behind in a row that no longer fits. */
  .site-nav__links,
  .site-nav__auth {
    display: none;
  }

  .site-nav__mobile {
    position: relative;
    display: block;
  }

  /* Direct child only — a descendant selector here also captured the nested
     Products summary and squashed it to a 40x40 hamburger box. */
  .site-nav__mobile > summary {
    display: flex;
    width: 40px;
    height: 40px;
    align-items: center;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    border-radius: 7px;
    cursor: pointer;
    list-style: none;
  }

  .site-nav__mobile > summary::-webkit-details-marker {
    display: none;
  }

  .site-nav__mobile > summary span {
    display: block;
    width: 19px;
    height: 1px;
    background: var(--light);
  }

  .site-nav__mobile-panel {
    position: absolute;
    top: calc(100% + 0.7rem);
    right: 0;
    display: flex;
    width: min(260px, calc(100vw - 2rem));
    /* The full Navbar menu is a lot taller than the four links this panel used to
       carry: cap it to the viewport and scroll, otherwise the lower entries are
       unreachable on a short phone. Opaque for the same reason the site's menu is —
       at 0.98 the page ghosted through a now much larger surface. */
    max-height: calc(100vh - var(--header-height) - 1.5rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    flex-direction: column;
    padding: 0.6rem;
    border: 1px solid rgba(94, 227, 211, 0.2);
    border-radius: 10px;
    background: var(--deep);
    box-shadow: 0 18px 45px rgba(0, 0, 0, 0.42);
  }

  /* `:not(.button)` is load-bearing. At (0,1,1) this rule outweighs `.button--accent`
     (0,1,0) and was repainting the Sign Up CTA white-on-mint (~1.5:1) with a 6px
     radius, while the :hover variant stripped its fill entirely. */
  .site-nav__mobile-panel a:not(.button) {
    padding: 0.7rem 0.85rem;
    border-radius: 6px;
    color: var(--white);
    font-size: 16px;
    font-weight: 500;
    text-decoration: none;
    transition: color 200ms ease, background-color 200ms ease;
  }

  /* Accent tint rather than the site's bg-kstudio-dark: the panel itself is now
     kstudio-dark, so that highlight would be invisible here. */
  .site-nav__mobile-panel a:not(.button):hover,
  .site-nav__mobile-panel a:not(.button).is-active {
    background: rgba(94, 227, 211, 0.08);
    color: var(--accent);
  }

  /* Collapsible Products group, same as the mobile Navbar accordion. */
  .site-nav__mobile-products > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.7rem 0.85rem;
    border-radius: 6px;
    color: var(--white);
    font-size: 16px;
    font-weight: 500;
    cursor: pointer;
    list-style: none;
  }

  .site-nav__mobile-products > summary::-webkit-details-marker {
    display: none;
  }

  .site-nav__mobile-products[open] > summary .site-nav__chevron {
    transform: rotate(180deg);
  }

  .site-nav__mobile-products > div {
    display: flex;
    flex-direction: column;
    padding-left: 1rem;
  }

  .site-nav__mobile-products a {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    font-size: 14px;
  }

  .site-nav__mobile-products a svg {
    flex: 0 0 auto;
    color: rgba(94, 227, 211, 0.7);
  }

  /* Navbar.jsx: pt-4 border-t border-white/20 mt-4 above the auth buttons. */
  .site-nav__mobile-auth {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid rgba(255, 255, 255, 0.2);
  }

  .site-nav__mobile-auth .button {
    width: 100%;
    min-height: 40px;
    font-size: 16px;
  }

  /* The mobile panel is a column of full-width controls; Sign out stays a text
     control but gets a 44px tap target (design review measured the first cut at
     40px) and the panel's body size. */
  .site-nav__mobile-auth .site-nav__signout {
    align-self: flex-start;
    min-height: 44px;
    padding-inline: 0.25rem;
    font-size: 16px;
  }
}

@media (max-width: 767px) {
  .index-hero {
    gap: 2rem;
    padding-block: 3rem 1.3rem;
  }

  .index-hero__intro h1 {
    font-size: 2.25rem;
  }

  .index-hero__intro > p {
    margin-top: 1rem;
    font-size: 16px;
  }

  .featured-story {
    grid-template-columns: 1fr;
    min-height: 0;
  }

  .featured-story__media {
    min-height: 220px;
    aspect-ratio: 16 / 9;
  }

  .featured-story__content {
    padding: 1.45rem;
  }

  .latest-section {
    padding-top: 1rem;
  }

  .category-filters {
    margin-right: calc(var(--gutter) * -1);
    padding-right: var(--gutter);
  }

  /* Same trade as desktop: a floor, not a ceiling. A three-line headline here
     is normal and used to overrun the fixed 128px. */
  .insight-row {
    grid-template-columns: 116px minmax(0, 1fr) 32px;
    min-height: 128px;
  }

  .insight-row__content {
    padding: 0.7rem 0.8rem;
  }

  .insight-row h3 {
    display: -webkit-box;
    overflow: hidden;
    font-size: 0.95rem;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
  }

  .insight-row__content > p:not(.story-category) {
    display: none;
  }

  .story-meta {
    align-items: flex-start;
    flex-direction: column;
    gap: 0;
  }

  .story-meta [aria-hidden="true"] {
    display: none;
  }

  .blog-cta {
    margin-block: 2.4rem;
    padding: 1.5rem;
  }

  .blog-cta__actions {
    width: 100%;
    align-items: stretch;
    flex-direction: column;
  }

  .blog-cta__actions .button {
    width: 100%;
  }

  .article-header {
    padding-top: 2.7rem;
    padding-bottom: 1.7rem;
  }

  .article-header h1 {
    font-size: 2.25rem;
  }

  .article-cover {
    width: calc(100% - 2rem);
    margin-bottom: 2rem;
  }

  .article-cover__image {
    aspect-ratio: 16 / 10;
  }

  .article-body {
    width: calc(100% - 2rem);
    font-size: 16.5px;
    line-height: 1.68;
  }

  .blog-figure--wide {
    width: calc(100vw - 2rem);
  }

  .blog-figure--float-right {
    width: 100%;
    margin: 1.6rem 0;
    float: none;
  }

  .article-tags,
  .article-share {
    width: calc(100% - 2rem);
  }

  .site-footer {
    padding-top: 2.8rem;
  }

  .site-footer__inner {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .site-footer__brand {
    grid-column: 1 / -1;
  }

  .site-footer__legal {
    align-items: flex-start;
    flex-direction: column;
  }
}

@media (max-width: 430px) {
  .insight-row {
    grid-template-columns: 96px minmax(0, 1fr) 28px;
  }

  .site-footer__inner {
    grid-template-columns: 1fr;
  }

  .site-footer__brand {
    grid-column: auto;
  }
}

/* Comments
   -------------------------------------------------------------------------
   The thread is a continuation of the article, not a widget bolted to the end
   of it: same reading column, same hairline rules as the share and tag rows,
   no cards. Depth is the one place this section spends anything — a reply sits
   behind a single teal rule, the same device the article body already uses for
   blockquotes, so a nested answer reads as a quoted aside rather than as a box
   inside a box. Everything else stays quiet. */
/* No rule of its own: the share row above it is close enough that a second
   hairline read as a stutter rather than as two sections. Separation is the
   step instead -- doubled, because at a single step the heading joined the
   share row into one block. Same scale as the rows above, twice the interval,
   which is what makes it read as the next section without a line. */
.article-comments {
  width: min(var(--reading), calc(100% - (var(--gutter) * 2)));
  margin: calc(var(--article-stack) * 2) auto 0;
}

.article-comments__title {
  margin: 0 0 1.25rem;
  color: var(--white);
  font-family: Montserrat, Inter, sans-serif;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.3;
}

.article-comments__title [data-count] {
  color: var(--dim);
  font-weight: 500;
}

.article-comments__closed {
  margin: 0 0 1.5rem;
  color: var(--muted);
  font-size: 14px;
}

.article-comments__signin {
  margin: 0 0 1.75rem;
}

.comment-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.comment {
  padding-block: 1.35rem;
}

.comment + .comment {
  border-top: 1px solid var(--border);
}

.comment--pending {
  opacity: 0.7;
}

.comment__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.55rem;
  color: var(--muted);
  font-size: 13px;
}

.comment__avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
}

/* Same monogram as .article-byline__avatar: a reader without a provider photo
   gets a mark, never a broken image frame. */
.comment__avatar--initial {
  display: grid;
  border: 1px solid rgba(94, 227, 211, 0.45);
  background: linear-gradient(135deg, rgba(94, 227, 211, 0.28), rgba(255, 77, 69, 0.22));
  color: var(--white);
  font-size: 13px;
  font-weight: 700;
  place-items: center;
}

.comment__author {
  color: var(--white);
  font-weight: 600;
}

.comment__badge {
  padding: 0.15rem 0.45rem;
  border: 1px solid rgba(94, 227, 211, 0.4);
  border-radius: 999px;
  color: var(--accent);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  line-height: 1.4;
  text-transform: uppercase;
}

.comment__edited {
  color: var(--dim);
  font-size: 12px;
}

.comment__body {
  margin: 0.6rem 0 0;
  color: var(--body);
  font-size: 16px;
  line-height: 1.7;
  /* A pasted URL is one unbreakable word and would otherwise widen the column. */
  overflow-wrap: anywhere;
}

.comment__pending {
  margin: 0.5rem 0 0;
  color: var(--dim);
  font-size: 12px;
}

.comment__actions {
  display: flex;
  gap: 1rem;
  margin: 0.6rem 0 0;
}

.comment__action,
.comment-form__cancel {
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: color 200ms ease;
}

.comment__action:hover,
.comment__action:focus-visible,
.comment-form__cancel:hover,
.comment-form__cancel:focus-visible {
  color: var(--accent);
}

.comment-list--replies {
  margin-top: 1rem;
  padding-left: 2.5rem;
  border-left: 1px solid rgba(94, 227, 211, 0.25);
}

.comment-list--replies .comment:first-child {
  padding-top: 0;
}

.comment-form {
  margin: 0 0 2rem;
}

.comment-form__label {
  display: block;
  margin-bottom: 0.55rem;
  color: var(--muted);
  font-size: 13px;
}

.comment-form textarea {
  display: block;
  width: 100%;
  min-height: 120px;
  padding: 0.85rem 1rem;
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  background: var(--surface);
  color: var(--body);
  font: inherit;
  font-size: 15px;
  line-height: 1.6;
  resize: vertical;
  transition: border-color 200ms ease, box-shadow 200ms ease;
}

.comment-form textarea::placeholder {
  color: var(--dim);
}

.comment-form textarea:focus {
  border-color: var(--accent);
  outline: none;
  box-shadow: 0 0 0 3px rgba(94, 227, 211, 0.15);
}

.comment-form__footer {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.9rem;
  margin-top: 0.85rem;
}

.comment-form__counter {
  margin-right: auto;
  color: var(--dim);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.comment-form__error {
  margin: 0.6rem 0 0;
  color: var(--coral);
  font-size: 13px;
}

/* The error paragraph is always in the DOM for aria-live to have a target, so
   it must take no space until it has something to announce. */
.comment-form__error:empty {
  display: none;
}

@media (max-width: 640px) {
  .comment-list--replies {
    padding-left: 1rem;
  }

  .article-comments__title {
    font-size: 20px;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
}

@media print {
  .site-header,
  .site-footer,
  .blog-cta,
  .preview-banner,
  .comment-form,
  .comment__actions,
  .article-comments__signin {
    display: none;
  }

  body {
    background: #fff;
    color: #111;
  }

  .blog-article {
    padding-top: 0;
  }

  .article-body,
  h1,
  h2,
  h3,
  h4,
  .article-header h1 {
    color: #111;
  }
}
