/*
Theme Name: PlugForge
Theme URI: https://plugforge.net/
Author: PlugForge
Author URI: https://plugforge.net/
Description: Block theme for the PlugForge plugin catalogue. No build step, no external requests.
Requires at least: 6.6
Tested up to: 7.1
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: plugforge
*/

/*
 * Alias layer.
 *
 * theme.json owns every value. These names exist so markup ported from the
 * design system keeps working unchanged: the mockup writes var(--ink-900),
 * WordPress generates --wp--preset--color--ink-900. An alias has no value of
 * its own, so the two cannot drift apart.
 */
:root {
	--ink-900:   var(--wp--preset--color--ink-900);
	--ink-800:   var(--wp--preset--color--ink-800);
	--sand-100:  var(--wp--preset--color--sand-100);
	--sand-200:  var(--wp--preset--color--sand-200);
	--surface-card: var(--wp--preset--color--surface-card);

	--text-primary:   var(--wp--preset--color--text-primary);
	--text-secondary: var(--wp--preset--color--text-secondary);
	--text-on-dark:   var(--wp--preset--color--text-on-dark);
	--text-on-dark-secondary: var(--wp--preset--color--text-on-dark-secondary);

	--cta:       var(--wp--preset--color--cta);
	--cta-hover: var(--wp--preset--color--cta-hover);
	--cta-tint:  var(--wp--preset--color--cta-tint);

	--brass:      var(--wp--preset--color--brass);
	--brass-tint: var(--wp--preset--color--brass-tint);

	--border-light: var(--wp--preset--color--border-light);
	--border-dark:  var(--wp--preset--color--border-dark);

	--success:        var(--wp--custom--semantic--success);
	--success-bg:     var(--wp--custom--semantic--success-bg);
	--success-text:   var(--wp--custom--semantic--success-text);
	--success-border: var(--wp--custom--semantic--success-border);
	--warning:        var(--wp--custom--semantic--warning);
	--warning-bg:     var(--wp--custom--semantic--warning-bg);
	--warning-text:   var(--wp--custom--semantic--warning-text);
	--warning-border: var(--wp--custom--semantic--warning-border);
	--error:          var(--wp--custom--semantic--error);
	--error-bg:       var(--wp--custom--semantic--error-bg);
	--error-text:     var(--wp--custom--semantic--error-text);
	--error-border:   var(--wp--custom--semantic--error-border);

	--focus-ring: var(--wp--custom--focus-ring);

	/*
	 * Reading colours.
	 *
	 * --cta and --brass are surface colours. Neither clears AA as text on sand:
	 * #1C7FD1 gives 3.62:1 and #B08542 gives 2.89:1, and brass carries the
	 * 12px eyebrow, the smallest type on the page. Anything a person reads uses
	 * these instead, at 5.28:1 and 5.31:1.
	 */
	--link:       var(--wp--preset--color--cta-hover);
	--brass-text: var(--wp--preset--color--brass-text);

	/*
	 * On ink-900 the surface brass is the right choice and clears AA at 5.12:1,
	 * so it gets a name of its own rather than an exception. Nothing in this
	 * theme paints text with --brass or --cta directly; every reading colour
	 * says which ground it belongs on.
	 */
	--brass-on-dark: var(--wp--preset--color--brass);

	--font-display: var(--wp--preset--font-family--sora);
	--font-body:    var(--wp--preset--font-family--plex-sans);
	--font-mono:    var(--wp--preset--font-family--plex-mono);

	--type-display: var(--wp--custom--type--display);
	--type-h1:      var(--wp--custom--type--h-1);
	--type-h2:      var(--wp--custom--type--h-2);
	--type-h3:      var(--wp--custom--type--h-3);
	--type-h4:      var(--wp--custom--type--h-4);
	--type-body-lg: var(--wp--custom--type--body-lg);
	--type-body:    var(--wp--custom--type--body);
	--type-body-sm: var(--wp--custom--type--body-sm);
	--type-caption: var(--wp--custom--type--caption);
	--type-eyebrow: var(--wp--custom--type--eyebrow);
	--type-button:  var(--wp--custom--type--button);
	--type-nav:     var(--wp--custom--type--nav);
	--type-code-inline: var(--wp--custom--type--code-inline);
	--type-code-block:  var(--wp--custom--type--code-block);

	--ls-display: -0.02em;
	--ls-h1: -0.015em;
	--ls-h2: -0.01em;
	--ls-h3: -0.005em;
	--ls-eyebrow: 0.09em;
	--ls-button: 0.01em;

	--sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px; --sp-5: 24px;
	--sp-6: 32px; --sp-7: 48px; --sp-8: 64px; --sp-9: 96px;

	--radius-sm: 6px; --radius-md: 8px; --radius-lg: 10px;
	--radius-xl: 14px; --radius-pill: 999px;

	--container: 1200px;
	--section-y: 96px;
	--card-pad: 24px;
	--card-pad-lg: 32px;

	--shadow-0: none;
	--shadow-1: 0 1px 2px rgba(22,28,36,.05), 0 1px 3px rgba(22,28,36,.04);
	--shadow-2: 0 4px 14px rgba(22,28,36,.10);
	--shadow-3: 0 20px 48px rgba(22,28,36,.20);
}

