/*
 * app.css — portal-wide custom rules that sit alongside Vanilla Framework 4.46.0.
 * Loaded from both internal/interfaces/http/views/layout/base.templ and admin.templ after
 * additional-icons.css so these rules can override icon defaults.
 *
 * Kept intentionally small. Add rules here only when Vanilla does not already
 * provide them.
 */

:root {
	/* Spacing scale — Vanilla 4.46.0 ships no native spacing tokens (colour-only).
	 * These complement --vf-color-* for a complete design system. Derived from
	 * actual in-repo usage; every value appears ≥ 2 times in the codebase. */
	--space-xs:  0.25rem;
	--space-sm:  0.5rem;
	--space-md:  0.75rem;
	--space-lg:  1rem;
	--space-xl:  1.5rem;
	--space-2xl: 2rem;
	--space-3xl: 3rem;

	/* Corner radius — Vanilla 4.46.0 is squared-off (no native radius token).
	 * House value is 4px; applied to rectangular surfaces below. Pill/circular
	 * elements (chips, badges, switches, radios, avatars) keep their own radius. */
	--radius: 4px;
	/* Brand palette — cluster-sampled from assets/brand/esoppari.png.
	 * --brand-blue is the ACTIONABLE accent (8.3:1 on white — AA for text);
	 * the teal fails text contrast (2.7:1) and is decorative only.
	 * --status-positive is SEMANTIC success (signed states) and must never
	 * be replaced by a brand colour — green-means-signed is load-bearing. */
	--brand-teal: #03B2A2;
	--brand-blue: #05508C;
	--brand-navy: #041A2F;
	--brand-tint: #EEF4FB;
	--status-positive: #0e8420;
	/* --vf-color-accent is deliberately NOT re-pointed at a brand colour.
	 * Vanilla reads it only in `.is-accent` heading variants and
	 * `.p-rule--highlight.is-accent::before` — neither of which this codebase
	 * uses — and re-declares it on .is-dark / .is-paper / .p-strip--* /
	 * .p-tooltip__message, so a :root override never reaches those subtrees
	 * anyway (the admin nav is .is-dark). The one rule here that did consume
	 * it was a :focus-visible ring, where the brand teal measures 2.66:1 on
	 * white — under the 3:1 WCAG 2.1 SC 1.4.11 wants from a focus indicator.
	 * Colour brand surfaces from the tokens above, explicitly. */

	/* PDF viewer theme — every hardcoded colour in component/pdf_viewer.templ. */
	--pdf-bg:      #525659;  /* canvas container background (dark grey) */
	--pdf-ctrl-bg: #f7f7f7;  /* control bar background (light grey) */
	--pdf-text:    #fff;     /* loading / error message text (white on dark) */
	--pdf-error:   #e95420;  /* error emphasis (Vanilla warning orange) */
}

/* PDF.js embed container — PDFViewer from pdf_viewer.mjs requires a positioned
 * ancestor with bounded height and its own overflow. The component wrapper sets
 * position:relative + bounded height; this rule makes the inner container fill it. */
.pdfjs-embed-container {
	position: absolute;
	inset: 0;
	overflow: auto;
	outline: none;
}

/* Focus ring: --brand-blue is 8.29:1 on white. It must clear WCAG 2.1
 * SC 1.4.11 (3:1 for non-text indicators) — the brand teal is 2.66:1 and
 * must never be used here. */
.pdfjs-embed-container:focus-visible {
	box-shadow: inset 0 0 0 2px var(--brand-blue, #06c);
}

/* PDFViewer component chrome (component/pdf_viewer.templ). The container height
 * is data-driven (data-viewer-height) and set via CSSOM in pdf-viewer.js init()
 * — it is intentionally NOT a style attribute, so the CSP needs no
 * style-src-attr allowance. */
.pdf-viewer {
	border: 1px solid #d9d9d9;
	display: flex;
	flex-direction: column;
}

.pdf-viewer__bar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: var(--space-xs) var(--space-sm);
	background: var(--pdf-ctrl-bg);
	border-bottom: 1px solid #d9d9d9;
	flex-shrink: 0;
}

.pdf-viewer__group {
	display: flex;
	align-items: center;
	gap: var(--space-xs);
}

.pdf-viewer__pageinfo {
	display: flex;
	align-items: center;
	gap: var(--space-xs);
	margin-left: var(--space-xs);
}

.pdf-viewer__page-input {
	width: 3.5rem;
	margin-bottom: 0;
	padding: 0.15rem 0.3rem;
}

/* Stage: the positioned, bounded ancestor PDFViewer scrolls inside. */
.pdf-viewer__stage {
	flex: 1;
	position: relative;
	background: var(--pdf-bg);
	min-height: 0;
}

/* Loading / error overlays painted over the stage. The loading overlay is
 * click-through (--loading) so it never traps interaction while the document
 * streams in; the error overlay keeps default pointer events so its message
 * stays selectable. */
.pdf-viewer__overlay {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--pdf-bg);
	color: var(--pdf-text);
}

.pdf-viewer__overlay--loading { pointer-events: none; }

.pdf-viewer__error-text { color: var(--pdf-error); }

/* Alpine.js x-cloak — hide elements until Alpine has initialised so users
 * never see the FOUC of every x-show branch rendered at once. */
[x-cloak] { display: none !important; }

/* Global confirm modal visibility. Vanilla's `.p-modal` is display:flex; the id
 * selector (higher specificity) keeps it hidden until confirm-modal.js adds the
 * `is-open` class. Class-based rather than an inline `style="display:none"` so
 * the signing portal's CSP needs no style-src-attr allowance. */
#confirm-modal { display: none; }
#confirm-modal.is-open { display: flex; }

