/* ---------------------------------------------------------------------------
   shortlinkpassservice — «номерок»
   A short link is a cloakroom ticket: you get a token, you hand it in once,
   it stops being worth anything. The whole interface is one dark counter
   with a paper stub on it.

   Contrast note: every text colour below clears WCAG AA (4.5:1) against the
   surface it sits on, including the mixed backgrounds produced by
   color-mix() on the void ticket.
--------------------------------------------------------------------------- */

:root {
	color-scheme: dark;

	--surface: #1b1d22;
	--hairline: #343841;
	/* Borders of interactive controls need 3:1 of their own (WCAG 1.4.11),
	   which the decorative hairline does not reach. */
	--control-border: #5a606b;
	/* The same 3:1 requirement on the paper side of the interface: white
	   fields sit on a light ticket, so the border is the only edge there is. */
	--control-border-paper: color-mix(in srgb, var(--ink) 50%, transparent);

	--stock: #efeae0;
	--stock-edge: #d9d2c4;
	--ink: #191b1f;
	--ink-soft: #4a4e56;

	--on-surface: #e7e4dd;
	--on-surface-mute: #8a8d96;
	--placeholder: #7a7f89;

	--accent: #2f49d1;
	--accent-lift: #4d66ee;
	--void: #8f2a1d;

	--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, "Cascadia Mono",
		"JetBrains Mono", Consolas, monospace;
	--sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
		Arial, sans-serif;

	--step: 1.5rem;
	--tap: 44px;
}

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

html {
	-webkit-text-size-adjust: 100%;
}

body {
	margin: 0;
	min-height: 100dvh;
	background: var(--surface);
	color: var(--on-surface);
	font-family: var(--sans);
	font-size: 15px;
	line-height: 1.55;
}

/* --- shell ---------------------------------------------------------------- */

.shell {
	max-width: 34rem;
	margin: 0 auto;
	padding: clamp(2.5rem, 9vh, 5.5rem) 1.25rem 4rem;
}

.masthead {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding-bottom: 0.25rem;
	border-bottom: 1px solid var(--hairline);
}

/* The header's right-hand side: the two ways of leaving this page, kept
   apart so neither reads as part of the other. */
.masthead__aside {
	display: flex;
	align-items: center;
	gap: 0.9rem;
}

/* The language switcher is set in the same quiet monospace as the rest of the
   header furniture: it is a setting, not an invitation. */
.langs {
	display: flex;
	align-items: center;
	gap: 0.15rem;
}

.langs__item {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/* The project's own tap target, not the 24px the guideline would accept:
	   there is room for it even on the narrowest screen that still shows the
	   exit label. */
	min-width: var(--tap);
	min-height: var(--tap);
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--on-surface-mute);
	text-decoration: none;
	transition: color 120ms ease;
}

.langs__item:hover {
	color: var(--on-surface);
}

.langs__item:focus-visible {
	outline: 2px solid var(--accent-lift);
	outline-offset: 3px;
}

/* The current language is not a link — it would lead to the page you are
   already on — so it is marked by weight rather than by looking clickable. */
.langs__item--on {
	color: var(--on-surface);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 4px;
	cursor: default;
}

/* The warning that stands between the switcher and a lost secret. It sits
   under the header, where the thing it is about was just clicked, and it is
   loud enough to stop a reflex: the cost of ignoring it is unrecoverable. */
.langwarn {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.5rem 1rem;
	margin: 0.75rem 0;
	padding: 0.7rem 0.9rem;
	border-left: 2px solid var(--accent-lift);
	background: color-mix(in srgb, var(--accent) 14%, transparent);
}

.langwarn__text {
	flex: 1 1 16rem;
	margin: 0;
	font-size: 14px;
	color: var(--on-surface);
}

.langwarn__actions {
	display: flex;
	align-items: center;
	gap: 0.9rem;
}

