/**
 * Server-rendered blog: index and single post.
 *
 * Ported from src/components/BlogPage.tsx, whose design lives entirely in
 * Tailwind utilities — so unlike the documentation there was no plain-CSS
 * system to reuse and every value is written out here.
 */

.hsx-blog { min-height: 100vh; background-color: hsl(var(--background)); }

/* No top padding on the wrapper: the React header is position: fixed and the
   hero's own pt-28 already clears it, exactly as the SPA does. Padding here as
   well would push the hero a second header's height down the page. */
.hsx-blog-body { padding-top: 0; }

/* ─────────── Index: ambient stage ───────────
   Hero and grid sit on one stage so the wash runs behind both. The blobs are
   painted with mix-blend-multiply over a near-white ground, which keeps them
   as tint rather than haze, and they are the only animated thing on the page. */
/* The edge every control on this page draws itself with.

   The theme's --border (hsl 160 18% 86%) is tuned for lines *inside* a card,
   where the card's own fill already separates it from the page. These controls
   are white on a near-white wash, so that token measured 1.2:1 against the
   background — no edge at all. This is about the lightest value whose *rendered* edge clears the 3:1
   WCAG asks for a control boundary. */
:root { --hsx-control-border: hsl(162 13% 49%); }

.hsx-blog-stage {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	background-color: #f8fafc;
}

.hsx-blog-aurora {
	position: absolute;
	inset: 0;
	z-index: 0;
	pointer-events: none;
	opacity: 0.6;
}