@media (max-width: 782px) {
	:root {
		--section-y: 56px;
		--type-display: var(--wp--custom--type--display-mobile);
		--type-h1: var(--wp--custom--type--h-1-mobile);
		--type-h2: var(--wp--custom--type--h-2-mobile);
		--type-h3: var(--wp--custom--type--h-3-mobile);
		--type-body: var(--wp--custom--type--body-mobile);
	}
}

/*
 * The footer belongs at the bottom of the viewport even when the page is short.
 * WordPress wraps everything in .wp-site-blocks, so the column lives there
 * rather than on body, and main takes the slack.
 */
.wp-site-blocks {
	box-sizing: border-box;
	display: flex;
	flex-direction: column;
	min-height: 100vh;
}

.wp-site-blocks > main { flex: 1 0 auto; }

/*
 * Core puts a 24px block gap between the top-level blocks, which on this site
 * means a band of page background between the header and the first section, and
 * another above the footer. Header, main and footer meet edge to edge.
 */
.wp-site-blocks > * { margin-block-start: 0; }

/*
 * wp:html emits raw markup with no wrapper, so every top-level element in a
 * pattern lands as a direct child of the section and inherits the constrained
 * layout's auto margins. Anything carrying a max-width then centres itself
 * under a left-aligned heading, which is what made the lede paragraphs float.
 * The section owns the centring; its children do not.
 */
.pf-section > *,
.pf-hero > *,
.pf-prose > * { margin-inline: 0; }
body {
	background: var(--sand-100);
	color: var(--text-primary);
	font: var(--type-body);
	-webkit-font-smoothing: antialiased;
}

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

/*
 * --text-secondary clears AA on sand-100 at 4.77:1 but not on sand-200, where
 * it drops to 4.31:1. Alternating sections use the primary text colour instead;
 * the theme check enforces that the two never meet.
 */
.has-sand-200-background-color { color: var(--text-primary); }

:where(a, button, input, select, textarea):focus-visible {
	outline: none;
	box-shadow: var(--focus-ring);
}

/*
 * Icons.
 *
 * One sprite, referenced as
 * <svg class="pf-icon"><use href="…/assets/icons/sprite.svg#check"></use></svg>.
 * Sizing follows the text it sits in, and fill follows the colour, so an icon
 * never needs its own colour rule.
 */
.pf-icon {
	width: 1em;
	height: 1em;
	fill: currentColor;
	vertical-align: -0.125em;
	flex: none;
}

/*
 * Motion.
 *
 * One duration for every state change, because there is only one kind here:
 * something under the cursor acknowledging it.
 */
:where(a, button, summary, .pf-card, .pf-faq__item) {
	transition: background-color .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease;
}

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

/*
 * Header.
 *
 * Dark and fixed, the way the mockup's SiteHeader is built: ink-900, 72px, a
 * border-dark hairline, and the page passing underneath. On the home page it
 * meets the dark hero and the two read as one block, which is what the mockup
 * does too.
 *
 * Fixed rather than sticky because sticky reserves its own space in the flow
 * and fixed does not; the body offset below is what replaces it, and it is one
 * number rather than a layout that shifts as the header changes height.
 */
.pf-header {
	position: fixed;
	inset-inline: 0;
	top: 0;
	z-index: 60;
	background: var(--ink-900);
	border-bottom: 1px solid var(--border-dark);
}

