/* ==========================================================================
   Optix Secrets — design system

   Tokens are lifted from the Optix SSO theme so this app matches the rest of
   the estate: sso-ui/src/themes/index.ts (colours), tokens.ts (spacing, radii,
   elevation), ThemeProvider.tsx (glass), and sso-portal/src/client/styles.

   The SSO project has no light/dark toggle — "theme" there means white-label
   brand, chosen server-side by Keycloak. Both palettes exist in its source
   though, so the light theme below is its real baseLightTheme rather than
   something invented.

   Theme resolution, in precedence order:
     :root                                  light palette (the base)
     @media (prefers-color-scheme: dark)    follows the OS by default
     [data-theme="dark"] / ="light"         an explicit choice wins over both

   VERTICAL RHYTHM IS OWNED BY CONTAINERS, NOT BY CHILDREN.
   Use .flow (flex column + gap) and let it space its children. Components here
   deliberately carry no outer margins. This is not only tidier — the CSP is
   `style-src 'self'` with no unsafe-inline, so a `style="margin-top: …"`
   attribute in the markup is silently dropped by the browser and the elements
   end up against each other. test/markup.spec.ts fails if one comes back.

   No build step. This file is served as written.
   ========================================================================== */

/* --------------------------------------------------------------- reset --- */

*,
*::before,
*::after {
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}

html {
	-webkit-text-size-adjust: 100%;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

html,
body {
	width: 100%;
	min-height: 100%;
}

/* --------------------------------------------------------------- tokens -- */

:root {
	color-scheme: light;

	/* surfaces — baseLightTheme */
	--color-background: #f5f5f5;
	--color-background-alt: #ffffff;
	--color-surface: #ffffff;
	--color-surface-alt: #f0f0f0;

	--color-text: #1a1a1a;
	--color-text-secondary: #565656;
	--color-text-disabled: #8c8c8c;

	--color-border: #e0e0e0;
	--color-border-light: #eeeeee;

	/* glass — LIGHT_GLASS, opened up.
	   The card sits over a high-contrast sunset photograph. At the source's
	   0.72 white, 28% of that photo bled through and saturate(1.4) amplified
	   the orange, so the card composited to a dingy warm grey instead of
	   white — the "light mode looks off" problem. */
	--glass-background: rgba(255, 255, 255, 0.84);
	--glass-border: rgba(0, 0, 0, 0.1);
	--glass-border-inset: rgba(255, 255, 255, 0.5);
	--glass-shadow: rgba(0, 0, 0, 0.18);
	--glass-saturate: 1.15;

	/* Recessed surfaces. The source put panel at 4% and input at 6% black —
	   two points apart, which is below the perceptual floor, so a panel
	   containing an input read as one flat slab. */
	--glass-input-background: rgba(0, 0, 0, 0.05);
	--glass-input-outline: rgba(0, 0, 0, 0.18);
	--glass-panel-background: rgba(0, 0, 0, 0.03);
	--glass-panel-border: rgba(0, 0, 0, 0.1);

	/* The scrim over the photo. Light mode lightens it; darkening a photo and
	   then laying a white card over it is what made the card look muddy. */
	--overlay-top: rgba(255, 255, 255, 0.55);
	--overlay-mid: rgba(255, 255, 255, 0.34);
	--overlay-bottom: rgba(255, 255, 255, 0.18);

	/* The Optix lockup ships as a WHITE wordmark with an orange ring, because
	   the SSO login page it comes from is dark-only — there is no
	   light-background variant of the asset anywhere in that project. On a light
	   card the wordmark is invisible.

	   invert(1) turns the white text black. hue-rotate(180deg) then puts the
	   ring back to exactly #F26722: inverting a colour and rotating its hue by
	   half a turn is a round trip, so the brand orange survives untouched while
	   the neutral goes from white to black. Verified against a rendered pixel,
	   not just the matrix arithmetic.

	   Replace with a real light-mode logo file if the brand team has one. */
	--logo-filter: invert(1) hue-rotate(180deg);

	/* brand — identical across themes */
	--color-primary: #f26722;
	--color-primary-light: #f58b52;
	--color-primary-dark: #d85417;
	--color-primary-contrast: #ffffff;
	--color-border-focus: #f26722;

	/* semantic — identical across themes in the SSO source */
	--color-success: #22c55e;
	--color-warning: #f59e0b;
	--color-error: #ef4444;
	--color-info: #3b82f6;

	/* spacing */
	--spacing-xs: 4px;
	--spacing-sm: 8px;
	--spacing-md: 16px;
	--spacing-lg: 24px;
	--spacing-xl: 32px;
	--spacing-xxl: 48px;

	/* radii — note --radius-full is 50%, correct for circles and wrong for
	   pills, which is why buttons hardcode 25px exactly as sso-ui does */
	--radius-sm: 2px;
	--radius-md: 4px;
	--radius-lg: 8px;
	--radius-xl: 12px;
	--radius-full: 50%;
	--radius-pill: 25px;

	/* elevation — Material shadows, from tokens.ts */
	--elevation-1: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
	--elevation-2: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);

	/* motion */
	--duration-short: 250ms;
	--duration-medium: 300ms;
	--ease: cubic-bezier(0.4, 0, 0.2, 1);
	--ease-out: cubic-bezier(0, 0, 0.2, 1);

	/* type */
	--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
	--font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
	--text-caption: 0.75rem;
	--text-body2: 0.875rem;
	--text-body1: 1rem;
	--text-h5: 1rem;
	--text-h3: 1.25rem;
	--text-h2: 1.5rem;
}

