/* ==========================================================================
   Buttons, dot motif, labels, cards, forms, prose, reveal.
   ========================================================================== */

/* --- The dot --------------------------------------------------------------
   The brand element. Always #FFD500, always small, always decorative. */

.zd-dot {
	background: var(--color-brand);
	border-radius: var(--radius-pill);
	/* Every dot emits. It's the brand element, so it should read as lit rather
	   than printed. */
	box-shadow: var(--glow-sm);
	display: inline-block;
	flex: none;
	height: 8px;
	width: 8px;
}

.zd-dot--sm {
	height: 5px;
	width: 5px;
}

/* Breathing glow rather than an expanding ring — a status LED, not a ripple. */
.zd-dot--pulse {
	animation: zd-pulse 2.4s var(--ease) infinite;
}

@keyframes zd-pulse {
	0%,
	100% {
		box-shadow: var(--glow-sm);
	}
	50% {
		box-shadow: var(--glow-md);
	}
}

/* --- Dot grid -------------------------------------------------------------
   Engineering-diagram backing. Masked so it fades out rather than ending on a
   hard edge — you should notice it second, not first. */

.zd-dotgrid {
	position: absolute;
	inset: 0;
	background-image: radial-gradient(circle at 1px 1px, var(--dot-color) 1px, transparent 0);
	background-size: var(--dot-pitch) var(--dot-pitch);
	-webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 20%, transparent 75%);
	mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 20%, transparent 75%);
	pointer-events: none;
	z-index: 0;
}

/* --- Mono label ----------------------------------------------------------- */

.zd-label {
	align-items: center;
	color: var(--color-text-faint);
	display: flex;
	font-family: var(--font-mono);
	font-size: var(--fs-label);
	font-weight: 500;
	gap: var(--space-1);
	letter-spacing: var(--tracking-label);
	text-transform: uppercase;
}

.zd-section-header--center .zd-label {
	justify-content: center;
}

/* --- Buttons -------------------------------------------------------------- */

.zd-btn {
	align-items: center;
	border: 1px solid transparent;
	border-radius: var(--radius-btn);
	cursor: pointer;
	display: inline-flex;
	font-size: var(--fs-sm);
	font-weight: 700;
	gap: var(--space-1);
	height: var(--control-h);
	letter-spacing: -0.01em;
	justify-content: center;
	padding-inline: var(--space-3);
	text-align: center;
	transition: transform var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease),
		border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
	white-space: nowrap;
}

/* Primary is lit at rest and burns brighter on hover — the glow is the point,
   so it shouldn't need a hover to exist. */
.zd-btn--primary {
	background: var(--color-brand);
	box-shadow: var(--glow-md);
	color: var(--color-brand-ink);
}

.zd-btn--primary:hover {
	background: var(--color-brand-bright);
	box-shadow: var(--glow-lg);
	color: var(--color-brand-ink);
	transform: translateY(-2px);
}

/* The secondary sits next to a solid, glowing primary, so its edge is the only
   thing saying it's a button at all — it can't borrow the hairline the cards
   and dividers use. */
.zd-btn--secondary {
	background: transparent;
	border-color: var(--color-border-control);
	color: var(--color-text);
}

/* Secondary picks up the brand edge and a tight glow, so the pair reads as one
   electric system rather than "the yellow one and the plain one". Full brand
   rather than the 32%-alpha tint the cards use: against the stronger rest
   border that tint composites darker than the edge it replaces, so the hover
   read as the button dimming. */
.zd-btn--secondary:hover {
	background: var(--color-surface-2);
	border-color: var(--color-brand);
	box-shadow: var(--glow-sm);
	color: var(--color-text);
	transform: translateY(-2px);
}

.zd-btn--ghost {
	background: transparent;
	color: var(--color-text);
	height: auto;
	padding: 0;
}

.zd-btn--ghost:hover {
	color: var(--color-brand);
}

.zd-btn--ghost .zd-dot {
	transition: transform var(--dur-fast) var(--ease);
}

.zd-btn--ghost:hover .zd-dot {
	transform: translateX(4px);
}

/* A ghost button is a line of text with no box, so on a touch screen it offers
   a 25px target where 44 is the floor. A min-height rather than padding: the
   button is already a centred inline-flex, so the label stays where it was and
   only the tappable box grows. */
@media (max-width: 768px) {
	.zd-btn--ghost {
		min-height: var(--space-6);
	}
}

.zd-btn-group {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
}

/* --- Cards ---------------------------------------------------------------- */