.langwarn__go,
.langwarn__stay {
	/* Both are flex items, so both are block-level; without this a button
	   centres its text and a link does not, and the two sit 13px apart. */
	display: inline-flex;
	align-items: center;
	min-height: var(--tap);
	padding: 0;
	border: 0;
	background: none;
	font-family: var(--mono);
	font-size: 12px;
	letter-spacing: 0.06em;
	cursor: pointer;
}

/* The emphasis sits on staying, not on leaving: the destructive option is
   the one that cannot be undone, so it does not also get to look primary. */
.langwarn__stay {
	color: var(--on-surface);
	text-decoration: underline;
	text-underline-offset: 4px;
}

.langwarn__go {
	color: var(--on-surface-mute);
}

.langwarn__go:hover {
	color: var(--on-surface);
}

.langwarn__go:focus-visible,
.langwarn__stay:focus-visible {
	outline: 2px solid var(--accent-lift);
	outline-offset: 3px;
}

/* Leaving a session is a header action, kept quiet: it must be findable
   without competing with anything on the page. */
.exit {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	min-height: var(--tap);
	padding: 0 0.15rem;
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--on-surface-mute);
	text-decoration: none;
	transition: color 120ms ease;
}

.exit:hover {
	color: var(--on-surface);
}

.exit:focus-visible {
	outline: 2px solid var(--accent-lift);
	outline-offset: 3px;
}

.exit__icon {
	flex: none;
}

/* On a narrow screen the caption and the eyebrow fight for the same line.
   The label stays in the accessibility tree, so the link keeps its name, and
   the hit area is widened to stay square once only the icon shows. */
@media (max-width: 24rem) {
	.masthead__aside {
		gap: 0.5rem;
	}

	.exit {
		min-width: var(--tap);
		justify-content: flex-end;
	}

	.exit__label {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		padding: 0;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}
}

.eyebrow {
	margin: 0;
	font-family: var(--mono);
	font-size: 11px;
	font-weight: 500;
	letter-spacing: 0.2em;
	text-transform: uppercase;
	color: var(--on-surface-mute);
}

.lede {
	margin: var(--step) 0 calc(var(--step) * 1.75);
	font-size: 18px;
	line-height: 1.45;
	max-width: 26rem;
}

.lede b {
	font-weight: 600;
	color: #fff;
}

/* --- form ----------------------------------------------------------------- */

.field {
	display: block;
	margin-bottom: calc(var(--step) * 1.5);
}

.field__label,
.group__label {
	display: block;
	margin-bottom: 0.6rem;
	padding: 0;
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--on-surface-mute);
}

.field__input {
	width: 100%;
	min-height: var(--tap);
	padding: 0.55rem 0 0.65rem;
	border: 0;
	border-bottom: 1px solid var(--control-border);
	border-radius: 0;
	background: transparent;
	color: var(--on-surface);
	font-family: var(--mono);
	font-size: 17px;
	line-height: 1.4;
	transition: border-color 140ms ease;
}

.field__input::placeholder {
	color: var(--placeholder);
}

.field__input:hover {
	border-bottom-color: var(--on-surface-mute);
}

.field__input:focus {
	outline: none;
	border-bottom-color: var(--accent-lift);
}

.field__input:focus-visible {
	outline: 2px solid var(--accent-lift);
	outline-offset: 4px;
}

.field__input[aria-invalid="true"] {
	border-bottom-color: var(--void);
}

.group {
	margin: 0 0 calc(var(--step) * 1.6);
	padding: 0;
	border: 0;
}

.segments {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
}

/* Native radios keep keyboard and screen reader behaviour; the adjacent span
   is what gets styled. The sibling selector is used rather than :has() so the
   selected state survives on browsers without :has() support — without it the
   selector would show five identical boxes and no selection at all. */
.segment {
	display: inline-flex;
	cursor: pointer;
	user-select: none;
}

.segment span {
	display: inline-flex;
	align-items: center;
	min-height: var(--tap);
	padding: 0.45rem 0.85rem;
	border: 1px solid var(--control-border);
	border-radius: 2px;
	color: var(--on-surface-mute);
	font-family: var(--mono);
	font-size: 12px;
	letter-spacing: 0.06em;
	transition: color 120ms ease, border-color 120ms ease, background-color 120ms ease;
}

