/*
Theme Name:        Nightsky
Theme URI:         
Author:            Jeremy Brand
Author URI:        
Description:       A dark, image-forward block theme for astrophotography portfolios. Built to let wide-format night sky photography breathe, with a calm serif + airy sans pairing.
Requires at least: 6.8
Tested up to:      6.8
Requires PHP:      7.4
Version:           0.1.0
License:           GPL-2.0-or-later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html
Text Domain:       nightsky.theme

This is a block theme. Visual styling lives almost entirely in theme.json,
not in this file. This file exists to satisfy WP's theme header requirement
and to hold the handful of utility classes WP expects themes to provide.
*/

/**
 * Held Light — motion + light tokens.
 *
 * The palette proper lives in theme.json (it generates the
 * --wp--preset--color--* custom properties and the editor swatches).
 * These are the Held Light values theme.json *can't* express: raw light
 * sources needed for blend math (mix-blend-mode: screen on the aurora
 * blobs, where the color must be a real value, not a themed token),
 * blur radii, animation timings, and the one animatable hue. See
 * docs/held-light-migration.md. Namespaced --hl-* to avoid collisions
 * with any generic custom props.
 */
:root {
	--hl-base: #140a1f; /* warm deep plum — mirrors theme.json --base */

	/* pastel-on-dark light sources (raw — fed to blend math) */
	--hl-light-pink: #ff5fa2;
	--hl-light-mag: #c65bff;
	--hl-light-peri: #6a7bff;

	/* atmosphere (Phase 1) */
	--hl-aurora-blur: 90px;
	--hl-aurora-opacity: 0.6; /* raised from 0.5 — cards occlude most of the field now, so the aurora that peeks through (gutters, header, edges) needs to punch a bit harder for the scroll-hue shift to read */
	--hl-drift-slow: 46s; /* atmosphere never rushes */

	/* hue arc — clamped pink → magenta → periwinkle, never rainbow. Driven
	   by scroll distance now (assets/js/atmosphere.js), not a time loop. */
	--hl-glow-start: 328deg;
	--hl-glow-mid: 292deg;
	--hl-glow-end: 252deg;

	/* cinematic type (Phase 2 / 5) — tracking eases open on focus */
	--hl-track-rest: 0.04em;
	--hl-track-focus: 0.12em;

	/* parked — reserved for a future single-hero surface (lightbox) */
	--hl-breathe-cycle: 5.5s;
}

/**
 * @property registrations — required so these custom props can be
 * *animated*. A plain custom property is an untyped string and snaps
 * between keyframe values; typing it as <angle>/<number> lets the
 * browser interpolate it smoothly on the compositor. Used from Phase 1
 * (hue drift) and Phase 2 (per-card focus) onward.
 */
@property --hl-glow-hue {
	syntax: '<angle>';
	initial-value: 328deg;
	inherits: true; /* set high, read by the fixed atmosphere layer below it */
}

@property --hl-focus {
	syntax: '<number>';
	initial-value: 0;
	/* inherits so a card's children (scrim, metadata, cursor glow, and the
	   subject's cinematic tracking) can all read the card's current focus
	   level. Each .nightsky-gallery-item still sets its OWN value, so
	   siblings never cross-contaminate — inheritance only flows down into
	   each card's own subtree. */
	inherits: true;
}

.screen-reader-text {
	border: 0;
	clip: rect(1px, 1px, 1px, 1px);
	clip-path: inset(50%);
	height: 1px;
	margin: -1px;
	overflow: hidden;
	padding: 0;
	position: absolute;
	width: 1px;
	word-wrap: normal !important;
}

/**
 * Sticky header.
 *
 * `position: sticky` on the <header> element itself (which is the
 * tagName of the template part — WP renders it as a real <header> tag
 * when tagName is set in the block markup). `top: 0` keeps it pinned
 * to the viewport top on scroll. `z-index` high enough to clear the
 * photo grid cards below but below the FilterPanel (1000) and the
 * PhotoSwipe lightbox (which uses its own stacking context entirely).
 *
 * Admin bar: WP's admin bar is `position: fixed` at the very top for
 * logged-in users, ~32px tall. The header's `top: 0` would slide
 * underneath it without this correction.
 */
.nightsky-site-header {
	position: sticky;
	top: 0;
	top: var( --wp-admin--admin-bar--height, 0px );
	z-index: 100;
}

