/*
Theme Name: CT Courses
Theme URI: https://ctcourses.net/
Author: CT Courses
Author URI: https://ctcourses.net/
Description: A block theme for CT Courses, an accredited professional-development provider for US schoolteachers. Brand-led design, no page builder, no jQuery, no build step. All design tokens live in theme.json.
Requires at least: 6.7
Tested up to: 7.0
Requires PHP: 8.2
Version: 1.1.3
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: ctcourses
Tags: block-theme, full-site-editing, education, e-commerce, accessibility-ready, one-column, two-columns, block-patterns, custom-colors, custom-menu, editor-style, featured-images, full-width-template, template-editing, translation-ready, wide-blocks
*/

/* -------------------------------------------------------------------------
 * This stylesheet holds ONLY what theme.json cannot express: focus behaviour,
 * the skip link, the three block-style variations, form fields belonging to
 * WooCommerce and Gravity Forms, and the print / forced-colours / reduced-motion
 * blocks.
 *
 * Every value below references a --wp--preset--* custom property that exists in
 * theme.json. Do not introduce literal colours or font sizes here. The only
 * exception is the forced-colors block at the end, which must use CSS system
 * colour keywords (Highlight, CanvasText, ButtonBorder) because the whole point
 * is to hand colour back to the OS.
 *
 * 1.1.0 rewrote this file. The 1.0.1 version was written against the previous
 * editorial palette and referenced ten tokens the brand palette does not define
 * -- `primary`, `accent`, `accent-soft`, `base-2`, `base-3`, `contrast-2`,
 * `deep`, `deep-2`, the display and mono font families, and the `lifted` and
 * `raised` shadows. It also styled seven classes (`site-header`, `course-card`,
 * `stat-figure` and friends) that no template or pattern now emits. Both sets
 * were silently dead, so they are gone rather than retokened.
 * ---------------------------------------------------------------------- */

/* --- Focus: visible, high contrast, never removed --------------------------
 * WCAG 2.2 AA 2.4.7 (Focus Visible) and 2.4.11 (Focus Not Obscured, Minimum).
 * ---------------------------------------------------------------------- */

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

/* Dark fields need a light ring; brand blue on slate is not enough separation. */
:where(.has-contrast-background-color, .has-contrast-strong-background-color)
	:where(a, button, input, select, summary, [tabindex]):focus-visible {
	outline-color: var(--wp--preset--color--base);
}

/* Remove the ring for pointer users only; keyboard users always keep it. */
:where(a, button):focus:not(:focus-visible) {
	outline: none;
}

::selection {
	background-color: var(--wp--preset--color--brand-yellow);
	color: var(--wp--preset--color--contrast);
}

/* --- Skip link (WordPress injects the anchor; we style it) --------------- */

.skip-link.screen-reader-text:focus {
	background-color: var(--wp--preset--color--base);
	border-radius: 2px;
	box-shadow: var(--wp--preset--shadow--card-hover);
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--sans);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
	left: var(--wp--preset--spacing--30);
	padding: var(--wp--preset--spacing--20) var(--wp--preset--spacing--30);
	text-decoration: none;
	top: var(--wp--preset--spacing--20);
	z-index: 100000;
}

/* --- Typographic detail ------------------------------------------------- */

