/* ==================================================================
   EPISODE WEB
   The [episode_web] shortcode: category hubs with episodes rooting off
   them. Loaded only on pages that use the shortcode.

   --web-accent and --web-height are set inline by the shortcode.
   ================================================================== */

.pst-web {
	--web-accent: #E8B04B;
	--web-height: 760px;
	--web-ink: #F5F5F0;
	--web-ink-muted: #9A9AA2;
	--web-deep: #07070A;

	position: relative;
	margin: 48px 0;
	padding: 0 var(--container-pad, 24px);
}

.pst-web-heading {
	font-family: var(--font-display, sans-serif);
	font-size: clamp(22px, 3vw, 34px);
	letter-spacing: -0.01em;
	margin: 0 0 18px;
}

/* ------------------------------------------------------------------
   Stage
   ------------------------------------------------------------------ */
.pst-web-stage {
	position: relative;
	height: var(--web-height);
	min-height: 420px;
	overflow: hidden;
	border: 1px solid var(--color-border, #26262C);
	border-radius: 12px;
	background:
		radial-gradient(120% 100% at 50% 0%, #12141C 0%, var(--web-deep) 62%, #050508 100%);
	cursor: grab;
	touch-action: none;
	opacity: 0;
	transition: opacity 0.5s ease;
}
.pst-web.is-ready .pst-web-stage { opacity: 1; }
.pst-web-stage.is-dragging { cursor: grabbing; }

/* ------------------------------------------------------------------
   Parallax backdrop
   Three layers that trail the pan at different rates (js sets the
   transforms) plus a slow-drifting glow.
   ------------------------------------------------------------------ */
.pst-web-parallax {
	position: absolute;
	inset: -12%;
	pointer-events: none;
}
.pst-web-layer {
	position: absolute;
	inset: 0;
	will-change: transform;
	transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.3, 1);
}

.pst-web-layer-grid {
	background-image:
		linear-gradient(to right, rgba(255, 255, 255, 0.06) 1px, transparent 1px),
		linear-gradient(to bottom, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
	background-image:
		linear-gradient(to right, color-mix(in srgb, var(--web-accent) 9%, transparent) 1px, transparent 1px),
		linear-gradient(to bottom, color-mix(in srgb, var(--web-accent) 9%, transparent) 1px, transparent 1px);
	background-size: 72px 72px;
	mask-image: radial-gradient(70% 60% at 50% 50%, #000 20%, transparent 100%);
	-webkit-mask-image: radial-gradient(70% 60% at 50% 50%, #000 20%, transparent 100%);
	opacity: 0.55;
}

/* Star fields: two densities so depth reads on movement. */
.pst-web-layer-far,
.pst-web-layer-near {
	background-repeat: repeat;
}
.pst-web-layer-far {
	background-image:
		radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,0.5) 50%, transparent 51%),
		radial-gradient(1px 1px at 70% 15%, rgba(255,255,255,0.35) 50%, transparent 51%),
		radial-gradient(1px 1px at 45% 78%, rgba(255,255,255,0.4) 50%, transparent 51%),
		radial-gradient(1px 1px at 88% 62%, rgba(255,255,255,0.3) 50%, transparent 51%);
	background-size: 340px 340px;
	opacity: 0.5;
}
.pst-web-layer-near {
	background-image:
		radial-gradient(2px 2px at 15% 55%, rgba(255,255,255,0.55) 45%, transparent 46%),
		radial-gradient(2px 2px at 82% 25%, rgba(255,255,255,0.5) 45%, transparent 46%),
		radial-gradient(1.5px 1.5px at 55% 88%, rgba(255,255,255,0.45) 45%, transparent 46%);
	background-image:
		radial-gradient(2px 2px at 15% 55%, rgba(255,255,255,0.55) 45%, transparent 46%),
		radial-gradient(2px 2px at 82% 25%, color-mix(in srgb, var(--web-accent) 70%, white) 45%, transparent 46%),
		radial-gradient(1.5px 1.5px at 55% 88%, rgba(255,255,255,0.45) 45%, transparent 46%);
	background-size: 520px 520px;
	opacity: 0.6;
}

.pst-web-glow {
	position: absolute;
	left: 50%;
	top: 50%;
	width: 78%;
	height: 78%;
	transform: translate3d(0, 0, 0);
	margin: -39% 0 0 -39%;
	background: radial-gradient(circle, rgba(255, 255, 255, 0.07) 0%, transparent 68%);
	background: radial-gradient(
		circle,
		color-mix(in srgb, var(--web-accent) 14%, transparent) 0%,
		transparent 68%
	);
	filter: blur(28px);
	will-change: transform;
	transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.3, 1);
}

/* ------------------------------------------------------------------
   SVG
   ------------------------------------------------------------------ */
.pst-web-svg {
	position: relative;
	display: block;
	width: 100%;
	height: 100%;
	overflow: visible;
}
.pst-web-viewport.is-animating {
	transition: transform 0.45s cubic-bezier(0.22, 0.8, 0.3, 1);
}

/* ---------- links ---------- */
.pst-web-link {
	stroke-linecap: round;
	fill: none;
}
.pst-web-link-spoke {
	stroke-width: 1.5;
	opacity: 0.28;
	stroke-dasharray: 5 9;
}
.pst-web-link-branch {
	stroke-width: 2.5;
	opacity: 0.5;
}

/* A slow signal travelling outward along the roots. */
@media (prefers-reduced-motion: no-preference) {
	.pst-web-link-spoke {
		animation: pst-web-flow 6s linear infinite;
	}
}
@keyframes pst-web-flow {
	to { stroke-dashoffset: -56; }
}

/* ---------- nodes ---------- */
.pst-web-node text {
	font-family: var(--font-body, sans-serif);
	text-anchor: middle;
	pointer-events: none;
	paint-order: stroke;
	stroke: rgba(5, 5, 8, 0.85);
	stroke-width: 3px;
	stroke-linejoin: round;
}

.pst-web-node {
	animation: pst-web-pop 0.5s cubic-bezier(0.2, 1.2, 0.4, 1) both;
	animation-delay: var(--delay, 0ms);
}
@keyframes pst-web-pop {
	from { opacity: 0; }
	to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
	.pst-web-node { animation: none; }
}

/* Core */
.pst-web-core-disc {
	fill: #0E0E13;
	stroke: var(--web-accent);
	stroke-width: 1.5;
}
.pst-web-core-halo {
	fill: none;
	stroke: var(--web-accent);
	stroke-width: 1;
	opacity: 0.3;
	stroke-dasharray: 3 6;
}
.pst-web-core-label {
	fill: var(--web-accent);
	font-family: var(--font-mono, monospace);
	font-size: 11px;
	letter-spacing: 0.14em;
}

/* Category hubs */
.pst-web-node-hub { cursor: default; }
.pst-web-hub-halo {
	opacity: 0.16;
	filter: blur(6px);
}
.pst-web-hub-disc {
	stroke: rgba(255, 255, 255, 0.28);
	stroke-width: 1.5;
}
.pst-web-hub-label {
	fill: var(--web-ink);
	font-size: 19px;
	font-weight: 700;
	letter-spacing: 0.02em;
}
.pst-web-hub-count {
	/* Fallback only: js/episode-web.js sets dark or light per hub colour,
	   whichever contrasts more, since admins can choose any colour. */
	fill: #050508;
	stroke: none;
	font-family: var(--font-mono, monospace);
	font-size: 22px;
	font-weight: 700;
}

/* Episode bubbles */
.pst-web-node-episode {
	cursor: pointer;
	outline: none;
}
.pst-web-ep-disc {
	fill: #0C0C11;
	stroke-width: 3;
	transition: stroke-width 0.18s ease, fill 0.18s ease;
}
.pst-web-ep-inner {
	fill: none;
	stroke-width: 1;
	opacity: 0.45;
}
.pst-web-ep-halo {
	opacity: 0;
	filter: blur(9px);
	transition: opacity 0.22s ease;
	pointer-events: none;
}
.pst-web-ep-label {
	fill: var(--web-ink-muted);
	/* Set from JS so titles stay the same size on screen at any zoom. */
	font-size: var(--ep-label-size, 13px);
	opacity: 0;
}
/* Only fade labels once the web is up. Transitioning on the first paint made
   every load fade its titles in from nothing. */
.pst-web.is-ready .pst-web-ep-label { transition: opacity 0.2s ease; }
/* Labels appear per node, once that bubble has clear space on screen (see
   updateLabels in episode-web.js) — a single zoom threshold either hid a
   sparse web's titles or let a crowded one overlap into mush. */
.pst-web-node-episode.is-labelled .pst-web-ep-label { opacity: 1; }

.pst-web-node-episode.is-hover .pst-web-ep-disc,
.pst-web-node-episode:focus-visible .pst-web-ep-disc {
	stroke-width: 5;
	fill: #16161F;
}
.pst-web-node-episode.is-linked .pst-web-ep-halo { opacity: 0.55; }
.pst-web-node-episode.is-linked .pst-web-ep-label { opacity: 1; }
.pst-web-node-episode:focus-visible .pst-web-ep-halo { opacity: 0.55; }

/* ------------------------------------------------------------------
   Tooltip
   ------------------------------------------------------------------ */
.pst-web-tip {
	position: absolute;
	top: 0;
	left: 0;
	z-index: 4;
	width: 300px;
	padding: 14px 16px;
	border: 1px solid var(--color-border, #26262C);
	border-radius: 10px;
	background: rgba(12, 12, 17, 0.985);
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
	box-shadow: 0 18px 48px rgba(0, 0, 0, 0.6);
	pointer-events: none;
	opacity: 0;
	transition: opacity 0.15s ease;
}
.pst-web-tip.is-visible { opacity: 1; }

.pst-web-tip-cats {
	display: flex;
	gap: 5px;
	margin-bottom: 9px;
}
.pst-web-tip-chip {
	width: 22px;
	height: 3px;
	border-radius: 2px;
	background: var(--chip);
}
.pst-web-tip-title {
	font-size: 15px;
	font-weight: 700;
	line-height: 1.3;
	color: var(--web-ink);
}
.pst-web-tip-meta {
	margin-top: 4px;
	font-family: var(--font-mono, monospace);
	font-size: 11px;
	color: var(--web-accent);
}
.pst-web-tip-meta:empty { display: none; }
.pst-web-tip-desc {
	margin-top: 8px;
	font-size: 12.5px;
	line-height: 1.5;
	color: var(--web-ink-muted);
}
.pst-web-tip-desc:empty { display: none; }

/* ------------------------------------------------------------------
   Legend + controls
   ------------------------------------------------------------------ */
.pst-web-legend {
	position: absolute;
	top: 16px;
	left: 16px;
	z-index: 3;
	display: flex;
	flex-direction: column;
	gap: 4px;
	max-height: calc(100% - 120px);
	overflow-y: auto;
}
.pst-web-legend-item {
	display: flex;
	align-items: center;
	gap: 9px;
	padding: 6px 11px 6px 8px;
	border: 1px solid transparent;
	border-radius: 6px;
	background: rgba(12, 12, 17, 0.6);
	color: var(--web-ink-muted);
	font-family: inherit;
	font-size: 12px;
	text-align: left;
	transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}
.pst-web-legend-item:hover {
	color: var(--web-ink);
	border-color: var(--color-border, #26262C);
	background: rgba(22, 22, 30, 0.9);
}
.pst-web-swatch {
	width: 10px;
	height: 10px;
	flex: none;
	border-radius: 50%;
	background: var(--swatch);
	box-shadow: 0 0 10px var(--swatch);
}
.pst-web-legend-name { font-weight: 600; }
.pst-web-legend-count {
	margin-left: auto;
	padding-left: 8px;
	font-family: var(--font-mono, monospace);
	font-size: 10px;
	color: var(--color-text-muted, #9A9AA2);
}

/* Selected / muted legend entries while a category filter is on. */
.pst-web-legend-item.is-active {
	color: var(--web-ink);
	border-color: var(--swatch-border, var(--color-border, #26262C));
	background: rgba(22, 22, 30, 0.95);
}
.pst-web-legend-item.is-active .pst-web-swatch {
	box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.14), 0 0 12px var(--swatch);
}
.pst-web-legend-item.is-muted { opacity: 0.42; }
.pst-web-legend-item.is-muted:hover { opacity: 1; }

/* "All categories" — only present while a filter is applied. Sits just
   under the zoom controls, which hold the top-right corner. */
.pst-web-clear {
	position: absolute;
	top: 60px;
	right: 16px;
	z-index: 4;
	padding: 7px 13px;
	border: 1px solid var(--web-accent);
	border-radius: 6px;
	background: rgba(12, 12, 17, 0.9);
	color: var(--web-accent);
	font-family: var(--font-mono, monospace);
	font-size: 11.5px;
	letter-spacing: 0.03em;
	transition: background 0.18s ease, color 0.18s ease;
}
.pst-web-clear:hover { background: var(--web-accent); color: #17130A; }
.pst-web-clear[hidden] { display: none; }

.pst-web-controls {
	position: absolute;
	top: 16px;
	right: 16px;
	z-index: 3;
	display: flex;
	gap: 6px;
}
.pst-web-btn {
	min-width: 34px;
	height: 34px;
	padding: 0 10px;
	border: 1px solid var(--color-border, #26262C);
	border-radius: 6px;
	background: rgba(12, 12, 17, 0.85);
	color: var(--web-ink-muted);
	font-family: var(--font-mono, monospace);
	font-size: 15px;
	line-height: 1;
	transition: color 0.18s ease, border-color 0.18s ease;
}
.pst-web-btn:hover {
	color: var(--web-accent);
	border-color: var(--web-accent);
}
.pst-web-btn-wide { font-size: 12px; }
.pst-web-btn svg { width: 15px; height: 15px; display: block; }

/* ------------------------------------------------------------------
   Full screen
   The stage normally has a fixed height from the shortcode; in full
   screen it takes the whole viewport instead.
   ------------------------------------------------------------------ */
.pst-web:fullscreen,
.pst-web.is-fullscreen {
	margin: 0;
	padding: 0;
	width: 100%;
	height: 100%;
	background: var(--web-deep);
	display: flex;
	flex-direction: column;
}
.pst-web:fullscreen .pst-web-heading,
.pst-web.is-fullscreen .pst-web-heading {
	padding: 18px 24px 0;
	margin: 0;
}
.pst-web:fullscreen .pst-web-stage,
.pst-web.is-fullscreen .pst-web-stage {
	height: auto;
	flex: 1 1 auto;
	border-radius: 0;
	border-left: none;
	border-right: none;
}
.pst-web:fullscreen .pst-web-stage { border: none; }

.pst-web-hint {
	position: absolute;
	left: 16px;
	bottom: 16px;
	z-index: 3;
	margin: 0;
	font-family: var(--font-mono, monospace);
	font-size: 10.5px;
	letter-spacing: 0.04em;
	color: var(--color-text-faint, #84848C);
	pointer-events: none;
}

.pst-web-empty {
	padding: 20px;
	border: 1px dashed var(--color-border, #26262C);
	border-radius: 8px;
	color: var(--color-text-muted, #9A9AA2);
}

/* ------------------------------------------------------------------
   Small screens
   ------------------------------------------------------------------ */
@media (max-width: 782px) {
	.pst-web-stage { height: min(var(--web-height), 62vh); }
	.pst-web-tip { width: min(260px, 70vw); }
	/* The legend runs the full width here, so it starts below the controls
	   instead of sliding under them, and "All categories" moves to the
	   bottom corner the hidden hint leaves free. */
	.pst-web-legend {
		top: 60px;
		flex-direction: row;
		flex-wrap: wrap;
		max-width: calc(100% - 32px);
		max-height: none;
	}
	.pst-web-clear {
		top: auto;
		bottom: 16px;
	}
	.pst-web-hint { display: none; }
}
