/* ==========================================================================
   Epileptic.NEWS — shell: reset, header, ticker, logo, footer.
   Screen-specific cascade lives alongside each template.
   ========================================================================== */

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

html { -webkit-text-size-adjust: 100%; }

body {
	margin: 0;
	padding: 0;
	min-height: 100vh;
	overflow-x: clip;
	background: var(--page-wash);
	background-attachment: fixed;
	color: var(--fg);
	font-family: var(--font-sans);
	font-size: 15px;
	line-height: 1.5;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4 {
	font-stretch: var(--stretch-head);
	text-wrap: pretty;
	margin: 0;
}

p { margin: 0; text-wrap: pretty; }

a {
	color: var(--ui-accent-soft);
	text-decoration: none;
	transition: color 160ms ease;
}

a:hover { color: var(--ui-accent-bright); }

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

button { font: inherit; color: inherit; }

::selection { background: rgba(47, 125, 255, 0.35); color: #fff; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb {
	background: #1B1F28;
	border: 2px solid var(--bg);
	border-radius: var(--r-chip);
}
::-webkit-scrollbar-thumb:hover { background: #2A3040; }

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

.epi-visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
}

.epi-skip-link {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 100;
	padding: 10px 16px;
	background: var(--ui-accent);
	color: #fff;
	font-family: var(--font-mono);
	font-size: 12px;
}

.epi-skip-link:focus { left: 8px; top: 8px; color: #fff; }

/* --- shared primitives ---------------------------------------------------- */

.epi-shell {
	max-width: var(--shell);
	margin: 0 auto;
	padding-left: var(--gutter);
	padding-right: var(--gutter);
}

.epi-mono {
	font-family: var(--font-mono);
	font-variant-numeric: tabular-nums;
}

.epi-label {
	font-family: var(--font-mono);
	font-size: 10px;
	letter-spacing: 1.8px;
	text-transform: uppercase;
	color: var(--fg-dim);
}

.epi-dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	flex-shrink: 0;
	animation: epi-dot 1.6s ease-in-out infinite;
}

/* The only two hooks that may carry a logo-tier colour. */
.epi-flash {
	animation: epi-news-flash var(--flash-cycle) steps(1, end) infinite;
}

.epi-flash--bg {
	animation: epi-news-flash-bg var(--flash-cycle) steps(1, end) infinite;
}

/* ==========================================================================
   Header — 66px bar over a 34px ticker strip, sticky as one 100px chrome.
   ========================================================================== */

.epi-header {
	position: sticky;
	top: 0;
	z-index: 40;
	backdrop-filter: blur(18px) saturate(150%);
	-webkit-backdrop-filter: blur(18px) saturate(150%);
	background: rgba(8, 9, 12, 0.82);
	border-bottom: 1px solid var(--line);
}

.epi-header__bar {
	max-width: var(--shell);
	margin: 0 auto;
	padding: 0 var(--gutter);
	height: var(--header-h);
	display: flex;
	align-items: center;
	gap: 28px;
}

.epi-header__spacer { flex: 1; }

/* ==========================================================================
   The mobile menu.

   `.epi-nav` is a horizontal bar and stops fitting below 990px. What replaced
   it for a while was nothing at all — thirteen screens and no way off the one
   you were on. This is the way through: a button in the chrome and a sheet.
   ========================================================================== */

.epi-burger {
	display: none;
	flex-direction: column;
	justify-content: center;
	gap: 4px;
	width: 40px;
	height: 40px;
	flex-shrink: 0;
	padding: 0 9px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r-ctrl);
	background: var(--bg-card);
	cursor: pointer;
}

.epi-burger span {
	display: block;
	height: 1.5px;
	background: var(--fg-soft);
	border-radius: 1px;
	transition: transform 180ms var(--ease-lift), opacity 140ms ease;
}

.epi-burger[aria-expanded='true'] span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.epi-burger[aria-expanded='true'] span:nth-child(2) { opacity: 0; }
.epi-burger[aria-expanded='true'] span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

.epi-menu {
	position: fixed;
	inset: 0;
	z-index: 90;
}

/* `hidden` on the container is the closed state, so the sheet is out of the
   tab order without any script having to manage focus on every link. */
.epi-menu[hidden] { display: none; }

.epi-menu__backdrop {
	position: absolute;
	inset: 0;
	background: rgba(4, 5, 7, 0.72);
	backdrop-filter: blur(2px);
}

.epi-menu__sheet {
	position: absolute;
	inset: 0 0 0 auto;
	width: min(340px, 86vw);
	display: flex;
	flex-direction: column;
	gap: 4px;
	padding: 18px 16px calc(18px + env(safe-area-inset-bottom));
	background: var(--bg-soft);
	border-left: 1px solid var(--line-strong);
	overflow-y: auto;
	animation: epi-menu-in 220ms var(--ease-lift);
}

@keyframes epi-menu-in {
	from { transform: translateX(18px); opacity: 0; }
	to   { transform: none; opacity: 1; }
}

.epi-menu__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	margin-bottom: 10px;
}

.epi-menu__x {
	width: 38px;
	height: 38px;
	border: 1px solid var(--line-strong);
	border-radius: var(--r-ctrl);
	background: var(--bg-card);
	color: var(--fg-soft);
	font-size: 22px;
	line-height: 1;
	cursor: pointer;
}