/* Follows the OS unless an explicit light choice overrides it. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme='light']) {
		color-scheme: dark;

		/* The shipped asset is already the dark-background lockup. */
		--logo-filter: none;

		--color-background: #121212;
		--color-background-alt: #1e1e1e;
		--color-surface: #1e1e1e;
		--color-surface-alt: #2a2a2a;

		--color-text: #ffffff;
		--color-text-secondary: #b0b0b0;
		--color-text-disabled: #6b6b6b;

		--color-border: #3d3d3d;
		--color-border-light: #525252;

		--glass-background: rgba(18, 18, 18, 0.72);
		--glass-border: rgba(255, 255, 255, 0.12);
		--glass-border-inset: rgba(255, 255, 255, 0.06);
		--glass-shadow: rgba(0, 0, 0, 0.4);
		--glass-saturate: 1.4;

		--glass-input-background: rgba(0, 0, 0, 0.3);
		--glass-input-outline: rgba(255, 255, 255, 0.18);
		--glass-panel-background: rgba(255, 255, 255, 0.04);
		--glass-panel-border: rgba(255, 255, 255, 0.1);

		--overlay-top: rgba(0, 0, 0, 0.7);
		--overlay-mid: rgba(0, 0, 0, 0.55);
		--overlay-bottom: rgba(0, 0, 0, 0.45);
	}
}

/* An explicit choice beats the OS in both directions. */
:root[data-theme='dark'] {
	color-scheme: dark;

	/* The shipped asset is already the dark-background lockup. */
	--logo-filter: none;

	--color-background: #121212;
	--color-background-alt: #1e1e1e;
	--color-surface: #1e1e1e;
	--color-surface-alt: #2a2a2a;

	--color-text: #ffffff;
	--color-text-secondary: #b0b0b0;
	--color-text-disabled: #6b6b6b;

	--color-border: #3d3d3d;
	--color-border-light: #525252;

	--glass-background: rgba(18, 18, 18, 0.72);
	--glass-border: rgba(255, 255, 255, 0.12);
	--glass-border-inset: rgba(255, 255, 255, 0.06);
	--glass-shadow: rgba(0, 0, 0, 0.4);
	--glass-saturate: 1.4;

	--glass-input-background: rgba(0, 0, 0, 0.3);
	--glass-input-outline: rgba(255, 255, 255, 0.18);
	--glass-panel-background: rgba(255, 255, 255, 0.04);
	--glass-panel-border: rgba(255, 255, 255, 0.1);

	--overlay-top: rgba(0, 0, 0, 0.7);
	--overlay-mid: rgba(0, 0, 0, 0.55);
	--overlay-bottom: rgba(0, 0, 0, 0.45);
}