body {
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

h1,
.wp-block-post-title,
.has-display-font-size,
.has-xxx-large-font-size {
	text-wrap: balance;
}

p,
li {
	text-wrap: pretty;
}

/* Yellow is a field colour, never a text colour. Brand yellow on white is
   1.3:1 and illustration yellow is worse. If either is set as text without a
   background behind it, fall back to slate rather than ship unreadable copy. */
.has-brand-yellow-color:not(.has-background),
.has-illus-yellow-color:not(.has-background) {
	color: var(--wp--preset--color--contrast);
}

/* --- Outline buttons ----------------------------------------------------
 * theme.json's button element styles apply to every button, which would
 * otherwise fill the outline variation. Restore it here, without stamping on an
 * explicit colour choice made in the editor.
 * ---------------------------------------------------------------------- */

.wp-block-button.is-style-outline > .wp-block-button__link:not(.has-background) {
	background-color: transparent;
	border-color: currentColor;
	border-width: 1px;
}

.wp-block-button.is-style-outline > .wp-block-button__link:not(.has-background):not(.has-text-color) {
	color: var(--wp--preset--color--contrast);
}

/* A tint of the current colour works on both light and dark fields, so the
   hover state never needs to know which surface it is sitting on. */
.wp-block-button.is-style-outline > .wp-block-button__link:not(.has-background):where(:hover, :focus-visible) {
	background-color: color-mix(in srgb, currentColor 12%, transparent);
}

@supports not (color: color-mix(in srgb, red 50%, transparent)) {
	.wp-block-button.is-style-outline > .wp-block-button__link:not(.has-background):where(:hover, :focus-visible) {
		text-decoration: underline;
		text-underline-offset: 0.2em;
	}
}

/* --- Navigation --------------------------------------------------------- */

.wp-block-navigation .wp-block-navigation-item__content {
	padding-block: 0.35rem;
	text-decoration: none;
}

.wp-block-navigation .wp-block-navigation-item__content:where(:hover, :focus-visible) {
	color: var(--wp--preset--color--brand-red);
}

.wp-block-navigation .wp-block-navigation__submenu-container {
	border-color: var(--wp--preset--color--rule);
	box-shadow: var(--wp--preset--shadow--card-hover);
}

/* --- Page-level seams ----------------------------------------------------
 * WordPress wraps every template in `.wp-site-blocks` and applies the global
 * block gap between its children:
 *
 *   :where(.wp-site-blocks) > * { margin-block-start: var(--wp--style--block-gap) }
 *
 * With a 1.5rem gap that puts a 24px white band between the header and the
 * page, and between the page and the footer -- most visible where a dark
 * section meets the dark footer, which is exactly where the eye lands.
 *
 * Templates set blockGap:0 inside <main>; this is the same fix one level up.
 * Header, main and footer all carry their own padding, so they should butt.
 *
 * Core's rule uses :where(), so its specificity is 0 and a plain class-plus-type
 * selector wins without !important.
 * ---------------------------------------------------------------------- */

.wp-site-blocks > header,
.wp-site-blocks > main,
.wp-site-blocks > footer {
	margin-block-start: 0;
}

/* --- Footer images -------------------------------------------------------
 * Sized here rather than with the image block's `width` attribute. Core's image
 * save() has several deprecations that disagree about whether width lands as an
 * HTML attribute or an inline style, and getting it wrong makes the block fail
 * validation in the Site Editor ("Block contains unexpected or invalid
 * content"). A class cannot be wrong.
 * ---------------------------------------------------------------------- */

.wp-block-image.ct-footer-logo img {
	max-width: 220px;
	width: 100%;
}

/* The accreditor logo is orange and taupe on a transparent ground; straight on
 * the slate footer the taupe text disappears. Partner logos on dark surfaces
 * get a white chip so the third party's own colours stay untouched. */
.wp-block-image.ct-footer-accreditor img {
	max-width: 160px;
	width: 100%;
	background: #ffffff;
	padding: 12px 16px;
	border-radius: 6px;
}

/* --- Footer link lists ---------------------------------------------------
 * Bullets add nothing to a column of six navigation links and cost horizontal
 * room the column does not have.
 * ---------------------------------------------------------------------- */

.wp-block-list.is-style-ct-footer-list {
	list-style: none;
	padding-left: 0;
}

.wp-block-list.is-style-ct-footer-list li {
	margin: 0;
}

/* --- Benefit icons -------------------------------------------------------
 * Inline SVG rather than an icon font: this theme ships no webfonts, and eight
 * glyphs do not justify a render-blocking request. The colour comes from an
 * inline custom property on the wrapper, so the icon inherits the brand palette
 * and needs no per-icon CSS.
 * ---------------------------------------------------------------------- */

.ct-benefit-icon {
	display: inline-flex;
	flex: 0 0 auto;
	line-height: 0;
}

.ct-benefit-icon svg {
	height: auto;
	width: 2.5rem;
}

@media (max-width: 781px) {
	.ct-benefit-icon svg {
		width: 2rem;
	}
}

/* --- Links on dark surfaces ----------------------------------------------
 * theme.json sets the link colour globally to brand blue, which is correct on
 * white and unusable on slate: #3a54a5 on #33495d measures 1.33:1, against a
 * 4.5:1 requirement. The footer and the closing CTA band are both dark, so every
 * link in them was effectively invisible.
 *
 * Inherit the surrounding text colour instead and keep the underline, so the
 * link is still identifiable without relying on hue -- WCAG 1.4.1, which matters
 * more here than the brand colour does.
 * ---------------------------------------------------------------------- */

/* Specificity note: theme.json's link element style compiles to
   `:root :where(a:where(:not(.wp-element-button)))`, which is (0,1,0). A
   selector of the form `:where(.class) a` is only (0,0,1) and loses. The class
   is therefore left OUTSIDE :where() so these rules land at (0,1,1) and win
   without needing !important. */
.has-contrast-background-color a:not(.wp-block-button__link),
.has-contrast-strong-background-color a:not(.wp-block-button__link) {
	color: inherit;
	text-decoration: underline;
	text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
	text-underline-offset: 0.2em;
}

.has-contrast-background-color a:not(.wp-block-button__link):where(:hover, :focus-visible),
.has-contrast-strong-background-color a:not(.wp-block-button__link):where(:hover, :focus-visible) {
	text-decoration-color: currentColor;
}

@supports not (color: color-mix(in srgb, red 50%, transparent)) {
	.has-contrast-background-color a:not(.wp-block-button__link),
	.has-contrast-strong-background-color a:not(.wp-block-button__link) {
		text-decoration-color: currentColor;
	}
}

/* --- Course card (core/group, "Course card" variation) -------------------
 * register_block_style() emits `is-style-course-card`, not `is-course-card`.
 * The design handoff styled the latter, so its card hover never matched
 * anything. Corrected here.
 * ---------------------------------------------------------------------- */

.is-style-course-card {
	box-shadow: var(--wp--preset--shadow--card);
	transition: box-shadow 200ms ease-out, transform 200ms ease-out;
}

.is-style-course-card:where(:hover, :focus-within) {
	box-shadow: var(--wp--preset--shadow--card-hover);
	transform: translateY(-2px);
}

/* --- FAQ accordion (core/details, "FAQ row" variation) ------------------- */

.wp-block-details.is-style-ct-faq {
	border-bottom: 1px solid var(--wp--preset--color--rule);
	padding: 0;
}

.wp-block-details.is-style-ct-faq summary {
	align-items: baseline;
	cursor: pointer;
	display: flex;
	font-size: var(--wp--preset--font-size--large);
	font-weight: 700;
	gap: var(--wp--preset--spacing--40);
	justify-content: space-between;
	line-height: 1.4;
	list-style: none;
	padding-block: var(--wp--preset--spacing--40);
}

.wp-block-details.is-style-ct-faq summary::-webkit-details-marker {
	display: none;
}

/* The marker is decorative: the open/closed state is already exposed to
   assistive technology by <details> itself, so it is hidden from the tree. */
.wp-block-details.is-style-ct-faq summary::after {
	content: "+";
	color: var(--wp--preset--color--brand-blue);
	font-size: var(--wp--preset--font-size--x-large);
	font-weight: 400;
	line-height: 1;
}

.wp-block-details.is-style-ct-faq[open] summary::after {
	content: "\2212";
}

.wp-block-details.is-style-ct-faq summary:where(:hover, :focus-visible) {
	color: var(--wp--preset--color--brand-red);
}

.wp-block-details.is-style-ct-faq > :not(summary) {
	padding: 0 var(--wp--preset--spacing--70) var(--wp--preset--spacing--40) 0;
}

/* --- Cost table (core/table, "Cost table" variation) --------------------- */

.wp-block-table.is-style-ct-price-table table {
	border-collapse: collapse;
	width: 100%;
}

.wp-block-table.is-style-ct-price-table th {
	background-color: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.07em;
	padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
	text-align: start;
	text-transform: uppercase;
}

.wp-block-table.is-style-ct-price-table td {
	border-bottom: 1px solid var(--wp--preset--color--rule);
	padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
}

.wp-block-table.is-style-ct-price-table tbody tr:nth-child(even) {
	background-color: var(--wp--preset--color--surface);
}

.wp-block-table.is-style-ct-price-table td:last-child {
	font-weight: 700;
	text-align: end;
}

/* Money lines up in a column or it is not a price table. */
.woocommerce-Price-amount,
.is-style-ct-price-table td:last-child {
	font-variant-numeric: tabular-nums lining-nums;
}

/* --- Forms: core, WooCommerce blocks and Gravity Forms ------------------- */

input:where([type="text"], [type="email"], [type="url"], [type="tel"], [type="number"], [type="search"], [type="password"], [type="date"]),
select,
textarea,
.wc-block-components-text-input input,
.wc-block-components-select__select {
	background-color: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--rule-strong);
	border-radius: 2px;
	color: var(--wp--preset--color--contrast);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--medium);
	padding: 0.7em 0.85em;
}