.segment:hover span {
	color: var(--on-surface);
	border-color: var(--on-surface-mute);
}

.segment input:checked + span {
	color: #fff;
	border-color: var(--accent-lift);
	background: color-mix(in srgb, var(--accent) 26%, transparent);
}

.segment input:focus-visible + span {
	outline: 2px solid var(--accent-lift);
	outline-offset: 2px;
}

.group__note {
	margin: 0.65rem 0 0;
	font-size: 13px;
	color: var(--on-surface-mute);
	min-height: 1.6em; /* reserve the line so the button never jumps */
}

.btn {
	appearance: none;
	display: inline-flex;
	align-items: center;
	min-height: var(--tap);
	padding: 0.7rem 1.3rem;
	border: 0;
	border-radius: 2px;
	background: var(--accent);
	color: #fff;
	font-family: var(--mono);
	font-size: 12px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	cursor: pointer;
	transition: background-color 120ms ease;
}

.btn:hover {
	background: var(--accent-lift);
}

.btn:focus-visible {
	outline: 2px solid var(--accent-lift);
	outline-offset: 3px;
}

/* The rule lives on the label line: an optional field should not grow by a
   row for a condition that only applies if it gets filled in. */
.field__label--split {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: 0.2rem 0.75rem;
}

/* Lowercase and looser tracking tell the rule apart from the label without
   dropping contrast: the colour is inherited from the label itself. */
.field__rule {
	text-transform: none;
	letter-spacing: 0.04em;
}

.formerror {
	margin: 0 0 var(--step);
	padding: 0.6rem 0.85rem;
	border-left: 2px solid #d0483a;
	background: color-mix(in srgb, var(--void) 22%, transparent);
	color: #f6d8d4;
	font-size: 14px;
}

/* Hidden by the attribute, not by :empty — a stray whitespace node would
   otherwise show an empty red bar on a clean form. */
.formerror[hidden] {
	display: none;
}

/* --- the stub ------------------------------------------------------------- */

.stub {
	--seam: 3.25rem;
	position: relative;
	display: grid;
	grid-template-columns: var(--seam) 1fr;
	margin: 0 0 var(--step);
	border-radius: 3px;
	background: var(--stock);
	color: var(--ink);
	box-shadow: 0 1px 0 var(--stock-edge), 0 18px 40px -24px #000;
	overflow: hidden;
}

@media (prefers-reduced-motion: no-preference) {
	.stub {
		animation: print 340ms cubic-bezier(0.22, 0.8, 0.3, 1) both;
	}
}

@keyframes print {
	from {
		clip-path: inset(0 0 100% 0);
		transform: translateY(-6px);
	}
	to {
		clip-path: inset(0 0 0 0);
		transform: none;
	}
}

/* Counterfoil: the strip that stays behind when the ticket is torn.
   nowrap inside an overflow:hidden ticket means a long label would be cut
   silently — keep the text under about 18 characters. */
.stub__counterfoil {
	display: grid;
	place-items: center;
	padding: 1rem 0;
	background: color-mix(in srgb, var(--stock) 88%, var(--ink));
	border-right: 2px dashed color-mix(in srgb, var(--ink) 28%, transparent);
}

.stub__counterfoil span {
	font-family: var(--mono);
	font-size: 10px;
	letter-spacing: 0.24em;
	text-transform: uppercase;
	color: var(--ink-soft);
	writing-mode: vertical-rl;
	transform: rotate(180deg);
	white-space: nowrap;
}

/* Punch holes at both ends of the seam. They are filled with the page colour,
   so the ticket only looks perforated while it sits on --surface. */
.stub::before,
.stub::after {
	content: "";
	position: absolute;
	left: var(--seam);
	width: 12px;
	height: 12px;
	margin-left: -7px;
	border-radius: 50%;
	background: var(--surface);
	z-index: 1;
}