.epi-menu__links,
.epi-menu__foot {
	display: flex;
	flex-direction: column;
}

.epi-menu__foot {
	margin-top: 14px;
	padding-top: 14px;
	border-top: 1px solid var(--line);
}

.epi-menu__link {
	display: flex;
	align-items: center;
	/* 48px, which is a comfortable thumb target — the menu is the one place on
	   the site where every row is meant to be tapped. */
	min-height: 48px;
	padding: 0 12px;
	border-radius: var(--r-ctrl);
	font-family: var(--font-mono);
	font-size: 12px;
	letter-spacing: 1.6px;
	text-transform: uppercase;
	color: var(--fg-body);
}

.epi-menu__link:hover,
.epi-menu__link:focus-visible { background: rgba(255, 255, 255, 0.04); color: var(--fg); }

.epi-menu__link[aria-current='page'] {
	background: rgba(47, 125, 255, 0.12);
	color: var(--ui-accent-bright);
}

.epi-menu__gauge {
	display: flex;
	align-items: baseline;
	gap: 8px;
	margin-top: 16px;
	padding: 14px 12px;
	border: 1px solid var(--line);
	border-radius: var(--r-ctrl);
	background: var(--bg-card);
}

@media (prefers-reduced-motion: reduce) {
	.epi-menu__sheet { animation: none; }
	.epi-burger span { transition: none; }
}

/* --- the read counter's eye ------------------------------------------------
 * Drawn, not typed. An emoji would depend on the reader's system font and would
 * be rewritten into a remote <img> by WordPress; an icon font would be a whole
 * request for one glyph. Two elements and a border do it.
 *
 * Lives in main.css because the article page, the Feed cards and the stream
 * rows all use it, and none of them may depend on another screen's sheet.
 */

.epi-eye {
	display: inline-block;
	width: 13px;
	height: 9px;
	border: 1.4px solid currentColor;
	border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
	position: relative;
	opacity: 0.85;
	vertical-align: -1px;
}

.epi-eye::after {
	content: '';
	position: absolute;
	inset: 50% auto auto 50%;
	width: 3.4px;
	height: 3.4px;
	margin: -1.7px 0 0 -1.7px;
	border-radius: 50%;
	background: currentColor;
}

.epi-card__foot-right {
	display: inline-flex;
	align-items: center;
	gap: 10px;
}

.epi-card__views,
.epi-row__views {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	color: var(--fg-mute);
}

/* --- logo: two cubes rotating on Y, wordmark with .NEWS on the brand cycle -- */

.epi-logo {
	display: flex;
	align-items: center;
	gap: 12px;
	flex-shrink: 0;
	color: var(--fg);
}

.epi-logo:hover { color: var(--fg); }

/*
 * The 🟥🟩 mark: two real cubes, six faces each.
 *
 * Sized to the wordmark rather than to a round number — the text block beside
 * it measures 30.8px (17px word + 1px gap + 12.8px sub-label), so the cubes are
 * 30px and the two read as one object at the same height.
 *
 * No box, no border, no plate behind them. A rotating solid does not need a
 * frame, and the frame was clipping the illusion at the corners of the spin.
 * Depth comes from the faces and a contact shadow instead.
 *
 * Transforms do not affect layout, so the flex row reserves the untransformed
 * 30px square while the cube sweeps wider than that mid-rotation. Nothing
 * reflows.
 */
.epi-logo__cubes {
	/*
	 * 32, not 30. The wordmark block beside this is 30.8px tall, and the
	 * rotateX(-16deg) tilt foreshortens the cube to cos(16deg) of its edge —
	 * 0.961. So 32 x 0.961 = 30.8px on screen, and the two line up exactly.
	 * Sizing it to 30 leaves the cube visibly 2px short of the text.
	 */
	--cube: 32px;

	display: flex;
	align-items: center;
	gap: 10px;
	/* Low enough to foreshorten hard, high enough not to distort the near face. */
	perspective: 320px;
	perspective-origin: 50% 40%;
}

.epi-logo__cube {
	position: relative;
	width: var(--cube);
	height: var(--cube);
	transform-style: preserve-3d;
	/* Slower than the predecessor's 2.4s: the cube is two-thirds larger, and a
	   big object spinning at a small object's rate reads as frantic. */
	animation: epi-cube-spin 3.2s linear infinite;
	will-change: transform;
}

/*
 * Both cubes turn the SAME way. They used to counter-rotate, which kept the
 * silhouette varied but read as two unrelated objects; turning together they
 * read as one mark with two parts. The delay is kept — it offsets them in the
 * cycle so they are never showing the identical face, which is what the
 * counter-rotation was really buying.
 */
.epi-logo__cube--green {
	animation-delay: -1.1s;
}