/* ------------------------------------------------------------ structure -- */

body {
	font-family: var(--font);
	font-size: var(--text-body1);
	line-height: 1.5;
	color: var(--color-text);
	background-color: var(--color-background);
	/* A long unbroken secret must scroll inside its own box, never sideways
	   across the whole page. */
	overflow-x: hidden;
}

/* The truck photo and its overlay are fixed, so they do not scroll with the
   content. Both are pseudo-elements behind the page (z-index -2 and -1). */
body::before {
	content: '';
	position: fixed;
	inset: 0;
	background-image: url('/assets/optix_truck_sunset.webp');
	background-size: cover;
	background-position: center;
	background-repeat: no-repeat;
	z-index: -2;
	pointer-events: none;
	animation: fade-in var(--duration-medium) var(--ease-out);
}

body::after {
	content: '';
	position: fixed;
	inset: 0;
	background: linear-gradient(160deg, var(--overlay-top) 0%, var(--overlay-mid) 40%, var(--overlay-bottom) 100%);
	z-index: -1;
	pointer-events: none;
	animation: fade-in var(--duration-medium) var(--ease-out);
}

.page {
	min-height: 100svh;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: var(--spacing-lg);
	position: relative;
	z-index: 1;
}

.card {
	display: flex;
	flex-direction: column;
	gap: var(--spacing-lg);
	width: 100%;
	max-width: 560px;
	padding: var(--spacing-xxl);
	border-radius: var(--radius-lg);
	background: var(--glass-background);
	backdrop-filter: blur(24px) saturate(var(--glass-saturate));
	-webkit-backdrop-filter: blur(24px) saturate(var(--glass-saturate));
	border: 1px solid var(--glass-border);
	box-shadow:
		0 8px 32px var(--glass-shadow),
		inset 0 0 0 1px var(--glass-border-inset);
	animation: fade-in var(--duration-medium) var(--ease-out) both;
}

.card__header {
	display: grid;
	justify-items: center;
	gap: var(--spacing-xs);
	text-align: center;
}

.panel {
	background-color: var(--glass-panel-background);
	border: 1px solid var(--glass-panel-border);
	border-radius: var(--radius-lg);
	padding: var(--spacing-md);
}

/* The one spacing primitive. Anything that needs vertical rhythm gets this
   rather than margins on its children. */
.flow {
	display: flex;
	flex-direction: column;
	gap: var(--spacing-lg);
}

.flow--tight {
	gap: var(--spacing-md);
}

/* ---------------------------------------------------------------- logo --- */

.logo {
	display: flex;
	justify-content: center;
	margin-bottom: var(--spacing-sm);
}

.logo img {
	filter: var(--logo-filter);
	height: 48px;
	width: auto;
	max-width: 80%;
	object-fit: contain;
}

.title {
	font-size: var(--text-h2);
	/* 300 is too thin over a photograph; SSO gets away with it on an opaque
	   dark surface. */
	font-weight: 400;
	text-wrap: balance;
}

.subtitle {
	font-size: var(--text-body2);
	color: var(--color-text-secondary);
	text-wrap: balance;
}

/* -------------------------------------------------------------- fields --- */

.field {
	display: block;
}

.label {
	display: block;
	margin-bottom: var(--spacing-sm);
	font-size: var(--text-body2);
	font-weight: 500;
	color: var(--color-text);
}

.hint {
	margin-top: var(--spacing-xs);
	font-size: var(--text-caption);
	color: var(--color-text-secondary);
}

/* No border at all — the "border" is an inset outline, so focus can thicken it
   from 1px to 2px with zero layout shift. Taken from sso-ui's FormField. */