.stub::before {
	top: -6px;
}

.stub::after {
	bottom: -6px;
}

.stub__body {
	padding: 1.4rem 1.4rem 1.25rem;
	min-width: 0;
}

.stub__label {
	margin: 0 0 0.5rem;
	font-family: var(--mono);
	font-size: 10px;
	letter-spacing: 0.2em;
	text-transform: uppercase;
	color: var(--ink-soft);
}

/* The whole URL is one flowing line so a mouse selection yields something
   that pastes correctly; the slug is merely larger, not a separate block. */
.stub__url {
	margin: 0;
	font-family: var(--mono);
	line-height: 1.2;
	word-break: break-all;
}

.stub__origin {
	font-size: 15px;
	color: var(--ink-soft);
}

.stub__slug {
	font-size: clamp(26px, 7.5vw, 36px);
	font-weight: 600;
	letter-spacing: 0.01em;
	color: var(--ink);
}

.stub__rule {
	margin: 1.15rem 0 0.9rem;
	border: 0;
	border-top: 2px dashed color-mix(in srgb, var(--ink) 24%, transparent);
}

.stub__terms {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.35rem 0.6rem;
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--ink-soft);
}

.stub__terms strong {
	font-weight: 600;
	color: var(--ink);
}

.stub__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem 0.75rem;
	margin-top: 1.1rem;
}

.stub__btn {
	appearance: none;
	display: inline-flex;
	align-items: center;
	min-height: var(--tap);
	padding: 0.5rem 0.9rem;
	border: 1px solid color-mix(in srgb, var(--ink) 22%, transparent);
	border-radius: 2px;
	background: transparent;
	color: var(--ink);
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	cursor: pointer;
	transition: background-color 120ms ease;
}

.stub__btn:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

.stub__btn--primary {
	border-color: var(--accent);
	background: var(--accent);
	color: #fff;
}

.stub__btn--primary:hover {
	background: var(--accent-lift);
}

/* The confirmation lives next to the button instead of renaming it: renaming
   changes the accessible name of a focused control and resizes it mid-click. */
.stub__hint {
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.06em;
	color: var(--ink-soft);
}

.stub__hint--warn {
	color: var(--void);
}

/* --- void ----------------------------------------------------------------- */

.stub--void {
	background: color-mix(in srgb, var(--stock) 82%, var(--surface));
	color: var(--ink-soft);
}

.stamp {
	display: inline-block;
	margin: 0 0 0.9rem;
	padding: 0.3rem 0.7rem;
	border: 2px solid var(--void);
	border-radius: 2px;
	color: var(--void);
	font-family: var(--mono);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.28em;
	text-transform: uppercase;
	/* The worn look comes from the tilt, not from opacity: fading the one
	   element that carries the outcome would push it below AA. */
	transform: rotate(-4deg);
}

.void__title {
	margin: 0 0 0.4rem;
	font-size: 20px;
	font-weight: 600;
	line-height: 1.3;
	color: var(--ink);
}

.void__text {
	margin: 0;
	font-size: 14px;
	line-height: 1.5;
	color: var(--ink-soft);
}

/* --- footer --------------------------------------------------------------- */

.footnote {
	margin-top: calc(var(--step) * 2);
	padding-top: 1rem;
	border-top: 1px solid var(--hairline);
	font-size: 13px;
	color: var(--on-surface-mute);
}

.backlink {
	display: inline-flex;
	align-items: center;
	min-height: var(--tap);
	margin-top: 0.75rem;
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--on-surface-mute);
}

.backlink:hover {
	color: var(--on-surface);
}

.backlink:focus-visible {
	outline: 2px solid var(--accent-lift);
	outline-offset: 3px;
}

.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* --- textarea, password and the content selector --------------------------- */

/* Matches the URL field's underline so the two read as one control that
   changes shape, not as two different kinds of input. */
