/* fans.gabewoods.com — after-dark register of the république system.
   Same tokens as the main theme, inverted. Monochrome is locked:
   photography supplies all colour, so with no photo the page is pure
   black and white and still correct. */

:root {
	font-size: 16px;

	/* Inverted from the main theme. Every colour resolves back to these. */
	--color-text: #f4f4f4;
	--color-link: #f4f4f4;
	--color-bg: #0a0a0a;
	--color-gray-500: #7a7a7a;
	--color-hover: #7a7a7a;
	--color-rule: #2a2a2a;
	/* --color-rule is a HAIRLINE for dividers, and at 1.38:1 on this
	   background it is correctly near-invisible for that job. It must never
	   be used on an interactive boundary: WCAG 1.4.11 requires 3:1 for the
	   visible edge of a control. #5d5d5d measures 3.01:1. */
	--color-rule-interactive: #5d5d5d;
	--color-invert-text: #0a0a0a;
	--color-invert-bg: #f4f4f4;

	/* Two spacing tokens only. */
	--sm-base-space: 1rem;
	--md-base-space: 1.5rem;

	--font-mono: "IBM Plex Mono", ui-monospace, "Courier New", monospace;
	--fs-main: 12px;
	--lh-main: 1.22em;
	--tracking-ui: 0;

	--ease: cubic-bezier(0.7, 0, 0.3, 1);
	--dur-fast: 0.2s;
	--dur-base: 0.3s;
}

@font-face {
	font-family: "IBM Plex Mono";
	src: url("fonts/ibm.woff2") format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: "IBM Plex Mono";
	src: url("fonts/ibm-medium.woff2") format("woff2");
	font-weight: 500;
	font-style: normal;
	font-display: swap;
}

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

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

body {
	margin: 0;
	background: var(--color-bg);
	color: var(--color-text);
	font-family: var(--font-mono);
	font-size: var(--fs-main);
	line-height: var(--lh-main);
	letter-spacing: var(--tracking-ui);
	font-weight: 400;
	-webkit-font-smoothing: antialiased;
}

a { color: var(--color-link); text-decoration: none; }

:focus-visible {
	outline: 2px solid var(--color-text);
	outline-offset: 3px;
}

.wrap {
	max-width: 640px;
	margin: 0 auto;
	padding: 0 var(--md-base-space);
}

/* ---------- 18+ splash ---------- */
/* Its own screen. One decision, nothing else on it. */

.splash {
	min-height: 100vh;
	min-height: 100dvh;
	display: flex;
	flex-direction: column;
	justify-content: center;
	/* Vertical only. A `padding` shorthand here would reset .wrap's side
	   padding to zero and slam the copy against the screen edge. */
	padding-top: var(--md-base-space);
	padding-bottom: var(--md-base-space);
}

.splash-mark {
	/* Identical to .hero-name. These are the same two words on consecutive
	   screens one tap apart, and they used to be 12.2vw vs 19vw: a 56% jump
	   across the gate at 375px, the audience's viewport. */
	font-size: min(19vw, 9.4rem);
	line-height: 0.9;
	letter-spacing: -0.06em;
	margin: 0 0 var(--md-base-space);
	font-weight: 400;
	/* Optical alignment lives on .ln below, not here. See the note there. */
}

.splash-body {
	margin: 0 0 var(--md-base-space);
	max-width: 42ch;
	color: var(--color-gray-500);
}

.splash-body strong { color: var(--color-text); font-weight: 500; }

.splash-actions {
	display: flex;
	gap: var(--sm-base-space);
	flex-wrap: wrap;
	align-items: stretch;
}

/* ---------- buttons ---------- */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 44px;
	padding: 0 var(--md-base-space);
	border: 1px solid var(--color-text);
	background: transparent;
	color: var(--color-text);
	font-family: var(--font-mono);
	font-size: var(--fs-main);
	letter-spacing: -0.02em;
	text-transform: uppercase;
	cursor: pointer;
	transition: background var(--dur-fast) var(--ease),
	            color var(--dur-fast) var(--ease);
}

.btn:hover,
.btn:focus-visible {
	background: var(--color-invert-bg);
	color: var(--color-invert-text);
}

.btn-quiet {
	border-color: var(--color-rule-interactive);
	color: var(--color-gray-500);
}

.btn-quiet:hover,
.btn-quiet:focus-visible {
	background: transparent;
	color: var(--color-text);
	border-color: var(--color-text);
}

/* The one loud element on the page. Scale carries the force, not weight. */
.btn-primary {
	width: 100%;
	min-height: 64px;
	background: var(--color-invert-bg);
	color: var(--color-invert-text);
	font-size: 1.25rem;
	letter-spacing: -0.04em;
	font-weight: 500;
}

.btn-primary:hover,
.btn-primary:focus-visible {
	background: var(--color-bg);
	color: var(--color-text);
}

/* ---------- hub ---------- */