.input,
.textarea {
	width: 100%;
	padding: var(--spacing-md);
	border: none;
	border-radius: var(--radius-lg);
	background-color: var(--glass-input-background);
	color: var(--color-text);
	font-family: inherit;
	font-size: var(--text-body1);
	outline: 1px solid var(--glass-input-outline);
	outline-offset: -1px;
	transition: outline-color var(--duration-short) var(--ease);
	min-height: 48px;
}

.textarea {
	min-height: 132px;
	resize: vertical;
	font-family: var(--font-mono);
	font-size: var(--text-body2);
	line-height: 1.6;
}

.input:focus,
.textarea:focus,
.input:focus-visible,
.textarea:focus-visible {
	outline: 2px solid var(--color-border-focus);
	outline-offset: -1px;
}

.input[aria-invalid='true'],
.textarea[aria-invalid='true'] {
	outline: 2px solid var(--color-error);
	outline-offset: -1px;
}

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

/* The browser's autofill background would otherwise punch a pale blue hole in
   the glass card. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
	-webkit-box-shadow: 0 0 0 1000px var(--color-surface) inset !important;
	-webkit-text-fill-color: var(--color-text) !important;
	caret-color: var(--color-text) !important;
	transition: background-color 5000s ease-in-out 0s;
}

/* --------------------------------------------------------------- chips --- */

/* Expiry. This replaces an "Options" disclosure containing a native <select>:
   a card the user had to open, to change a setting most people never touch.
   Radios rather than buttons, so arrow-key navigation, radiogroup semantics
   and form.reset() all come for free. */

.chips {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: var(--spacing-sm);
}

.chip {
	position: relative;
	display: flex;
}

.chip input {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	margin: 0;
	opacity: 0;
	cursor: pointer;
}

.chip span {
	display: flex;
	flex: 1;
	align-items: center;
	justify-content: center;
	min-height: 42px;
	padding: var(--spacing-sm) var(--spacing-sm);
	border-radius: var(--radius-pill);
	background-color: var(--glass-input-background);
	outline: 1px solid var(--glass-input-outline);
	outline-offset: -1px;
	font-size: var(--text-body2);
	font-weight: 500;
	color: var(--color-text-secondary);
	white-space: nowrap;
	transition:
		background-color var(--duration-short) var(--ease),
		color var(--duration-short) var(--ease),
		outline-color var(--duration-short) var(--ease);
}

.chip input:hover + span {
	color: var(--color-text);
	outline-color: var(--color-primary-light);
}

.chip input:checked + span {
	background-color: var(--color-primary);
	color: var(--color-primary-contrast);
	outline-color: var(--color-primary);
	font-weight: 600;
}

.chip input:focus-visible + span {
	outline: 2px solid var(--color-primary);
	outline-offset: 2px;
}

@media (max-width: 480px) {
	.chips {
		grid-template-columns: repeat(2, 1fr);
	}
}

/* ------------------------------------------------------------- buttons --- */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--spacing-sm);
	min-height: 44px;
	padding: var(--spacing-md) var(--spacing-lg);
	border: none;
	border-radius: var(--radius-pill);
	font-family: inherit;
	font-size: var(--text-body1);
	font-weight: 600;
	/* <a class="btn"> would otherwise be underlined and inherit link colour. */
	text-align: center;
	text-decoration: none;
	cursor: pointer;
	transition:
		background-color var(--duration-short) var(--ease),
		border-color var(--duration-short) var(--ease),
		color var(--duration-short) var(--ease),
		box-shadow var(--duration-short) var(--ease),
		transform var(--duration-short) var(--ease),
		opacity var(--duration-short) var(--ease);
}

.btn--block {
	display: flex;
	width: 100%;
}

.btn--primary {
	background-color: var(--color-primary);
	color: var(--color-primary-contrast);
}

.btn--primary:hover:not(:disabled) {
	background-color: var(--color-primary-dark);
	box-shadow: var(--elevation-2);
}