/**
 * Scroll scrim. The header is fully transparent at the top of the page
 * — hero + aurora show straight through, per the thesis (it sits over the
 * field, not on a bar). Once scrolling starts, a gentle plum gradient
 * fades in behind the header content so the title/tagline stay legible
 * over photos travelling underneath. A ::before (opacity-transitioned)
 * rather than the header's own background, since gradients can't
 * transition; z-index:-1 seats it behind the header's own flow content
 * but still over the scrolling page (the header's z-index:100 makes this
 * a self-contained stacking context).
 */
.nightsky-site-header::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	background: linear-gradient(
		to bottom,
		rgba( 20, 10, 31, 0.6 ) 0%,
		rgba( 20, 10, 31, 0 ) 100%
	);
	opacity: 0;
	transition: opacity 0.4s ease;
}

.nightsky-site-header.is-scrolled::before {
	opacity: 1;
}

/**
 * Site title — understated on scroll, expressive at rest.
 *
 * Thin, light Raleway (from theme.json) over the photo grid + aurora. A
 * soft dark halo (text-shadow) is what keeps it legible as photos scroll
 * under the sticky header — this replaces the old mix-blend-mode:
 * difference approach, which was tied to the content directly behind the
 * header but never quite landed (decision #3). The scroll scrim above is
 * the other half of that legibility story.
 *
 * On scroll (.is-scrolled): opacity drops so the title retreats to the
 * background — present but undemanding. On hover/focus-within: opacity
 * returns to full and a pink rim-glow (the arc's warm end) blooms over
 * the dark halo, so the title catches the Held Light on approach.
 *
 * `will-change: opacity` — the opacity transition fires on every scroll
 * direction change, so promoting this element to its own compositor
 * layer avoids triggering a repaint of the header's backdrop each time.
 */
.nightsky-site-header .wp-block-site-title,
.nightsky-site-header .wp-block-site-title a {
	font-size: 1rem;
	letter-spacing: 0.12em; /* thin display wants air */
	opacity: 1;
	/* Soft dark halo keeps the thin, light title legible over bright photo
	   regions scrolling under the sticky header — replaces the old
	   mix-blend-mode: difference trick (decision #3: never landed). */
	text-shadow: 0 1px 12px rgba( 8, 4, 16, 0.7 );
	transition: opacity 0.5s ease, text-shadow 0.5s ease;
	will-change: opacity;
}

@media ( min-width: 640px ) {
	.nightsky-site-header .wp-block-site-title,
	.nightsky-site-header .wp-block-site-title a {
		font-size: 1.125rem;
	}
}

.nightsky-site-header.is-scrolled .wp-block-site-title a,
.nightsky-site-header.is-scrolled .wp-block-site-title {
	opacity: 0.6;
}

.nightsky-site-header:hover .wp-block-site-title a,
.nightsky-site-header:focus-within .wp-block-site-title a,
.nightsky-site-header.is-scrolled:hover .wp-block-site-title a,
.nightsky-site-header.is-scrolled:focus-within .wp-block-site-title a {
	opacity: 1;
	/* Rim-lit: the title catches the arc's pink light on approach, layered
	   over the dark legibility halo. */
	text-shadow: 0 1px 12px rgba( 8, 4, 16, 0.7 ), 0 0 22px hsl( 328 100% 72% / 0.4 );
}

/**
 * Tagline — visible at the top of the page, fades out on scroll.
 * Separate from the title's own opacity so the two can move
 * independently (tagline disappears faster/fully; title only dims).
 */
.nightsky-site-tagline {
	opacity: 1;
	transition: opacity 0.4s ease;
}

.nightsky-site-header.is-scrolled .nightsky-site-tagline {
	opacity: 0;
	pointer-events: none; /* prevent invisible text from intercepting clicks */
}

/* Phantom spacer — see header.html for the full rationale.
 * Width must stay in sync with .nightsky-filter-panel's collapsed
 * width (100px in view.scss). flex-shrink: 0 prevents it from being
 * squeezed on very narrow viewports, which would re-introduce the
 * title/button overlap the spacer exists to prevent. */
.nightsky-filter-spacer {
	width: 100px;
	flex-shrink: 0;
}

/**
 * Held Light atmosphere.
 *
 * Mount markup (.hl-atmosphere + .hl-grain) is injected once at
 * wp_body_open by includes/atmosphere.php, so it sits behind every
 * template. Tokens come from the :root block at the top of this file.
 *
 * Layering: the body background (base plum, from theme.json) is backmost;
 * .hl-atmosphere (z-index -2) paints over it; .hl-grain (-1) over that;
 * all real page content (z-index: auto, normal flow) paints on top. Both
 * layers are pointer-events:none, so nothing here intercepts input. The
 * aurora therefore shows through every transparent surface for free —
 * the header, the footer, the gutters between grid cards, and the margins
 * of text pages — with no per-surface work.
 */