.epi-logo__face {
	position: absolute;
	inset: 0;
	border-radius: 4px;
	/*
	 * Three layers, and each one is doing a job:
	 *   1. a tight specular streak across the upper-left — the glint
	 *   2. a broad sheen that keeps the lit half from going flat
	 *   3. the shading ramp that makes it read as a lit solid
	 * `background-color` under all of it supplies the hue.
	 */
	background-image:
		linear-gradient(128deg, rgba(255, 255, 255, 0.85) 0%, rgba(255, 255, 255, 0) 26%),
		linear-gradient(150deg, rgba(255, 255, 255, 0.30) 0%, rgba(255, 255, 255, 0.04) 46%),
		linear-gradient(150deg, rgba(255, 255, 255, 0.16), rgba(0, 0, 0, 0.34));
	/* The rim is what stops a saturated face from bleeding into its neighbour,
	   and the inner glow is the light the gloss implies. */
	box-shadow:
		inset 0 0 0 1px rgba(255, 255, 255, 0.26),
		inset 0 -6px 12px -6px rgba(0, 0, 0, 0.55),
		inset 0 6px 14px -6px rgba(255, 255, 255, 0.42);
	backface-visibility: hidden;
}

/*
 * 🔴 No `filter` anywhere on the cube or its wrapper. A filter creates a
 * containing block and FLATTENS `preserve-3d`, which collapses six faces into a
 * stack of squares. The per-face brightness below is the one exception that
 * works, because it sits on a leaf with nothing 3D under it.
 */
.epi-logo__cube--red   .epi-logo__face { background-color: var(--cube-red); }
.epi-logo__cube--green .epi-logo__face { background-color: var(--cube-green); }

/* The lit faces get the brighter tint outright rather than relying on
   brightness(), which washes towards white instead of towards the hue. */
.epi-logo__cube--red   .epi-logo__face--top { background-color: var(--cube-red-lit); }
.epi-logo__cube--green .epi-logo__face--top { background-color: var(--cube-green-lit); }

/*
 * Half the cube each way. Kept as a calc off --cube so one number resizes the
 * whole thing — a hard-coded translateZ is how these come apart.
 */
/* The shading range is deliberately shallower than it was (0.42–1.24 became
   0.72–1.14). The old floor is what made the mark look washed out: for most of
   every turn the face you are looking at was one of the dark ones. */
.epi-logo__face--front  { transform: translateZ(calc(var(--cube) / 2)); filter: brightness(1.06) saturate(1.18); }
.epi-logo__face--back   { transform: rotateY(180deg) translateZ(calc(var(--cube) / 2)); filter: brightness(0.78) saturate(1.3); }
.epi-logo__face--right  { transform: rotateY(90deg)  translateZ(calc(var(--cube) / 2)); filter: brightness(0.95) saturate(1.24); }
.epi-logo__face--left   { transform: rotateY(-90deg) translateZ(calc(var(--cube) / 2)); filter: brightness(0.86) saturate(1.28); }
.epi-logo__face--top    { transform: rotateX(90deg)  translateZ(calc(var(--cube) / 2)); filter: brightness(1.14) saturate(1.1); }
.epi-logo__face--bottom { transform: rotateX(-90deg) translateZ(calc(var(--cube) / 2)); filter: brightness(0.72) saturate(1.32); }

/* The tilt is what shows the top face; without it this is a spinning card. */
@keyframes epi-cube-spin {
	from { transform: rotateX(-16deg) rotateY(0deg); }
	to   { transform: rotateX(-16deg) rotateY(360deg); }
}

@media (prefers-reduced-motion: reduce) {
	/* Frozen on a three-quarter view, which is the pose that still reads as a
	   cube. Stopping at 0deg would leave two flat squares. */
	.epi-logo__cube {
		animation: none !important;
		transform: rotateX(-16deg) rotateY(-32deg);
	}
}

.epi-logo__text {
	display: flex;
	flex-direction: column;
	gap: 1px;
}

/* Small screens drop the sub-label, so the wordmark alone is 17px and a 30px
   cube beside it looks top-heavy. One number brings the pair back into
   proportion. */
@media (max-width: 420px) {
	/* The sub-label is gone here, so the wordmark alone is 17px. Same tilt, same
	   correction: 18 x cos(16deg) = 17.3px. */
	.epi-logo__cubes { --cube: 18px; gap: 7px; }
}

.epi-logo__word {
	font-stretch: var(--stretch-head);
	font-size: 17px;
	font-weight: 800;
	letter-spacing: -0.4px;
	line-height: 1;
}

.epi-logo__sub {
	font-family: var(--font-mono);
	font-size: 8.5px;
	letter-spacing: 1.6px;
	color: var(--fg-faint);
	text-transform: uppercase;
}

/* --- nav ------------------------------------------------------------------- */

.epi-nav {
	display: flex;
	align-items: center;
	gap: 2px;
	margin-left: 6px;
	min-width: 0;
	overflow: hidden;
}

.epi-nav__link {
	font-family: var(--font-mono);
	font-size: 11px;
	font-weight: 500;
	letter-spacing: 1.1px;
	text-transform: uppercase;
	padding: 9px 13px;
	border-radius: var(--r-chip);
	color: var(--fg-label);
	transition: color 160ms ease, background 160ms ease;
	white-space: nowrap;
}

/* ==========================================================================
   Two nav entries that are not like the others.

   🔴 Both are decoration on a label that must stay readable. The rule the whole
   project runs on is that a flash is the LOGO tier and honours
   `prefers-reduced-motion`; these do too, and both fall back to a plain,
   legible, high-contrast link when motion is off. A navigation item nobody can
   read is not a navigation item.
   ========================================================================== */