/* htmx filter indicator — visible only while the associated form has an
 * in-flight request. Pair with `hx-indicator="#some-id"` on the form and
 * `class="filter-indicator p-icon--status-in-progress u-animation--spin"` on
 * the indicator span so htmx toggles .htmx-request on it during the request.
 */
.filter-indicator {
	display: none;
}

.htmx-request.filter-indicator {
	display: inline-block;
	vertical-align: middle;
}

/* Admin page-header bottom rule — matches the mockup's bordered title band
 * (mockups/pages/dashboard.html .mockup-page-header) so the title + actions
 * strip is visually separated from the page content below.
 *
 * Vanilla ships `.p-panel__header{display:flex}` with no wrap, so a long title
 * (e.g. the Finnish "Allekirjoituspyynnöt") squeezes the action cluster into a
 * narrow column — the buttons then wrap *inside* that column, stacking and
 * colliding with the title. Allow the header to wrap so the actions drop onto
 * their own row instead of being compressed. */
.l-main > .p-panel > .p-panel__header {
	border-bottom: 1px solid var(--vf-color-border-default, #e5e5e5);
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-sm);
}

/* The action cluster in a page header or an aside header. Vanilla only sets
 * `margin-left:auto`, so the buttons are plain inline flow with no gap control.
 * Make it a real flex row and zero the buttons' default bottom margin (a header
 * is not a form footer).
 *
 * Deliberately NOT a bare `.p-panel__controls` rule: the nav drawers reuse that
 * class for the "Menu" toggle (admin_nav / operator_nav), which must keep its
 * own layout. */
.l-main > .p-panel > .p-panel__header > .p-panel__controls,
.l-aside .p-panel__header > .p-panel__controls {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-sm);
}

.l-main > .p-panel > .p-panel__header > .p-panel__controls > *,
.l-aside .p-panel__header > .p-panel__controls > * {
	margin-bottom: 0;
}

@media (max-width: 619px) {
	/* Actions get their own full-width row beneath the title, left-aligned with
	 * it, rather than a cramped right-hand column. The header's `gap` already
	 * separates the row from the title, so drop Vanilla's padding-top; the
	 * padding-bottom is what keeps the buttons clear of the header's bottom rule
	 * (without it they sit flush on the line). */
	.l-main > .p-panel > .p-panel__header > .p-panel__controls {
		flex-basis: 100%;
		margin-left: 0;
		padding-top: 0;
		padding-bottom: var(--space-md);
	}
}

/* Clickable dashboard stat cards — the card <div> stays as the outer box,
 * the <a> wraps only the inner content so hover/focus work consistently
 * across browsers. No :has() required. */
.p-data-spotlight__block a {
	display: block;
	text-decoration: none;
	color: inherit;
}

.p-data-spotlight__block a:hover,
.p-data-spotlight__block a:focus-visible {
	background: #f7f7f7;
	outline: none;
}

/* Circular icon-only action button for crowded table action columns. Vanilla
 * has no icon-button variant, so this pairs with p-button--base / --positive /
 * --negative (colour) and a p-tooltip wrapper (hover label) via
 * component.IconAction. ~32px hit area; on touch a tap fires the action and
 * the aria-label is the accessible name (the tooltip is a hover-only extra). */
.icon-button {
	width: 2rem;
	height: 2rem;
	min-width: 2rem;
	min-height: 2rem;
	padding: 0;
	margin: 0;
	line-height: 1;
	border-radius: 50%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
}

.icon-button > [class*="p-icon"] {
	margin: 0;
}

/* The neutral (base) icon button has no resting border of its own, so give it
 * a visible circular outline at rest and keep the same colour on hover/focus
 * (Vanilla would otherwise change it on hover). */
.p-button--base.icon-button,
.p-button--base.icon-button:hover,
.p-button--base.icon-button:focus-visible {
	border-color: var(--vf-color-border-high-contrast, #666);
}

/* Icon buttons are circular. The portal-wide 4px `--radius` on `.p-button*`
 * (rectangular surfaces) would otherwise square them; re-assert the circle with
 * a two-class selector so it wins regardless of rule order. Per the --radius
 * token contract, pill/circular elements keep 50%. */
.p-button.icon-button,
.p-button--base.icon-button,
.p-button--positive.icon-button,
.p-button--negative.icon-button {
	border-radius: 50%;
}

/* Row-action tooltips are repositioned to position:fixed by tooltip.js (so they
 * escape every overflow ancestor and never clip on short rows). Its arrow would
 * misalign once the bubble is detached/flipped, so hide it for icon-button
 * tooltips. */
.icon-button + .p-tooltip__message::before,
.icon-button + .p-tooltip__message::after {
	display: none;
}

/* Defensive no-JS fallback: if tooltip.js hasn't run, open up the MAIN content
 * panel so the CSS-positioned (absolute) bubble isn't clipped at the panel edge.
 * The page still scrolls via `.l-main`; aside panels (`.l-aside .p-panel__content`,
 * which rely on their own overflow for the PDF viewer) are untouched. */
.l-main .p-panel__content {
	overflow: visible;
}

/* Make the content panel fill the viewport. Vanilla ships `.p-panel{min-height:100%}`,
 * but a percentage min-height only resolves when the parent has a definite height —
 * which the grid-item `.l-main` does not reliably provide across breakpoints (it
 * fails on the mobile shell), so on short pages the panel collapsed to content
 * height and the bare area below it showed through. A flex column with the panel as
 * a grow-only child fills the height without depending on percentage resolution:
 * short content stretches the (white) panel to the viewport bottom; tall content
 * keeps its natural height and `.l-main` scrolls. */
.l-main {
	display: flex;
	flex-direction: column;
}

.l-main > .p-panel {
	flex: 1 0 auto;
}

/* §5.1 Responsive signer grid — 5 columns on desktop, stacked below
 * Vanilla's 620px mobile breakpoint. Used by the signing-request
 * wizard's Step 3 (signers). The per-row visually-hidden labels from
 * Phase 1 §1.1 continue to serve screen readers when stacked. */
.signer-grid {
	display: grid;
	/* Country + language are wider than the English labels need because the
	   Finnish/Swedish options ("Mikä tahansa", "Sama kuin asiakirja") fill the
	   box; the larger column-gap keeps the two select boxes visibly separated. */
	grid-template-columns: 2rem 1fr 1fr 7.5rem 7.5rem 2rem;
	gap: var(--space-md);
	align-items: center;
}

.signer-grid--header {
	margin-bottom: var(--space-sm);
}

@media (max-width: 619px) {
	.signer-grid--header { display: none; }
	.signer-grid { grid-template-columns: 1fr; }
	/* The single-column stack stretches every cell; keep the remove (×)
	   button its natural size at the row's end instead of a full-width
	   negative bar (wizard Step 3 and the template form share this grid). */
	.signer-grid > .p-button--negative { justify-self: end; min-width: 3rem; }
}

/* §5.1a Signing capacity — sub-row below each signer in the wizard Step 3 grid.
 * Contains capacity chips, a compact "Add capacity" trigger, and an inline
 * form panel for adding new capacities. */
.signer-capacity {
	margin: var(--space-xs) 0 0 2.5rem;
}

/* Quick-send has no leading index column (unlike the wizard's signer grid),
   so its capacity block sits flush-left rather than indented 2.5rem. */
.signer-capacity--flush {
	margin-left: 0;
}

/* Chips + the "personal (default)" hint + the Add-capacity trigger share one
   wrapping row, so the control is compact with or without capacities. */
.signer-capacity__inline {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-xs);
}