.zd-card {
	background: var(--color-surface);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-card);
	box-shadow: var(--shadow-rest);
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	padding: var(--space-4);
	position: relative;
	transition: transform var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
		box-shadow var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}

.zd-card:hover {
	background: var(--color-surface-2);
	border-color: var(--color-border-brand);
	/* Depth shadow plus a brand bloom — the card lifts and energises together. */
	box-shadow: var(--shadow-hover), var(--glow-sm);
	transform: translateY(var(--lift));
}

.zd-card__title {
	color: var(--color-text);
	font-size: var(--fs-h3);
}

.zd-card__text {
	color: var(--color-text-muted);
	font-size: var(--fs-sm);
}

/* Stretched link — makes the whole card clickable without nesting interactive
   elements or wrapping a heading in an anchor. */
.zd-card__link::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
}

.zd-card:has(.zd-card__link:focus-visible) {
	outline: 2px solid var(--color-brand);
	outline-offset: 2px;
}

/* --- Icons ---------------------------------------------------------------- */

.zd-icon {
	height: 22px;
	width: 22px;
}

/* Vendor marks, inlined by zippydot_tech_mark(). Sized by height rather than a
   box, so the one wordmark in the set keeps its real proportions instead of being
   squashed into a square. Colour and hover belong to whichever section shows them. */
.zd-tech {
	flex: none;
	height: var(--space-3);
	width: auto;
}

/* A wordmark carries its weight across its width, so matching the symbols'
   height would make it the loudest thing in the row. */
.zd-tech--wide {
	height: var(--space-2);
}

.zd-icon-box {
	align-items: center;
	background: var(--color-surface-2);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-btn);
	color: var(--color-brand);
	display: inline-flex;
	height: var(--space-6);
	justify-content: center;
	margin-bottom: var(--space-2);
	transition: border-color var(--dur-fast) var(--ease);
	width: var(--space-6);
}

.zd-card:hover .zd-icon-box {
	border-color: var(--color-border-brand);
	box-shadow: var(--glow-sm);
}

/* --- Headline highlight ----------------------------------------------------
   Headlines are white with key words in brand yellow. Never gray — a muted
   word reads as de-emphasised, which is the opposite of a highlight. */

.zd-hl {
	color: var(--color-brand);
	text-shadow: var(--glow-text);
}

/* --- Prose (WP content) ---------------------------------------------------- */

.zd-prose {
	max-width: var(--measure);
	/* Editors paste raw URLs. One of those in a 320px column drags the whole
	   page sideways unless it's allowed to break. */
	overflow-wrap: break-word;
}

.zd-prose > * + * {
	margin-top: var(--space-3);
}

.zd-prose h2 {
	margin-top: var(--space-6);
}

.zd-prose h3 {
	margin-top: var(--space-5);
}

.zd-prose ul,
.zd-prose ol {
	padding-left: var(--space-3);
}

.zd-prose li + li {
	margin-top: var(--space-1);
}

/* Dot bullets — the motif carried into body copy. */
.zd-prose ul {
	list-style: none;
	padding-left: 0;
}

.zd-prose ul > li {
	padding-left: var(--space-3);
	position: relative;
}

.zd-prose ul > li::before {
	background: var(--color-brand);
	border-radius: var(--radius-pill);
	content: "";
	height: 5px;
	left: 0;
	position: absolute;
	top: 0.65em;
	width: 5px;
}

.zd-prose img,
.zd-prose figure {
	border-radius: var(--radius-card);
	overflow: hidden;
}

/* A figure is a card: same border and radius as the rest, with the caption
   sitting inside the frame rather than floating under it. */
.zd-prose figure {
	border: 1px solid var(--color-border);
}

/* Kills the inline-element gap under the image so the caption sits flush
   against it. */
.zd-prose figure img {
	display: block;
	width: 100%;
}

.zd-prose figcaption {
	background: var(--color-surface);
	border-top: 1px solid var(--color-border);
	color: var(--color-text-muted);
	font-family: var(--font-mono);
	font-size: var(--fs-sm);
	padding: var(--space-2) var(--space-3);
}

/* --- Code blocks ----------------------------------------------------------
   Token markup comes from highlight.js (assets/vendor/highlightjs). We ship no
   upstream theme — every colour below is ours, because the usual eight-hue
   scheme would put seven new accents on a page that is allowed one.

   The scheme is monochrome plus the brand: literals take the accent,
   identifiers and keywords brighten to white, structure recedes to faint.
   Distinctions are carried by value and weight rather than hue. */

pre code,
pre code.hljs {
	background: none;
	color: var(--color-text-muted);
	padding: 0;
}