/*
 * Meme-Battle: the LETTERS flash, not a wash behind them.
 *
 * This runs `epi-news-flash` — the same keyframes, at the same 90ms cycle, as
 * the `.NEWS` in the wordmark. Reusing them rather than writing a second set is
 * the point: two definitions of the house strobe would drift, and the whole
 * brand rests on that one rate.
 *
 * No `!important` is needed even though `.epi-nav__link` and its `:hover` both
 * set `color`: an animated property outranks a normal declaration in the
 * cascade for as long as the animation runs. The hover below therefore changes
 * only the background — setting a colour there would be dead code that looks
 * like it does something.
 */
.epi-nav__link--flash,
.epi-menu__link--flash {
	font-weight: 700;
	animation: epi-news-flash var(--flash-cycle) steps(1, end) infinite;
}

.epi-nav__link--flash:hover,
.epi-menu__link--flash:hover {
	background: rgba(255, 255, 255, 0.05);
}

/*
 * Community: a red-to-green gradient that never stops and never jumps.
 *
 * 🔴 The seam is the whole problem here, and it comes from the geometry, not
 * the timing. The first version used `linear-gradient` with
 * `background-size: 260%` and animated `background-position` from 0% to 260%.
 * A percentage background-position is measured against (container − image), so
 * "260%" is not one period of anything — the pattern arrived at the end of the
 * cycle in a different phase from where it started and snapped back. It read as
 * a break every few seconds, which is exactly what it was.
 *
 * Two changes make it seamless:
 *
 *   1. `repeating-linear-gradient`, so the pattern is genuinely periodic and
 *      the first and last stop are the same colour.
 *   2. The animation advances by exactly ONE period, in px, not percent.
 *
 * The gradient runs at 100deg rather than 90, so a pure horizontal shift
 * travels the axis foreshortened by cos(10°) = 0.985. One 160px period
 * therefore needs 160 / cos(10°) = 162.47px of x-movement. That number is not a
 * fudge — it is why the loop closes. Rounding it to 162.4 leaves 0.07px of
 * drift per cycle, which is invisible but is also not what "seamless" means.
 */
/*
 * Community: a red-to-green wave that flows across the letters and never stops.
 *
 * ─── Why this is not `background-clip: text` ────────────────────────────────
 *
 * It was, for two rounds, and it painted nothing.
 *
 * 🔴 Measured: under this header's `backdrop-filter` ancestor, an element with
 * `background-clip: text` and `-webkit-text-fill-color: transparent` renders no
 * fill at all. The word stayed legible only because of a zero-offset
 * `text-shadow` added as a safety net — so what was on screen was one flat
 * green, reported twice as "only green, no red", and correctly. Shortening the
 * gradient period from 160px to 60px changed nothing, because the gradient was
 * never being drawn. Nor did removing the `translateZ(0)` layer hint. The proof
 * was to repaint the shadow blue and blow the word up to 64px: solid blue.
 *
 * `color` cannot fail to paint. Each letter runs the same four-colour cycle a
 * fraction of a beat behind the one before it, which is the same travelling
 * wave — through the property that always works.
 *
 * The ramp goes red → warm → green → warm → red rather than red → green
 * directly: a straight interpolation between #FF1F3D and #00E87E passes through
 * olive-grey at the midpoint, and half the animation would be spent in a colour
 * that belongs to neither brand.
 */
.epi-nav__link--gradient,
.epi-menu__link--gradient {
	font-weight: 800;
	font-stretch: 118%;
	letter-spacing: 1.4px;
}

.epi-wave {
	display: inline;
	white-space: nowrap;
}

.epi-wave i {
	font-style: normal;
	color: var(--cube-red);
	animation: epi-wave-flow 2.4s linear infinite;

	/*
	 * NEGATIVE, so every letter starts already advanced rather than waiting its
	 * turn — the wave is running across the whole word from the first frame
	 * instead of filling in from the left once.
	 */
	animation-delay: calc(var(--i) * -0.13s);
}