/* The admin bar sits above everything when it is there. */
.admin-bar .pf-header { top: 32px; }

@media screen and (max-width: 782px) {
	.admin-bar .pf-header { top: 46px; }
}

body .wp-site-blocks { padding-top: 72px; }
body.pf-holding .wp-site-blocks { padding-top: 0; }

.pf-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--sp-6);
	height: 72px;
}

.pf-logo { display: block; line-height: 0; flex: none; }
.pf-logo img { display: block; width: 179px; height: auto; }

.pf-nav { display: flex; flex-wrap: wrap; gap: var(--sp-6); }

/*
 * Nav hover dims rather than filling. A pill behind a two-item menu is more
 * chrome than the menu is worth, and on a dark bar the cheapest legible signal
 * is a change in weight of colour.
 */
.pf-nav a {
	font: var(--type-nav);
	color: var(--text-on-dark);
	padding: var(--sp-3) 0;
	position: relative;
	display: inline-flex;
	align-items: center;
}

.pf-nav a:hover {
	color: var(--text-on-dark-secondary);
	text-decoration: none;
}

/* The page you are on, marked the way the mockup marks it. */
.pf-nav a[aria-current="page"]::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	bottom: 0;
	height: 2px;
	background: var(--brass-on-dark);
}

/*
 * The small-screen menu.
 *
 * details/summary, so the open state lives in the DOM rather than in a script.
 * The sheet is positioned against the header, which is the containing block
 * because the header is fixed.
 */
.pf-menu { display: none; position: relative; }

.pf-menu__button {
	list-style: none;
	cursor: pointer;
	width: 42px;
	height: 42px;
	display: flex;
	align-items: center;
	justify-content: center;
	border: 1px solid var(--border-dark);
	border-radius: var(--radius-md);
	color: var(--text-on-dark);
}

.pf-menu__button::-webkit-details-marker { display: none; }
.pf-menu__button:hover { border-color: var(--text-on-dark-secondary); }

/*
 * Three bars from one element: the middle is the box, the outer two are its
 * shadows. Open, the middle disappears and the outer two cross.
 */
.pf-menu__bars,
.pf-menu__bars::before,
.pf-menu__bars::after {
	display: block;
	width: 18px;
	height: 2px;
	background: currentColor;
	transition: transform .15s ease, opacity .15s ease;
}

.pf-menu__bars::before,
.pf-menu__bars::after {
	content: "";
	position: absolute;
}

.pf-menu__bars::before { transform: translateY(-6px); }
.pf-menu__bars::after { transform: translateY(6px); }

.pf-menu[open] .pf-menu__bars { background: transparent; }
.pf-menu[open] .pf-menu__bars::before { transform: rotate(45deg); }
.pf-menu[open] .pf-menu__bars::after { transform: rotate(-45deg); }

.pf-menu__sheet {
	position: absolute;
	top: calc(100% + var(--sp-3));
	right: 0;
	min-width: 240px;
	background: var(--ink-800);
	border: 1px solid var(--border-dark);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-3);
	padding: var(--sp-2);
	display: grid;
}

.pf-menu__sheet a {
	font: var(--type-body-lg);
	color: var(--text-on-dark);
	padding: var(--sp-3) var(--sp-4);
	border-radius: var(--radius-sm);
	min-height: 44px;
	display: flex;
	align-items: center;
}

.pf-menu__sheet a:hover {
	background: var(--ink-900);
	text-decoration: none;
}

@media (max-width: 782px) {
	.pf-nav { display: none; }
	.pf-menu { display: block; }
	.pf-logo img { width: 148px; }
	.pf-header__inner { height: 64px; }
	body .wp-site-blocks { padding-top: 64px; }
}

/*
 * Buttons.
 *
 * The fill is cta-hover, not cta: white on cta measures 4.20:1, below AA, and a
 * button label is the one piece of text that has to read at a glance. Hover
 * darkens with an overlay rather than switching token, so contrast can only
 * improve.
 */
.pf-button {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-2);
	background: var(--cta-hover);
	color: #FFFFFF;
	font: var(--type-button);
	letter-spacing: var(--ls-button);
	padding: 15px var(--sp-5);
	min-height: 44px;
	border-radius: var(--radius-md);
	white-space: nowrap;
	cursor: pointer;
}