.field__input--area {
	min-height: 6rem;
	padding: 0.55rem 0 0.65rem;
	font-size: 14px;
	line-height: 1.5;
	resize: vertical;
	tab-size: 4;
}

/* On paper: the passphrase prompt sits inside the ticket. */
.field__input--paper {
	min-height: var(--tap);
	padding: 0.45rem 0.6rem;
	border: 1px solid var(--control-border-paper);
	border-radius: 2px;
	background: #fff;
	color: var(--ink);
	font-size: 15px;
}

.field__input--paper::placeholder {
	color: var(--ink-soft);
}

.field__input--paper:focus {
	outline: none;
	border-color: var(--accent);
}

.field__input--paper:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

/* The descendant selector is not redundant: App.RecoverError patches this
   paragraph without knowing which page it landed on, so it arrives with the
   bare .formerror class. Its dark-surface colours on the light ticket give
   about 1.3:1 — an internal failure would look like an empty bar. */
.formerror--paper,
.stub--locked .formerror {
	margin: 0 0 0.75rem;
	background: color-mix(in srgb, var(--void) 12%, transparent);
	border-left-color: var(--void);
	color: var(--void);
}

/* A busy service is not a rejected passphrase. The notice keeps the layout of
   an error and loses the red, which everywhere else in this service means the
   ticket is spent. */
.formerror--notice,
.stub--locked .formerror--notice {
	background: color-mix(in srgb, var(--ink) 8%, transparent);
	border-left-color: var(--ink-soft);
	color: var(--ink);
}

/* --- locked and revealed tickets ------------------------------------------- */

.stub--locked,
.stub--reveal {
	background: var(--stock);
	color: var(--ink);
}

/* Aligned along the bottom, not the top: the field carries a visible label
   above the input, so its top edge is the label's, and a top-aligned button
   would float level with the caption instead of the box it submits. */
.unlock {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
	align-items: flex-end;
	margin-top: 1.1rem;
}

.unlock .formerror--paper {
	flex-basis: 100%;
}

.unlock__field {
	flex: 1 1 12rem;
	min-width: 0;
}

.unlock__field .field__input--paper {
	width: 100%;
}

.reveal {
	margin: 0;
	padding: 0.85rem 0.9rem;
	/* dvh, not vh: on mobile the address bar makes vh overshoot. */
	max-height: min(60dvh, 32rem);
	overflow: auto;
	border: 1px solid var(--control-border-paper);
	border-radius: 2px;
	background: #fff;
	color: var(--ink);
	font-family: var(--mono);
	font-size: 13px;
	line-height: 1.55;
	tab-size: 4;
	/* Wrap long lines instead of forcing a horizontal scroll, but keep every
	   space and newline the author typed. */
	white-space: pre-wrap;
	overflow-wrap: anywhere;
}

.reveal:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

/* --- flags on the created ticket ------------------------------------------- */

.stub__flag {
	display: inline-block;
	margin-left: 0.4rem;
	padding: 0.1rem 0.35rem;
	border: 1px solid var(--control-border-paper);
	border-radius: 2px;
	font-size: 10px;
	letter-spacing: 0.1em;
	color: var(--ink-soft);
}

/* The unlock prompt keeps its label visible: the placeholder disappears
   behind the dots the moment anything is typed. */
.unlock__label {
	display: block;
	margin-bottom: 0.35rem;
	font-family: var(--mono);
	font-size: 10px;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--ink-soft);
}

.stub__btn[disabled],
.btn[disabled] {
	opacity: 0.6;
	cursor: progress;
}

/* Anchors styled as buttons need the vertical rhythm buttons get for free. */
a.stub__btn {
	text-decoration: none;
}

/* Monospace at 13px fits about 55 characters in the standard column, which
   wraps nearly every line of code. The reveal page gets more room. */
@media (min-width: 48rem) {
	.shell:has(.stub--reveal) {
		max-width: 46rem;
	}
}

/* One scroller instead of two: on a small screen the page already scrolls. */
@media (max-width: 30rem) {
	.reveal {
		max-height: none;
	}
}