@keyframes epi-wave-flow {
	0%,   14% { color: var(--cube-red); }
	30%       { color: #FF8A3D; }
	46%, 60%  { color: var(--cube-green); }
	76%       { color: #FF8A3D; }
	92%, 100% { color: var(--cube-red); }
}

.epi-nav__link--gradient:hover .epi-wave i,
.epi-menu__link--gradient:hover .epi-wave i {
	/* The hover rule elsewhere sets `color`, which an animated property
	   outranks for as long as it runs. Brightening is the equivalent gesture. */
	filter: brightness(1.25) saturate(1.1);
}

@media (prefers-reduced-motion: reduce) {
	.epi-nav__link--flash::after,
	.epi-menu__link--flash::after { animation: none; opacity: 0.14; }

	/*
	 * Frozen, but still a red-to-green ramp: each letter keeps the colour it
	 * would have had, mixed by its position. A word that collapses to one flat
	 * colour under this setting loses the thing it was for.
	 */
	.epi-wave i {
		animation: none;
		color: var(--cube-green);
		color: color-mix(in oklab, var(--cube-red), var(--cube-green) calc(var(--i) * 12%));
	}
}

.epi-nav__link:hover {
	color: var(--fg);
	background: rgba(255, 255, 255, 0.05);
}

.epi-nav__link[aria-current='page'] {
	color: var(--fg);
	background: rgba(255, 255, 255, 0.05);
}

/* --- search trigger -------------------------------------------------------- */

.epi-search-trigger {
	display: flex;
	flex-shrink: 0;
	align-items: center;
	gap: 9px;
	height: 36px;
	padding: 0 12px 0 11px;
	border-radius: 9px;
	border: 1px solid var(--line-strong);
	background: var(--bg-card);
	cursor: pointer;
	transition: border-color 160ms ease;
	color: var(--fg-label);
}

.epi-search-trigger:hover { border-color: rgba(47, 125, 255, 0.55); }

.epi-search-trigger__glass {
	width: 12px;
	height: 12px;
	border: 1.5px solid var(--fg-label);
	border-radius: 50%;
	position: relative;
	flex-shrink: 0;
}

.epi-search-trigger__glass::after {
	content: '';
	position: absolute;
	width: 6px;
	height: 1.5px;
	background: var(--fg-label);
	transform: rotate(45deg);
	right: -5px;
	bottom: 0;
}

.epi-search-trigger__text {
	font-family: var(--font-mono);
	font-size: 11px;
	letter-spacing: 0.4px;
	white-space: nowrap;
}

.epi-search-trigger__key {
	font-family: var(--font-mono);
	font-size: 9.5px;
	color: var(--fg-ghost);
	border: 1px solid rgba(255, 255, 255, 0.1);
	border-radius: 4px;
	padding: 2px 5px;
}

/* ==========================================================================
   The Cryptopulse radar.

   Replaces a static label-score-bar block. Two parts: a 30px dish with a sweep
   going round it, and the formula travelling past on a tape. The dish says
   "this is live"; the tape says what the number actually is, which a bare
   `+9/100` never did.

   Sized to the 66px bar on purpose — a radar big enough to admire would take
   the space two nav labels live in, and those are measured.
   ========================================================================== */

.epi-radar {
	display: flex;
	flex-shrink: 0;
	align-items: center;
	gap: 10px;
	height: 38px;
	padding: 0 12px 0 8px;
	border-radius: var(--r-ctrl);
	border: 1px solid var(--line-strong);
	background: linear-gradient(180deg, #101319, #0B0D12);
	transition: border-color 160ms ease;
	overflow: hidden;
}

.epi-radar:hover { border-color: var(--line-hover); }

.epi-radar__dish {
	position: relative;
	width: 30px;
	height: 30px;
	flex-shrink: 0;
	border-radius: 50%;
	background: radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--radar) 22%, transparent), transparent 70%), #080A0E;
	overflow: hidden;
	/* The glow is the "live" tell and is slower than the sweep, so the two do
	   not beat against each other into something that reads as a strobe. */
	animation: epi-radar-glow 2.6s ease-in-out infinite;
}

.epi-radar__ring {
	position: absolute;
	inset: 3px;
	border: 1px solid color-mix(in srgb, var(--radar) 34%, transparent);
	border-radius: 50%;
}

.epi-radar__ring--inner { inset: 9px; opacity: 0.7; }

/*
 * The sweep is a conic wedge, not a rotating line: a 1px line at 30px is one
 * or two pixels of anti-aliased grey and reads as a smudge. A wedge with a
 * hard leading edge reads as a sweep at any size.
 */
.epi-radar__sweep {
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background: conic-gradient(
		from 0deg,
		color-mix(in srgb, var(--radar) 62%, transparent) 0deg,
		transparent 62deg,
		transparent 360deg
	);
	animation: epi-radar-sweep 2.4s linear infinite;
}

.epi-radar__blip {
	position: absolute;
	top: 8px;
	right: 8px;
	width: 3.5px;
	height: 3.5px;
	border-radius: 50%;
	background: var(--radar);
	/* Lit as the sweep passes it, once per revolution — the same 2.4s. */
	animation: epi-radar-blip 2.4s ease-out infinite;
}

@keyframes epi-radar-sweep {
	from { transform: rotate(0deg); }
	to   { transform: rotate(360deg); }
}

/*
 * 🔴 The glow's REACH is the confidence, not just its colour.
 *
 * `--radar-confidence` comes off the reading (0..1). A thin week gets a dish
 * that barely breathes; a full one pulses properly. Together with the colour
 * being mixed toward neutral in PHP, that is two independent signals both
 * saying the same thing — which is what stops a reader taking a number
 * seriously that the site itself does not.
 */
@keyframes epi-radar-glow {
	0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--radar) 0%, transparent); }
	50%      { box-shadow: 0 0 calc(4px + 9px * var(--radar-confidence, 1)) 0 color-mix(in srgb, var(--radar) 42%, transparent); }
}

/* A reading standing on almost nothing says so in the chrome, not only in a
   tooltip nobody opens. */
.epi-radar.is-thin .epi-radar__score { opacity: 0.72; }
.epi-radar.is-thin .epi-radar__ring { opacity: 0.45; }