input:where([type="text"], [type="email"], [type="url"], [type="tel"], [type="number"], [type="search"], [type="password"], [type="date"]):hover,
select:hover,
textarea:hover {
	border-color: var(--wp--preset--color--contrast);
}

label,
.gform_wrapper .gfield_label,
.wc-block-components-text-input label {
	color: var(--wp--preset--color--contrast);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
}

/* Long labels wrap rather than overflowing their control. District names and
   awarding-institution names in this audience run long. */
label,
legend {
	max-width: 100%;
	overflow-wrap: break-word;
	white-space: normal;
}

/* Invalid state: never colour alone (WCAG 1.4.1) -- the border thickens too. */
input:where([type="text"], [type="email"], [type="url"], [type="tel"], [type="number"], [type="search"], [type="password"], [type="date"]):user-invalid,
select:user-invalid,
textarea:user-invalid,
.gform_wrapper .gfield_error input,
.gform_wrapper .gfield_error select,
.gform_wrapper .gfield_error textarea,
.wc-block-components-text-input.has-error input {
	border-color: var(--wp--preset--color--brand-red);
	border-width: 2px;
}

.wc-block-components-validation-error,
.gform_wrapper .gfield_error .validation_message {
	color: var(--wp--preset--color--brand-red);
	font-size: var(--wp--preset--font-size--small);
}