.pf-button:hover {
	box-shadow: inset 0 0 0 999px rgba(0, 0, 0, .14);
	text-decoration: none;
}

.pf-button .pf-icon { transition: transform .15s ease; }
.pf-button:hover .pf-icon { transform: translateX(3px); }

/* A second action on the same row, when there is one worth offering. */
.pf-button--quiet {
	background: transparent;
	color: var(--link);
	box-shadow: inset 0 0 0 1px var(--border-light);
}

.pf-button--quiet:hover {
	background: var(--cta-tint);
	box-shadow: inset 0 0 0 1px var(--cta-hover);
}

.pf-hero .pf-button--quiet {
	color: var(--text-on-dark);
	box-shadow: inset 0 0 0 1px var(--border-dark);
}

.pf-hero .pf-button--quiet:hover {
	background: var(--ink-800);
	box-shadow: inset 0 0 0 1px var(--text-on-dark-secondary);
}

/*
 * Sections.
 *
 * Every band opens with a hairline. Alternating sand was doing the separating
 * on its own, which works where the colours change and does nothing at all
 * where two light sections meet, and that is most of this site.
 */
.pf-section {
	padding-block: var(--section-y);
	border-top: 1px solid var(--border-light);
}

.pf-section--alt { border-bottom: 1px solid var(--border-light); }

/* The band straight after the dark hero already has an edge. */
.pf-hero + .pf-section { border-top: none; }

.pf-section__title {
	font: var(--type-h2);
	letter-spacing: var(--ls-h2);
	margin: 0 0 var(--sp-4);
	max-width: 22ch;
	text-wrap: balance;
}

.pf-section__lede {
	font: var(--type-body-lg);
	color: var(--text-secondary);
	max-width: 58ch;
	margin: 0 0 var(--sp-7);
	text-wrap: pretty;
}

.pf-section--alt .pf-section__lede { color: var(--text-primary); }

/*
 * The eyebrow carries a short rule, so the label reads as a mark on the
 * section rather than as text that happens to be small and orange.
 */
.pf-eyebrow {
	font: var(--type-eyebrow);
	letter-spacing: var(--ls-eyebrow);
	text-transform: uppercase;
	color: var(--brass-text);
	margin: 0 0 var(--sp-4);
	display: flex;
	align-items: center;
	gap: var(--sp-3);
}

.pf-eyebrow::before {
	content: "";
	width: 24px;
	height: 2px;
	background: currentColor;
	flex: none;
}

/* On ink-900 the surface brass is correct and clears AA at 5.12:1. */
.pf-eyebrow--on-dark { color: var(--brass-on-dark); }

.pf-eyebrow--spaced { margin-top: var(--sp-9); }

/*
 * Hero.
 */
.pf-hero {
	padding-block: var(--sp-9);
	overflow: hidden;
	border-bottom: 1px solid var(--border-dark);
}

.pf-hero__grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(400px, 100%), 1fr));
	gap: var(--sp-8);
	align-items: center;
}

.pf-hero__copy { max-width: 560px; }

.pf-hero__title {
	font: var(--type-display);
	letter-spacing: var(--ls-display);
	color: var(--text-on-dark);
	margin: var(--sp-5) 0 0;
	text-wrap: balance;
}

.pf-hero__sub {
	font: var(--type-body-lg);
	color: var(--text-on-dark-secondary);
	margin: var(--sp-5) 0 0;
	max-width: 52ch;
	text-wrap: pretty;
}

.pf-hero__actions {
	margin: var(--sp-6) 0 0;
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-3);
}

/*
 * The screenshot sits in a frame that names the screen it came from, cropped to
 * the matching card. The whole settings page in a wide slot is a grey blur, and
 * a hero image nobody can read is decoration.
 */
.pf-window {
	margin: 0;
	background: var(--ink-800);
	border: 1px solid var(--border-dark);
	border-top-left-radius: var(--radius-xl);
	border-top-right-radius: var(--radius-xl);
	border-bottom: none;
	overflow: hidden;
	box-shadow: var(--shadow-3);
	margin-bottom: calc(var(--sp-9) * -1);
}