.hl-atmosphere {
	position: fixed;
	inset: 0;
	z-index: -2;
	overflow: hidden;
	background: var( --hl-base );
	pointer-events: none;
	/* Contain the blobs' screen blend to this layer so it composites
	   against the atmosphere's own base + each other, never against page
	   content. The z-index already makes this a stacking context; isolate
	   is the explicit belt-and-suspenders guarantee. */
	isolation: isolate;
	/* Resting hue (balanced mid-arc magenta). The arc is driven by SCROLL,
	   not time: assets/js/atmosphere.js writes --hl-glow-hue from scroll
	   distance, so the whole field travels pink↔periwinkle as you move down
	   the gallery and holds still when you stop (a parked hue = no gradient
	   repaint of the blurred blobs, so idle is cheaper than the old loop).
	   This is the pre-JS / no-JS / reduced-motion resting state. */
	--hl-glow-hue: var( --hl-glow-mid );
}

.hl-blob {
	position: absolute;
	width: 65vmax;
	height: 65vmax;
	border-radius: 50%;
	filter: blur( var( --hl-aurora-blur ) );
	opacity: var( --hl-aurora-opacity );
	/* screen: overlapping light ADDS, so the blobs glow where they meet
	   rather than muddying — the core of the "held light" look. */
	mix-blend-mode: screen;
}

/* Three blobs offset around the current arc hue so the field reads as one
   light with depth, not three separate colors. Offsets stay small and
   clamped so it never leaves the pink/magenta/periwinkle family. */
.hl-blob-a {
	background: radial-gradient( circle, hsl( var( --hl-glow-hue ) 90% 66% ) 0%, transparent 68% );
	animation: hl-drift-a var( --hl-drift-slow ) ease-in-out infinite;
}

.hl-blob-b {
	background: radial-gradient( circle, hsl( calc( var( --hl-glow-hue ) - 34deg ) 88% 64% ) 0%, transparent 68% );
	animation: hl-drift-b calc( var( --hl-drift-slow ) * 1.3 ) ease-in-out infinite;
}

.hl-blob-c {
	background: radial-gradient( circle, hsl( calc( var( --hl-glow-hue ) + 22deg ) 85% 68% ) 0%, transparent 68% );
	animation: hl-drift-c calc( var( --hl-drift-slow ) * 1.6 ) ease-in-out infinite;
}

@keyframes hl-drift-a {
	0%,
	100% { transform: translate( -15vw, -10vh ) scale( 1 ); }
	50% { transform: translate( 10vw, 15vh ) scale( 1.15 ); }
}

@keyframes hl-drift-b {
	0%,
	100% { transform: translate( 30vw, 20vh ) scale( 1.1 ); }
	50% { transform: translate( -10vw, -15vh ) scale( 0.9 ); }
}

@keyframes hl-drift-c {
	0%,
	100% { transform: translate( 20vw, -25vh ) scale( 0.95 ); }
	50% { transform: translate( -20vw, 10vh ) scale( 1.2 ); }
}

/* Grain: warms the dark from "screen" to "paper/film." A static
   feTurbulence SVG baked into a data URI (rasterized once), nudged around
   on a steps() timeline so it shimmers without the browser recomputing
   the noise every frame. */
.hl-grain {
	position: fixed;
	inset: -50%;
	z-index: -1;
	opacity: 0.05;
	pointer-events: none;
	background-image: url( "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E" );
	animation: hl-grain-shift 0.6s steps( 2 ) infinite;
}

@keyframes hl-grain-shift {
	0% { transform: translate( 0, 0 ); }
	50% { transform: translate( -2%, 1% ); }
	100% { transform: translate( 1%, -1% ); }
}

/**
 * Reduced motion: the atmosphere stays PRESENT but goes still — no drift,
 * no hue sweep, no grain shimmer. A calm brand living its thesis: a
 * nervous system that asked the page to stop moving gets a still lit
 * room, not a blank one. The hue rests at the arc's midpoint (magenta, set
 * on .hl-atmosphere above) and never engages — atmosphere.js bails out
 * entirely under reduced-motion, so scroll doesn't drive it.
 */
@media ( prefers-reduced-motion: reduce ) {
	.hl-blob,
	.hl-grain {
		animation: none;
	}
}