.epi-radar__bit { font-weight: 600; }
.epi-radar__bit--dim   { font-weight: 400; color: var(--fg-ghost); }
.epi-radar__bit--bull  { color: var(--ui-bull); }
.epi-radar__bit--bear  { color: var(--ui-bear); }
.epi-radar__bit--score { color: var(--radar); }

@keyframes epi-radar-blip {
	0%   { opacity: 1; transform: scale(1.5); }
	22%  { opacity: 0.9; transform: scale(1); }
	60%, 100% { opacity: 0.12; transform: scale(1); }
}

.epi-radar__read {
	display: flex;
	align-items: center;
	gap: 9px;
	min-width: 0;
}

.epi-radar__score {
	font-family: var(--font-mono);
	font-size: 13px;
	font-weight: 600;
	letter-spacing: -0.3px;
	color: var(--radar);
	white-space: nowrap;
}

/* The tape. A fixed window with the formula scrolling through it — fixed,
   because a window that sizes to its content would make the whole header
   reflow every time a story lands and the numbers change width. */
.epi-radar__tape {
	position: relative;
	width: 132px;
	overflow: hidden;
	mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
	-webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}

.epi-radar__tapetrack {
	display: flex;
	width: max-content;
	animation: epi-marquee 14s linear infinite;
}

.epi-radar__frag {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding-right: 18px;
	font-family: var(--font-mono);
	font-size: 9.5px;
	letter-spacing: 0.9px;
	color: var(--fg-mute);
	white-space: nowrap;
}

/* The tape's pieces now carry their own `--bit` classes and the tone comes from
   the reading, so a bare element selector here would silently outrank nothing
   but would be one more place the tape's look is decided. Removed on purpose. */

@media (prefers-reduced-motion: reduce) {
	.epi-radar__sweep,
	.epi-radar__blip,
	.epi-radar__dish,
	.epi-radar__tapetrack { animation: none; }

	/* Frozen mid-sweep so the dish still reads as a radar rather than an empty
	   circle, and the tape parks on the first copy of the formula. */
	.epi-radar__sweep { transform: rotate(310deg); }
	.epi-radar__blip { opacity: 1; }
}

/* --- cryptopulse gauge (legacy, kept for the menu sheet) ------------------- */