.pf-window__bar {
	display: flex;
	align-items: center;
	gap: var(--sp-3);
	padding: var(--sp-3) var(--sp-4);
	border-bottom: 1px solid var(--border-dark);
}

.pf-window__dot {
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: #3F4A57;
	flex: none;
}

.pf-window__path {
	font: var(--type-code-inline);
	color: var(--text-on-dark-secondary);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.pf-window img {
	display: block;
	width: 100%;
	height: auto;
}

/*
 * Cards.
 */
.pf-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
	gap: var(--sp-5);
}

.pf-card {
	background: var(--surface-card);
	border: 1px solid var(--border-light);
	border-radius: var(--radius-lg);
	padding: var(--card-pad-lg);
	box-shadow: var(--shadow-1);
}

.pf-card__title { font: var(--type-h3); margin: 0 0 var(--sp-3); }
.pf-card__line { margin: 0; text-wrap: pretty; }

.pf-card__meta {
	font: var(--type-body-sm);
	color: var(--text-secondary);
	margin: var(--sp-4) 0 0;
}

.pf-card--tight { padding: var(--card-pad); }
.pf-card__title--sm { font: var(--type-h4); margin: 0 0 var(--sp-2); }

/*
 * The catalogue card.
 *
 * One plugin stretched across a three-column grid advertises an empty shelf. A
 * single wide card with the icon beside the copy reads as one finished thing
 * rather than as the first of several missing ones.
 */
.pf-card--feature {
	display: grid;
	grid-template-columns: 88px 1fr;
	gap: var(--sp-5) var(--sp-6);
	align-items: start;
	padding: var(--sp-7);
}

.pf-card--feature:hover {
	border-color: var(--cta-hover);
	box-shadow: var(--shadow-2);
}

.pf-card__icon {
	width: 88px;
	height: 88px;
	border-radius: var(--radius-lg);
	border: 1px solid var(--border-light);
	display: block;
}

.pf-card--feature .pf-card__line { font: var(--type-body-lg); max-width: 60ch; }

.pf-card__tags {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-2);
	margin: var(--sp-5) 0 0;
	padding: 0;
	list-style: none;
}

.pf-card__tags li {
	font: var(--type-caption);
	font-family: var(--font-mono);
	color: var(--text-secondary);
	background: var(--sand-100);
	border: 1px solid var(--border-light);
	border-radius: var(--radius-pill);
	padding: 4px var(--sp-3);
}

@media (max-width: 640px) {
	.pf-card--feature { grid-template-columns: 1fr; padding: var(--card-pad-lg); }
	.pf-card__icon { width: 64px; height: 64px; }
}

/*
 * The plugin card on the holding page.
 *
 * The banner is the file WordPress.org will show, and the icon sits beside
 * the name the way it does in a plugin search result.
 *
 * The icon does not overlap the banner the way the directory's own header
 * does: both banners already carry a mark of their own in a corner, and two
 * marks a few pixels apart read as a mistake.
 *
 * The aspect-ratio matches the banner rather than being a round number: the
 * box holds its shape before the image arrives, and the image fills the box
 * exactly when it does.
 */
.pf-plugins {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
	gap: var(--sp-6);
	margin: var(--sp-7) 0 0;
}

.pf-plugin {
	display: flex;
	flex-direction: column;
	background: var(--surface-card);
	border: 1px solid var(--border-light);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-1);
	overflow: hidden;
}

.pf-plugin:hover {
	border-color: var(--cta-hover);
	box-shadow: var(--shadow-2);
}

.pf-plugin__banner {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 1160 / 376;
	object-fit: cover;
}

.pf-plugin__body {
	display: flex;
	flex: 1;
	flex-direction: column;
	padding: var(--card-pad-lg);
}

.pf-plugin__head {
	display: flex;
	align-items: center;
	gap: var(--sp-4);
	margin: 0 0 var(--sp-4);
}

.pf-plugin__icon {
	flex: none;
	width: 48px;
	height: 48px;
	border: 1px solid var(--border-light);
	border-radius: var(--radius-md);
}

.pf-plugin__title { font: var(--type-h3); margin: 0; }
.pf-plugin__line { margin: 0; text-wrap: pretty; }

/* Pushed to the bottom, so two cards of unequal copy still line their tags up. */
.pf-plugin .pf-card__tags { margin-top: auto; padding-top: var(--sp-5); }