/* --- Mini-cart drawer: stop it scrolling the page sideways ---------------
 * WooCommerce sizes the mini-cart drawer at 100vw below its 480px breakpoint.
 * `100vw` includes the scrollbar gutter but `clientWidth` does not, so on any
 * platform that reserves a classic scrollbar the drawer is ~16px wider than the
 * viewport -- measured on the homepage at 375px: documentElement.scrollWidth 391
 * against a 375 viewport. The whole page then scrolls sideways, on the width
 * where that is most damaging.
 *
 * A percentage on a position:fixed element resolves against the initial
 * containing block, which excludes the scrollbar, so `100%` is the correct
 * width and `100vw` never was. `overflow-x: clip` on the body is the second
 * lock: `clip` rather than `hidden` because `hidden` makes the body a scroll
 * container and silently breaks `position: sticky` in any descendant.
 * ---------------------------------------------------------------------- */

body {
	overflow-x: clip;
}

.wc-block-components-drawer,
.wc-block-components-drawer__content,
.wc-block-components-drawer__screen-overlay,
.wp-block-woocommerce-mini-cart-contents {
	max-width: 100%;
}

/* --- Embedded courseware -------------------------------------------------
 * The Synergy Quiz is an Articulate Storyline 3 publish embedded by iframe. Its
 * own markup is a fixed 960x800, which overflows every phone. The wrapper gives
 * it a responsive box; the player scales itself inside.
 * ---------------------------------------------------------------------- */