.epi-gauge {
	display: flex;
	flex-shrink: 0;
	align-items: center;
	gap: 12px;
	height: 38px;
	padding: 0 14px;
	border-radius: var(--r-ctrl);
	border: 1px solid var(--line-strong);
	background: linear-gradient(180deg, #101319, #0B0D12);
}

.epi-gauge__label {
	font-family: var(--font-mono);
	font-size: 9.5px;
	letter-spacing: 1.5px;
	color: var(--fg-label);
	text-transform: uppercase;
}

.epi-gauge__value {
	display: flex;
	align-items: baseline;
	gap: 5px;
	font-family: var(--font-mono);
}

.epi-gauge__score {
	font-size: 15px;
	font-weight: 600;
	letter-spacing: -0.3px;
}

.epi-gauge__of {
	font-size: 9.5px;
	color: var(--fg-faint);
}

.epi-gauge__bar {
	width: 62px;
	height: 5px;
	border-radius: 3px;
	background: var(--bg-track);
	overflow: hidden;
	display: flex;
}

.epi-gauge__bear { background: var(--ui-bear); opacity: 0.55; }
.epi-gauge__bull { background: linear-gradient(90deg, #1E8A5A, #2FD98A); }

/* --- pro button ------------------------------------------------------------ */

.epi-pro {
	display: flex;
	flex-shrink: 0;
	align-items: center;
	gap: 8px;
	height: 38px;
	padding: 0 15px;
	border: 0;
	border-radius: var(--r-ctrl);
	background: linear-gradient(180deg, #2F7DFF, #1B5AD9);
	box-shadow: 0 6px 20px -8px rgba(47, 125, 255, 0.85);
	cursor: pointer;
	position: relative;
	overflow: hidden;
	font-family: var(--font-mono);
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: 1.4px;
	color: #fff;
	text-transform: uppercase;
	transition: box-shadow 160ms ease;
}

.epi-pro:hover { box-shadow: 0 8px 26px -6px rgba(47, 125, 255, 1); color: #fff; }

/* --- the signed-in slot ----------------------------------------------------
   Same footprint as the sign-in button so the header does not reflow when a
   reader signs in — 38px tall, same radius, same gap into the gauge. */

.epi-me {
	display: flex;
	flex-shrink: 0;
	align-items: center;
	gap: 9px;
	height: 38px;
	padding: 0 13px 0 8px;
	border-radius: var(--r-ctrl);
	border: 1px solid var(--line-strong);
	background: linear-gradient(180deg, #101319, #0B0D12);
	font-family: var(--font-mono);
	font-size: 10.5px;
	letter-spacing: 1.1px;
	text-transform: uppercase;
	color: var(--fg-soft);
	transition: border-color 160ms ease;
	max-width: 200px;
}

.epi-me:hover { border-color: var(--line-hover); color: var(--fg); }

.epi-me__avatar {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	border-radius: 7px;
	background: linear-gradient(150deg, var(--ui-accent), #1B5AD9);
	color: #fff;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0;
	flex-shrink: 0;
}

.epi-me.is-verified .epi-me__avatar {
	background: linear-gradient(150deg, var(--cube-green), #009C58);
}

.epi-me__name {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* An unconfirmed account can play the Daily but cannot rank, so the state is
   worth a mark in the chrome rather than only on the account screen. */
.epi-me__flag {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 15px;
	height: 15px;
	border-radius: 50%;
	background: var(--ui-bear);
	color: #fff;
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0;
	flex-shrink: 0;
}

.epi-pro::after {
	content: '';
	position: absolute;
	inset: 0;
	width: 40%;
	background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.28), transparent);
	animation: epi-sheen 4.2s ease-in-out infinite;
}

/* --- ticker strip ---------------------------------------------------------- */

.epi-ticker {
	height: var(--ticker-h);
	border-top: 1px solid var(--line-soft);
	background: var(--bg-footer);
	overflow: hidden;
	position: relative;
}

.epi-ticker__track {
	display: flex;
	width: max-content;
	height: var(--ticker-h);

	/* The duration is a fallback for the no-JS case only. chrome.js overwrites
	   it from the measured width, so the strip travels at a constant 23px/s
	   whatever the price provider returned. */
	animation: epi-marquee 68s linear infinite;
}

.epi-ticker__set {
	display: flex;
	height: var(--ticker-h);
	flex: 0 0 auto;
}

.epi-ticker__item {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 0 18px;
	border-right: 1px solid var(--line-soft);
	font-family: var(--font-mono);
	font-size: 11px;
}

.epi-ticker__pip {
	width: 5px;
	height: 5px;
	border-radius: 50%;
}

.epi-ticker__sym {
	font-weight: 600;
	color: var(--fg-soft);
	letter-spacing: 0.6px;
}

.epi-ticker__price { color: var(--fg-mute); }
.epi-ticker__chg { font-weight: 500; }

.epi-ticker__fade {
	position: absolute;
	top: 0;
	width: 90px;
	height: var(--ticker-h);
	pointer-events: none;
}

.epi-ticker__fade--l { left: 0; background: linear-gradient(90deg, var(--bg-footer), transparent); }
.epi-ticker__fade--r { right: 0; background: linear-gradient(270deg, var(--bg-footer), transparent); }

/* ==========================================================================
   Footer
   ========================================================================== */

.epi-footer {
	border-top: 1px solid var(--line);
	background: var(--bg-footer);
	padding: 30px var(--gutter) 40px;
}

.epi-footer__row {
	max-width: var(--shell);
	margin: 0 auto;
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 40px;
	flex-wrap: wrap;
}

.epi-footer__mark {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-bottom: 12px;
	flex-wrap: wrap;
}

.epi-footer__cubes { display: flex; gap: 3px; }

.epi-footer__cube {
	width: 11px;
	height: 11px;
	border-radius: 3px;
}

.epi-footer__name { font-size: 14px; font-weight: 600; }

.epi-footer__note {
	font-family: var(--font-mono);
	font-size: 10px;
	color: var(--fg-faint);
}

.epi-footer__token {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
}

.epi-footer__token-label {
	font-family: var(--font-mono);
	font-size: 9.5px;
	letter-spacing: 1.3px;
	color: var(--fg-ghost);
	text-transform: uppercase;
}

.epi-footer__ca {
	font-family: var(--font-mono);
	font-size: 10px;
	color: var(--fg-mute);
	padding: 6px 10px;
	border-radius: 7px;
	background: var(--bg-raised);
	border: 1px solid var(--line);
	word-break: break-all;
}

.epi-footer__links {
	display: flex;
	gap: 26px;
	flex-wrap: wrap;
	font-family: var(--font-mono);
	font-size: 10.5px;
	letter-spacing: 1.2px;
	text-transform: uppercase;
}

.epi-footer__links a { color: var(--fg-label); }
.epi-footer__links a:hover { color: var(--fg); }

.epi-footer__legal {
	max-width: var(--shell);
	margin: 22px auto 0;
	font-family: var(--font-mono);
	font-size: 9px;
	letter-spacing: 1.4px;
	color: var(--fg-trace);
	text-transform: uppercase;
}

/* ==========================================================================
   Mobile tab bar. The header sheds its nav below 990px; this takes over below
   720px, where a five-item bar is the only navigation that still fits.
   ========================================================================== */

.epi-tabbar { display: none; }

@media (max-width: 720px) {
	.epi-tabbar {
		position: fixed;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 45;
		display: grid;
		grid-template-columns: repeat(5, 1fr);
		background: rgba(8, 9, 12, 0.94);
		backdrop-filter: blur(14px);
		-webkit-backdrop-filter: blur(14px);
		border-top: 1px solid var(--line-strong);
		padding-bottom: env(safe-area-inset-bottom);
	}

	.epi-tabbar__item {
		/* 44px is the floor the design sets for every touch target. */
		min-height: 52px;
		display: flex;
		align-items: center;
		justify-content: center;
		font-family: var(--font-mono);
		font-size: 10px;
		letter-spacing: 1.3px;
		text-transform: uppercase;
		color: var(--fg-ghost);
	}

	.epi-tabbar__item[aria-current] {
		color: var(--fg);
		box-shadow: inset 0 2px 0 0 var(--ui-accent);
	}

	/* Clear the bar so the last row of content is never trapped under it. */
	.epi-footer { padding-bottom: 84px; }
}

/* ==========================================================================
   Narrow chrome. The design is drawn at a 1320px minimum; below that the
   header sheds the pieces that cannot survive the width, in priority order.
   ========================================================================== */

/* ==========================================================================
   The header bar's width budget.

   Measured, not guessed. At full size the pieces want: logo 229 · nav 576 ·
   search 181 · gauge 240 · account 139, plus five gaps and the gutter. That is
   more than the 1560px shell the bar sits in, so it sheds pieces on the way
   down — in order of how replaceable each one is elsewhere on the site.

   🔴 The failure mode this is guarding against is silent. An over-full flex row
   does not overflow the page; it just squeezes `.epi-nav` and clips the last
   labels. Nothing scrolls sideways, nothing warns, and Meme and Battle are
   simply not in the header any more. Check `nav.scrollWidth > nav.clientWidth`,
   not the page width.
   ========================================================================== */

/*
 * 🔴 Unconditional, NOT `@media (max-width: 1560px)`.
 *
 * The bar is capped at the shell, so above 1560 it stops growing with the
 * viewport. A max-width rule switches off there while the bar it was fixing is
 * exactly the same size — which clipped the nav on the widest monitors only,
 * the last place anybody would look for a responsive bug.
 *
 * The score is the part of the gauge that carries information; the word and the
 * split bar are decoration around it, and both are on the Pulse screen in full.
 */
.epi-header__bar { gap: 22px; }
.epi-gauge { gap: 7px; padding: 0 11px; }
.epi-gauge__label,
.epi-gauge__bar { display: none; }

/* The gauge goes entirely. Pulse carries it, and so does the menu sheet. */
/*
 * The radar sheds its tape first and then goes entirely. The Pulse screen
 * carries the same number in full, and the menu sheet keeps the plain gauge —
 * so nothing is lost at either step, only repeated less.
 */
@media (max-width: 1480px) {
	.epi-radar__tape { display: none; }
	.epi-gauge { display: none; }
}

@media (max-width: 1300px) {
	.epi-radar { display: none; }
}

/*
 * 🔴 The search is an icon, unconditionally — not from 1320px down.
 *
 * The budget was re-measured after the radar replaced the gauge and the labels
 * grew ("Meme-Battle", "Community"): the bar needs 1548px inside a 1490px
 * shell. Something has to give, and the ranking is by what each piece is worth:
 * nav labels are navigation, the radar is a live reading somebody asked for,
 * and the word "Search stories" beside a magnifying glass is neither.
 *
 * The ⌘K hint goes with it — a keyboard hint nobody can see is not a hint.
 */
.epi-search-trigger__text,
.epi-search-trigger__key { display: none; }

.epi-search-trigger { padding: 0 11px; }

/*
 * The nav hands over to the menu. 1180 rather than 1140: measured, the nav
 * begins clipping at about 1160, and a breakpoint set below where the clipping
 * starts leaves a narrow band where the bar is shown and wrong.
 */
/*
 * 🔴 1360, not 1300, since CARDS joined the bar (2026-09-22). Measured before
 * the change at 1301px: nav 656/656 and a 29px spacer — and "Cards" costs 67px.
 * Nothing overflowed; the last label would simply have been clipped. The
 * burger takes over 60px earlier instead, which leaves the same ~20px of slack
 * at the new edge that 1301 used to have. Re-measure `nav.scrollWidth >
 * nav.clientWidth` at 1361 before adding anything else to the bar.
 */
@media (max-width: 1360px) {
	.epi-nav { display: none; }
	.epi-burger { display: flex; }
}

@media (max-width: 720px) {
	:root { --gutter: 16px; }

	.epi-header__bar { gap: 10px; }

	/*
	 * 🔴 The logo has to be allowed to give ground.
	 *
	 * It is 198px and `flex-shrink: 0`, which was fine while the bar held a
	 * logo, a search icon and one button. Adding the menu button pushed the row
	 * to 424px inside a 390px screen — every route overflowed by 18px at once,
	 * from one element added to the chrome. The strapline goes and the wordmark
	 * is allowed to compress; the mark itself never shrinks.
	 */
	.epi-logo { min-width: 0; flex-shrink: 1; }
	.epi-logo__cubes { flex-shrink: 0; }
	.epi-logo__text { min-width: 0; }
	.epi-logo__sub { display: none; }
	.epi-logo__word { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

	/*
	 * 🔴 Not `display: none`. This slot used to hold a "Go Pro" button that did
	 * nothing, so hiding it on a phone cost nothing. It is the sign-in door now
	 * and a door that is missing on mobile is a door that does not exist for
	 * most readers. It shrinks instead.
	 */
	.epi-pro { padding: 0 11px; font-size: 9.5px; letter-spacing: 1px; }

	.epi-me { padding: 0 6px; max-width: none; }
	.epi-me__name { display: none; }

	.epi-footer__row { gap: 24px; }
}

@media (max-width: 420px) {
	.epi-logo__sub { display: none; }
}