@media (max-width: 640px) {
	.pf-plugin__body { padding: var(--card-pad); }
	.pf-plugin__icon { width: 40px; height: 40px; }
}

/*
 * Use cases.
 *
 * Two columns exactly. An auto-fit grid put three across and left one orphan on
 * its own row, which reads as a mistake rather than as a set of four.
 */
.pf-cases {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--sp-7) var(--sp-8);
	counter-reset: pf-case;
}

@media (min-width: 783px) {
	.pf-cases { grid-template-columns: 1fr 1fr; }
}

.pf-case {
	max-width: 52ch;
	padding-top: var(--sp-5);
	border-top: 1px solid var(--border-light);
	counter-increment: pf-case;
}

.pf-case__title {
	font: var(--type-h4);
	margin: 0 0 var(--sp-3);
	display: flex;
	gap: var(--sp-3);
	align-items: baseline;
}

/*
 * A counter rather than a bullet: it numbers the set, so a reader can see at a
 * glance that four cases are being made, and it is the second place on the page
 * where brass appears.
 */
.pf-case__title::before {
	content: counter(pf-case, decimal-leading-zero);
	font: var(--type-eyebrow);
	letter-spacing: var(--ls-eyebrow);
	color: var(--brass-text);
	flex: none;
}

.pf-case__problem { margin: 0 0 var(--sp-3); text-wrap: pretty; }

.pf-case__after {
	display: flex;
	gap: var(--sp-2);
	margin: 0;
	text-wrap: pretty;
}

.pf-case__tick { color: var(--success); margin-top: 0.35em; }

/*
 * Feature list.
 */
.pf-list {
	list-style: none;
	margin: 0 0 var(--sp-7);
	padding: 0;
	display: grid;
	gap: var(--sp-3);
	max-width: 78ch;
}

.pf-list li { display: flex; gap: var(--sp-3); text-wrap: pretty; }
.pf-list__tick { color: var(--success); margin-top: 0.35em; }

/*
 * Closing call to action.
 */
.pf-cta { text-align: center; }

.pf-cta .pf-eyebrow { justify-content: center; }

.pf-cta__title {
	font: var(--type-h1);
	letter-spacing: var(--ls-h1);
	margin: 0 auto;
	max-width: 20ch;
	text-wrap: balance;
}

.pf-cta__sub {
	font: var(--type-body-lg);
	color: var(--text-secondary);
	margin: var(--sp-4) auto 0;
	max-width: 54ch;
	text-wrap: pretty;
}

.pf-cta__actions {
	margin: var(--sp-6) 0 0;
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-3);
	justify-content: center;
}

/*
 * Plugin page.
 */
.pf-hero--plugin { padding-block: var(--sp-6) var(--sp-9); }
.pf-hero__copy--wide { max-width: 760px; }
.pf-hero__title--plugin { font: var(--type-h1); letter-spacing: var(--ls-h1); }

.pf-crumbs {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--sp-2);
	font: var(--type-body-sm);
	color: var(--text-on-dark-secondary);
	margin-bottom: var(--sp-7);
}

.pf-crumbs a { color: var(--text-on-dark-secondary); }
.pf-crumbs a:hover { color: var(--text-on-dark); }

/*
 * Requirements, read out of readme.txt rather than typed here, so a release
 * cannot leave the page claiming the previous version.
 */
.pf-specs {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-6);
	margin: var(--sp-7) 0 0;
	padding-top: var(--sp-5);
	border-top: 1px solid var(--border-dark);
}

.pf-specs div { display: grid; gap: var(--sp-1); }

.pf-specs dt {
	font: var(--type-eyebrow);
	letter-spacing: var(--ls-eyebrow);
	text-transform: uppercase;
	color: var(--text-on-dark-secondary);
	margin: 0;
}

.pf-specs dd {
	font: var(--type-body);
	color: var(--text-on-dark);
	margin: 0;
}

.pf-hero__note {
	font: var(--type-body-sm);
	color: var(--text-on-dark-secondary);
	margin: var(--sp-5) 0 0;
	max-width: 60ch;
	padding-left: var(--sp-4);
	border-left: 2px solid var(--brass-on-dark);
}

/*
 * Screenshot on the plugin page: shown whole, because here the reader came to
 * look at it.
 */