.ct-embed-quiz {
	aspect-ratio: 6 / 5;
	border: 1px solid var(--wp--preset--color--rule);
	max-width: 100%;
	width: 100%;
}

.ct-embed-quiz iframe {
	border: 0;
	display: block;
	height: 100%;
	width: 100%;
}

@media (max-width: 781px) {
	.ct-embed-quiz {
		aspect-ratio: 3 / 4;
	}
}

/* --- Motion --------------------------------------------------------------
 * Everything above uses only transform / opacity / box-shadow, and all of it is
 * switched off for users who ask for reduced motion.
 * ---------------------------------------------------------------------- */

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

	.is-style-course-card:where(:hover, :focus-within) {
		transform: none;
	}
}

/* --- Forced colours (Windows high-contrast) ------------------------------
 * School district managed devices are a real slice of this audience and
 * high-contrast mode is common on them. Outline colours must be handed back to
 * the OS, and anything that carried meaning through a background alone needs a
 * border so it survives.
 * ---------------------------------------------------------------------- */

@media (forced-colors: active) {
	:where(a, button, input, select, textarea, summary, [tabindex], .wp-block-button__link):focus-visible {
		outline-color: Highlight;
	}

	.is-style-course-card,
	.wp-block-button__link {
		border: 1px solid ButtonBorder;
	}

	.wp-block-details.is-style-ct-faq,
	.is-style-ct-price-table td {
		border-color: CanvasText;
	}
}

/* --- Art gallery: masonry, two rows, paginated --------------------------
 *
 * NO JAVASCRIPT, deliberately. This theme ships none -- see the docblock on
 * ctcourses_enqueue_assets() -- and one paginated gallery is not a good enough
 * reason to start. The whole control is a radio group: each page has a hidden
 * radio, the labels are the page buttons, and :has() on the container decides
 * which page is shown.
 *
 * PROGRESSIVE ENHANCEMENT, AND THE FALLBACK IS THE CURRENT BEHAVIOUR. Everything
 * that hides a page lives inside @supports selector(:has(*)). Without :has() the
 * nav stays hidden and all twenty paintings render in one masonry flow, which is
 * exactly what the page did before pagination existed. Nothing becomes
 * unreachable, and no image is ever hidden from a crawler or from Reader mode.
 *
 * The radios are clipped, NOT display:none, so they keep their place in the tab
 * order. A radio group is the right native control here: arrow keys move between
 * pages for free, and a screen reader announces "Page 2 of 4, radio button",
 * which is more use than a div with a click handler.
 *
 * Masonry is CSS columns rather than grid. grid-template-rows: masonry is still
 * not shipping outside a Firefox flag. Columns fill top-to-bottom, so reading
 * order within a page is column-major -- acceptable for a wall of paintings, and
 * with five near-identical portrait formats per page it reads as a grid anyway.
 */

/* Owned here on purpose. This class is used by the gallery pager and by the
 * "opens in a new tab" hints on the resource buttons, and until now the only
 * definitions on the page came from WooCommerce's stylesheets and a core inline
 * block. A page that ever loads without WooCommerce would have printed that
 * hint text visibly next to every button. An accessibility utility should not
 * depend on the shop being loaded. */
.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.ct-gal {
	margin-block-start: var(--wp--preset--spacing--40);
}

