/*
Theme Name: Project Liberal
Theme URI: https://projectliberal.org
Description: Block child theme of Twenty Twenty-Five for projectliberal.org. Native blocks only — no page builder. Design tokens live in theme.json so they are version-controlled and rsyncable.
Author: Project Liberal
Author URI: https://projectliberal.org
Template: twentytwentyfive
Version: 0.1.2
Requires at least: 6.7
Tested up to: 7.0
Requires PHP: 8.1
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: pl-theme
Tags: block-theme, full-site-editing
*/

/* ==========================================================================
   Project Liberal — child theme CSS

   Almost everything lives in theme.json. This file carries only what
   theme.json cannot express: focus rings, sticky behaviour, the animated
   headline, and reduced-motion handling.

   Rules: vanilla CSS only. No framework, no build step, no utility classes.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Accessibility — keyboard focus
   Never remove the ring. :focus-visible so pointer users do not see it.
   -------------------------------------------------------------------------- */

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline: 3px solid var(--wp--preset--color--accent);
	outline-offset: 2px;
	border-radius: 2px;
}

/* Fallback for engines without :focus-visible */
@supports not selector(:focus-visible) {
	:where(a, button, input, select, textarea, summary, [tabindex]):focus {
		outline: 3px solid var(--wp--preset--color--accent);
		outline-offset: 2px;
	}
}

/* Skip link — visible only once focused */
.pl-skip-link {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 1000;
	padding: 0.75rem 1.25rem;
	background: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	text-decoration: none;
	font-weight: 600;
}

.pl-skip-link:focus {
	left: 0;
}

/* --------------------------------------------------------------------------
   2. Header — sticky (spec §5)
   -------------------------------------------------------------------------- */

.pl-header-sticky {
	position: sticky;
	top: 0;
	z-index: 100;
	background-color: var(--wp--preset--color--base);
}

/* Utility bar sits above the sticky header and scrolls away */
.pl-utility-bar {
	font-size: var(--wp--preset--font-size--small);
}

.pl-site-logo img {
	height: auto;
	max-width: 220px;
}

/* --------------------------------------------------------------------------
   3. Animated headline (spec §5)
   Reserve the line box so rotating words do not reflow the hero.
   -------------------------------------------------------------------------- */

.pl-headline-rotator {
	display: inline-block;
	min-width: 8ch;
	color: var(--wp--preset--color--accent);
	will-change: opacity;
	transition: opacity 220ms ease-in-out;
}

.pl-headline-rotator.is-swapping {
	opacity: 0;
}

/* --------------------------------------------------------------------------
   4. Person cards (spec §5 — /about/ person CPT + Query Loop)
   -------------------------------------------------------------------------- */

.pl-person-card {
	height: 100%;
}

.pl-person-card img {
	aspect-ratio: 1 / 1;
	object-fit: cover;
	width: 100%;
	height: auto;
}

/* --------------------------------------------------------------------------
   3b. Animated headline markers (replaces Elementor animated-headline
       "highlight" mode). SVG is injected by assets/js/motion.js.
   -------------------------------------------------------------------------- */

.pl-mark {
	position: relative;
	display: inline-block;
	white-space: nowrap;
	color: inherit;
}

/* Underline family sits below the baseline; strikethrough crosses the text. */
.pl-mark__svg {
	position: absolute;
	left: -0.06em;
	width: calc(100% + 0.12em);
	height: 0.42em;
	bottom: -0.12em;
	overflow: visible;
	color: var(--wp--preset--color--accent);
	pointer-events: none;
}

.pl-mark[data-mark="strikethrough"] .pl-mark__svg {
	top: 50%;
	bottom: auto;
	transform: translateY(-50%);
	height: 0.5em;
}

.pl-mark[data-mark="double_underline"] .pl-mark__svg {
	height: 0.55em;
	bottom: -0.2em;
}

/* Stroke draw-on. path length is unknown, so use a dasharray large enough for
   any of these paths and animate the offset to 0. */
.pl-mark__svg path {
	stroke-dasharray: 320;
	stroke-dashoffset: 320;
}