/* Vanilla gives buttons and small-text blocks a bottom margin; in this centered
   row that shifts their visible centre off the chips. Zero every item's margin
   so the chips, the "personal (default)" hint, and the button all line up.
   !important beats Vanilla's two-class `.is-small.p-button` margin rule. */
.signer-capacity__inline > * {
	margin-bottom: 0 !important;
}

.signer-capacity__form {
	border: 1px solid #cdcdcd;
	border-radius: var(--radius);
	padding: var(--space-sm) var(--space-sm) var(--space-xs);
	margin-top: var(--space-xs);
	background: #fafafa;
}

.signer-capacity__form .p-form__group {
	margin-bottom: var(--space-xs);
}

/* Collapse the stacked label/control gap so the inline capacity form stays
   compact (it is a rarely-used, secondary affordance). */
.signer-capacity__form .p-form__label {
	margin-bottom: 0;
	padding-bottom: 0;
}

.signer-capacity__form input,
.signer-capacity__form select {
	margin-bottom: 0;
	min-height: 0;
	padding-top: 0.2rem;
	padding-bottom: 0.2rem;
	font-size: 0.875rem;
}

.signer-capacity__form-actions {
	display: flex;
	gap: var(--space-sm);
}

/* Desktop (>=620px): the three capacity fields (Type / Org name / Business ID)
   sit inline on one row; below that they stack (default block flow). The
   representative wrapper uses display:contents so its two fields become direct
   flex items alongside Type. */
@media (min-width: 620px) {
	.signer-capacity__form {
		display: flex;
		flex-wrap: wrap;
		align-items: flex-end;
		gap: var(--space-sm);
	}
	.signer-capacity__rep {
		display: contents;
	}
	.signer-capacity__form > .p-form__group,
	.signer-capacity__rep > .p-form__group {
		flex: 1 1 8rem;
		margin-bottom: 0;
	}
	.signer-capacity__form > .p-form-validation,
	.signer-capacity__form-actions {
		flex-basis: 100%;
	}
}

.signer-capacity__form .p-form-validation__message {
	margin: 0 0 var(--space-xs);
}

/* §5.2 Responsive filter row — wraps on narrow widths on desktop. Below
 * Vanilla's 620px mobile breakpoint the fields collapse behind a "Filters"
 * toggle button (component.FilterBar / filterToggle) and, when expanded, lay
 * out as a compact 2-column grid instead of a full-width vertical stack.
 * Used by every admin + operator list-page filter form. */
.filter-row {
	display: flex;
	gap: var(--space-lg);
	flex-wrap: wrap;
	align-items: flex-end;
	margin: var(--space-lg) 0;
	padding: var(--space-lg);
	border: 1px solid var(--vf-color-border-low-contrast, #d9d9d9);
	border-radius: var(--radius);
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

/* Mobile "Filters" toggle bar — hidden on desktop, shown <620px. Holds a
 * right-aligned, icon-only .icon-button (Vanilla p-tooltip) that reveals/hides
 * the filter fields; desktop renders the fields inline instead. */
.filter-toggle-bar { display: none; }

@media (max-width: 619px) {
	.filter-toggle-bar {
		display: flex;
		justify-content: flex-end;
		margin-top: var(--space-md);
		margin-bottom: var(--space-lg);
	}
	/* Collapsed = hidden ONLY on mobile. On desktop the class is inert, so a
	 * statically-collapsed region still renders inline >=620px. */
	.filter-row.is-collapsed {
		display: none;
	}
	/* Expanded: compact 2-column grid. The raised card chrome (border, padding,
	 * radius, shadow) is inherited from the base .filter-row rule.
	 * minmax(0,1fr) + min-width:0 below neutralise the u-min-width-* utilities so
	 * two cells never overflow a ~375px viewport. */
	.filter-row {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--space-md);
		align-items: end;
	}
	.filter-row .p-form__group {
		margin-bottom: 0;
		min-width: 0;
	}
	/* Span the action cluster full-width so buttons get their own row. It is
	 * either a .filter-actions div (Filter/Reset/spinner) or a trailing
	 * .u-align-self--end group (Apply/spinner) — target both explicitly rather
	 * than :last-child, so a field appended after the actions can't be
	 * accidentally full-widthed. */
	.filter-row > .filter-actions,
	.filter-row > .u-align-self--end {
		grid-column: 1 / -1;
	}
}

/* ─── Public signing portal ─────────────────────────────────────────────────
 * Tenant strip, breadcrumb stepper, trust strip, eID provider tiles,
 * receipt-style completion card. Used only by templates under
 * internal/interfaces/http/views/page/signing/. Admin / registration are unaffected. */

/* Body background for signing-portal pages. Must match .trust-strip and
 * .signing-footer (#f7f7f7) so the trust-strip and footer read as seamless
 * extensions of the page rather than distinct grey bands. The admin /
 * registration portals continue to use Vanilla's `.is-paper` body tone. */
.signing-body {
	background: #f7f7f7;
}

/* Tenant brand strip at the top of every signing-portal page. */
.signing-topbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: var(--space-md) 0;
	border-bottom: 1px solid rgba(0, 0, 0, 0.08);
	background: #fff;
}