.hsx-blog-blob {
	position: absolute;
	display: block;
	border-radius: 9999px;
	mix-blend-mode: multiply;
	filter: blur(100px);
	animation: hsx-blog-blob 10s infinite;
}
.hsx-blog-blob--a { top: -10%; left: -10%; width: 24rem; height: 24rem; background-color: #5eead4; }
.hsx-blog-blob--b { top: 20%; right: -5%; width: 24rem; height: 24rem; background-color: #bfdbfe; animation-delay: -2s; }
.hsx-blog-blob--c { bottom: -10%; left: 20%; width: 31.25rem; height: 31.25rem; background-color: #d1fae5; filter: blur(120px); animation-delay: -4s; }

@keyframes hsx-blog-blob {
	0%   { transform: translate(0, 0) scale(1); }
	33%  { transform: translate(30px, -50px) scale(1.1); }
	66%  { transform: translate(-20px, 20px) scale(0.9); }
	100% { transform: translate(0, 0) scale(1); }
}

/* ─────────── Index hero ─────────── */
.hsx-blog-hero {
	position: relative;
	z-index: 1;
	/* The header is position:fixed, so its height has to be cleared before any
	   gap is visible. site-header.php measures it into --site-header-height;
	   the 26px on top of that is the actual space under the header, and it
	   stays 26px whether or not the promo bar wraps to two lines. */
	padding: calc(var(--site-header-height, 120px) + 26px) 16px 0;
	text-align: center;
}
@media (min-width: 768px) { .hsx-blog-hero { padding-inline: 32px; } }

.hsx-blog-wrap { width: 100%; max-width: 80rem; margin-inline: auto; }

/* The eyebrow is the site-wide `.hsx-tag` pill from blocks-base.css, the same
   one every other page hero uses; only its spacing is local. */
.hsx-blog-tag { margin-bottom: 24px; }

.hsx-blog-h1 {
	max-width: 56rem;
	margin-inline: auto;
	margin-bottom: 24px;
	font-size: 40px;
	line-height: 1.1;
	font-weight: 800;
	letter-spacing: -0.025em;
	color: #0f172a;
	text-wrap: balance;
}
@media (min-width: 768px)  { .hsx-blog-h1 { font-size: 64px; } }

/* The second line is the muted half of the pair. A gradient clipped to the
   text needs a transparent fill, so the colour above is the fallback for any
   engine that does not support background-clip: text. */
/* The second line is the muted half of the pair. A gradient clipped to the
   text needs a transparent fill, so the colour above is the fallback for any
   engine that does not support background-clip: text. */
/* The second line takes the site accent, matching how every other page
   highlights a word in its heading (`.hsx-accent`). */
.hsx-blog-h1-alt {
	display: block;
	color: hsl(var(--primary, 170 52% 42%));
}

.hsx-blog-intro {
	max-width: 42rem;
	margin-inline: auto;
	margin-bottom: 40px;
	font-size: 17px;
	line-height: 1.625;
	color: #475569;
}

/* ── Search and category filters ── */
.hsx-blog-controls {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 16px;
	width: 100%;
}
@media (min-width: 1280px) { .hsx-blog-controls { flex-direction: row; justify-content: center; } }

/* Both only work once the filter script runs; without it every article is
   listed, which is the useful state. */
.hsx-blog-search, .hsx-blog-catwrap { display: none; }
.hsx-blog-controls.is-ready .hsx-blog-search { display: block; }
.hsx-blog-controls.is-ready .hsx-blog-catwrap { display: block; }

.hsx-blog-search { position: relative; width: 100%; flex-shrink: 0; }
@media (min-width: 1280px) { .hsx-blog-search { width: 20rem; } }

.hsx-blog-search-icon {
	position: absolute;
	left: 16px; top: 50%;
	transform: translateY(-50%);
	width: 16px; height: 16px;
	pointer-events: none;
	color: #94a3b8;
}

.hsx-blog-search-input {
	width: 100%;
	padding: 12px 16px 12px 44px;
	/* Solid, with a border from the theme's own token. Translucent white on a
	   near-white wash left the control with no edge at all — it read as part of
	   the background rather than something to type into. */
	border: 1px solid var(--hsx-control-border);
	border-radius: 9999px;
	background-color: #fff;
	font-size: 14px; line-height: 20px;
	color: #334155;
	box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
	transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.hsx-blog-search-input::placeholder { color: #64748b; }
.hsx-blog-search-input:focus {
	outline: none;
	box-shadow: 0 0 0 2px rgba(64, 155, 134, 0.5);
}

.hsx-blog-cats {
	align-items: center;
	gap: 8px;
	width: 100%;
	padding-block: 8px;
	overflow-x: auto;
	flex-wrap: nowrap;
	scrollbar-width: none;
}
.hsx-blog-cats::-webkit-scrollbar { display: none; }
/* One line at every width; the row scrolls when the categories outgrow it.
   `safe center` is what makes that survivable: plain `center` on an
   overflowing no-wrap scroller pushes the leading items outside the
   scrollable area where nothing can reach them, so the active "All" pill
   vanished off the left edge. The safe keyword centres while the pills fit
   and falls back to start-alignment the moment they don't. */
.hsx-blog-cats {
	display: flex;
	justify-content: safe center;
	/* Keep a pill scrolled into view clear of the arrow sitting over that end. */
	scroll-padding-inline: 44px;
}

/* The arrows are the only hint that the row scrolls, since its scrollbar is
   hidden. They sit over each end of the scroller and the script shows each one
   only while there is actually something further that way. */
.hsx-blog-catwrap { position: relative; width: 100%; min-width: 0; }

.hsx-blog-catnav {
	position: absolute;
	top: 50%;
	z-index: 2;
	display: grid;
	place-items: center;
	width: 34px; height: 34px;
	padding: 0;
	transform: translateY(-50%);
	border: 1px solid var(--hsx-control-border);
	border-radius: 9999px;
	background-color: #fff;
	box-shadow: 0 2px 10px rgba(15, 23, 42, 0.14);
	color: #334155;
	cursor: pointer;
	transition: color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
/* An author `display` beats the UA sheet's `[hidden] { display: none }`. */
.hsx-blog-catnav[hidden] { display: none; }
.hsx-blog-catnav--prev { left: -4px; }
.hsx-blog-catnav--next { right: -4px; }
.hsx-blog-catnav svg { width: 16px; height: 16px; }
@media (hover: hover) {
	.hsx-blog-catnav:hover {
		border-color: var(--hsx-accent, #409b86);
		box-shadow: 0 4px 14px rgba(15, 23, 42, 0.18);
		color: var(--hsx-accent, #409b86);
	}
}
.hsx-blog-catnav:focus-visible {
	outline: none;
	box-shadow: 0 0 0 2px rgba(64, 155, 134, 0.5);
}

/* Fade the pills out under whichever arrow is showing, so one is never left
   half-covered by the circle. A mask fades to transparent rather than to a
   colour, so it works over the hero's gradient without matching it, and it
   changes no geometry — no feedback loop with the arrows it reacts to. */
.hsx-blog-catwrap:has( .hsx-blog-catnav--next:not( [hidden] ) ) .hsx-blog-cats {
	-webkit-mask-image: linear-gradient( to right, #000 calc( 100% - 58px ), transparent 100% );
	mask-image: linear-gradient( to right, #000 calc( 100% - 58px ), transparent 100% );
}
.hsx-blog-catwrap:has( .hsx-blog-catnav--prev:not( [hidden] ) ) .hsx-blog-cats {
	-webkit-mask-image: linear-gradient( to right, transparent 0, #000 58px );
	mask-image: linear-gradient( to right, transparent 0, #000 58px );
}
.hsx-blog-catwrap:has( .hsx-blog-catnav--prev:not( [hidden] ) ):has( .hsx-blog-catnav--next:not( [hidden] ) ) .hsx-blog-cats {
	-webkit-mask-image: linear-gradient( to right, transparent 0, #000 58px, #000 calc( 100% - 58px ), transparent 100% );
	mask-image: linear-gradient( to right, transparent 0, #000 58px, #000 calc( 100% - 58px ), transparent 100% );
}

.hsx-blog-cat {
	flex-shrink: 0;
	padding: 10px 24px;
	border: 1px solid var(--hsx-control-border);
	border-radius: 9999px;
	background-color: #fff;
	box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
	color: #334155;
	font-size: 14px; line-height: 20px;
	font-weight: 500;
	white-space: nowrap;
	cursor: pointer;
	transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
@media (hover: hover) {
	.hsx-blog-cat:hover { border-color: hsl(var(--primary)); color: hsl(var(--primary)); }
}

.hsx-blog-cat.hsx-blog-cat--on {
	background-color: hsl(var(--primary));
	border-color: hsl(var(--primary));
	color: hsl(var(--primary-foreground));
	box-shadow: 0 2px 8px hsl(var(--primary) / 0.28);
}

/* ─────────── Card grid ─────────── */
.hsx-blog-grid-section { position: relative; z-index: 1; padding: 64px 16px 96px; }
@media (min-width: 768px) { .hsx-blog-grid-section { padding-inline: 32px; } }

.hsx-blog-grid { display: grid; grid-template-columns: 1fr; gap: 24px; }
/* Rows size to their own content. `grid-auto-rows: 1fr` would make every row as
   tall as the tallest — the one holding the featured card — so a single large
   headline padded out all eight cards below it. Cards within a row still
   stretch to match each other, which is the part that matters. */
@media (min-width: 768px)  { .hsx-blog-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .hsx-blog-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* ── The glass card ──
   One translucent white for every card; the palette shows only in the accent
   rail and the category pill, so nine colours never turn into nine washes. */
.hsx-blog-card {
	position: relative;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	border: 1px solid rgba(255, 255, 255, 0.6);
	border-radius: 24px;
	background: rgba(255, 255, 255, 0.45);
	backdrop-filter: blur(20px);
	-webkit-backdrop-filter: blur(20px);
	box-shadow: 0 8px 32px rgba(0, 0, 0, 0.04);
	transition: transform 0.3s ease, box-shadow 0.3s ease, background-color 0.3s ease, border-color 0.3s ease;
}
/* Hover only where hovering exists: on a touch screen it latches after a tap
   and the card stays lifted until something else is tapped. */
@media (hover: hover) {
	.hsx-blog-card:hover {
		transform: translateY(-4px);
		background: rgba(255, 255, 255, 0.65);
		border-color: rgba(255, 255, 255, 0.9);
		box-shadow: 0 12px 40px rgba(0, 0, 0, 0.08);
	}
}
.hsx-blog-card:focus-within {
	background: rgba(255, 255, 255, 0.65);
	border-color: rgba(255, 255, 255, 0.9);
	box-shadow: 0 12px 40px rgba(0, 0, 0, 0.08);
}

.hsx-blog-card-accent {
	position: absolute;
	top: 0; left: 0;
	height: 4px; width: 100%;
	background: var(--card-dot);
	opacity: 0.8;
}

.hsx-blog-card-inner { display: flex; flex-direction: column; flex: 1 1 auto; padding: 24px; }

.hsx-blog-card-cat {
	align-self: flex-start;
	display: inline-flex;
	align-items: center;
	margin-bottom: 16px;
	padding: 4px 12px;
	border: 1px solid var(--card-pill-border);
	border-radius: 9999px;
	background: var(--card-pill-bg);
	color: var(--card-pill-fg);
	font-size: 11px; line-height: 16px;
	font-weight: 700;
	letter-spacing: 0.025em;
	text-transform: uppercase;
}

.hsx-blog-card-title { margin-bottom: 12px; font-size: 20px; line-height: 1.375; font-weight: 700; }
.hsx-blog-card-title a { color: #0f172a; text-decoration: none; transition: color 0.2s ease; }
@media (hover: hover) {
	.hsx-blog-card:hover .hsx-blog-card-title a { color: var(--card-dot); }
}

/* Two lines of title, three of excerpt: the grid keeps its rhythm however long
   an editor's copy runs. */
.hsx-blog-card-title,
.hsx-blog-card-excerpt { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
.hsx-blog-card-title { -webkit-line-clamp: 2; }
.hsx-blog-card-excerpt {
	-webkit-line-clamp: 3;
	margin-bottom: 24px;
	font-size: 14px; line-height: 1.625;
	color: #475569;
}

.hsx-blog-card-foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	margin-top: auto;
	padding-top: 20px;
	border-top: 1px solid rgba(203, 213, 225, 0.5);
	font-size: 12px; line-height: 16px;
	color: #64748b;
}
.hsx-blog-card-author { display: flex; align-items: center; gap: 8px; min-width: 0; }
.hsx-blog-card-id { display: flex; flex-direction: column; min-width: 0; }

.hsx-blog-card-avatar {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	overflow: hidden;
	width: 32px; height: 32px;
	border-radius: 9999px;
	background: #e2e8f0;
	color: #64748b;
	font-size: 11px;
	font-weight: 700;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.hsx-blog-card-avatar img { width: 100%; height: 100%; object-fit: cover; }
.hsx-blog-card-name { font-size: 13px; font-weight: 600; color: #0f172a; }

/* The featured card stacks the date under the byline and shows the read time
   as a pill; every other card runs them together on the right. */
.hsx-blog-card-date,
.hsx-blog-card-read { display: none; }
.hsx-blog-card-meta { flex-shrink: 0; }

/* ── Featured card ──
   First in the grid and two columns wide, but only one row tall: spanning two
   rows left a column of dead space between the excerpt and the footer. The row
   itself is 1fr like every other, so the card ends up the same height as the
   cards beside it. The class is moved by the filter script to whichever card
   leads the visible set. */
@media (min-width: 768px)  { .hsx-blog-card--featured { grid-column: span 2; } }

.hsx-blog-card--featured .hsx-blog-card-accent {
	height: 6px;
	background-image: linear-gradient(to right, #409b86, #34d399);
}
.hsx-blog-card--featured .hsx-blog-card-inner { padding: 32px; }
.hsx-blog-card--featured .hsx-blog-card-cat { margin-bottom: 20px; }
/* Three lines in a narrow column, two once the card is two columns wide — the
   same headline needs more lines at 390px than it does at 1280px. */
.hsx-blog-card--featured .hsx-blog-card-title {
	-webkit-line-clamp: 3;
	margin-bottom: 16px; font-size: 30px; line-height: 1.15;
}
@media (min-width: 1024px) {
	.hsx-blog-card--featured .hsx-blog-card-title { -webkit-line-clamp: 2; font-size: 40px; }
}
.hsx-blog-card--featured .hsx-blog-card-excerpt {
	-webkit-line-clamp: 3;
	max-width: 42rem;
	margin-bottom: 24px;
	font-size: 17px;
}
.hsx-blog-card--featured .hsx-blog-card-foot { padding-top: 24px; }
.hsx-blog-card--featured .hsx-blog-card-avatar { width: 40px; height: 40px; font-size: 13px; }
.hsx-blog-card--featured .hsx-blog-card-name { font-size: 14px; }
.hsx-blog-card--featured .hsx-blog-card-date { display: block; font-size: 12px; color: #64748b; }
.hsx-blog-card--featured .hsx-blog-card-meta { display: none; }
.hsx-blog-card--featured .hsx-blog-card-read {
	display: block;
	flex-shrink: 0;
	padding: 4px 12px;
	border: 1px solid rgba(255, 255, 255, 0.6);
	border-radius: 9999px;
	background: rgba(255, 255, 255, 0.5);
	font-size: 13px;
	font-weight: 500;
	color: #64748b;
}

/* ── Load more ── */
.hsx-blog-more { display: flex; justify-content: center; margin-top: 48px; }

.hsx-blog-more-btn {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 13px 32px;
	border: 1px solid var(--hsx-control-border);
	border-radius: 9999px;
	background-color: #fff;
	box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
	color: #0f172a;
	font: inherit;
	font-size: 14px; line-height: 20px;
	font-weight: 600;
	cursor: pointer;
	transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
@media (hover: hover) {
	.hsx-blog-more-btn:hover {
		background-color: hsl(var(--primary));
		border-color: transparent;
		color: hsl(var(--primary-foreground));
		box-shadow: 0 6px 18px hsl(var(--primary) / 0.28);
	}
}
.hsx-blog-more-btn:focus-visible { outline: 2px solid hsl(var(--primary)); outline-offset: 3px; }

/* An author `display` beats the UA sheet's `[hidden] { display: none }`, so
   without this the button stays on screen once every article is shown. */
.hsx-blog-more-btn[hidden] { display: none; }

/* The card a press scrolls to takes focus; it should not draw a ring for a
   pointer user who never left the button. */
.hsx-blog-card:focus { outline: none; }
.hsx-blog-card:focus-visible { outline: 2px solid hsl(var(--primary)); outline-offset: 3px; }

.hsx-blog-count { margin-top: 32px; text-align: center; font-size: 13px; line-height: 16px; font-weight: 500; color: #64748b; }

.hsx-blog-empty { display: none; padding-block: 96px; text-align: center; }
.hsx-blog-empty-title { margin-bottom: 8px; font-size: 18px; line-height: 28px; font-weight: 600; }
.hsx-blog-empty-text { font-size: 14px; line-height: 20px; color: hsl(var(--muted-foreground)); }

/* An author `display` beats the UA sheet's `[hidden] { display: none }`, so
   without this a filtered-out card stays on screen. */
.hsx-blog-card[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
	.hsx-blog-blob { animation: none; }
	.hsx-blog-card { transition: none; }
	.hsx-blog-card { transition: none; }
}

/* ─────────── Single post ─────────── */
/* The header is position:fixed, so 96px of padding put the "All articles" link
   underneath it. site-header.php measures the header into --site-header-height;
   the 32px on top of that is the gap the link actually gets. */
.hsx-post { padding: calc(var(--site-header-height, 120px) + 32px) 16px 80px; }
@media (min-width: 768px) { .hsx-post { padding-inline: 24px; } }

.hsx-post-head { max-width: 48rem; }
.hsx-post-tags-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 20px; }

.hsx-post-cat {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 6px 12px;
	border-radius: 9999px;
	background-color: hsl(var(--primary) / 0.1);
	color: hsl(var(--primary));
	font-size: 12px; line-height: 16px;
	font-weight: 600;
}
.hsx-post-cat-icon { width: 14px; height: 14px; }

.hsx-post-tag {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 4px 10px;
	border-radius: 9999px;
	background-color: hsl(var(--muted) / 0.6);
	color: hsl(var(--muted-foreground));
	font-size: 12px; line-height: 16px;
}
.hsx-post-tag-icon { width: 12px; height: 12px; }

.hsx-post-h1 {
	margin-bottom: 16px;
	font-size: 30px;
	line-height: 1.25;
	font-weight: 700;
	color: hsl(var(--foreground));
}
@media (min-width: 768px)  { .hsx-post-h1 { font-size: 36px; line-height: 40px; } }
@media (min-width: 1024px) { .hsx-post-h1 { font-size: 2.75rem; } }

.hsx-post-lead { margin-bottom: 24px; font-size: 18px; line-height: 1.625; color: hsl(var(--muted-foreground)); }

/* The article's one byline: who wrote it and when it was last checked. It
   replaced a meta row here plus an author card after the body, which showed the
   same name twice. */
.hsx-post-byline {
	display: flex;
	align-items: center;
	gap: 12px;
	padding-block: 14px;
	border-top: 1px solid hsl(var(--border));
	border-bottom: 1px solid hsl(var(--border));
	font-size: 15px; line-height: 22px;
	color: hsl(var(--muted-foreground));
}

/* The monogram is the floor, not the fallback: get_avatar_url() always returns
   something, so the picture is layered over the initials and simply fails to
   paint when the author has no Gravatar. */
.hsx-post-byline-mark {
	position: relative;
	display: grid;
	place-items: center;
	flex-shrink: 0;
	overflow: hidden;
	width: 38px; height: 38px;
	border-radius: 9999px;
	background-color: #1e293b;
	color: #fff;
	font-size: 13px; font-weight: 700;
	letter-spacing: 0.02em;
}
.hsx-post-byline-mark img {
	position: absolute;
	inset: 0;
	width: 100%; height: 100%;
	object-fit: cover;
	border-radius: inherit;
}

.hsx-post-byline-who { min-width: 0; }
.hsx-post-byline-line { display: flex; align-items: center; gap: 8px; margin: 0; }
.hsx-post-byline-name { font-weight: 700; color: hsl(var(--foreground)); }

/* The teal bullet is drawn by the role, so no separator is left behind when
   the author has not set one. */
.hsx-post-byline-role {
	display: flex;
	align-items: center;
	gap: 8px;
	color: hsl(var(--primary));
}
.hsx-post-byline-role::before {
	content: "";
	width: 5px; height: 5px;
	border-radius: 9999px;
	background-color: hsl(var(--primary));
}

.hsx-post-byline-when { margin-top: 2px; font-size: 14px; }
.hsx-post-byline-dot {
	width: 3px; height: 3px;
	border-radius: 9999px;
	background-color: currentColor;
	opacity: 0.6;
}
.hsx-post-byline-read { display: flex; align-items: center; gap: 6px; }
.hsx-post-byline-icon { width: 14px; height: 14px; flex-shrink: 0; }

.hsx-post-byline-right {
	display: flex;
	align-items: center;
	gap: 14px;
	margin-left: auto;
	flex-shrink: 0;
}

.hsx-post-byline-share { display: flex; align-items: center; gap: 8px; }

.hsx-post-byline-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	min-width: 38px;
	height: 38px;
	padding: 0 12px;
	border: 1px solid hsl(var(--border) / 0.8);
	border-radius: 11px;
	background-color: hsl(var(--background));
	color: hsl(var(--foreground));
	font: inherit;
	font-size: 14px;
	font-weight: 500;
	text-decoration: none;
	cursor: pointer;
	transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.hsx-post-byline-brand { width: 16px; height: 16px; }
@media (hover: hover) {
	.hsx-post-byline-btn:hover {
		border-color: hsl(var(--primary) / 0.5);
		background-color: hsl(var(--primary) / 0.06);
		color: hsl(var(--primary));
	}
}
.hsx-post-byline-btn:focus-visible {
	outline: none;
	box-shadow: 0 0 0 2px hsl(var(--primary) / 0.5);
}
.hsx-post-byline-copy.is-done {
	border-color: hsl(var(--primary) / 0.5);
	color: hsl(var(--primary));
}

/* Stacked once the two halves stop fitting on one line. */
@media (max-width: 640px) {
	.hsx-post-byline { flex-wrap: wrap; row-gap: 12px; }
	.hsx-post-byline-right { margin-left: 0; width: 100%; }
}

.hsx-post-cover {
	margin-bottom: 48px;
	overflow: hidden;
	border: 1px solid hsl(var(--border));
	border-radius: 16px;
	background-color: hsl(var(--muted) / 0.2);
	box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.hsx-post-cover img { display: block; width: 100%; height: auto; }

/* Stacked below 1024px, where the rail is not a rail: it sits above the
   article, which is the order the markup is already in. */
.hsx-post-cols { display: flex; flex-direction: column; gap: 28px; align-items: stretch; }
@media (min-width: 1024px) {
	.hsx-post-cols { flex-direction: row; gap: 48px; align-items: flex-start; }
}
.hsx-post-body { flex: 1 1 0%; min-width: 0; }

/* ── Table of contents ──
   The rail itself is the sticky, internally scrolling container — not the
   table-of-contents card inside it. Sticky on the inner card would leave any
   sibling below it (the preferred-source button) positioned against the card's
   original flow box, so the two overlapped as soon as the card stuck. This is
   the same structure the docs rail uses, where .docs-toc is the sticky element
   and .docs-toc-inner is a plain column. */
/* Shown at every width. On a phone the contents are the first thing worth
   seeing — a long guide is hard to judge from its opening paragraph — so the
   rail renders above the article rather than being hidden. */
.hsx-post-toc { display: block; }
@media (min-width: 1024px) {
	.hsx-post-toc {
		display: block;
		width: 16rem;
		flex-shrink: 0;
		align-self: flex-start;
		position: sticky;
		/* 6rem was a guess that sat above the real header, so the card's top
		   slid underneath it. site-header.php measures the header — which grows
		   with the promo bar — into --site-header-height; stick below that. */
		top: calc(var(--site-header-height, 120px) + 16px);
		padding-bottom: 8px;
		/* No overflow on the rail. It used to scroll its own content, which
		   clipped the Summarise menu to three rows — an absolutely positioned
		   dropdown cannot escape a scrolling ancestor. The contents list caps
		   and scrolls itself now, so the rail no longer needs to. */
		overflow: visible;
	}
}
@media (min-width: 1280px) { .hsx-post-toc { width: 18rem; } }

/* ── The rail card ──
   Contents, the Google button and the Summarise menu share one card instead of
   sitting in three separate boxes. overflow is visible because the Summarise
   dropdown has to escape the card; the header's own corners are rounded
   instead of being clipped by the parent. */
.hsx-post-rail {
	border: 1px solid hsl(var(--border) / 0.7);
	border-radius: 16px;
	background-color: #fff;
	box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
}

/* No bottom rule: it used to separate the contents from the Google button in
   the card's foot, and that button moved into the Summarise bar. With nothing
   below it the rule was a line hanging under the last entry. */
.hsx-post-rail-toc { border-bottom: 0; }

.hsx-post-rail-head {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 13px 16px;
	border-radius: 15px 15px 0 0;
	background-image: linear-gradient(135deg, hsl(var(--primary)), hsl(var(--primary) / 0.78));
	color: #fff;
	font-size: 15px;
	font-weight: 700;
	cursor: pointer;
	list-style: none;
	user-select: none;
}
/* Both are needed to drop the native disclosure triangle. */
.hsx-post-rail-head::-webkit-details-marker { display: none; }
.hsx-post-rail-head::marker { content: ""; }
.hsx-post-rail-head:focus-visible { outline: 2px solid hsl(var(--primary)); outline-offset: 2px; }

.hsx-post-rail-head-icon { width: 19px; height: 19px; flex-shrink: 0; }
.hsx-post-rail-head-text { flex: 1 1 auto; }
.hsx-post-rail-head-chev {
	width: 17px; height: 17px;
	flex-shrink: 0;
	opacity: 0.85;
	transition: transform 0.2s ease;
}
.hsx-post-rail-toc[open] .hsx-post-rail-head-chev { transform: rotate(180deg); }

.hsx-post-rail-nav { padding: 12px 8px 10px; }

/* ── The VPS advert under the contents ──
   Dark where the rest of the rail is light, because an advert that matches the
   page around it stops reading as one. It sits in the rail's dead space below
   the contents list, which on a long guide is most of the screen. */
.hsx-railad {
	margin-top: 14px;
	padding: 18px 18px 16px;
	border-radius: 16px;
	background-image: linear-gradient(158deg, #16302b 0%, #0d1f21 55%, #101a2b 100%);
	box-shadow: 0 10px 26px rgba(10, 25, 22, 0.22);
}

.hsx-railad-eyebrow {
	display: flex;
	align-items: center;
	gap: 7px;
	margin: 0 0 12px;
	font-size: 11px;
	line-height: 16px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: hsl(168 62% 62%);
}
.hsx-railad-eyebrow-icon { width: 14px; height: 14px; flex-shrink: 0; }

/* The slides are stacked in the same grid cell, so the card keeps the height
   of its tallest plan and does not jump as they rotate. */
.hsx-railad-stack { display: grid; }
.hsx-railad-plan { grid-area: 1 / 1; }
/* An author display beats the UA sheet's [hidden] { display: none }. */
.hsx-railad-plan[hidden] { display: none; }

.hsx-railad-name {
	margin: 0 0 3px;
	font-size: 16px;
	line-height: 22px;
	font-weight: 700;
	color: #fff;
}

.hsx-railad-specs {
	margin: 0;
	font-size: 12.5px;
	line-height: 18px;
	color: #9fb3ae;
}

/* Only ever printed when the catalogue answered; there is no placeholder and
   no fallback figure. */
.hsx-railad-price {
	display: flex;
	align-items: baseline;
	gap: 4px;
	margin: 10px 0 0;
}
.hsx-railad-price strong {
	font-size: 22px;
	line-height: 28px;
	font-weight: 800;
	font-variant-numeric: tabular-nums;
	color: #fff;
}
.hsx-railad-from,
.hsx-railad-cycle {
	font-size: 12px;
	line-height: 17px;
	color: #8aa09b;
}

.hsx-railad-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 7px;
	margin-top: 13px;
	padding: 9px 14px;
	border-radius: 9999px;
	background-color: #fff;
	color: #0d1f21;
	font-size: 13px;
	line-height: 18px;
	font-weight: 700;
	text-decoration: none;
	transition: background-color 0.15s ease, transform 0.15s ease;
}
.hsx-railad-btn-icon { width: 14px; height: 14px; flex-shrink: 0; }
@media (hover: hover) {
	.hsx-railad-btn:hover { background-color: hsl(168 62% 88%); }
	.hsx-railad-btn:hover .hsx-railad-btn-icon { transform: translateX(2px); }
}
.hsx-railad-btn:focus-visible {
	outline: 2px solid hsl(168 62% 62%);
	outline-offset: 2px;
}

/* ── The rotation indicator ── */
.hsx-railad-dots {
	display: flex;
	justify-content: center;
	gap: 6px;
	margin-top: 14px;
}
.hsx-railad-dots[hidden] { display: none; }

.hsx-railad-dot {
	width: 20px; height: 4px;
	padding: 0;
	border: 0;
	border-radius: 9999px;
	background-color: rgba(255, 255, 255, 0.22);
	cursor: pointer;
	transition: background-color 0.2s ease;
}
.hsx-railad-dot.is-on { background-color: hsl(168 62% 62%); }
.hsx-railad-dot:focus-visible {
	outline: 2px solid hsl(168 62% 62%);
	outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
	.hsx-railad-btn,
	.hsx-railad-btn-icon,
	.hsx-railad-dot { transition: none; }
}



/* A long article's contents can run past the viewport, which pushes everything
   below it off the fold and leaves the rail unable to scroll independently.
   Capping the list keeps the rail the same height whatever the outline is. */
.hsx-post-toc-list {
	list-style: none;
	margin: 0;
	padding: 0 4px 0 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
	max-height: 19rem;
	overflow-y: auto;
	overscroll-behavior: contain;
	scrollbar-width: thin;
	scrollbar-color: hsl(var(--foreground) / 0.2) transparent;
}
.hsx-post-toc-list::-webkit-scrollbar { width: 6px; }
.hsx-post-toc-list::-webkit-scrollbar-track { background: transparent; }
.hsx-post-toc-list::-webkit-scrollbar-thumb {
	border-radius: 9999px;
	background-color: hsl(var(--foreground) / 0.18);
}
.hsx-post-toc-list::-webkit-scrollbar-thumb:hover { background-color: hsl(var(--foreground) / 0.3); }

.hsx-post-toc-link {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	padding: 6px 8px;
	border-radius: 8px;
	font-size: 14px; line-height: 20px;
	color: hsl(var(--muted-foreground));
	text-decoration: none;
	transition: color 0.15s ease, background-color 0.15s ease;
}
@media (hover: hover) {
	.hsx-post-toc-link:hover {
		background-color: hsl(var(--primary) / 0.07);
		color: hsl(var(--primary));
	}
}

/* A bullet, not a number: the headings are places to jump to, not steps in an
   order, and numbering them implies a sequence the article does not have. */
.hsx-post-toc-dot {
	flex-shrink: 0;
	width: 7px; height: 7px;
	margin-top: 7px;
	border-radius: 9999px;
	background-color: hsl(var(--primary) / 0.45);
	transition: background-color 0.15s ease;
}
.hsx-post-toc-link:hover .hsx-post-toc-dot { background-color: hsl(var(--primary)); }
.hsx-post-toc-link--nested { font-size: 13px; }
.hsx-post-toc-link--nested .hsx-post-toc-dot { width: 5px; height: 5px; margin-top: 8px; }

/* ── H3s nested under their H2 ──
   The row holds the link and the chevron side by side; the link takes the
   space so the whole heading, not just its text, stays the click target. */
.hsx-post-toc-row { display: flex; align-items: flex-start; gap: 2px; }
.hsx-post-toc-row .hsx-post-toc-link { flex: 1 1 auto; min-width: 0; }

.hsx-post-toc-twist {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	width: 24px; height: 28px;
	margin-top: 2px;
	padding: 0;
	border: 0;
	border-radius: 7px;
	background: none;
	color: hsl(var(--muted-foreground));
	cursor: pointer;
	transition: background-color 0.15s ease, color 0.15s ease;
}
/* An author display beats the UA sheet's [hidden] { display: none }. */
.hsx-post-toc-twist[hidden] { display: none; }
@media (hover: hover) {
	.hsx-post-toc-twist:hover {
		background-color: hsl(var(--primary) / 0.1);
		color: hsl(var(--primary));
	}
}
.hsx-post-toc-twist:focus-visible { outline: 2px solid hsl(var(--primary)); outline-offset: -2px; }

.hsx-post-toc-twist-icon {
	width: 15px; height: 15px;
	transition: transform 0.2s ease;
}
.hsx-post-toc-twist[aria-expanded="false"] .hsx-post-toc-twist-icon { transform: rotate(-90deg); }

/* The rule the children hang from, so a sub-list reads as belonging to the
   heading above it rather than as more top-level entries. */
.hsx-post-toc-sub {
	list-style: none;
	margin: 2px 0 2px 11px;
	padding-left: 11px;
	border-left: 1px solid hsl(var(--primary) / 0.22);
}
.hsx-post-toc-sub[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
	.hsx-post-toc-twist-icon { transition: none; }
}

.hsx-post-rail-foot { padding: 14px 16px 16px; }

/* A hash jump lands the heading at the very top of the viewport, where the
   fixed header covers it. globals.css sets 6rem on .doc-content headings, which
   was a guess from before the promo bar and is 15px short of the real header;
   site-header.php measures the actual height into --site-header-height, so use
   that plus room to breathe. Overrides rather than edits globals.css, whose
   bundle is shared with the rest of the site. */
/* The same first/last-child reset globals.css carries, repeated here so the
   fix ships with the blog stylesheet rather than waiting on the React bundle
   that compiles globals.css. Identical declarations, so whichever loads last
   wins and the result is the same. */
.hsx-post .doc-content blockquote > :first-child { margin-top: 0; }
.hsx-post .doc-content blockquote > :last-child { margin-bottom: 0; }
.hsx-post .doc-content blockquote > :last-child > li:last-child { margin-bottom: 0; }

.hsx-post .doc-content :is( h2, h3, h4 )[id] {
	/* Clear of the site header and of the Summarise bar stuck beneath it.
	   blog-ai-summary.js measures that bar and publishes --hsx-ai-bar, set to
	   0 wherever the bar is not sticky, so this one rule covers both. */
	scroll-margin-top: calc(
		var( --site-header-height, 120px ) + var( --hsx-ai-bar, 0px ) + 24px
	);
}


/* ── Tags, author card, related ── */
.hsx-post-related { margin-top: 56px; }
.hsx-post-related-title {
	display: flex; align-items: center; gap: 8px;
	margin-bottom: 24px;
	font-size: 20px; line-height: 28px;
	font-weight: 700;
	color: hsl(var(--foreground));
}
.hsx-post-related-icon { width: 20px; height: 20px; color: hsl(var(--primary)); }
.hsx-post-related .hsx-blog-grid { gap: 16px; }

@media (prefers-reduced-motion: reduce) {
	.hsx-blog-card { transition: none; }
	.hsx-blog-card:hover { transform: none; }
}

/* ─────────── Blog switched off ───────────
   The panel can disable the blog; the index then shows this instead of the
   grid, which is what the live site is doing today. */
.hsx-blog-off {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding-block: 96px;
	text-align: center;
}

.hsx-blog-off-tile {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 64px; height: 64px;
	margin-bottom: 20px;
	border-radius: 16px;
	background-color: hsl(var(--primary) / 0.1);
}
.hsx-blog-off-icon { width: 32px; height: 32px; color: hsl(var(--primary) / 0.5); }

.hsx-blog-off-pill {
	display: inline-block;
	margin-bottom: 16px;
	padding: 4px 12px;
	border-radius: 9999px;
	background-color: hsl(var(--primary) / 0.1);
	color: hsl(var(--primary));
	font-size: 12px; line-height: 16px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.05em;
}

.hsx-blog-off-title { margin-bottom: 12px; font-size: 24px; line-height: 32px; font-weight: 700; color: hsl(var(--foreground)); }
.hsx-blog-off-text { max-width: 24rem; font-size: 14px; line-height: 1.625; color: hsl(var(--muted-foreground)); }

/* Same reasoning as the docs body: inline code holding a path or a long token
   has no break opportunity and would scroll the page sideways on a phone. */
.hsx-post-content :not(pre) > code {
	overflow-wrap: break-word;
	word-break: break-word;
}

/* ─────────── Summarise with AI ─────────── */
/* It sits under the contents box now, so the gap belongs above it. */
/* ── Summarise with AI ──
   A bordered row of brand marks sitting between the byline and the article.
   It was a dropdown; with eight services the menu was a click in front of a
   list of logos that are quicker to recognise than to read. */
.hsx-ais {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 6px 14px;
	/* Clear of the byline's bottom rule above it. */
	margin: 18px 0 24px;
	padding: 5px 12px;
	border: 1px solid hsl(var(--border) / 0.8);
	border-radius: 14px;
	/* Opaque, because it travels over the article once it sticks. */
	background-color: hsl(var(--background));

	/* Follows the reader down the page. The offset is the measured header
	   height, the same variable the contents rail sticks to, so the two line
	   up and neither slides under the header. */
	position: sticky;
	top: calc(var(--site-header-height, 120px) + 16px);
	z-index: 15;
}

/* The shadow only appears once it has actually left its place in the flow, so
   a bar sitting where it was drawn does not look like it is floating. */
.hsx-ais.is-stuck {
	box-shadow: 0 6px 20px rgba(15, 23, 42, 0.1);
}

.hsx-ais .hsx-prefsrc { margin: 0 0 0 auto; }

/* ── The bar on a phone ──
   Not sticky: at this width it wraps to two rows, and a bar that tall pinned
   to the top eats a third of the viewport and covers the paragraph the reader
   is on. The desktop rail has room to spare; a phone does not.

   The Google button also stops floating right. Right-aligned under a
   left-aligned row of icons it read as a stray third element; left-aligned it
   lines up with everything above it. */
@media (max-width: 1023px) {
	.hsx-ais {
		position: static;
		gap: 8px 12px;
	}
	.hsx-ais.is-stuck { box-shadow: none; }
	.hsx-ais .hsx-prefsrc { margin: 0; }
}

.hsx-ais-label {
	margin: 0;
	font-size: 14px;
	line-height: 20px;
	font-weight: 600;
	color: hsl(var(--foreground));
}

.hsx-ais-row {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 4px;
	list-style: none;
	margin: 0;
	padding: 0;
}

.hsx-ais-item {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 30px; height: 30px;
	padding: 0;
	border: 0;
	border-radius: 8px;
	background: none;
	cursor: pointer;
	transition: background-color 0.15s ease;
}
@media (hover: hover) {
	.hsx-ais-item:hover { background-color: hsl(var(--primary) / 0.09); }
}
.hsx-ais-item:focus-visible { outline: 2px solid hsl(var(--primary)); outline-offset: -2px; }

/* The mark is the service's own SVG when one is present in assets/ai/, and a
   tinted monogram otherwise — never a hand-drawn copy of someone's logo. */
.hsx-ais-mark {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 18px; height: 18px;
	color: var(--ai-tint, #475569);
	font-size: 12px;
	font-weight: 700;
}
.hsx-ais-mark img { width: 18px; height: 18px; display: block; }
.hsx-ais-mark--plain { color: hsl(var(--foreground) / 0.5); }
.hsx-ais-copy-icon { width: 15px; height: 15px; }

/* ── The tooltip ──
   Built from the item's own data-ai-tip so there is one source of truth for
   the label, which is also the aria-label. It is pointer-events:none and
   aria-hidden by construction — the accessible name is on the control. */
.hsx-ais-item::after {
	/* Empty until hovered: a nowrap tooltip that exists while hidden still
	   widens the scrollable area, which on a phone is a horizontal scrollbar
	   nobody asked for. */
	content: "";
	position: absolute;
	top: calc(100% + 8px);
	left: 50%;
	z-index: 30;
	transform: translateX(-50%) translateY(-3px);
	padding: 7px 12px;
	border-radius: 9px;
	background-color: hsl(var(--foreground));
	color: hsl(var(--background));
	font-size: 13px;
	line-height: 18px;
	font-weight: 500;
	white-space: nowrap;
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
	box-shadow: 0 6px 18px rgba(15, 23, 42, 0.18);
	transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
}
@media (hover: hover) {
	.hsx-ais-item:hover::after {
		content: attr(data-ai-tip);
		opacity: 1;
		visibility: visible;
		transform: translateX(-50%) translateY(0);
	}
}
.hsx-ais-item:focus-visible::after {
	content: attr(data-ai-tip);
	opacity: 1;
	visibility: visible;
	transform: translateX(-50%) translateY(0);
}

/* The row can start near the left edge, where a centred tooltip would run off
   the page; the first and last items anchor to their own edge instead. */
.hsx-ais-row li:first-child .hsx-ais-item::after { left: 0; transform: translateY(-3px); }
.hsx-ais-row li:first-child .hsx-ais-item:hover::after,
.hsx-ais-row li:first-child .hsx-ais-item:focus-visible::after { transform: translateY(0); }
.hsx-ais-row li:last-child .hsx-ais-item::after { left: auto; right: 0; transform: translateY(-3px); }
.hsx-ais-row li:last-child .hsx-ais-item:hover::after,
.hsx-ais-row li:last-child .hsx-ais-item:focus-visible::after { transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
	.hsx-ais-item::after { transition: opacity 0.15s ease, visibility 0.15s; }
}



/* ─────────── Closing CTA above the footer ───────────
   .hsx-cta--overlap pulls itself 5rem down so the card straddles the footer's
   top edge. On a page that works because the block canvas has bottom padding
   for it to pull into; on the blog the footer is the CTA's immediate sibling
   and only has 32px of its own, so the footer's links ended up behind the card.

   This selector matches only here: on a page the CTA sits inside <main>, so
   the footer is main's sibling, not the CTA's. */
.hsx-cta--overlap + .hsx-ftr .hsx-ftr-main { padding-top: 9rem; }