.btn--secondary {
	background-color: transparent;
	color: var(--color-primary);
	border: 2px solid var(--color-primary);
}

.btn--secondary:hover:not(:disabled) {
	background-color: var(--color-primary);
	color: var(--color-primary-contrast);
}

/* Ghost buttons previously had `background: transparent` and a 1px border in
   --color-border (#e0e0e0 / #3d3d3d). Those values are calibrated for the
   opaque --color-surface; on the glass card #e0e0e0 composites to roughly
   1.1:1, which is invisible. Transparent fill plus an invisible border plus
   secondary text is three separate signals saying "not a button". A fill and
   a glass-calibrated border fix it. */
.btn--ghost {
	background-color: var(--glass-input-background);
	color: var(--color-text);
	border: 1px solid var(--glass-input-outline);
}

.btn--ghost:hover:not(:disabled) {
	background-color: color-mix(in srgb, var(--color-primary) 10%, transparent);
	border-color: var(--color-primary);
	color: var(--color-primary);
}

.btn--small {
	min-height: 38px;
	padding: var(--spacing-sm) var(--spacing-md);
	font-size: var(--text-body2);
}

.btn:active:not(:disabled) {
	transform: scale(0.98);
}

.btn:disabled {
	opacity: 0.6;
	cursor: not-allowed;
}

.btn:focus-visible {
	outline: 2px solid var(--color-primary);
	outline-offset: 2px;
}

@media (max-width: 640px) {
	.btn {
		min-height: 48px;
	}

	.btn--small {
		min-height: 40px;
	}
}

/* ------------------------------------------------------------- spinner --- */

.spinner {
	display: inline-block;
	width: 16px;
	height: 16px;
	border: 2px solid currentColor;
	border-top-color: transparent;
	border-radius: var(--radius-full);
	animation: spin 0.6s linear infinite;
}

.spinner--brand {
	color: var(--color-primary);
	width: 24px;
	height: 24px;
}

@keyframes spin {
	to {
		transform: rotate(360deg);
	}
}

@keyframes fade-in {
	from {
		opacity: 0;
		transform: translateY(8px);
	}
	to {
		opacity: 1;
		transform: translateY(0);
	}
}

/* Honour a reduced-motion preference: keep the fades, drop the movement. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
	}
}

/* -------------------------------------------------------------- alerts --- */

.alert {
	display: flex;
	align-items: flex-start;
	gap: var(--spacing-md);
	padding: var(--spacing-md);
	border-radius: var(--radius-md);
	font-size: var(--text-body2);
	animation: fade-in var(--duration-medium) var(--ease-out) both;
}

/* color-mix keeps these tied to the palette tokens. sso-ui hardcodes legacy
   Bootstrap RGBs here that no longer match its own --color-error.
   1px rather than 2px: a fully saturated 2px ring made the success alert the
   loudest thing on a page whose payload is the link and the password. The
   icon carries the semantics instead. */
.alert--error {
	background-color: color-mix(in srgb, var(--color-error) 14%, transparent);
	border: 1px solid var(--color-error);
}

.alert--warning {
	background-color: color-mix(in srgb, var(--color-warning) 14%, transparent);
	border: 1px solid var(--color-warning);
}

.alert--success {
	background-color: color-mix(in srgb, var(--color-success) 14%, transparent);
	border: 1px solid var(--color-success);
}

.alert--info {
	background-color: color-mix(in srgb, var(--color-info) 14%, transparent);
	border: 1px solid var(--color-info);
}

.alert__icon {
	flex-shrink: 0;
	width: 20px;
	height: 20px;
}

.alert--error .alert__icon {
	color: var(--color-error);
}
.alert--warning .alert__icon {
	color: var(--color-warning);
}
.alert--success .alert__icon {
	color: var(--color-success);
}
.alert--info .alert__icon {
	color: var(--color-info);
}

/* --------------------------------------------------------------- toast --- */