/* Inner flex row of the brand strip — tenant brand on the left, secure-session
 * badge on the right. The .u-fixed-width wrapper is the topbar's single flex
 * child, so the layout row lives one level in. */
.signing-topbar__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	width: 100%;
}

.signing-topbar__brand {
	display: flex;
	align-items: center;
	gap: var(--space-md);
}

.signing-topbar__initials {
	width: 2rem;
	height: 2rem;
	border-radius: var(--radius);
	background: var(--brand-navy);
	color: #fff;
	font-weight: 600;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
}

.signing-topbar__secure {
	color: #666;
	font-size: 0.875rem;
}

/* Right-hand group: secure badge + language trigger, kept in one flex row so
 * they collapse gracefully on mobile. */
.signing-topbar__right {
	display: flex;
	align-items: center;
	gap: var(--space-lg);
}

/* Language trigger in the topbar — always visible, all breakpoints. */
.signing-topbar__right .locale-trigger {
	font-size: 0.875rem;
	color: #333;
	white-space: nowrap;
	margin-bottom: 0;
}

@media (max-width: 619px) {
	.signing-topbar__secure { display: none; }
}

/* 4-step breadcrumb stepper. Vanilla's p-breadcrumbs is the structural base;
 * we add `aria-current="page"` styling and a muted state for upcoming steps.
 * The visited (clickable) link styling is whatever Vanilla applies by default. */
.signing-stepper { margin: 0; }
.signing-stepper .p-breadcrumbs__items {
	display: flex;
	gap: var(--space-md);
	flex-wrap: wrap;
	margin: 0;
	padding: 0;
	list-style: none;
}
.signing-stepper .p-breadcrumbs__item[aria-current="page"] { font-weight: 500; }
.signing-stepper .p-breadcrumbs__item.is-upcoming { color: #999; }

/* Trust strip: 4 inline trust signals (eIDAS, RFC 3161, PAdES, GDPR) below
 * the landing hero. Wraps to multi-line on narrow widths. */
.trust-strip {
	background: #f7f7f7;
	padding: var(--space-xl) var(--space-lg);
	border-top: 1px solid rgba(0, 0, 0, 0.05);
	border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}

.trust-strip ul {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-2xl);
	margin: 0;
	padding: 0;
	list-style: none;
}

.trust-strip li {
	display: inline-flex;
	align-items: center;
	gap: var(--space-sm);
}

.trust-strip strong { font-weight: 500; }

/* eID provider selectable tile. Uses :has() so the entire <label> reflects
 * the radio-input state without per-tile JS. */
.eid-card {
	display: block;
	cursor: pointer;
	padding: var(--space-xl);
	border: 2px solid rgba(0, 0, 0, 0.1);
	border-radius: var(--radius);
	transition: border-color 0.15s, box-shadow 0.15s;
	background: #fff;
	text-decoration: none;
	color: inherit;
	height: 100%;
	width: 100%;
}