.pf-shot {
	margin: 0;
	border: 1px solid var(--border-light);
	border-radius: var(--radius-lg);
	overflow: hidden;
	background: var(--surface-card);
	box-shadow: var(--shadow-2);
}

.pf-shot img { display: block; width: 100%; height: auto; }

.pf-shot figcaption {
	font: var(--type-body-sm);
	padding: var(--sp-4) var(--sp-5);
	border-top: 1px solid var(--border-light);
	text-wrap: pretty;
}

/*
 * FAQ.
 *
 * details/summary, so the one interactive control on the site is the browser's
 * own: no script to load, no ARIA state to keep in sync, and it opens with
 * JavaScript off.
 */
.pf-faq { display: grid; gap: var(--sp-3); }

.pf-faq__item {
	background: var(--surface-card);
	border: 1px solid var(--border-light);
	border-radius: var(--radius-lg);
}

.pf-faq__item:hover { border-color: var(--text-secondary); }
.pf-faq__item[open] { border-color: var(--cta-hover); }

.pf-faq__q {
	font: var(--type-h4);
	cursor: pointer;
	list-style: none;
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: var(--sp-4);
	padding: var(--sp-4) var(--sp-5);
	min-height: 44px;
	text-wrap: pretty;
}

.pf-faq__q::-webkit-details-marker { display: none; }

.pf-faq__q::after {
	content: "+";
	font: var(--type-h3);
	line-height: 1;
	color: var(--text-secondary);
	flex: none;
}

.pf-faq__item[open] .pf-faq__q::after { content: "\2212"; color: var(--link); }

.pf-faq__a { padding: 0 var(--sp-5) var(--sp-5); max-width: 68ch; }
.pf-faq__a p { margin: 0 0 var(--sp-3); text-wrap: pretty; }
.pf-faq__a p:last-child { margin-bottom: 0; }

/*
 * Prose pages: About, the legal ones, and the 404.
 */
.pf-prose__title {
	font: var(--type-h1);
	letter-spacing: var(--ls-h1);
	margin: 0 0 var(--sp-5);
	max-width: 20ch;
	text-wrap: balance;
}

.pf-prose__lede {
	font: var(--type-body-lg);
	color: var(--text-secondary);
	margin: 0 0 var(--sp-8);
	max-width: 58ch;
	text-wrap: pretty;
}

/*
 * A hairline above each heading, so a long legal page reads as a set of
 * sections rather than as one column of paragraphs.
 */
.pf-prose h2 {
	font: var(--type-h3);
	letter-spacing: var(--ls-h3);
	margin: var(--sp-8) 0 var(--sp-4);
	padding-top: var(--sp-5);
	border-top: 1px solid var(--border-light);
}

.pf-prose p { margin: 0 0 var(--sp-4); max-width: 68ch; text-wrap: pretty; }
.pf-prose ul { list-style: none; padding: 0; margin: 0 0 var(--sp-4); max-width: 68ch; }

.pf-prose li {
	margin-bottom: var(--sp-3);
	padding-left: var(--sp-5);
	position: relative;
	text-wrap: pretty;
}

.pf-prose li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.62em;
	width: 8px;
	height: 2px;
	background: var(--brass-text);
}

/*
 * The near miss on the 404.
 *
 * Set apart from the rest of the page because it is the one thing the visitor
 * might act on. The score is shown deliberately: a suggestion that says how
 * sure it is invites judgement, where a bare link asks for trust.
 */
.pf-suggestion {
	background: var(--cta-tint);
	border: 1px solid var(--border-light);
	border-left: 4px solid var(--cta-hover);
	border-radius: var(--radius-md);
	padding: var(--sp-5);
	margin: 0 0 var(--sp-6);
	max-width: 68ch;
}

.pf-suggestion__q { font: var(--type-h4); margin: 0 0 var(--sp-2); }
.pf-suggestion__why { font: var(--type-body-sm); margin: 0; text-wrap: pretty; }

/*
 * Unfilled copy.
 *
 * Deliberately loud. These pages are drafts and several facts in them are the
 * owner's to supply. Group I counts these spans and stays red until every one
 * is gone, so the site cannot be deployed still asking the question.
 */