.toast {
	position: fixed;
	left: 50%;
	bottom: var(--spacing-lg);
	z-index: 9999;
	padding: var(--spacing-md) var(--spacing-lg);
	border-radius: var(--radius-pill);
	background-color: var(--color-primary);
	color: var(--color-primary-contrast);
	font-size: var(--text-body2);
	font-weight: 600;
	box-shadow: var(--elevation-2);
	pointer-events: none;
	/* A percentage translate is relative to the toast's own height, so
	   translateY(120%) on a 53px toast moved it 63.6px — less than the 24px
	   offset plus its own height, leaving 13px of orange pill permanently
	   stuck to the bottom of every page. Offset it past its own box plus the
	   gap plus the shadow, and hide it outright so it cannot peek at all. */
	transform: translateX(-50%) translateY(calc(100% + var(--spacing-lg) + 8px));
	opacity: 0;
	visibility: hidden;
	transition:
		transform var(--duration-short) var(--ease),
		opacity var(--duration-short) var(--ease),
		visibility 0s linear var(--duration-short);
}

.toast.is-visible {
	transform: translateX(-50%) translateY(0);
	opacity: 1;
	visibility: visible;
	transition-delay: 0s;
}

/* ---------------------------------------------------------- disclosure --- */

.disclosure {
	border-radius: var(--radius-lg);
	background-color: var(--glass-panel-background);
	border: 1px solid var(--glass-panel-border);
}

.disclosure > summary {
	padding: var(--spacing-md);
	cursor: pointer;
	font-size: var(--text-body2);
	font-weight: 500;
	color: var(--color-text-secondary);
	list-style: none;
	display: flex;
	align-items: center;
	gap: var(--spacing-sm);
	border-radius: var(--radius-lg);
}

.disclosure > summary::-webkit-details-marker {
	display: none;
}

.disclosure > summary::before {
	content: '';
	width: 0;
	height: 0;
	border-left: 5px solid currentColor;
	border-top: 4px solid transparent;
	border-bottom: 4px solid transparent;
	transition: transform var(--duration-short) var(--ease);
}

.disclosure[open] > summary::before {
	transform: rotate(90deg);
}

.disclosure > summary:hover {
	color: var(--color-text);
}

.disclosure > summary:focus-visible {
	outline: 2px solid var(--color-primary);
	outline-offset: 2px;
}

.disclosure__body {
	padding: 0 var(--spacing-md) var(--spacing-md);
}

/* ----------------------------------------------------------- key/value --- */

/* The generated key and the share link. Monospace, wrapping on any character,
   because these are long base64url strings with no natural break points. */
.value {
	font-family: var(--font-mono);
	font-size: var(--text-body2);
	word-break: break-all;
	line-height: 1.7;
	color: var(--color-text);
	user-select: all;
}

.value--key {
	letter-spacing: 0.04em;
}

.value-block {
	display: block;
	padding: var(--spacing-md);
	border-radius: var(--radius-lg);
	background-color: var(--glass-input-background);
	outline: 1px solid var(--glass-input-outline);
	outline-offset: -1px;
}

/* A revealed secret is arbitrary text and may be one very long unbroken line.
   <pre> defaults to white-space: pre, which does not wrap: the page grew to
   7,200px wide and scrolled sideways. */
.value--pre {
	white-space: pre-wrap;
	overflow-wrap: anywhere;
	max-height: 40svh;
	overflow-y: auto;
}

.value-label {
	display: block;
	font-size: var(--text-caption);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.5px;
	color: var(--color-text-secondary);
	margin-bottom: var(--spacing-xs);
}

/* --------------------------------------------------------- copy actions --- */

/* The copy affordance is attached to the thing it copies rather than sitting
   in a row of three peer buttons below. That row could not work: at 560px the
   card gives 448px for three flex items, "Copy link & password" needs ~228px,
   so it wrapped to two lines and default align-items: stretch then inflated
   the two small ghosts to match — 64px-tall boxes holding 14px of text, which
   is why they stopped reading as buttons at all. */

