/*
Theme Name: Elliott Bear
Theme URI: https://elliottbear.co.uk/
Author: Elliott Bear
Author URI: https://elliottbear.co.uk/
Description: Single-screen rate card for a freelance digital versatilist. Self-hosted Barlow, a bracket monogram that draws itself once on load, a chamfered CTA with a cursor-tracked sheen, and a pixel lattice revealed under the pointer.
Version: 2.15.0
Requires at least: 6.9
Tested up to: 7.1
Requires PHP: 8.0
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: elliottbear
Tags: one-page, minimal, custom-colors, accessibility-ready
*/

/* ==========================================================================
   1. Type. Self-hosted so the page makes no third-party font request.
   ========================================================================== */

@font-face {
	font-family: 'Barlow';
	font-style: normal;
	font-weight: 300;
	font-display: swap;
	src: url('assets/fonts/barlow-300.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: 'Barlow';
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url('assets/fonts/barlow-600.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ==========================================================================
   2. Tokens. The whole palette is black, white, and one cool near-neutral
      (hue 260 at very low chroma) used for the CTA and the pixel field.
   ========================================================================== */

:root {
	--eb-ground: #fff;
	--eb-ink: #000;

	--eb-font: 'Barlow', system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;
	--eb-size: 16px;
	--eb-leading: 30px;
	--eb-track: 1px;
	--eb-light: 300;
	--eb-bold: 600;

	--eb-cta-ink: oklch(0.99 0.008 260);
	--eb-cta-chamfer: 11px;
	--eb-cta-bg: linear-gradient(158deg, oklch(0.16 0.008 260) 0%, oklch(0.09 0.008 260) 55%, oklch(0.12 0.008 260) 100%);
	--eb-cta-bg-hover: linear-gradient(158deg, oklch(0.26 0.008 260 / .84) 0%, oklch(0.11 0.008 260 / .92) 45%, oklch(0.09 0.008 260 / .95) 100%);
	--eb-cta-shadow: 0 18px 44px oklch(0.25 0.008 260 / .22), inset 0 1px 0 rgba(255, 255, 255, .22), inset 0 0 0 1px rgba(255, 255, 255, .16);
	--eb-cta-shadow-hover: 0 20px 46px oklch(0.25 0.008 260 / .30), inset 0 1px 0 rgba(255, 255, 255, .36), inset 0 0 0 1px rgba(255, 255, 255, .14);

	/* Arriving is quicker than leaving. The asymmetry is what reads as soft. */
	--eb-cta-in: 260ms;
	--eb-cta-out: 620ms;
	--eb-sheen-fade: 180ms;
	--eb-ease: cubic-bezier(.22, .61, .36, 1);

	--eb-rates-top: 40px;
	--eb-rate-gap: 17.6px;
	--eb-cta-top: 20px;
	--eb-gutter: 24px;

	/* Set from the Customizer; PHP prints the overrides inline just after this
	   stylesheet, so these values are the fallbacks. */
	--eb-mask-size: 760px;
	--eb-shine-core: 0.18;
	--eb-shine-mid: 0.05;
	--eb-logo-shine-core: 0.18;
	--eb-logo-shine-mid: 0.05;
}

/* Registered so the mask centre can be animated and quantised from JS. */
@property --eb-x {
	syntax: '<length-percentage>';
	inherits: false;
	initial-value: 50%;
}

@property --eb-y {
	syntax: '<length-percentage>';
	inherits: false;
	initial-value: 25%;
}

/* The trail: a second centre that eases toward the first rather than toward
   the pointer, so it is always a moment behind. */
@property --eb-trail-x {
	syntax: '<length-percentage>';
	inherits: false;
	initial-value: 50%;
}

@property --eb-trail-y {
	syntax: '<length-percentage>';
	inherits: false;
	initial-value: 25%;
}

/* ==========================================================================
   3. Reset. Wrapped in :where() so every component rule below outranks it.
   ========================================================================== */

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

:where(body, p, ul, li, h1, figure) {
	margin: 0;
	padding: 0;
}

:where(ul) {
	list-style: none;
}

html {
	background: var(--eb-ground);
}

body {
	min-height: 100svh;
	background: var(--eb-ground);
	color: var(--eb-ink);
	font-family: var(--eb-font);
	font-size: var(--eb-size);
	font-weight: var(--eb-light);
	line-height: var(--eb-leading);
	letter-spacing: var(--eb-track);
	-webkit-font-smoothing: antialiased;
}

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

/* ==========================================================================
   4. The screen. One centred column, one viewport tall.
   ========================================================================== */

.eb-screen {
	position: relative;
	z-index: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	min-height: 100svh;
	padding: var(--eb-gutter);
}

body.admin-bar .eb-screen {
	min-height: calc(100svh - 32px);
}

@media screen and (max-width: 782px) {
	body.admin-bar .eb-screen {
		min-height: calc(100svh - 46px);
	}
}

/* ==========================================================================
   5. Monogram. Four strokes that draw themselves once, in sequence.
   ========================================================================== */

.eb-logo {
	position: relative;
	flex: none;
	line-height: 0;
}

/* The same cursor-tracked highlight the call to action carries.
   `screen` is what lets one technique serve both surfaces: against the white
   ground it changes nothing, and against the black strokes it lifts them, so
   the light appears to cross the mark rather than wash over the page.
   The inset lets the highlight spill past the mark instead of stopping at a
   62px box, which would read as a lit rectangle rather than a glint. */
.eb-logo__sheen {
	position: absolute;
	inset: -14px;
	opacity: 0;
	pointer-events: none;
	mix-blend-mode: screen;
	background: radial-gradient(44px 30px at var(--eb-sheen-x, 50%) var(--eb-sheen-y, 50%),
			rgba(255, 255, 255, var(--eb-logo-shine-core)),
			rgba(255, 255, 255, var(--eb-logo-shine-mid)) 45%,
			transparent 78%);
	transition: opacity var(--eb-sheen-fade) linear;
}

.eb-logo svg {
	display: block;
	width: 62px;
	height: 32px;
	overflow: visible;
}

.eb-logo path {
	fill: none;
	stroke: var(--eb-ink);
	stroke-width: 7;
	stroke-linecap: square;
	stroke-miterlimit: 1.5;
	animation-duration: 1s;
	animation-iteration-count: 1;
	animation-timing-function: linear;
	animation-fill-mode: forwards;
}

#eb-stroke-1 { animation-name: eb-draw-1; }
#eb-stroke-2 { animation-name: eb-draw-2; }
#eb-stroke-3 { animation-name: eb-draw-3; }
#eb-stroke-4 { animation-name: eb-draw-4; }

@keyframes eb-draw-1 {
	0%   { stroke-dasharray: 60.29; stroke-dashoffset: 60.29; }
	25%  { stroke-dashoffset: 0; }
	100% { stroke-dasharray: 60.29; stroke-dashoffset: 0; }
}

@keyframes eb-draw-2 {
	0%      { opacity: 0; stroke-dasharray: 60.29; stroke-dashoffset: 60.29; }
	23.33%  { opacity: 0; stroke-dashoffset: 60.29; }
	25%     { opacity: 1; }
	50%     { stroke-dashoffset: 0; }
	100%    { opacity: 1; stroke-dasharray: 60.29; stroke-dashoffset: 0; }
}

@keyframes eb-draw-3 {
	0%      { opacity: 0; stroke-dasharray: 5.36; stroke-dashoffset: -5.36; }
	56.67%  { opacity: 0; }
	58.33%  { opacity: 1; stroke-dashoffset: -5.36; }
	66.67%  { stroke-dashoffset: 0; }
	100%    { opacity: 1; stroke-dasharray: 5.36; stroke-dashoffset: 0; }
}

@keyframes eb-draw-4 {
	0%      { opacity: 0; stroke-dasharray: 5.36; stroke-dashoffset: -5.36; }
	63.33%  { opacity: 0; }
	65%     { opacity: 1; stroke-dashoffset: -5.36; }
	73.33%  { stroke-dashoffset: 0; }
	100%    { opacity: 1; stroke-dasharray: 5.36; stroke-dashoffset: 0; }
}

/* ==========================================================================
   6. Rates. Tier and unit sit light, the figure carries the weight.
   ========================================================================== */

.eb-rates {
	margin-top: var(--eb-rates-top);
	display: flex;
	flex-direction: column;
	gap: var(--eb-rate-gap);
	text-align: center;
}

.eb-rate {
	line-height: 1;
	font-weight: var(--eb-bold);
}

.eb-rate__tier,
.eb-rate__unit {
	font-weight: var(--eb-light);
}

/* ==========================================================================
   7. Call to action. Chamfered top-left and bottom-right, lit from above.
   ========================================================================== */

.eb-cta {
	position: relative;
	display: inline-flex;
	align-items: center;
	margin-top: var(--eb-cta-top);
	padding: 10px 20px;
	font-family: var(--eb-font);
	font-size: 14px;
	font-weight: var(--eb-bold);
	letter-spacing: .16em;
	text-decoration: none;
	color: var(--eb-cta-ink);
	background: var(--eb-cta-bg);
	box-shadow: var(--eb-cta-shadow);
	clip-path: polygon(var(--eb-cta-chamfer) 0, 100% 0, 100% calc(100% - var(--eb-cta-chamfer)), calc(100% - var(--eb-cta-chamfer)) 100%, 0 100%, 0 var(--eb-cta-chamfer));
	/* Drift is set by JS as two custom properties so the press state composes. */
	transform: translate3d(var(--eb-drift-x, 0px), var(--eb-drift-y, 0px), 0);
	/* No background here: two gradient images cannot interpolate, so listing it
	   snapped rather than faded. The hover gradient cross-fades on ::after. */
	transition:
		box-shadow var(--eb-cta-out) var(--eb-ease),
		transform var(--eb-cta-out) var(--eb-ease);
}

/* The hover gradient, held as its own layer purely so its opacity can be
   animated. Everything inside the button is ordered explicitly from here. */
.eb-cta::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 0;
	opacity: 0;
	background: var(--eb-cta-bg-hover);
	pointer-events: none;
	transition: opacity var(--eb-cta-out) var(--eb-ease);
}

/* Gloss band across the top half. */
.eb-cta::before {
	content: '';
	position: absolute;
	left: 1px;
	right: 1px;
	top: 1px;
	height: 48%;
	z-index: 1;
	background: linear-gradient(rgba(255, 255, 255, .08), rgba(255, 255, 255, 0));
	pointer-events: none;
}

.eb-cta:hover {
	box-shadow: var(--eb-cta-shadow-hover);
	transition-duration: var(--eb-cta-in);
}

.eb-cta:hover::after {
	opacity: 1;
	transition-duration: var(--eb-cta-in);
}

/* clip-path would crop an outline, so the focus ring is drawn inside. */
.eb-cta:focus-visible {
	outline: none;
	box-shadow: inset 0 0 0 2px var(--eb-cta-ink), inset 0 0 0 5px oklch(0.16 0.008 260), var(--eb-cta-shadow);
}

.eb-cta:active {
	transform: translate3d(var(--eb-drift-x, 0px), calc(var(--eb-drift-y, 0px) + 1px), 0);
}

/* Cursor-tracked highlight, positioned by JS. */
.eb-cta__sheen {
	position: absolute;
	inset: 0;
	opacity: 0;
	pointer-events: none;
	z-index: 2;
	mix-blend-mode: screen;
	/* JS moves the centre and nothing else. Keeping the gradient itself here
	   is what lets the Customizer change its strength without touching JS. */
	background: radial-gradient(150px 110px at var(--eb-sheen-x, 50%) var(--eb-sheen-y, 50%),
			rgba(255, 255, 255, var(--eb-shine-core)),
			rgba(255, 255, 255, var(--eb-shine-mid)) 45%,
			transparent 78%);
	/* Short, because JS supplies a continuous value; this only smooths the
	   gap between samples. A long duration here would just feel laggy. */
	transition: opacity var(--eb-sheen-fade) linear;
}

.eb-cta__label {
	position: relative;
	z-index: 3;
}

/* ==========================================================================
   8. Pixel field. Three dot layers behind everything, revealed only where
      the pointer is. Sits under the content, never intercepts a click.
   ========================================================================== */

.eb-bg {
	position: fixed;
	inset: 0;
	z-index: 0;
	overflow: hidden;
	contain: paint;
	pointer-events: none;
}

.eb-bg__grid {
	position: absolute;
	inset: 0;
	/* Two reveals rather than one. The first sits under the pointer; the second
	   is wider, fainter and lags behind, which is what reads as a trail. Several
	   mask layers composite additively by default, so no mask-composite needed. */
	--eb-mask:
		radial-gradient(var(--eb-mask-size) var(--eb-mask-size) at var(--eb-x) var(--eb-y),
			rgba(0, 0, 0, .78) 0%, rgba(0, 0, 0, .30) 48%, transparent 82%),
		radial-gradient(calc(var(--eb-mask-size) * 1.4) calc(var(--eb-mask-size) * 1.4) at var(--eb-trail-x) var(--eb-trail-y),
			rgba(0, 0, 0, .30) 0%, rgba(0, 0, 0, .12) 45%, transparent 76%);
	-webkit-mask-image: var(--eb-mask);
	mask-image: var(--eb-mask);
}

.eb-bg__pix {
	position: absolute;
	inset: 0;
}

.eb-bg__pix--1 {
	background-image: radial-gradient(oklch(0.30 0.008 260 / .15) .9px, transparent 1.2px);
	background-size: 10px 10px;
	animation: eb-drift-a 22s linear infinite;
}

.eb-bg__pix--2 {
	background-image: radial-gradient(oklch(0.30 0.008 260 / .09) .9px, transparent 1.2px);
	background-size: 10px 10px;
	background-position: 5px 5px;
	animation: eb-drift-b 30s linear infinite, eb-pulse 6s ease-in-out infinite;
}

.eb-bg__pix--3 {
	background-image: radial-gradient(oklch(0.40 0.008 260 / .20) 1.1px, transparent 1.4px);
	background-size: 50px 50px;
	animation: eb-spark 9s steps(1) infinite;
}

@keyframes eb-drift-a {
	to { background-position: 10px 10px; }
}

@keyframes eb-drift-b {
	from { background-position: 5px 5px; }
	to   { background-position: -5px 15px; }
}

@keyframes eb-pulse {
	0%, 100% { opacity: .35; }
	50%      { opacity: 1; }
}

@keyframes eb-spark {
	0%   { opacity: .9;  background-position: 0 0; }
	25%  { opacity: .3;  background-position: 25px 15px; }
	50%  { opacity: .85; background-position: 10px 35px; }
	75%  { opacity: .25; background-position: 35px 5px; }
	100% { opacity: .9;  background-position: 0 0; }
}

/* ==========================================================================
   9. Document pages. The rate card centres itself; a list cannot, so these
      pages share the type and palette but sit at the top of the page.
   ========================================================================== */

.eb-doc {
	position: relative;
	z-index: 1;
	width: 100%;
	max-width: 720px;
	margin-inline: auto;
	padding-block: clamp(48px, 12vh, 120px) 96px;
	padding-inline: var(--eb-gutter);
}

.eb-doc__back {
	display: inline-block;
	margin-bottom: 18px;
	color: var(--eb-ink);
	font-size: 13px;
	text-decoration: none;
	text-transform: uppercase;
	letter-spacing: .12em;
	opacity: .6;
}

.eb-doc__back::before {
	content: '';
	display: inline-block;
	width: 18px;
	height: 1px;
	margin-right: 8px;
	vertical-align: middle;
	background: currentColor;
}

.eb-doc__back:hover,
.eb-doc__back:focus-visible {
	opacity: 1;
}

.eb-doc__title {
	font-size: clamp(32px, 7vw, 54px);
	font-weight: var(--eb-bold);
	line-height: 1.04;
	letter-spacing: -.01em;
}

.eb-doc__lede {
	max-width: 62ch;
	margin-top: 14px;
	line-height: 1.6;
	letter-spacing: 0;
}

.eb-doc__lede p + p {
	margin-top: .7em;
}

.eb-doc__empty {
	margin-top: 36px;
	opacity: .6;
}

.eb-list {
	margin-top: clamp(32px, 6vh, 56px);
	border-top: 1px solid rgba(0, 0, 0, .14);
}

.eb-list__item {
	padding-block: 18px;
	border-bottom: 1px solid rgba(0, 0, 0, .14);
}

.eb-list__link {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 16px;
	color: var(--eb-ink);
	text-decoration: none;
}

.eb-list__name {
	font-size: 20px;
	font-weight: var(--eb-bold);
	line-height: 1.2;
	letter-spacing: 0;
}

.eb-list__link:hover .eb-list__name,
.eb-list__link:focus-visible .eb-list__name {
	text-decoration: underline;
	text-underline-offset: 4px;
}

.eb-list__count {
	flex: none;
	font-size: 13px;
	opacity: .55;
	white-space: nowrap;
}

.eb-list__note {
	max-width: 60ch;
	margin-top: 6px;
	font-size: 15px;
	line-height: 1.55;
	letter-spacing: 0;
	opacity: .7;
}

@media (max-width: 520px) {
	.eb-list__link {
		flex-direction: column;
		gap: 4px;
	}
}

/* ==========================================================================
   10. Reduced motion. Everything resolves to its finished, static state.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
	.eb-logo path,
	.eb-bg__pix {
		animation: none;
	}

	.eb-cta,
	.eb-cta::after,
	.eb-cta__sheen,
	.eb-logo__sheen {
		transition: none;
	}

	.eb-cta,
	.eb-cta:active {
		transform: none;
	}
}

/* ------------------------------------------------------------- dark mode */

/* Follows the device. The ground and ink swap; the call to action flips to
   a light chamfered block, as the builder's buttons do in the dark, and its
   sheen darkens rather than lightens, since a white light shows nothing on
   white. The pixel lattice turns to faint light points. */
:root { color-scheme: light dark; }

@media (prefers-color-scheme: dark) {
	:root {
		--eb-ground: #0e0e0e;
		--eb-ink: #fff;

		--eb-cta-ink: oklch(0.12 0.008 260);
		--eb-cta-bg: linear-gradient(158deg, oklch(0.99 0.004 260) 0%, oklch(0.93 0.006 260) 55%, oklch(0.96 0.005 260) 100%);
		--eb-cta-bg-hover: linear-gradient(158deg, oklch(1 0 0 / .96) 0%, oklch(0.9 0.006 260 / .96) 45%, oklch(0.94 0.005 260 / .97) 100%);
		--eb-cta-shadow: 0 18px 44px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255, 255, 255, .7), inset 0 0 0 1px rgba(255, 255, 255, .4);
		--eb-cta-shadow-hover: 0 20px 46px rgba(0, 0, 0, .65), inset 0 1px 0 rgba(255, 255, 255, .9), inset 0 0 0 1px rgba(255, 255, 255, .5);
	}

	.eb-cta__sheen {
		mix-blend-mode: multiply;
		background: radial-gradient(150px 110px at var(--eb-sheen-x, 50%) var(--eb-sheen-y, 50%),
				oklch(0.55 0.008 260 / calc(var(--eb-shine-core) * 2.2)),
				oklch(0.55 0.008 260 / calc(var(--eb-shine-mid) * 2.2)) 45%,
				transparent 78%);
	}

	/* The logo's glint stays a light, but gentler: on a dark ground it would
	   otherwise light up the page around the mark. */
	.eb-logo__sheen { mix-blend-mode: soft-light; }

	.eb-cta:focus-visible {
		box-shadow: inset 0 0 0 2px var(--eb-cta-ink), inset 0 0 0 5px oklch(0.96 0.005 260), var(--eb-cta-shadow);
	}

	.eb-bg__pix--1 { background-image: radial-gradient(oklch(0.92 0.008 260 / .13) .9px, transparent 1.2px); }
	.eb-bg__pix--2 { background-image: radial-gradient(oklch(0.92 0.008 260 / .08) .9px, transparent 1.2px); }
	.eb-bg__pix--3 { background-image: radial-gradient(oklch(0.97 0.008 260 / .2) 1.1px, transparent 1.4px); }

	.eb-list { border-top-color: rgba(255, 255, 255, .16); }
	.eb-list__item { border-bottom-color: rgba(255, 255, 255, .16); }
}
