/*
Theme Name: Webscanner
Theme URI: https://github.com/tonyskovi7/webscanner
Description: Classic PHP theme on native block.json + ACF PRO fields + PHP render. Zero build step, WPML/ACFML translation, theme.json design tokens.
Author: tonyskovi7
Version: 1.0.96
Requires at least: 6.3
Requires PHP: 8.1
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: webscanner
*/

/*
 * Global styles live here only when they are genuinely theme-wide.
 * Per-block CSS belongs in blocks/<slug>/style.css, wired by block.json.
 *
 * "Version:" above is the hand-written base. CI appends the build number on
 * deploy (1.0 -> 1.0.42). Bump the base yourself for meaningful releases and
 * never commit a stamped value.
 */

/* Tokens --------------------------------------------------------------- */

/*
 * The theme's one easing curve, ported from the shared button. It lives at :root
 * rather than on a component because .wsc-link below is theme-wide and the site
 * chrome consumes the same value — a component-scoped copy would have to be
 * redeclared in every stylesheet that animates.
 */
:root {
	--wsc-ease: cubic-bezier(0.645, 0.045, 0.355, 1);
}

/* Typography ----------------------------------------------------------- */

/*
 * Bold body text opens where regular body text tightens: the design's
 * Body-Bold is +1% against the -1% theme.json sets on the body. A rule here
 * rather than a token because theme.json has no element style for <strong>.
 */
strong,
b {
	letter-spacing: 0.01em;
}

/*
 * Headings declare their own tracking, and bold inside one is still heading
 * text. Without this the rule above wins on source order — theme.json emits
 * `h2 { letter-spacing }` at the same 0-0-1 specificity, earlier in the
 * cascade. :where() keeps the guard at zero specificity so it never competes
 * with a block that styles its own emphasis.
 */
:where(h1, h2, h3, h4, h5, h6) :is(strong, b) {
	letter-spacing: inherit;
}

/* Reset ---------------------------------------------------------------- */

/*
 * Border-box everywhere. Without it `min-height` measures the content box, so
 * a block that pins a frame height and also pads it renders the sum of the two
 * — which is exactly how the hero's 660px frame became 1048px on the frontend.
 * The editor canvas ships its own border-box reset, so that class of bug shows
 * up live and not in preview. Declared once here rather than per block.
 */
*,
*::before,
*::after {
	box-sizing: border-box;
}

/* Section primitives -------------------------------------------------- */

/*
 * Every block section applies these two classes rather than re-implementing
 * them; see the starter kit's styling-tokens.md for the markup contract.
 *
 *   <section class="wsc-<slug> wsc-section">
 *     <div class="wsc-container">…</div>
 *   </section>
 *
 * The padding tokens interpolate between 480px and 1200px viewports (24→40px
 * inline, 48→80px block). Those two figures are clamp calibration endpoints,
 * NOT breakpoints — the canonical 640/768/1024/1280 scale is untouched and no
 * @media is involved.
 *
 * A block that owns its own frame (hero) skips .wsc-section and consumes the
 * inline token directly, so there is no same-specificity override contest.
 */
.wsc-section {
	padding-block: var(--wp--custom--section--padding-block);
	padding-inline: var(--wp--custom--section--padding-inline);
}

/*
 * Pure centring box — the section owns the gutter, so this carries no padding
 * of its own. Width comes from theme.json's layout.contentSize, the single
 * source of truth for the 1200px measure.
 */
.wsc-container {
	width: 100%;
	max-width: var(--wp--style--global--content-size); /* 75rem — 1200px */
	margin-inline: auto;
}

/*
 * The document reading measure — a narrower column for long-form prose.
 *
 * Legal and policy sections (document-links, faq-groups, clause-list) set 800px
 * rather than the 1200px content width, because running text at 1200px is past
 * the comfortable line length and reads as a wall.
 *
 * A modifier here rather than a max-width in each block's stylesheet: block CSS
 * must never re-declare the container width, or the two measures drift and
 * neither is the source of truth.
 */
.wsc-container--narrow {
	max-width: var(--wp--custom--container--narrow); /* 50rem — 800px */
}