.value-row {
	display: flex;
	align-items: flex-start;
	gap: var(--spacing-sm);
	padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-sm) var(--spacing-md);
	border-radius: var(--radius-lg);
	background-color: var(--glass-input-background);
	outline: 1px solid var(--glass-input-outline);
	outline-offset: -1px;
}

.value-row .value {
	flex: 1;
	min-width: 0;
	padding-block: var(--spacing-xs);
}

.copy-btn {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	border: none;
	border-radius: var(--radius-md);
	background-color: transparent;
	color: var(--color-text-secondary);
	cursor: pointer;
	transition:
		background-color var(--duration-short) var(--ease),
		color var(--duration-short) var(--ease),
		transform var(--duration-short) var(--ease);
}

.copy-btn:hover {
	background-color: color-mix(in srgb, var(--color-primary) 14%, transparent);
	color: var(--color-primary);
}

.copy-btn:active {
	transform: scale(0.94);
}

.copy-btn:focus-visible {
	outline: 2px solid var(--color-primary);
	outline-offset: 2px;
}

.copy-btn svg {
	width: 18px;
	height: 18px;
}

.copy-btn__done {
	display: none;
}

.copy-btn.is-copied {
	color: var(--color-success);
}

.copy-btn.is-copied .copy-btn__copy {
	display: none;
}

.copy-btn.is-copied .copy-btn__done {
	display: block;
}

/* The link, the password and the one-press copy read as a single object. */
.result-group {
	display: flex;
	flex-direction: column;
	gap: var(--spacing-md);
	padding: var(--spacing-md);
	border-radius: var(--radius-lg);
	background-color: var(--glass-panel-background);
	border: 1px solid var(--glass-panel-border);
}

/* A one-line confirmation rather than a bordered alert: the expiry is now a
   chip the user chose, so a 70px green box above the payload was repeating
   what they already knew. */
.result-banner {
	display: flex;
	/* flex-start, not center: the line wraps on a phone, and a centred tick
	   then floats between the two lines instead of marking the first one. */
	align-items: flex-start;
	gap: var(--spacing-sm);
	font-size: var(--text-body2);
	color: var(--color-text);
}

.result-banner svg {
	flex-shrink: 0;
	width: 18px;
	height: 18px;
	margin-top: 2px;
	color: var(--color-success);
}

.status-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--spacing-md);
}

/* --------------------------------------------------------------- meta ---- */

.meta-list {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: var(--spacing-sm) var(--spacing-md);
	font-size: var(--text-body2);
}

.meta-list dt {
	color: var(--color-text-secondary);
}

.meta-list dd {
	color: var(--color-text);
	font-weight: 500;
}

.status-dot {
	display: inline-block;
	width: 8px;
	height: 8px;
	border-radius: var(--radius-full);
	margin-right: var(--spacing-sm);
	vertical-align: middle;
}

.status-dot--active {
	background-color: var(--color-success);
}
.status-dot--opened {
	background-color: var(--color-text-secondary);
}
.status-dot--warning {
	background-color: var(--color-warning);
}
.status-dot--error {
	background-color: var(--color-error);
}

/* -------------------------------------------------------------- header --- */

.topbar {
	position: absolute;
	top: var(--spacing-lg);
	right: var(--spacing-lg);
	display: flex;
	gap: var(--spacing-sm);
	z-index: 2;
}

.icon-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 40px;
	height: 40px;
	border-radius: var(--radius-full);
	border: 1px solid var(--glass-border);
	background: var(--glass-background);
	backdrop-filter: blur(12px);
	-webkit-backdrop-filter: blur(12px);
	color: var(--color-text);
	cursor: pointer;
	transition:
		background-color var(--duration-short) var(--ease),
		border-color var(--duration-short) var(--ease),
		color var(--duration-short) var(--ease),
		transform var(--duration-short) var(--ease);
}