/* Vertical only — see the note on .splash. The hero is the only visual
   event on the page now, so it gets the top of the screen to itself. */
.hub {
	padding-top: calc(var(--md-base-space) * 2.5);
	padding-bottom: var(--md-base-space);
}

.hero-name {
	/* min(), not a breakpoint override. A flat 19vw with a 9.4rem desktop
	   rule INVERTED at the boundary: 194.56px at 1024 dropping to 150.4px at
	   1025, so the name got smaller as the screen got wider, and optical
	   fill of the measure fell from 87.7% to 59.5%. min() caps it smoothly
	   at the same 9.4rem with no step. */
	font-size: min(19vw, 9.4rem);
	line-height: 0.85;
	letter-spacing: -0.06em;
	margin: 0;
	font-weight: 400;
}

.hero-line {
	margin: var(--md-base-space) 0 0;
	max-width: 40ch;
}

/* No photography on this page, by decision (Gabe, 2026-07-27): a shot from
   the main site would reverse-image-link the clean entity to the adult one,
   and a new shoot is not a blocker worth holding the build for. The type
   carries it. */

.cta-block { margin: calc(var(--md-base-space) * 1.5) 0 0; }

.cta-note {
	margin: var(--sm-base-space) 0 0;
	color: var(--color-gray-500);
}

/* ---------- capture ---------- */

.capture {
	margin: calc(var(--md-base-space) * 1.5) 0 0;
	padding-top: var(--md-base-space);
	border-top: 1px solid var(--color-rule);
}

.capture h2 {
	margin: 0;
	font-size: var(--fs-main);
	font-weight: 500;
	text-transform: uppercase;
	letter-spacing: -0.02em;
}

.capture-promise {
	margin: var(--sm-base-space) 0 var(--md-base-space);
	color: var(--color-gray-500);
	max-width: 44ch;
}

.capture-row { display: flex; gap: var(--sm-base-space); flex-wrap: wrap; }

/* Shrink the field rather than wrap the button onto its own line: one row
   reads as one action. 8rem basis keeps them paired down to 320px. */
.capture-row input[type="email"] {
	flex: 1 1 8rem;
	min-width: 0;
	min-height: 44px;
	padding: 0 var(--sm-base-space);
	background: transparent;
	border: 1px solid var(--color-rule-interactive);
	color: var(--color-text);
	font-family: var(--font-mono);
	font-size: var(--fs-main);
}

.capture-row input[type="email"]::placeholder { color: var(--color-gray-500); }

.capture-row input[type="email"]:focus-visible { border-color: var(--color-text); }

.consent {
	margin: var(--sm-base-space) 0 0;
	color: var(--color-gray-500);
	max-width: 46ch;
}

/* The opt-out. Removing pressure from a low-stakes ask raises completion,
   and it keeps the deadpan: no begging. Quieter than the consent line so
   it never competes with the CTA above.

   It is a real link to OF_URL (same tracking link as the primary CTA), so
   the sentence does what it says instead of only looking clickable. Text
   only by instruction (Gabe, 2026-07-27): no border, no button chrome,
   dim at rest and full white on hover. 44px tap target matches
   .hub-footer a, the same defect already fixed once.

   NO opacity here, deliberately. It was gray-500 at opacity 0.75, which
   composites to rgb(94,94,94) on this background = 3.05:1, under the
   4.5:1 WCAG AA floor. Touch devices never get :hover, so that failing
   state was the ONLY state a phone visitor ever saw, and phones are this
   page's audience. Colour alone at full opacity measures 4.61:1 and
   passes, while still reading clearly dimmer than the white hover. Do
   not reintroduce opacity to "quieten" it. */
.capture-skip {
	margin: var(--sm-base-space) 0 0;
	color: var(--color-gray-500);
}

/* The 44px tap target comes from PADDING, never from line-height. Setting
   line-height: 44px made the leading 44px too: at 320px this sentence wraps
   and its two lines landed 44px apart against a 14.64px --lh-main, 3x the
   token, so one sentence read as two disconnected fragments. Half-leading
   also pushed the ink 14px below the element box, throwing the vertical
   grid. Padding gives the same 44px without touching the type. */
.capture-skip a {
	display: inline-block;
	min-height: 44px;
	line-height: var(--lh-main);
	padding-block: calc((44px - 1.22em) / 2);
	color: var(--color-gray-500);
	transition: color var(--dur-fast) var(--ease);
}

.capture-skip a:hover,
.capture-skip a:focus-visible {
	color: var(--color-text);
}