.eid-card:hover {
	border-color: var(--brand-blue);
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.eid-card:has(input:checked) {
	border-color: var(--brand-blue);
	background: var(--brand-tint);
}

.eid-card__head {
	display: flex;
	align-items: center;
	gap: var(--space-md);
	margin-bottom: var(--space-md);
}

.eid-card__logo {
	width: 3rem;
	height: 3rem;
	border-radius: var(--radius);
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	font-weight: 700;
	font-size: 0.875rem;
	color: #fff;
}

/* Provider brand colours for the logo badge. Closed provider set — keyed by the
 * provider code so the templ tile passes a class, not an inline background. */
.eid-card__logo--ftn   { background: #003580; }
.eid-card__logo--sbid  { background: #006aa7; }
.eid-card__logo--nbid  { background: #ba0c2f; }
.eid-card__logo--mitid { background: #c8102e; }

.eid-card__meta {
	color: #666;
	font-size: 0.875rem;
}

.eid-card input[type="radio"] { margin: 0 0 0 auto; }

/* Hero CTA on the landing page — oversized so it's the obvious next step. */
.signer-cta {
	display: inline-block;
	min-width: 18rem;
	font-size: 1.125rem !important;
	padding: var(--space-lg) var(--space-2xl) !important;
}

/* Width overrides for the hero CTA in constrained layouts: --fill makes it span
 * its flex parent (paired Decline/Sign forms); --auto lets it shrink to content
 * (the secondary Decline on the landing overview). */
.signer-cta--fill { width: 100%; min-width: 0; }
.signer-cta--auto { min-width: auto; }

/* Receipt-style centred card used by the Identity-Verified and Complete pages. */
.receipt {
	max-width: 40rem;
	margin: 0 auto;
	padding: var(--space-3xl);
	text-align: center;
}

.receipt .p-list--divided { text-align: left; margin: var(--space-2xl) auto; max-width: 32rem; }
.receipt .p-list--divided > div {
	display: grid;
	grid-template-columns: 12rem 1fr;
	gap: var(--space-lg);
	padding: var(--space-sm) 0;
	border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.receipt .p-list--divided dt { color: #666; font-size: 0.875rem; }
.receipt .p-list--divided dd { margin: 0; }

@media (max-width: 619px) {
	.receipt { padding: var(--space-xl); }
	.receipt .p-list--divided > div {
		grid-template-columns: 1fr;
		gap: var(--space-xs);
	}
}

/* Big green medallion that anchors the receipt visually. */
.success-medal {
	width: 5rem;
	height: 5rem;
	border-radius: 50%;
	background: var(--status-positive);
	color: #fff;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	margin-bottom: var(--space-lg);
	box-shadow: 0 4px 16px rgba(14, 132, 32, 0.25);
}

.success-medal i { font-size: 2.5rem !important; }

/* Medal colour variants for non-success terminal states. Base .success-medal is
 * green; --caution is amber (cancelled / expired / timed-out / failed) and
 * --neutral is grey (GDPR-erased). Replaces per-element inline background +
 * box-shadow overrides on the signing-portal terminal pages. */
.success-medal--caution { background: #cc7a1d; box-shadow: 0 4px 16px rgba(204, 122, 29, 0.25); }
.success-medal--neutral { background: #888; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); }

/* "Legally binding under eIDAS" hint under the receipt details. */
.receipt-verify {
	background: var(--brand-tint);
	border: 1px solid var(--brand-blue);
	border-radius: var(--radius);
	padding: var(--space-md) var(--space-lg);
	margin-top: var(--space-xl);
	text-align: left;
	display: flex;
	align-items: flex-start;
	gap: var(--space-md);
}

.receipt-verify i {
	color: var(--brand-blue);
	flex-shrink: 0;
	margin-top: var(--space-xs);
}

/* Footer strip rendered at the bottom of every signing-portal page. */
.signing-footer {
	background: #f7f7f7;
	padding: var(--space-lg) 0;
	margin-top: var(--space-3xl);
	color: #666;
	font-size: 0.875rem;
}

.signing-footer p { margin: 0; text-align: center; max-width: none; }

/* Language modal — card-grid locale picker shared across admin, signing,
 * registration, and verify surfaces. Design B: circular language-code badge
 * (EN/FI/SV), green accent on selected card. */
.locale-modal .p-modal__dialog { max-width: 38rem; }
.locale-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-lg); }
@media (max-width: 619px) { .locale-grid { grid-template-columns: 1fr; } }
.locale-card {
	display: flex; flex-direction: column; align-items: center; cursor: pointer;
	padding: var(--space-xl) var(--space-lg); border: 2px solid rgba(0,0,0,0.1);
	border-radius: var(--radius); transition: border-color 0.15s, box-shadow 0.15s;
	background: #fff; min-height: 7rem;
}
.locale-card:hover { border-color: var(--brand-blue); box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.locale-card:focus-visible { outline: 2px solid var(--brand-blue); outline-offset: 2px; }
.locale-card.is-selected { border-color: var(--brand-blue); background: var(--brand-tint); }
.locale-card__code {
	width: 3rem; height: 3rem; border-radius: 50%; background: #e5e5e5;
	display: flex; align-items: center; justify-content: center;
	font-weight: 700; font-size: 0.875rem; color: #333; text-transform: uppercase;
	margin-bottom: var(--space-sm);
}
.locale-card.is-selected .locale-card__code { background: var(--brand-blue); color: #fff; }
.locale-card__native { font-size: 1.25rem; font-weight: 500; margin-bottom: 0.125rem; }
.locale-card__english { font-size: 0.875rem; color: #666; }
/* Trigger link in signing footer / registration. */
.locale-trigger { display: inline-flex; align-items: center; gap: var(--space-sm); cursor: pointer; color: #333; font-size: inherit; }
.locale-trigger:hover { text-decoration: underline; }

/* Public /verify/{id} page — centred integrity banner, upload card, and the
 * hash-status line. Constrained narrower than the receipt so the verify result
 * reads as a focused statement. */
.verify-banner { max-width: 36rem; margin: 0 auto var(--space-xl); }
.verify-status { min-height: 1.25rem; margin: var(--space-md) 0 0; }

/* Empty state — list page with zero rows or search prompt.
 * Dashed-border card with circular icon badge, heading, body, and optional CTA.
 * Pattern documented in mockups/components.html#states. */
.empty-state {
	text-align: center;
	padding: var(--space-3xl) var(--space-lg);
	border: 1px dashed #d9d9d9;
	border-radius: var(--radius);
	background: #fafafa;
}

.empty-state__icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 4rem;
	height: 4rem;
	border-radius: 50%;
	background: #eef5fa;
	margin-bottom: var(--space-lg);
}

.empty-state__icon i { font-size: 1.75rem; }

.empty-state h3 { margin: 0 0 var(--space-xs); }

.empty-state p {
	margin: 0 auto;
	max-width: 28rem;
}

.empty-state p:not(:last-child) { margin-bottom: var(--space-xl); }

/* Centres the CTA slot whatever it is handed. .empty-state sets text-align:
   center, which centres an inline button but does nothing for a CTA that is
   itself a flex row — those fell to flex-start and sat hard against the left
   edge. flex-wrap so a multi-button CTA wraps instead of overflowing. */
.empty-state__cta {
	display: flex;
	justify-content: center;
	flex-wrap: wrap;
}

/* Registration plan picker (Step 1) — Vanilla's p-pricing-block pattern used
   as a selectable radio group. `.plan-tier-picker` scopes the deviation from
   the stock (marketing) pattern: every tier is a bordered box so the choices
   read as cards, and the selected tier's border turns Vanilla's positive
   green (matching its "Selected" p-button--positive CTA — the theme accent
   var resolves teal, which would clash). aria-checked is bound by the Alpine
   `register` component, so no extra class toggling is needed.
   Annotated in mockups/pages/registration.html. */
.plan-tier-picker .p-pricing-block__tier {
	border: 1px solid #d9d9d9;
	border-radius: var(--radius);
	padding: var(--space-lg);
	margin-bottom: var(--space-lg);
}

.plan-tier-picker .p-pricing-block__tier[aria-checked="true"] {
	border-color: var(--brand-blue);
	/* Second ring instead of a wider border — no layout shift on select. */
	box-shadow: 0 0 0 1px var(--brand-blue);
}

/* The picker renders inside the registration <form>, and Vanilla zeroes
   list-item padding inside forms (`form .p-list--divided .p-list__item`,
   a checkbox-list affordance). That collapses the offerings list and leaves
   the ::before tick (fixed at top .75rem) riding below its text line.
   Restore the stock divided-list rhythm. */
.plan-tier-picker .p-list--divided .p-list__item {
	padding-top: 0.5rem;
	padding-bottom: 1rem;
}

/* ─── Layout utilities ──────────────────────────────────────────────────────
 * Small, composable single-property helpers that replace inline style=
 * attributes (the CSP drops style-src-attr 'unsafe-inline'; see
 * internal/interfaces/middleware/security_headers.go). Vanilla 4.46.0 ships no
 * spacing/flex utilities, so these complement its u-* set. Spacing uses the
 * --space-* scale (xs .25 · sm .5 · md .75 · lg 1 · xl 1.5 · 2xl 2 rem).
 *
 * Margin helpers are !important so they (a) override Vanilla component margins
 * and (b) compose with Vanilla's `u-no-margin` (also !important) — e.g. the old
 * `margin:0 0 1.5rem` becomes `class="u-no-margin u-mb--xl"`: u-no-margin zeroes
 * all sides, u-mb--xl wins for the bottom. Compose freely:
 * `class="u-flex u-gap--sm u-flex--wrap u-mb--lg"`. */
.u-mt--xs  { margin-top: var(--space-xs) !important; }
.u-mt--sm  { margin-top: var(--space-sm) !important; }
.u-mt--md  { margin-top: var(--space-md) !important; }
.u-mt--lg  { margin-top: var(--space-lg) !important; }
.u-mt--xl  { margin-top: var(--space-xl) !important; }
.u-mt--2xl { margin-top: var(--space-2xl) !important; }
/* Mobile-only top margin — separates a 50/50 split's right column from the left
 * column once they stack below 619px (Vanilla stacks .row columns flush). */
@media (max-width: 619px) { .u-mt--xl-mobile { margin-top: var(--space-xl) !important; } }
.u-mb--xs  { margin-bottom: var(--space-xs) !important; }
.u-mb--sm  { margin-bottom: var(--space-sm) !important; }
.u-mb--md  { margin-bottom: var(--space-md) !important; }
.u-mb--lg  { margin-bottom: var(--space-lg) !important; }
.u-mb--xl  { margin-bottom: var(--space-xl) !important; }
.u-mb--2xl { margin-bottom: var(--space-2xl) !important; }
.u-ml--sm  { margin-left: var(--space-sm) !important; }
.u-ml--lg  { margin-left: var(--space-lg) !important; }
.u-mr--xs  { margin-right: var(--space-xs) !important; }
.u-mr--sm  { margin-right: var(--space-sm) !important; }

.u-flex          { display: flex; }
.u-inline-flex   { display: inline-flex; }
.u-flex--center  { align-items: center; }
.u-flex--col     { flex-direction: column; }
.u-flex--wrap    { flex-wrap: wrap; }
.u-flex--1       { flex: 1; }
.u-align-self--end { align-self: flex-end; }
.u-justify--between { justify-content: space-between; }
.u-justify--center  { justify-content: center; }
.u-justify--end     { justify-content: flex-end; }
.u-gap--xs       { gap: var(--space-xs); }
.u-gap--sm       { gap: var(--space-sm); }
.u-gap--md       { gap: var(--space-md); }
.u-gap--lg       { gap: var(--space-lg); }
.u-row-gap--lg   { row-gap: var(--space-lg); }

.u-mr--xl        { margin-right: var(--space-xl) !important; }
.u-align--baseline { align-items: baseline; }

.u-inline        { display: inline; }
.u-inline-block  { display: inline-block; }
.u-full-width    { width: 100%; }
.u-height-full   { height: 100%; }
.u-overflow-hidden { overflow: hidden; }
.u-clickable     { cursor: pointer; }
.u-bold          { font-weight: bold; }
.u-fs--sm        { font-size: 0.75rem; }
.u-pad--2xl      { padding: var(--space-2xl); }

/* Readable-width card constraints (admin/operator/registration form panels). */
.u-maxw--18 { max-width: 18rem; }
.u-maxw--32 { max-width: 32rem; }
.u-maxw--36 { max-width: 36rem; }
.u-maxw--40 { max-width: 40rem; }
.u-maxw--44 { max-width: 44rem; }

/* Standalone-button comfortable minimum widths (form selects, CTAs). */
.u-min-width-10 { min-width: 10rem; }
.u-min-width-12 { min-width: 12rem; }
.u-min-width-14 { min-width: 14rem; }
.u-min-width-16 { min-width: 16rem; }

/* Link that inherits surrounding text styling (clickable cards / table rows). */
.u-plain-link { text-decoration: none; color: inherit; }

/* <button> stripped to look like inline text (nav logout, link-style submit). */
.u-button-reset { border: none; background: none; padding: 0; cursor: pointer; text-align: left; color: inherit; font: inherit; }

/* Unstyled <fieldset> wrapper (no border/margin/padding) — used to group radio
 * tiles without the default fieldset chrome. */
.u-fieldset-bare { margin: 0; padding: 0; border: 0; }

/* Flip a glyph 180° (e.g. reuse back-to-top icon as jump-to-bottom). */
.u-rotate-180 { transform: rotate(180deg); }

/* Two-column definition row used by the signing-request wizard + registration
 * review panes (label | value). One class per dt/dd row. The --plain modifier
 * drops the divider for the final row. */
.review-row {
	display: grid;
	grid-template-columns: 10rem 1fr;
	gap: var(--space-sm);
	padding: var(--space-sm) 0;
	border-bottom: 1px solid var(--vf-color-border-low-contrast, #eee);
}
.review-row--plain { border-bottom: 0; }

/* Dimmed table row for an offboarded membership (operator user detail). */
.is-offboarded { opacity: 0.5; }

/* Filter-row submit/clear button group — the 0.15rem bottom padding baselines
 * the buttons with the adjacent form-field controls. Used by every admin /
 * operator list-page filter form. */
.filter-actions { display: flex; gap: var(--space-sm); align-items: center; padding-bottom: 0.15rem; }

.u-capitalize { text-transform: capitalize; }

/* Long-tail exact-value helpers — one or two call sites each, kept as utilities
 * so no inline style attribute survives. */
.u-relative       { position: relative; }
.u-z--dropdown    { z-index: 200; }
.u-height-stretch { height: stretch; height: -webkit-fill-available; }
.u-no-border      { border: none; }
.u-no-shadow      { box-shadow: none; }
.u-bg--alt        { background-color: var(--vf-color-background-alt); }
.u-m--lg          { margin: var(--space-lg) !important; }
.u-mt--n-xs       { margin-top: -0.25rem !important; }
.u-mt--n-sm       { margin-top: -0.5rem !important; }
.u-mt--n-lg       { margin-top: -1rem !important; }
.u-fs--xs         { font-size: 0.8125rem; }
.u-fs--lg         { font-size: 1.5rem; }
.u-pad--xs-sm     { padding: var(--space-xs) var(--space-sm); }
.u-pad--sm-lg     { padding: var(--space-sm) var(--space-lg); }
.u-pad--2xl-lg    { padding: var(--space-2xl) var(--space-lg); }
.u-pad--3xl-xl    { padding: var(--space-3xl) var(--space-xl); }
/* Wizard "Signing Order Preview" divider (own border colour, not the token). */
.u-divider-top    { margin-top: var(--space-xl); padding-top: var(--space-lg); border-top: 1px solid #cdcdcd; }
/* Centred hero band on the full-page error layout. */
.error-hero       { min-height: 60vh; display: flex; align-items: center; }
/* Wizard per-signer validation message, indented under the signer field. */
.wizard-signer-error { margin: var(--space-xs) 0 0 2.5rem !important; }
/* Language modal — hidden until toggled via is-open class (same pattern as
 * confirm-modal). Display is class-gated, not inline style (CSP safe). */
#language-modal { display: none; }
#language-modal.is-open { display: flex; }
/* Signing confirmation countdown — turn the remaining-time figure red in the
 * final minute (is-urgent toggled by eid-countdown.js) as a non-blocking cue. */
[data-assertion-countdown].is-urgent strong { color: #c7162b; }
/* Quick-send landing — inherited-policy summary rail and its key/value list.
 * The compose/upload column and signer rows reuse Vanilla grid + the layout
 * utilities; only the rail panel + kv list need bespoke rules. */
.quick-send__rail   { background: #f4f7fb; border: 1px solid #d3e1f3; border-radius: var(--radius); padding: var(--space-lg) var(--space-xl); }
.quick-send__kv     { list-style: none; margin: var(--space-sm) 0 0; padding: 0; }
.quick-send__kv li  { display: flex; justify-content: space-between; gap: var(--space-lg); padding: 0.4rem 0; border-bottom: 1px dashed #d3e1f3; font-size: 0.8125rem; }
.quick-send__kv li:last-child { border-bottom: 0; }
.quick-send__kv li span:first-child { color: var(--vf-color-text-muted, #767676); }
.quick-send__doc    { border: 2px solid var(--brand-blue); border-radius: var(--radius); background: var(--brand-tint); padding: var(--space-md) var(--space-lg); }
/* Quick-send dropzone, signer grid, and section head — match mockups/pages/quick-send-dashboard.html. */
.quick-send__section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-lg); }
/* The template + group selects to the right of the section heading. On desktop
   they sit inline; below Vanilla's 620px breakpoint the heading row stacks and
   the selects go full-width — two 12rem-min selects on one row otherwise force
   the card past the viewport (horizontal scroll, clipped dropzone). */
.quick-send__section-controls { display: flex; gap: var(--space-sm); }
@media (max-width: 619px) {
	.quick-send__section-head { flex-direction: column; align-items: stretch; }
	.quick-send__section-controls { flex-direction: column; }
	.quick-send__section-controls .p-form__group { min-width: 0; }
	.quick-send__section-controls select { width: 100%; }
}
.quick-send__dropzone { border: 2px dashed #c7c7c7; border-radius: var(--radius); background: #fafafa; padding: var(--space-2xl) var(--space-xl); text-align: center; color: #666; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 220px; cursor: pointer; }
.quick-send__dropzone.is-dragover { border-color: #0066cc; background: #eef5fb; }
/* The Vanilla validation message tucks up under a normal input; after the tall
   dropzone (no bottom margin) that pulls it onto the dashed border. Push it clear. */
.p-form__group:has(> .quick-send__dropzone) .p-form-validation__message { margin: var(--space-sm) 0 0; }
/* Desktop: email|name grid takes the row, the remove button sits top-right; the
   email-error and capacity blocks wrap to their own full-width lines below. */
.quick-send__signer { display: flex; flex-wrap: wrap; gap: var(--space-sm); align-items: start; margin-bottom: var(--space-md); }
.quick-send__signer-row { flex: 1 1 18rem; display: grid; grid-template-columns: 1.6fr 1.2fr; gap: var(--space-sm); }
/* Vanilla inputs carry a ~1.25rem bottom margin; with the row wrapping the
   capacity block onto its own line that margin became dead vertical space
   between the signer and its capacities. Zero it — the flex gap spaces them. */
.quick-send__signer-row input { margin-bottom: 0; }
.quick-send__signer-remove { flex: 0 0 auto; }
.quick-send__signer > .signer-capacity,
.quick-send__signer-error { flex-basis: 100%; }
.quick-send__rail-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-sm); }
/* Mobile: stack the block and push the remove button below the capacities so it
   no longer sits awkwardly between the name field and the capacity controls. */
@media (max-width: 619px) {
	.quick-send__signer { flex-direction: column; align-items: stretch; }
	/* In a column flex the shorthand basis becomes the main-axis HEIGHT — reset
	   it so the stacked row/blocks size to their content, not 18rem/100% tall. */
	.quick-send__signer-row { flex: 0 0 auto; grid-template-columns: 1fr; }
	.quick-send__signer > .signer-capacity,
	.quick-send__signer-error { flex-basis: auto; }
	.quick-send__signer > .signer-capacity { order: 4; }
	.quick-send__signer-remove { order: 5; align-self: start; }
}

/* ---------------------------------------------------------------------------
 * Rounded corners — soften Vanilla's squared-off surfaces with the house
 * --radius token. Vanilla sets no radius on these, so plain overrides win by
 * source order (app.css loads last); no !important needed. Pill/circular
 * components (p-chip, p-badge, p-switch, p-radio, p-checkbox, tooltip pointers)
 * are deliberately excluded and keep their native radius.
 * --------------------------------------------------------------------------- */

/* Buttons (all variants used in the codebase). p-button--link is a text link
 * styled as a button — leave it flat (Vanilla resets it to 0). */
.p-button,
.p-button--base,
.p-button--positive,
.p-button--negative,
.p-button--brand {
	border-radius: var(--radius);
}

/* Cards, panels, strips */
.p-card,
.p-panel,
.p-strip.is-bordered {
	border-radius: var(--radius);
}

/* Form controls — text inputs, selects, textareas, validation wrappers.
 * (Exclude checkbox/radio — Vanilla styles those as squares/circles.) */
.p-form-validation__input,
input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="search"], input[type="tel"],
input[type="url"], input[type="date"], input[type="datetime-local"],
select, textarea {
	border-radius: var(--radius);
}

/* Notifications — the codebase uses variant-only classes (no base .p-notification),
 * and Vanilla carries the box styling on the same variant group, so match it. */
.p-notification,
.p-notification--positive,
.p-notification--negative,
.p-notification--caution,
.p-notification--information {
	border-radius: var(--radius);
}

/* Tables — round the outer frame. overflow:hidden clips cell backgrounds and
 * borders to the rounded corners. */
.p-table,
table.p-table--mobile-card {
	border-radius: var(--radius);
	overflow: hidden;
}

/* Mobile card view — below 1036px Vanilla collapses each row into a bordered
 * card (.p-table--mobile-card tbody tr → display:block; border:1px). Round those
 * cards too. Inert on desktop where tr is a table-row. */
.p-table--mobile-card tbody tr {
	border-radius: var(--radius);
}

/* Modals */
.p-modal__dialog {
	border-radius: var(--radius);
}

/* Remaining roundable surfaces: side-navigation items, search box, code */
.p-side-navigation__link,
.p-search-box,
.p-search-box__input,
.p-code-snippet__block,
pre, code {
	border-radius: var(--radius);
}

/* Legal documents (/legal/*) — goldmark renders the Markdown sources to
 * classless HTML, so the Vanilla component classes cannot be applied at the
 * element. These rules give that output the same table treatment the portal
 * uses, and are scoped to the wrapper so nothing else inherits them. */
.legal-document table {
	width: 100%;
	margin-bottom: var(--space-xl);
	border-collapse: collapse;
	text-align: left;
	/* Vanilla's base table is `table-layout: fixed`, which divides the DPA
	 * annexes' six columns equally regardless of content. Let the content set
	 * the widths instead. */
	table-layout: auto;
}

/* Vanilla's base cell is `overflow: hidden; text-overflow: ellipsis`, sized for
 * short data values. These cells hold sentences, so unclip them — otherwise a
 * clause is silently truncated mid-word and the reader never learns it. */
.legal-document th,
.legal-document td {
	padding: var(--space-sm) var(--space-md);
	border-bottom: 1px solid var(--vf-color-border-low-contrast);
	overflow: visible;
	text-overflow: clip;
	white-space: normal;
	vertical-align: top;
}

.legal-document thead th {
	border-bottom-width: 2px;
}

.legal-document h2 {
	margin-top: var(--space-2xl);
}

.legal-document h3,
.legal-document h4 {
	margin-top: var(--space-xl);
}

.legal-document blockquote {
	margin: var(--space-xl) 0;
	padding-left: var(--space-lg);
	border-left: 3px solid var(--vf-color-border-default);
}

/* Below the mobile breakpoint the DPA's six-column retention table cannot fit
 * in the viewport, and a table is the one block that will not wrap its way out
 * of it. Scroll it inside its own box so the page itself never scrolls
 * sideways. Vanilla's .p-table--mobile-card would be the nicer answer, but it
 * needs a data-heading on every cell and these tables come from Markdown. */
@media (max-width: 619px) {
	.legal-document table {
		display: block;
		overflow-x: auto;
	}
}