.icon-btn:hover {
	border-color: var(--color-primary);
	color: var(--color-primary);
}

.icon-btn:active {
	transform: scale(0.94);
}

.icon-btn:focus-visible {
	outline: 2px solid var(--color-primary);
	outline-offset: 2px;
}

.icon-btn svg {
	width: 20px;
	height: 20px;
}

/* ------------------------------------------------------------- install --- */

/* Shown only once the browser tells us an install is actually possible, or on
   iOS where it can never tell us and the banner carries instructions instead. */
.install {
	display: flex;
	align-items: center;
	gap: var(--spacing-md);
	padding: var(--spacing-md);
	border-radius: var(--radius-lg);
	background-color: color-mix(in srgb, var(--color-primary) 8%, transparent);
	border: 1px solid color-mix(in srgb, var(--color-primary) 35%, transparent);
	animation: fade-in var(--duration-medium) var(--ease-out) both;
}

.install__icon {
	flex-shrink: 0;
	width: 24px;
	height: 24px;
	color: var(--color-primary);
}

.install__text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	font-size: var(--text-body2);
}

.install__text span {
	color: var(--color-text-secondary);
	font-size: var(--text-caption);
}

.install__close {
	flex-shrink: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	padding: 0;
	border: none;
	border-radius: var(--radius-md);
	background: transparent;
	color: var(--color-text-secondary);
	cursor: pointer;
	transition:
		background-color var(--duration-short) var(--ease),
		color var(--duration-short) var(--ease);
}

.install__close:hover {
	background-color: var(--glass-panel-background);
	color: var(--color-text);
}

.install__close:focus-visible {
	outline: 2px solid var(--color-primary);
	outline-offset: 2px;
}

.install__close svg {
	width: 16px;
	height: 16px;
}

/* On a narrow screen the row becomes a stack, so the button gets its own line
   rather than squeezing the text to two words per line. */
@media (max-width: 480px) {
	.install {
		flex-wrap: wrap;
	}

	.install__text {
		flex-basis: calc(100% - 24px - 28px - var(--spacing-md) * 2);
	}

	.install .btn {
		flex: 1;
	}
}

/* -------------------------------------------------------------- sent ----- */

.sent-list {
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: var(--spacing-sm);
}

.sent-item {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--spacing-md);
	padding: var(--spacing-sm) var(--spacing-md);
	border-radius: var(--radius-md);
	background-color: var(--glass-input-background);
	font-size: var(--text-body2);
}

.sent-item__meta {
	min-width: 0;
}

.sent-item__id {
	font-family: var(--font-mono);
	font-size: var(--text-caption);
	color: var(--color-text-secondary);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* ------------------------------------------------------------ utilities -- */

.center {
	text-align: center;
}

.muted {
	color: var(--color-text-secondary);
	font-size: var(--text-body2);
}

.hidden {
	display: none !important;
}

.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* The offscreen textarea used by the execCommand('copy') fallback. It has to
   stay selectable, so it cannot be display:none or clipped to a point — and it
   cannot be an inline style, because the CSP drops those. */
.copy-shim {
	position: fixed;
	top: 0;
	left: 0;
	opacity: 0;
	pointer-events: none;
}

/* The widget is rendered with appearance: 'interaction-only', so most of the
   time it occupies no space. Collapsing it keeps the submit button from
   shifting under the cursor at the moment of the click. */
.turnstile-slot {
	display: flex;
	justify-content: center;
}

.turnstile-slot:empty,
.turnstile-slot.is-idle {
	display: none;
}

@media (max-width: 640px) {
	.card {
		padding: var(--spacing-lg);
		border-radius: var(--radius-md);
		backdrop-filter: blur(20px) saturate(var(--glass-saturate));
		-webkit-backdrop-filter: blur(20px) saturate(var(--glass-saturate));
	}

	.page {
		padding: var(--spacing-md);
		padding-top: 72px;
	}

	.topbar {
		top: var(--spacing-md);
		right: var(--spacing-md);
	}
}