.ct-gal-radio {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.ct-gal-page {
	columns: 3;
	column-gap: var(--wp--preset--spacing--30);
}

.ct-gal-page figure {
	margin: 0 0 var(--wp--preset--spacing--30);
	break-inside: avoid;
}

.ct-gal-page img {
	display: block;
	width: 100%;
	height: auto;
	border: 1px solid var(--wp--preset--color--rule);
}

/* Nav is hidden until :has() proves pagination can actually work. */
.ct-gal-nav {
	display: none;
}

@supports selector(:has(*)) {

	.ct-gal-nav {
		display: flex;
		flex-wrap: wrap;
		gap: var(--wp--preset--spacing--20);
		align-items: center;
		justify-content: center;
		margin-block-start: var(--wp--preset--spacing--30);
	}

	.ct-gal-nav label {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-width: 2.75rem;
		min-height: 2.75rem;
		padding: 0 0.5rem;
		border: 1px solid var(--wp--preset--color--rule-strong);
		border-radius: 2px;
		background: var(--wp--preset--color--base);
		color: var(--wp--preset--color--contrast);
		font-size: 0.95rem;
		line-height: 1;
		cursor: pointer;
		/* Compositor-friendly only: no width/padding transitions. */
		transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
	}

	.ct-gal-nav label:hover {
		border-color: var(--wp--preset--color--brand-blue);
		background: var(--wp--preset--color--surface);
	}

	/* 2.75rem min target above is the 44px AA touch target, so the hit area is
	   the label itself rather than a bare number. */

	.ct-gal-page {
		display: none;
	}

	.ct-gal:has(.ct-gal-r1:checked) .ct-gal-page-1,
	.ct-gal:has(.ct-gal-r2:checked) .ct-gal-page-2,
	.ct-gal:has(.ct-gal-r3:checked) .ct-gal-page-3,
	.ct-gal:has(.ct-gal-r4:checked) .ct-gal-page-4 {
		display: block;
	}

	.ct-gal:has(.ct-gal-r1:checked) .ct-gal-nav label[for="ct-gal-p1"],
	.ct-gal:has(.ct-gal-r2:checked) .ct-gal-nav label[for="ct-gal-p2"],
	.ct-gal:has(.ct-gal-r3:checked) .ct-gal-nav label[for="ct-gal-p3"],
	.ct-gal:has(.ct-gal-r4:checked) .ct-gal-nav label[for="ct-gal-p4"] {
		border-color: var(--wp--preset--color--brand-blue);
		background: var(--wp--preset--color--brand-blue);
		color: var(--wp--preset--color--base);
	}

	/* The focus ring belongs on the visible control, not on the clipped input.
	   Each radio sits immediately before its own label, so this is one rule. */
	.ct-gal-radio:focus-visible + label {
		outline: 3px solid var(--wp--preset--color--brand-red);
		outline-offset: 2px;
	}
}

/* Two columns below the three-column breakpoint. Five portrait paintings at one
   column would be five full-width rows on a phone, which is the opposite of the
   compact block this is meant to be. */
@media (max-width: 781px) {

	.ct-gal-page {
		columns: 2;
	}
}

@media (prefers-reduced-motion: reduce) {

	.ct-gal-nav label {
		transition: none;
	}
}

/* --- Print -------------------------------------------------------------- */


@media print {
	header,
	footer,
	.wp-block-navigation,
	.wp-block-search,
	.wp-block-woocommerce-mini-cart {
		display: none !important;
	}

	body {
		color: #000;
		font-size: 11pt;
	}

	a[href^="http"]::after {
		content: " (" attr(href) ")";
		font-size: 9pt;
	}
}

@media print {

	/* Pagination is meaningless on paper: print the whole gallery. */
	.ct-gal-page {
		display: block !important;
		columns: 2;
	}

	.ct-gal-nav {
		display: none !important;
	}
}