/*
 * The section title, pinned to one size across both tags it can be emitted as.
 *
 * inc/heading-level.php lets a handful of blocks promote their title from <h2>
 * to <h1>, because a document page's opening section IS the page title. That is
 * a DOCUMENT OUTLINE decision, not a size decision — but theme.json draws the
 * two tags at different metrics (h1 at clamp(2rem…2.5rem)/1.1/0 against h2's
 * 2rem/1.125/-0.01em), so without this the toggle resizes the heading. Pin both
 * to the h2 step and the tag becomes purely semantic, which is the point.
 *
 * A class is 0-1-0 and theme.json's element styles are 0-0-1, so this wins
 * regardless of load order — the same mechanism the strong/b guard above relies
 * on. No !important needed.
 *
 * letter-spacing is here specifically because h1's element style sets 0 and has
 * to be overridden; on an <h2> it restates the inherited value harmlessly.
 *
 * Consumed by document-links, faq-groups, clause-list, pricing-plans and
 * contact-form — every caller of wsc_heading_level_field(). A block that adds
 * the field must add this class too; heading-level.php's header comment says so.
 */
.wsc-section-title {
	margin-block: 0;
	font-family: var(--wp--preset--font-family--heading);
	font-size: 2rem; /* 32px */
	font-weight: 600;
	line-height: 1.125; /* 36/32 */
	letter-spacing: -0.01em;
}

/* Prose primitive ------------------------------------------------------ */

/*
 * Editor-authored rich text: the house bullet, applied wherever a wysiwyg field
 * renders. Defined once rather than per block, the same reason .wsc-section-title
 * above exists — clause-list and definition-list offer the identical 'basic'
 * toolbar, and a second hand-copy is how the two drift.
 *
 * BULLETS ONLY. <ol> keeps native numbering: an editor who reached for a
 * numbered list in legal text chose the sequence deliberately, and a decorative
 * dot would destroy it.
 *
 * One descendant selector reaches every nesting level, so a list inside a list
 * gets the same dot rather than the UA's circle-then-square progression.
 *
 * NOT RELATED TO THE prose-section BLOCK, whose root is .wsc-prose-section.
 * Class matching is per token, so the two never collide — but they do collide in
 * a grep, which is worth knowing before assuming a hit belongs to this rule.
 *
 * THE SEMANTICS TRAP. This is editor HTML through wp_kses_post(), so unlike the
 * theme's own lists there is no way to add role="list" to it — and `list-style:
 * none` is precisely what drops list semantics in Safari/VoiceOver. The empty
 * string keeps the marker box, and with it the list role, while rendering
 * nothing. `none` is the fallback rather than the target, and the @supports
 * guard is what makes being wrong survivable: Safari below 17 has no string
 * list-style-type, and without the guard the invalid declaration would leave the
 * disc in place UNDER the ::before dot — two bullets, not none.
 */
.wsc-prose ul {
	list-style-type: none;
	padding-inline-start: 0;
	margin-block: var(--wp--preset--spacing--30); /* 16px */
}

@supports (list-style-type: "") {
	.wsc-prose ul {
		list-style-type: "";
	}
}

/*
 * Flush left, and wrapped lines flush too — the design draws the bullet on the
 * same edge as the paragraph above it, with no hanging indent.
 */
.wsc-prose ul li::before {
	content: "";
	display: inline-block;
	width: 4px;
	height: 4px;
	margin-inline-end: 5px; /* off the spacing scale; the design's own figure */
	border-radius: 50%; /* a circle at any size — do not swap for a fixed radius */
	/* currentColor, not ink: at this size it is indistinguishable from the body text
	 * it sits in, and it stays correct if the section is ever put on a dark
	 * background, where a fixed near-black dot would disappear. */
	background-color: currentColor;
	vertical-align: middle;
}

/* The list's own margin sets the rhythm; items sit tight against each other. */
.wsc-prose ul li + li {
	margin-block-start: 0;
}

/* Section frame text inversion ----------------------------------------- */

/*
 * Applied by wsc_section_attrs() when an editor picks a section background, so
 * inherited text follows the colour underneath it. Named for the frame concept
 * rather than for .wsc-section membership: the blocks that own their frame
 * (hero, cta-bar, features-bar) carry these too.
 *
 * BOTH DIRECTIONS ARE NEEDED. --on-dark is the obvious one, stopping ink text
 * on an ink background. --on-light exists because cta-bar and features-bar
 * hardcode `color: <bg>` on their own root — white on green, white on ink — so
 * a light background chosen for either would otherwise be white on white.
 *
 * THE DOUBLED CLASS IS THE POINT, not a typo. Block stylesheets load after this
 * file and match their root at 0-1-0, so a single class here would tie and lose
 * on source order. Repeating it makes the selector 0-2-0, which wins without
 * reaching for !important — banned everywhere else in this theme, and there is
 * no reason to make an exception when specificity alone settles it.
 *
 * Inherited text only. Buttons, borders and icon fills carry their own colours
 * and keep them; the blocks that are dark by design are documented as not
 * guaranteed to read well on a light background.
 */