.pl-mark.is-drawn .pl-mark__svg path {
	transition: stroke-dashoffset 700ms cubic-bezier(0.65, 0, 0.35, 1);
	stroke-dashoffset: 0;
}

.pl-mark.is-drawn .pl-mark__svg path:nth-child(2) {
	transition-delay: 160ms;
}

/* Long highlighted phrases must be allowed to wrap on small screens, even
   though that splits the marker. Unreadable text is worse than a broken line. */
@media (max-width: 600px) {
	.pl-mark {
		white-space: normal;
	}
}

/* --------------------------------------------------------------------------
   3c. Scroll fade-in (replaces Elementor section "fadeIn" entrance animation)
   -------------------------------------------------------------------------- */

/* Content is visible by DEFAULT. The hidden start state only applies once JS
   has confirmed it can observe and reveal (html.pl-motion-ready), so a JS
   failure, a parse error, or an old browser can never leave the page blank. */
.pl-motion-ready .pl-fade-in {
	opacity: 0;
	transform: translateY(18px);
	transition: opacity 600ms ease-out, transform 600ms ease-out;
}

.pl-motion-ready .pl-fade-in.is-visible {
	opacity: 1;
	transform: none;
}

/* --------------------------------------------------------------------------
   4b. Checklist (/member/ benefits)
   The original inlined a checkmark SVG into every <li>. A plain list is the
   correct semantics — the tick is decoration, so it belongs in CSS.
   -------------------------------------------------------------------------- */

.pl-checklist {
	list-style: none;
	padding-left: 0;
}

.pl-checklist li {
	position: relative;
	padding-left: 2rem;
	margin-block-end: 0.75rem;
}

.pl-checklist li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.45em;
	width: 1.1rem;
	height: 0.6rem;
	border-left: 3px solid var(--wp--preset--color--accent);
	border-bottom: 3px solid var(--wp--preset--color--accent);
	transform: rotate(-45deg);
}

/* --------------------------------------------------------------------------
   4c. Zoho opt-in embed (/manifesto/)
   Third-party markup with fixed pixel widths. Contain it so it cannot cause
   horizontal page scroll on narrow screens.
   -------------------------------------------------------------------------- */

#zcampaignOptinForm,
#zcampaignOptinForm * {
	max-width: 100%;
	box-sizing: border-box;
}

/* --------------------------------------------------------------------------
   4d. Forms (spec §2) — three shapes, one stylesheet
   -------------------------------------------------------------------------- */

.pl-form-wrap {
	max-width: 34rem;
}

.pl-field {
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
	margin-block-end: 1.25rem;
}

.pl-field label {
	font-weight: 600;
}

.pl-req {
	color: var(--wp--preset--color--link);
	margin-inline-start: 0.15em;
}

.pl-field input,
.pl-field textarea,
.pl-field select {
	font: inherit;
	color: var(--wp--preset--color--contrast);
	background: var(--wp--preset--color--base);
	border: 2px solid var(--wp--preset--color--secondary);
	border-radius: 3px;
	padding: 0.6rem 0.75rem;
	width: 100%;
	box-sizing: border-box;
}

.pl-field input:focus-visible,
.pl-field textarea:focus-visible,
.pl-field select:focus-visible {
	border-color: var(--wp--preset--color--accent);
}

/* Error state is never colour-only: the message text carries the meaning. */
.pl-field input[aria-invalid="true"],
.pl-field textarea[aria-invalid="true"],
.pl-field select[aria-invalid="true"] {
	border-color: var(--wp--preset--color--link);
}