.hljs-comment,
.hljs-quote {
	color: var(--color-text-faint);
	font-style: italic;
}

/* Literals — the one place the accent belongs, since these are the values a
   reader scans a snippet for. */
.hljs-string,
.hljs-number,
.hljs-literal,
.hljs-regexp,
.hljs-symbol,
.hljs-bullet,
.hljs-addition {
	color: var(--color-brand);
}

.hljs-keyword,
.hljs-built_in,
.hljs-type,
.hljs-selector-tag,
.hljs-section,
.hljs-name,
.hljs-tag,
.hljs-doctag,
.hljs-meta,
.hljs-strong {
	color: var(--color-text);
	font-weight: 500;
}

.hljs-title,
.hljs-title.class_,
.hljs-title.function_,
.hljs-function,
.hljs-attr,
.hljs-attribute,
.hljs-variable,
.hljs-template-variable,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-params,
.hljs-subst,
.hljs-link {
	color: var(--color-text);
}

.hljs-punctuation,
.hljs-operator,
.hljs-deletion {
	color: var(--color-text-faint);
}

.hljs-emphasis {
	font-style: italic;
}

/* --- Forms ---------------------------------------------------------------- */

.zd-input,
input[type="text"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="tel"],
textarea {
	background: var(--color-surface);
	/* A field has to look enterable before it's clicked, which the hairline
	   never managed against the surface it sits on. */
	border: 1px solid var(--color-border-control);
	border-radius: var(--radius-btn);
	color: var(--color-text);
	padding-inline: var(--space-2);
	transition: border-color var(--dur-fast) var(--ease);
	width: 100%;
}

/* Single-line controls take the shared control height rather than deriving one
   from padding and line-height, which is what left them taller than the button
   beside them. A textarea keeps its own height and pads vertically instead. */
.zd-input,
input[type="text"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="tel"] {
	/* Safari styles a search field natively — rounded ends, its own inner
	   metrics — and ignores most of the above without this. */
	appearance: none;
	height: var(--control-h);
	padding-block: 0;
}

textarea {
	padding-block: var(--space-2);
}

input::placeholder,
textarea::placeholder {
	color: var(--color-text-faint);
}

/* The focused field takes the accent outright. --color-border-strong sat below
   the resting control border, so focus used to darken the outline it was meant
   to emphasise. Keyboard users get the :focus-visible ring in base.css on top
   of this; the border change is what a pointer user sees. */
.zd-input:focus,
input:focus,
textarea:focus {
	border-color: var(--color-brand);
}

.zd-search {
	align-items: center;
	display: flex;
	gap: var(--space-1);
	max-width: 420px;
}

/* The input carries width: 100% from the rule above, which would push the
   button out of the row and then squash its label. Let the input take the
   remaining space instead, and keep the button at its natural width. */
.zd-search .zd-input {
	flex: 1;
	min-width: 0;
	width: auto;
}

.zd-search .zd-btn {
	flex: none;
}

/* --- Reveal ---------------------------------------------------------------
   Content is visible by default; main.js adds .zd-reveal-ready to <html> and
   only then are items hidden. A JS failure degrades to a plain visible page
   rather than a blank one. */

.zd-reveal-ready [data-reveal] {
	opacity: 0;
	transform: translateY(8px);
	transition: opacity var(--dur-reveal) var(--ease), transform var(--dur-reveal) var(--ease);
	transition-delay: var(--reveal-delay, 0ms);
}

.zd-reveal-ready [data-reveal].is-visible {
	opacity: 1;
	transform: none;
}

/* --- Pagination ------------------------------------------------------------ */

.zd-pagination {
	display: flex;
	gap: var(--space-1);
	justify-content: center;
	margin-top: var(--space-8);
}

.zd-pagination .page-numbers {
	align-items: center;
	border: 1px solid var(--color-border-control);
	border-radius: var(--radius-sm);
	color: var(--color-text-muted);
	display: inline-flex;
	font-size: var(--fs-sm);
	height: 40px;
	justify-content: center;
	min-width: 40px;
	padding-inline: var(--space-1);
	transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.zd-pagination .page-numbers:hover {
	border-color: var(--color-brand);
	color: var(--color-text);
}

.zd-pagination .page-numbers.current {
	background: var(--color-brand);
	border-color: var(--color-brand);
	color: var(--color-brand-ink);
}

/* 40px is a comfortable mouse target and a poor thumb one, and pagination is
   the one control on the page that gets tapped repeatedly. */
@media (max-width: 768px) {
	.zd-pagination .page-numbers {
		height: var(--space-6);
		min-width: var(--space-6);
	}
}