.pf-tbc {
	background: var(--warning-bg);
	color: var(--warning-text);
	border-bottom: 2px solid var(--warning-border);
	padding: 0 4px;
	font-style: italic;
}

/*
 * Dormant patterns.
 *
 * Styled so that when the evidence exists, filling them in is a copy change and
 * not a design job. Nothing inserts them; group K checks that stays true.
 */
.pf-stats__row {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
	gap: var(--sp-6);
	margin: 0;
}

.pf-stats__value { font: var(--type-h2); margin: 0 0 var(--sp-2); }
.pf-stats__label { font: var(--type-body-sm); color: var(--text-primary); margin: 0; }

.pf-review { display: flex; flex-direction: column; gap: var(--sp-4); }
.pf-review__quote { margin: 0; }
.pf-review__quote p { margin: 0; text-wrap: pretty; }
.pf-review__by { font: var(--type-body-sm); color: var(--text-secondary); }

.pf-logos__row {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-6);
	align-items: center;
}

.pf-tiers__list { display: grid; gap: var(--sp-4); margin: 0; }

.pf-tiers__list div {
	display: grid;
	grid-template-columns: minmax(min(180px, 100%), 1fr) 2fr;
	gap: var(--sp-4);
	padding-bottom: var(--sp-4);
	border-bottom: 1px solid var(--border-light);
}

.pf-tiers__list dt { font: var(--type-h4); margin: 0; }
.pf-tiers__list dd { margin: 0; }

/*
 * Footer.
 *
 * Dark, so the page has an end. Links use the dimmer of the two on-dark greys
 * and brighten on hover, which is the reverse of the light sections and the
 * only place that inversion happens.
 */
.pf-footer { padding-block: var(--sp-8) var(--sp-7); }

.pf-footer__inner {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--sp-6);
}

@media (min-width: 783px) {
	.pf-footer__inner { grid-template-columns: 2fr 1fr 1fr; }

	/* Two children rather than three, so the last track would be left empty. */
	.pf-footer__inner--holding { grid-template-columns: 2fr 1fr; }
}

.pf-footer a { color: var(--text-on-dark-secondary); }
.pf-footer a:hover { color: var(--text-on-dark); }

.pf-footer__brand {
	font: var(--type-h4);
	font-family: var(--font-display);
	margin: 0 0 var(--sp-2);
}

.pf-footer__line {
	font: var(--type-body-sm);
	color: var(--text-on-dark-secondary);
	margin: 0;
	max-width: 34ch;
}

.pf-footer__heading {
	font: var(--type-eyebrow);
	letter-spacing: var(--ls-eyebrow);
	text-transform: uppercase;
	color: var(--brass-on-dark);
	margin: 0 0 var(--sp-3);
}

.pf-footer__links {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: var(--sp-2);
	font: var(--type-body-sm);
}

.pf-footer__bottom {
	margin-top: var(--sp-7);
	padding-top: var(--sp-5);
	border-top: 1px solid var(--border-dark);
	font: var(--type-caption);
	color: var(--text-on-dark-secondary);
}

.pf-footer__bottom p { margin: 0; }

/*
 * Pointer and touch behaviour.
 *
 * touch-action removes the 300ms tap delay that some mobile browsers still add
 * while they wait to see whether a tap is a double-tap to zoom. The tap
 * highlight is set rather than removed: the default is a translucent black box
 * that fights the palette, and removing it outright would take away the only
 * feedback a touch device gives before the page changes.
 */
:where(a, button, summary, .pf-faq__q) {
	touch-action: manipulation;
	-webkit-tap-highlight-color: rgba(28, 127, 209, .18);
}

/*
 * The header is sticky, so an anchor jump or a focus move would otherwise land
 * the target underneath it.
 */
html { scroll-padding-top: 96px; }

body { overflow-x: hidden; }
:where(h1, h2, h3, [id]) { scroll-margin-top: 96px; }

/*
 * One wrapper per pattern.
 *
 * WordPress writes per-container layout CSS with !important auto margins on
 * every direct child of a constrained group. wp:html has no wrapper of its own,
 * so each top-level element in a pattern was picking that up, and anything
 * carrying a max-width centred itself under a left-aligned heading. The section
 * now has exactly one child, so the rule lands on the wrapper where it belongs
 * and nothing inside is touched.
 */
.pf-wrap { width: 100%; }