.pl-field__error {
	color: var(--wp--preset--color--link);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

.pl-form__status {
	min-height: 1.5em;
	font-weight: 600;
}

.pl-form__submit[disabled] {
	opacity: 0.6;
	cursor: progress;
}

/* Honeypot: hidden from sight AND from assistive tech, but still fillable by a
   bot. Not display:none — some bots skip those. */
.pl-hp {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.pl-form-notice {
	border-left: 4px solid var(--wp--preset--color--accent);
	background: var(--wp--preset--color--surface);
	padding: 0.75rem 1rem;
	font-size: var(--wp--preset--font-size--small);
}

/* Visually hidden but still announced. Not defined by the parent theme, and
   the required-field markup already depends on it. */
.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* Inline layout — the homepage hero, matching the old site: fields and button
   on one row, labels carried by the placeholder. The <label> is still in the
   DOM (see pl-forms.php) because a placeholder is not an accessible name. */
.pl-form-wrap--inline {
	max-width: 52rem;
}

.pl-form--inline {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: 0.75rem;
}

.pl-form--inline .pl-field {
	flex: 1 1 10rem;
	/* Both margins, not just the end: the <p> keeps a 1em top margin otherwise,
	   which drops the fields below the button. */
	margin-block: 0;
	/* Anchors the absolutely-positioned error message below the input. */
	position: relative;
}

/* The email field earns more room than the two name fields. */
.pl-form--inline .pl-field:has( input[type="email"] ) {
	flex-grow: 1.6;
}

/* Lighter than the stacked forms — at hero size a 2px border reads as heavy. */
.pl-form--inline .pl-field input {
	border-width: 1px;
	padding-block: 0.7rem;
}

.pl-form--inline .pl-form__actions {
	flex: 0 0 auto;
	margin-block: 0;
	/* Stretch to the row's height so the button matches the inputs instead of
	   sizing to its own padding. */
	align-self: stretch;
	display: flex;
}

.pl-form--inline .pl-form__submit {
	height: 100%;
	padding-block: 0;
	padding-inline: 2rem;
}

/* Errors sit under their own input instead of stretching the row. */
.pl-form--inline .pl-field__error {
	position: absolute;
	inset-block-start: 100%;
	inset-inline-start: 0;
	padding-block-start: 0.2rem;
}

.pl-form--inline .pl-form__status {
	flex: 1 0 100%;
	margin-block: 0;
}

/* Invisible Turnstile still needs to be in the DOM; keep it out of the row. */
.pl-form--inline .cf-turnstile {
	flex: 0 0 auto;
}

@media ( max-width: 40rem ) {
	.pl-form--inline {
		flex-direction: column;
		align-items: stretch;
	}

	.pl-form--inline .pl-field__error {
		position: static;
	}

	.pl-form--inline .pl-form__submit {
		width: 100%;
	}
}

/* --------------------------------------------------------------------------
   4e. Discord login (/login/)
   -------------------------------------------------------------------------- */

.pl-login {
	margin-block: 1.5rem;
	max-width: 34rem;
}

.pl-login__button {
	display: inline-block;
	text-decoration: none;
}

/* The error is a status message, not decoration: it carries the reason a sign-in
   failed, so it needs contrast rather than colour alone. */
.pl-login__error {
	border-left: 4px solid var(--wp--preset--color--link);
	background: var(--wp--preset--color--surface);
	padding: 0.75rem 1rem;
	font-weight: 600;
}

.pl-login__signedin {
	font-size: var(--wp--preset--font-size--large);
}

.pl-login__hint {
	font-size: var(--wp--preset--font-size--small);
}

/* --------------------------------------------------------------------------
   5. Source entries (spec §4)
   Body HTML is only a/p/strong, so this is all the styling it needs.
   -------------------------------------------------------------------------- */

.pl-source-claims p {
	margin-block: 0 1.25rem;
}

.pl-source-claims strong {
	display: block;
	font-weight: 600;
}

/* --------------------------------------------------------------------------
   6. Reduced motion — kill every transition and animation
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}

	.pl-headline-rotator {
		transition: none;
		opacity: 1;
	}
}

/* --------------------------------------------------------------------------
   7. Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 781px) {
	.pl-site-logo img {
		max-width: 160px;
	}

	.pl-headline-rotator {
		min-width: auto;
		display: block;
	}
}

/* Never let a long unbroken URL blow out the layout — source entries are
   dense with links. */
.pl-source-claims a,
.entry-content a {
	overflow-wrap: break-word;
}