/* Honeypot. Off-screen, not display:none, so bots still see it. */
.hp {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* The error state used --color-rule (1.38:1), so it rendered as bare text
   with no visible container, while .msg-ok below rendered a crisp 18:1 box.
   The state that MUST be noticed was the weaker of the two. */
.msg {
	margin: var(--sm-base-space) 0 0;
	padding: var(--sm-base-space);
	border: 1px solid var(--color-text);
}

.msg-ok { border-color: var(--color-text); }

/* ---------- tiles ---------- */

.tiles {
	margin: calc(var(--md-base-space) * 1.5) 0 0;
	padding-top: var(--md-base-space);
	border-top: 1px solid var(--color-rule);
	display: grid;
	/* auto-fit only collapses tracks that are ENTIRELY empty, so it does not
	   prevent a ragged row: 3 tiles in 2 columns left a 171.5px hole at
	   375px, the target viewport. Single column below 520px instead. */
	grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
	gap: var(--sm-base-space);
}

.tile {
	display: flex;
	align-items: center;
	min-height: 44px;
	padding: var(--sm-base-space);
	border: 1px solid var(--color-rule-interactive);
	text-transform: uppercase;
	letter-spacing: -0.02em;
	transition: border-color var(--dur-fast) var(--ease),
	            color var(--dur-fast) var(--ease);
}

.tile:hover,
.tile:focus-visible { border-color: var(--color-text); }

/* ---------- promo strip ---------- */

/* Sits directly under the primary CTA and reads as part of it: butts up
   against the button, inverted the other way so it frames rather than
   competes. Off by default; only on during an OnlyFans promo window. */
.promo {
	margin: 0;
	padding: calc(var(--sm-base-space) * 0.75) var(--md-base-space);
	border: 1px solid var(--color-invert-bg);
	border-top: 0;
	background: transparent;
	color: var(--color-text);
	text-align: center;
	text-transform: uppercase;
	letter-spacing: -0.02em;
	font-weight: 500;
}

/* ---------- footer ---------- */

.hub-footer {
	margin: calc(var(--md-base-space) * 2) 0 0;
	padding: var(--md-base-space) 0;
	border-top: 1px solid var(--color-rule);
	color: var(--color-gray-500);
}

.hub-footer p { margin: 0 0 var(--sm-base-space); max-width: 48ch; }
.hub-footer p:last-child { margin-bottom: 0; }

/* Inline-block with a 44px line-box: the link keeps its text-sized
   underline but the tap target meets the minimum. */
/* Three faults fixed here 2026-07-27:
   (a) line-height: 44px put the "underline" 16px below the word, reading as
       a stray rule rather than an underline. Same leading bug as
       .capture-skip a above; padding gives the tap target instead.
   (b) the border was --color-rule (#2a2a2a on #0a0a0a = 1.38:1), invisible.
   (c) the link colour was identical to the .hub-footer p beside it, so the
       only link in the footer was distinguished from body copy by nothing
       at all. text-decoration follows the text and inherits the 4.61:1
       colour, which fixes the affordance and the contrast together. */
.hub-footer a {
	display: inline-block;
	min-height: 44px;
	line-height: var(--lh-main);
	padding-block: calc((44px - 1.22em) / 2);
	color: var(--color-gray-500);
	text-decoration: underline;
	text-underline-offset: 3px;
}
.hub-footer a:hover,
.hub-footer a:focus-visible { color: var(--color-text); }

/* ---------- desktop ---------- */

@media (min-width: 1025px) {
	.wrap { max-width: 720px; }
	/* Wordmark sizing is handled by min() on the base rules, so there is no
	   override here any more: an override is exactly what inverted at the
	   1024/1025 boundary. */
}

@media (prefers-reduced-motion: reduce) {
	* { transition-duration: 0.01ms !important; }
}


/* --------------------------------------------------------------------------
   Optical left alignment of the two display lines (.splash-mark, .hero-name).

   Both were `text-indent: 0.06em`, justified by "-0.06em pulls the first glyph
   past the box edge at display size". That premise is FALSE: CSS
   letter-spacing adds space AFTER each glyph and never moves the first one.
   And text-indent only ever applies to a block's FIRST line, so GABE was
   shoved right while WOODS was not. The same bug, and the same false comment,
   were fixed in the main theme on 2026-07-27; this file never got the port.

   The real cause is the left side bearing: IBM Plex Mono advances a uniform
   0.6em, but the ink inside that advance is not flush left and differs per
   glyph. Pixel-scanned at ten times the live size, so these are font
   constants, not nudges:

     G left side bearing = 0.0563em
     W left side bearing = 0.0349em

   Pull each line left by its own bearing and both ink edges land on the
   page's inset, at every size, because the compensation is in em. The markup
   uses two spans rather than a <br> because this has to be per line.
   -------------------------------------------------------------------------- */
.splash-mark .ln,
.hero-name .ln {
	display: block;
}

.splash-mark .ln--g,
.hero-name .ln--g {
	margin-left: -0.0563em;
}

.splash-mark .ln--w,
.hero-name .ln--w {
	margin-left: -0.0349em;
}

/* One column below 520px: at 375 the 2-column grid put MANYVIDS alone on
   row 2 with a 171.5px empty track beside it. */
@media (max-width: 520px) {
	.tiles { grid-template-columns: 1fr; }
}