.wsc-section--on-dark.wsc-section--on-dark {
	color: var(--wp--preset--color--bg);
}

.wsc-section--on-light.wsc-section--on-light {
	color: var(--wp--preset--color--ink);
}

/* Link primitive ------------------------------------------------------- */

/*
 * Navigational text link with the sweeping underline. Theme-wide because both
 * the header and the footer use it — defining it in either chrome stylesheet
 * would mean two copies drifting apart. Applies to text links only, never to
 * .wsc-button.
 *
 * theme.json sets elements.link { text-decoration: underline } globally, so the
 * static underline has to be turned off explicitly before the animated one can
 * stand in for it.
 *
 * A BACKGROUND, not a positioned ::before. An absolutely positioned rule spans
 * the bounding box of the whole link, so a link that wraps gets one line across
 * both rows instead of one under each. Footer links wrap freely and header panel
 * links drop `white-space: nowrap` below 1024, so this is a live case, not a
 * hypothetical. `box-decoration-break: clone` is what makes the background
 * repeat per line fragment rather than being sliced across them.
 *
 * WHY IT DOES NOT MOVE ANYTHING. The background is clipped to the border box, so
 * the 2px that drops the line clear of the baseline has to be padding — it
 * cannot be a negative offset. The matching negative margin hands those 2px
 * straight back, keeping the margin box the size it was: these links are flex
 * items in the header nav and in the panel, where the box drives centring and
 * gap. Vertical padding and margin are both inert on an inline box, so the
 * footer is unaffected either way.
 *
 * background-position flips between the two states the way transform-origin used
 * to: anchored left the line grows in from the left, anchored right it retracts
 * out toward the right. Both directions read left-to-right rather than the line
 * rewinding the way it came. Only background-size transitions — the anchor has
 * to snap at the state change or the line would slide across instead of sweep.
 */
.wsc-link {
	text-decoration: none;
	padding-block-end: 0.125rem; /* 2px — the gap under the baseline */
	margin-block-end: -0.125rem; /* ...given back, so the box is unchanged */
	-webkit-box-decoration-break: clone;
	box-decoration-break: clone;
	background-image: linear-gradient(currentColor, currentColor);
	background-repeat: no-repeat;
	background-size: 0 1px;
	background-position: 100% 100%;
	transition: background-size 0.3s var(--wsc-ease);
}

.wsc-link:hover,
.wsc-link:focus-visible {
	background-size: 100% 1px;
	background-position: 0 100%;
}

/*
 * Inverted modifier — the underline is already there and RETRACTS on hover,
 * rather than arriving. Some frames draw the link as permanently underlined
 * (the secondary CTA beside a button, a per-card link in a status list), where
 * the base behaviour would read as a missing underline until you point at it.
 *
 * The two states are the base rule's own anchors SWAPPED, not a reversed
 * transition. That is the whole point of the two-anchor design: anchored left
 * the line grows in from the left, anchored right it retracts out to the right,
 * so BOTH directions still sweep left-to-right instead of the line rewinding the
 * way it came. Reversing the transition would lose that.
 *
 * Everything mechanical — the 2px padding traded against a negative margin, the
 * cloned box-decoration-break, why it is a background and not a ::before — is the
 * base rule's; read the reasoning there. Placed after the base hover rules so it
 * wins on source order at equal specificity, and covered by the reduced-motion
 * block below through `.wsc-link`, which every inverted link also carries.
 *
 * blocks/media-checklist/style.css hand-rolls this same inversion for a link
 * inside a wysiwyg field. That one cannot use this class — the <a> is the
 * editor's and carries no theme class, so only a descendant selector can reach
 * it. Deliberate duplication, noted at both ends.
 */
.wsc-link--inverted {
	background-size: 100% 1px;
	background-position: 0 100%;
}

.wsc-link--inverted:hover,
.wsc-link--inverted:focus-visible {
	background-size: 0 1px;
	background-position: 100% 100%;
}

@media (prefers-reduced-motion: reduce) {
	.wsc-link {
		transition: none;
	}
}
