/* alert.css */
/* Entrance animation */
@keyframes alert-enter {
	from {
		opacity: 0;
		transform: translateY(4px);
	}
	to {
		opacity: 1;
		transform: translateY(0);
	}
}

/* Base */
.alert {
	position: relative;
	display: grid;
	width: 100%;
	gap: 0.25rem;
	border-radius: var(--radius-lg);
	border: 1px solid var(--border);
	padding: 0.625rem 0.75rem;
	text-align: left;
	font-size: var(--text-base);
	line-height: 1.25rem;
	/* Entrances ease out — same overshoot curve as the rest of the family */
	animation: alert-enter 250ms cubic-bezier(0.22, 1.1, 0.36, 1) both;
}

.alert:has(> svg) {
	grid-template-columns: auto 1fr;
	column-gap: 0.5rem;
}

.alert > svg {
	grid-row: span 2;
	translate: 0 0.125rem;
	color: currentColor;
	width: 1rem;
	height: 1rem;
}

/*
 * An action is a real grid column, not an absolutely positioned box
 * over a fixed 4.5rem gutter — a reserved gutter either wastes space
 * or lets a wider action overlap the text. The action is placed
 * explicitly (last column, all rows), so title and description
 * auto-place around it.
 */
.alert:has([data-slot="alert-action"]) {
	grid-template-columns: 1fr auto;
	column-gap: 0.5rem;
}

.alert:has(> svg):has([data-slot="alert-action"]) {
	grid-template-columns: auto 1fr auto;
	column-gap: 0.5rem;
}

/* Variant: default */
.alert-default {
	background-color: var(--card);
	color: var(--card-foreground);
}

/* Variant: destructive */
.alert-destructive {
	background-color: color-mix(in oklch, var(--destructive) 5%, var(--card));
	color: var(--destructive);
}

.alert-destructive .alert-description {
	color: color-mix(in oklch, var(--destructive) 90%, transparent);
}

.alert-destructive > svg {
	color: currentColor;
}

/* AlertTitle */
.alert-title {
	font-weight: 500;
	line-height: 1.375;
}

.alert:has(> svg) .alert-title {
	grid-column-start: 2;
}

.alert-title a {
	text-decoration: underline;
	text-underline-offset: 3px;
}

.alert-title a:hover {
	color: var(--foreground);
}

/* AlertDescription */
.alert-description {
	font-size: var(--text-base);
	color: var(--muted-foreground);
	text-wrap: balance;
}

@media (min-width: 768px) {
	.alert-description {
		text-wrap: pretty;
	}
}

.alert-description a {
	text-decoration: underline;
	text-underline-offset: 3px;
}

.alert-description a:hover {
	color: var(--foreground);
}

.alert-description p:not(:last-child) {
	margin-bottom: 1rem;
}

/* AlertAction — last column, centered across every text row */
.alert-action {
	grid-column: -2;
	grid-row: 1 / -1;
	align-self: center;
	justify-self: end;
}

/* Dark mode */
.dark .alert-destructive {
	background-color: color-mix(in oklch, var(--destructive) 10%, var(--card));
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.alert {
		animation: none;
	}
}

/* avatar.css */
/* Avatar: Base */
.avatar {
	position: relative;
	display: inline-flex;
	width: 2rem;
	height: 2rem;
	flex-shrink: 0;
	border-radius: 9999px;
	user-select: none;
	vertical-align: middle;
}

/* Subtle border overlay for definition against backgrounds */
.avatar::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: 9999px;
	border: 1px solid var(--border);
	mix-blend-mode: darken;
	pointer-events: none;
}

/* Size: sm */
.avatar-sm {
	width: 1.5rem;
	height: 1.5rem;
}

/* Size: lg */
.avatar-lg {
	width: 2.5rem;
	height: 2.5rem;
}

/* Avatar Image */
.avatar-image {
	aspect-ratio: 1 / 1;
	width: 100%;
	height: 100%;
	border-radius: 9999px;
	object-fit: cover;
}

/* Hide broken image when src fails to load */
.avatar-image[data-error="true"] {
	display: none;
}

/*
 * Avatar Fallback. An avatar is a physical token, so the fallback disc
 * is a solid face and gets the lit edges the kit gives every other one
 * (badge, checked checkbox, selected day) — crest on top, shade below,
 * no drop shadow. It stands in for a photo, so it must not read flatter
 * than the image it replaces.
 */
.avatar-fallback {
	display: flex;
	width: 100%;
	height: 100%;
	align-items: center;
	justify-content: center;
	border-radius: 9999px;
	background-color: var(--muted);
	color: var(--muted-foreground);
	font-size: var(--text-base);
	font-weight: 500;
	line-height: 1;
	box-shadow: var(--highlight-top), var(--shade-bottom);
}

.avatar-sm .avatar-fallback {
	font-size: var(--text-xs);
}

.avatar-lg .avatar-fallback {
	font-size: var(--text-md);
}

/* Fallback hidden when image loaded successfully */
.avatar-image:not([data-error="true"]) ~ .avatar-fallback {
	display: none;
}

/* Avatar Badge. The 2px ring is a cutout separating the dot from the
   avatar beneath it, not depth — the lit edges compose after it so the
   dot reads as a solid bead rather than a flat disc. */
.avatar-badge {
	position: absolute;
	right: 0;
	bottom: 0;
	z-index: 10;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border-radius: 9999px;
	background-color: var(--primary);
	color: var(--primary-foreground);
	box-shadow: 0 0 0 2px var(--background), var(--highlight-top), var(--shade-bottom);
	user-select: none;
}

/* Badge sizes per avatar size */
.avatar[data-size="sm"] .avatar-badge {
	width: 0.5rem;
	height: 0.5rem;
}
.avatar[data-size="sm"] .avatar-badge svg {
	display: none;
}

.avatar[data-size="default"] .avatar-badge {
	width: 0.625rem;
	height: 0.625rem;
}
.avatar[data-size="default"] .avatar-badge svg {
	width: 0.5rem;
	height: 0.5rem;
}

.avatar[data-size="lg"] .avatar-badge {
	width: 0.75rem;
	height: 0.75rem;
}
.avatar[data-size="lg"] .avatar-badge svg {
	width: 0.5rem;
	height: 0.5rem;
}

/* Avatar Group */
.avatar-group {
	display: flex;
}
.avatar-group > .avatar {
	margin-left: -0.5rem;
	box-shadow: 0 0 0 2px var(--background);
}
.avatar-group > .avatar:first-child {
	margin-left: 0;
}

/* Avatar Group Count — the same muted face as a fallback, so it takes
   the same lit edges; the 2px ring stays the cutout against the avatar
   it overlaps. */
.avatar-group-count {
	position: relative;
	display: flex;
	width: 2rem;
	height: 2rem;
	flex-shrink: 0;
	align-items: center;
	justify-content: center;
	border-radius: 9999px;
	background-color: var(--muted);
	color: var(--muted-foreground);
	font-size: var(--text-base);
	font-weight: 500;
	margin-left: -0.5rem;
	box-shadow: 0 0 0 2px var(--background), var(--highlight-top), var(--shade-bottom);
}

.avatar-group-count svg {
	width: 1rem;
	height: 1rem;
}

/* Group count adapts to avatar sizes in the group */
.avatar-group:has(.avatar-sm) .avatar-group-count {
	width: 1.5rem;
	height: 1.5rem;
	font-size: var(--text-xs);
}
.avatar-group:has(.avatar-sm) .avatar-group-count svg {
	width: 0.75rem;
	height: 0.75rem;
}

.avatar-group:has(.avatar-lg) .avatar-group-count {
	width: 2.5rem;
	height: 2.5rem;
}
.avatar-group:has(.avatar-lg) .avatar-group-count svg {
	width: 1.25rem;
	height: 1.25rem;
}

/* Image load transition */
.avatar-image {
	opacity: 0;
	transition: opacity 200ms ease-out;
}
.avatar-image[data-loaded="true"] {
	opacity: 1;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.avatar-image {
		transition-duration: 75ms;
		opacity: 1;
	}
}

/* Dark mode */
.dark .avatar::after {
	mix-blend-mode: lighten;
}

/* badge.css */
/* Base */
.badge {
	display: inline-flex;
	height: 1.5rem;
	width: fit-content;
	flex-shrink: 0;
	align-items: center;
	justify-content: center;
	gap: 0.25rem;
	overflow: hidden;
	border-radius: 9999px;
	border: 1px solid transparent;
	padding: 0.125rem 0.5rem;
	font-size: var(--text-xs);
	line-height: 1rem;
	font-weight: 500;
	white-space: nowrap;
	transition-property: background-color, color, border-color, box-shadow, opacity, transform;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
	/*
	 * Shadow slot — badges are labels, not elevated surfaces, so solid
	 * variants get only the lit-edge insets (no drop shadow), matching
	 * checked checkbox/radio faces. Rings compose on top of the slot.
	 */
	--badge-shadow: 0 0 rgba(0, 0, 0, 0);
	box-shadow: var(--badge-shadow);
}

.badge-default,
.badge-secondary {
	--badge-shadow: var(--highlight-top), var(--shade-bottom);
}

/* The badge itself is never focusable — an interactive badge is a span
   inside a link, so the ring has to be drawn from the anchor's focus. */
.badge:focus-visible,
a:focus-visible > .badge {
	border-color: var(--ring);
	box-shadow: var(--badge-shadow), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
	outline: none;
}
a:focus-visible:has(> .badge) {
	outline: none;
}

.badge[aria-invalid="true"] {
	border-color: var(--destructive);
	box-shadow: var(--badge-shadow), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

.badge svg {
	pointer-events: none;
	width: 0.75rem !important;
	height: 0.75rem !important;
}

/* Icon padding: more breathing room on the side with an icon */
.badge:has(> [data-icon="inline-start"]) {
	padding-left: 0.375rem;
}
.badge:has(> [data-icon="inline-end"]) {
	padding-right: 0.375rem;
}

/* Interactive badges (inside links). The bleed takes the 1.5rem badge to a
   2rem hit target without changing where it sits (Fitts). */
a:has(> .badge) {
	position: relative;
	display: inline-flex;
}
a:has(> .badge)::before {
	content: "";
	position: absolute;
	inset: -0.25rem;
}
a:active > .badge {
	transform: scale(0.97);
	transition-duration: 100ms;
}

/* Variant: default */
.badge-default {
	background-color: var(--primary);
	color: var(--primary-foreground);
}
a:hover > .badge-default {
	background-color: color-mix(in oklch, var(--primary) 80%, transparent);
}

/* Variant: secondary */
.badge-secondary {
	background-color: var(--secondary);
	color: var(--secondary-foreground);
}
a:hover > .badge-secondary {
	background-color: color-mix(in oklch, var(--secondary) 80%, transparent);
}

/* Variant: destructive */
.badge-destructive {
	background-color: color-mix(in oklch, var(--destructive) 15%, transparent);
	color: var(--destructive);
}
.badge-destructive:focus-visible,
a:focus-visible > .badge-destructive {
	box-shadow: var(--badge-shadow), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}
a:hover > .badge-destructive {
	background-color: color-mix(in oklch, var(--destructive) 25%, transparent);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.badge {
		transition-duration: 75ms;
	}
	a:active > .badge {
		transform: none;
	}
}

/* Dark mode */
.dark .badge-destructive {
	background-color: color-mix(in oklch, var(--destructive) 20%, transparent);
}
.dark a:hover > .badge-destructive {
	background-color: color-mix(in oklch, var(--destructive) 30%, transparent);
}
.dark .badge-destructive:focus-visible,
.dark a:focus-visible > .badge-destructive {
	box-shadow: var(--badge-shadow), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}
.dark .badge[aria-invalid="true"] {
	box-shadow: var(--badge-shadow), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}

/* breadcrumb.css */
/* Base nav */
.breadcrumb {
	font-size: var(--text-base);
	line-height: 1.25rem;
}

/* List */
.breadcrumb-list {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.375rem;
	overflow-wrap: break-word;
	list-style: none;
	padding: 0;
	margin: 0;
	color: var(--muted-foreground);
}

/* Item */
.breadcrumb-item {
	display: inline-flex;
	align-items: center;
	gap: 0.25rem;
}

/* Link */
.breadcrumb-link {
	color: var(--muted-foreground);
	text-decoration: none;
	transition-property: color;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.breadcrumb-link:hover {
	color: var(--foreground);
}

/* Current page */
.breadcrumb-page {
	font-weight: 400;
	color: var(--foreground);
}

/* Separator */
.breadcrumb-separator {
	display: inline-flex;
	align-items: center;
}

.breadcrumb-separator svg {
	width: 0.875rem;
	height: 0.875rem;
	color: var(--muted-foreground);
}

/* Ellipsis */
.breadcrumb-ellipsis {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 1.25rem;
	height: 1.25rem;
}

.breadcrumb-ellipsis svg {
	width: 1rem;
	height: 1rem;
	color: var(--muted-foreground);
}

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

/* Dark mode */
.dark .breadcrumb-link:hover {
	color: var(--foreground);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.breadcrumb-link {
		transition-duration: 0ms;
	}
}

/* button.css */
/* Base */
.btn {
	position: relative;
	display: inline-flex;
	flex-shrink: 0;
	align-items: center;
	justify-content: center;
	gap: 0.375rem;
	border-radius: var(--radius-lg);
	border: 1px solid transparent;
	/* No background-clip: padding-box here — the face must paint under
	   the transparent border, or the 1px gap shows the page through as
	   a light halo ring between the face and the cut shadow. */
	font-size: var(--text-base);
	line-height: 1.25rem;
	font-weight: 500;
	white-space: nowrap;
	transition-property: background-color, color, border-color, box-shadow, opacity, transform;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
	outline: none;
	user-select: none;
	cursor: pointer;
	text-decoration: none;
	color: inherit;
	/*
	 * Shadow slots — flat variants (ghost, link) keep the no-op value;
	 * raised variants opt in below. The ring is its own slot so the two
	 * compose in both directions: a ring never flattens the button, and
	 * a press never drops the ring. Rings set the slot, not box-shadow.
	 */
	--btn-shadow: 0 0 rgba(0, 0, 0, 0);
	--btn-shadow-pressed: 0 0 rgba(0, 0, 0, 0);
	--btn-ring: 0 0 rgba(0, 0, 0, 0);
	box-shadow: var(--btn-shadow), var(--btn-ring);
}

/*
 * Raised variants get the full six-technique anatomy via
 * --shadow-control: cut, ambient + top inner highlights, bottom
 * shade, three layered depth shadows (visual-button-shadow-anatomy).
 */
.btn-default,
.btn-secondary,
.btn-destructive {
	--btn-shadow: var(--shadow-control);
	--btn-shadow-pressed: var(--shadow-control-pressed);
}

/* Tinted faces swap to the cut-free variant (see base.templ) */
.btn-destructive {
	--btn-shadow: var(--shadow-control-tint);
	--btn-shadow-pressed: var(--shadow-control-tint-pressed);
}

/* Primary swaps to the deeper variant so its dark face reads as
   raised as the lighter faces beside it (see base.templ) */
.btn-default {
	--btn-shadow: var(--shadow-control-strong);
	--btn-shadow-pressed: var(--shadow-control-strong-pressed);
}

/* The raised faces that keep their own color get a quiet bevel.
   Each face shows the half it can: the white crest reads on dark
   faces, the shaded base on light ones — so one gradient serves
   both modes. The primary face gets a stronger cut below. */
.btn-secondary,
.btn-destructive {
	background-image: linear-gradient(
		to bottom,
		rgba(255, 255, 255, 0.06),
		rgba(255, 255, 255, 0) 45%,
		rgba(16, 24, 40, 0.05)
	);
}

.btn:focus-visible {
	border-color: var(--ring);
	--btn-ring: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

.btn:disabled {
	pointer-events: none;
	opacity: 0.5;
}

.btn[aria-invalid="true"] {
	border-color: var(--destructive);
	--btn-ring: 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

.btn svg {
	pointer-events: none;
	flex-shrink: 0;
	width: 1rem;
	height: 1rem;
}

.btn:active:not(:disabled),
.btn.btn-pressed {
	transform: scale(0.97);
	transition-duration: 120ms;
	/* Depth collapses with the press — the button meets the surface.
	   The ring slot rides along so a keyboard press keeps its focus
	   ring, which this rule outweighs on specificity. */
	box-shadow: var(--btn-shadow-pressed), var(--btn-ring);
}

/* Variant: default */
.btn-default {
	background-color: var(--primary);
	color: var(--primary-foreground);
	/* Technique 6, turned up into a full bevel: a white crest that
	   fades by mid-face and a shaded base rounding the bottom edge.
	   background-image, so the hover background-color still shows. */
	background-image: linear-gradient(
		to bottom,
		rgba(255, 255, 255, 0.12),
		rgba(255, 255, 255, 0.03) 40%,
		rgba(255, 255, 255, 0) 55%,
		rgba(16, 24, 40, 0.1)
	);
	/* Technique 5 — light text gets a hairline shadow for contrast */
	text-shadow: 0 1px 1px rgba(16, 24, 40, 0.2);
}
.btn-default:hover {
	background-color: color-mix(in oklch, var(--primary) 80%, transparent);
}

/* Variant: secondary */
.btn-secondary {
	border-color: var(--border);
	background-color: var(--background);
}
.btn-secondary:hover {
	background-color: var(--muted);
	color: var(--foreground);
}
.btn-secondary[aria-expanded="true"] {
	background-color: var(--muted);
	color: var(--foreground);
}

/* Variant: ghost */
.btn-ghost {
	background-color: transparent;
}
.btn-ghost:hover {
	background-color: var(--muted);
	color: var(--foreground);
}
.btn-ghost[aria-expanded="true"] {
	background-color: var(--muted);
	color: var(--foreground);
}

/* Variant: destructive */
.btn-destructive {
	background-color: color-mix(in oklch, var(--destructive) 10%, transparent);
	color: var(--destructive);
}
.btn-destructive:hover {
	background-color: color-mix(in oklch, var(--destructive) 20%, transparent);
}
.btn-destructive:focus-visible {
	border-color: color-mix(in oklch, var(--destructive) 40%, transparent);
	--btn-ring: 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

/* Variant: link */
.btn-link {
	background-color: transparent;
	color: var(--primary);
	text-underline-offset: 4px;
}
.btn-link:hover {
	text-decoration: underline;
}

/* Size: default */
.btn-size-default {
	height: 2.25rem;
	gap: 0.375rem;
	padding-left: 0.75rem;
	padding-right: 0.75rem;
}

/* Size: xs */
.btn-size-xs {
	height: 1.75rem;
	gap: 0.25rem;
	border-radius: min(var(--radius-md), 10px);
	padding-left: 0.5rem;
	padding-right: 0.5rem;
	font-size: var(--text-xs);
	line-height: 1rem;
}
.btn-size-xs svg {
	width: 0.75rem;
	height: 0.75rem;
}
/* Fitts — the 28px box is under the 32px minimum, so the pointer
   target is expanded past the paint box without changing layout */
.btn-size-xs::after,
.btn-size-icon-xs::after {
	content: '';
	position: absolute;
	inset: -2px;
}

/* Size: sm */
.btn-size-sm {
	height: 2rem;
	gap: 0.25rem;
	border-radius: min(var(--radius-md), 12px);
	padding-left: 0.625rem;
	padding-right: 0.625rem;
}
.btn-size-sm svg {
	width: 0.875rem;
	height: 0.875rem;
}

/* Size: lg */
.btn-size-lg {
	height: 2.5rem;
	gap: 0.375rem;
	padding-left: 0.75rem;
	padding-right: 0.75rem;
}

/* Size: icon */
.btn-size-icon {
	width: 2.25rem;
	height: 2.25rem;
	padding: 0;
}

/* Size: icon-xs */
.btn-size-icon-xs {
	width: 1.75rem;
	height: 1.75rem;
	padding: 0;
	border-radius: min(var(--radius-md), 10px);
}
.btn-size-icon-xs svg {
	width: 0.75rem;
	height: 0.75rem;
}

/* Size: icon-sm */
.btn-size-icon-sm {
	width: 2rem;
	height: 2rem;
	padding: 0;
	border-radius: min(var(--radius-md), 12px);
}

/* Size: icon-lg */
.btn-size-icon-lg {
	width: 2.5rem;
	height: 2.5rem;
	padding: 0;
}

/* Rounded */
.btn.rounded-full {
	border-radius: 9999px;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.btn {
		transition-duration: 75ms;
	}
	.btn:active:not(:disabled),
	.btn.btn-pressed {
		transform: none;
	}
}

/* Dark mode */
.dark .btn-secondary {
	border-color: var(--input);
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}
.dark .btn-secondary:hover {
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
}
.dark .btn-ghost:hover {
	background-color: color-mix(in oklch, var(--muted) 50%, transparent);
}
.dark .btn-destructive {
	background-color: color-mix(in oklch, var(--destructive) 20%, transparent);
}
.dark .btn-destructive:hover {
	background-color: color-mix(in oklch, var(--destructive) 30%, transparent);
}
.dark .btn-destructive:focus-visible {
	--btn-ring: 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}
.dark .btn[aria-invalid="true"] {
	border-color: color-mix(in oklch, var(--destructive) 50%, transparent);
	--btn-ring: 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}
/* Dark-mode primary is a light face with dark text — a dark text
   shadow would smear, not add contrast */
.dark .btn-default {
	text-shadow: none;
}

/* calendar.css */
/* ── Root ── */
.cal {
	--cal-cell: 2.2rem;
	--cal-radius: var(--radius-md);
	display: inline-flex;
	flex-direction: column;
	background: var(--background);
	padding: 0.5rem;
	width: fit-content;
	user-select: none;
}

/* ── Navigation ── */
.cal-nav {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.25rem;
	margin-bottom: 0.25rem;
}

.cal-nav-btn {
	width: var(--cal-cell);
	height: var(--cal-cell);
}

.cal-title {
	font-size: var(--text-base);
	font-weight: 500;
	text-align: center;
	flex: 1;
	/* Tabular figures — the year must not reflow between months */
	font-variant-numeric: tabular-nums;
}

/* ── Grid ── */
.cal-grid {
	width: 100%;
	border-collapse: collapse;
}

.cal-head {
	font-size: var(--text-sm);
	font-weight: 400;
	color: var(--muted-foreground);
	text-align: center;
	width: var(--cal-cell);
	height: var(--cal-cell);
	padding: 0;
}

.cal-cell {
	padding: 1px;
	text-align: center;
}

/* ── Day button ── */
.cal-day {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--cal-cell);
	height: var(--cal-cell);
	border-radius: var(--cal-radius);
	border: 1px solid transparent;
	background: transparent;
	font-size: var(--text-base);
	line-height: 1;
	font-weight: 400;
	cursor: pointer;
	outline: none;
	padding: 0;
	font-family: inherit;
	color: var(--foreground);
	/* Tabular figures — a date grid is a numeric table; proportional
	   digits make the columns visibly ragged */
	font-variant-numeric: tabular-nums;
	/* Shadow slot — an unselected day is a flat cell in a grid, so it
	   keeps the no-op value; the selected face opts in below. The ring
	   composes on top of the slot rather than replacing it, so focusing
	   the selected day does not flatten it. */
	--cal-day-shadow: 0 0 rgba(0, 0, 0, 0);
	box-shadow: var(--cal-day-shadow);
	transition-property: background-color, color, border-color, box-shadow;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Anticipation — hover hints interactivity before click */
.cal-day:hover:not(:disabled):not([data-selected]) {
	background: var(--muted);
}

.cal-day:focus-visible {
	border-color: var(--ring);
	box-shadow: var(--cal-day-shadow), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* Follow Through — press feedback mirrors Button component */
.cal-day:active:not(:disabled) {
	transform: scale(0.97);
	transition-duration: 100ms;
}

/*
 * Von Restorff — today gets a unique dot indicator that persists
 * across hover and selection states, acting as a spatial anchor
 * the user can always find (Working Memory).
 */
.cal-day[data-today]::after {
	content: '';
	position: absolute;
	bottom: 2px;
	left: 50%;
	transform: translateX(-50%);
	width: 4px;
	height: 4px;
	border-radius: 50%;
	background: var(--primary);
	transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.cal-day[data-today]:not([data-selected]) {
	font-weight: 500;
}

/* Selected today — dot inverts to stay visible (Selective Attention) */
.cal-day[data-today][data-selected]::after {
	background: var(--primary-foreground);
}

/*
 * Selected. A selected day is a checked state, not a raised control —
 * it cannot be lifted off the grid it belongs to. So it takes the same
 * lit edges as the other solid checked faces in the kit (badge, checked
 * checkbox and radio): a white crest on top, a shade along the bottom,
 * no drop shadow. Both atoms flip for dark mode on their own.
 */
.cal-day[data-selected] {
	background: var(--primary);
	color: var(--primary-foreground);
	font-weight: 500;
	--cal-day-shadow: var(--highlight-top), var(--shade-bottom);
}

.cal-day[data-selected]:hover {
	background: color-mix(in oklch, var(--primary) 90%, transparent);
}

/*
 * Squash & Stretch — selection pops with spring easing
 * to confirm the tap registered (Doherty Threshold < 400 ms).
 */
.cal-day[data-selecting] {
	animation: cal-pop 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

@keyframes cal-pop {
	/* Deformation stays inside the subtle 0.95-1.05 band; the overshoot
	   easing supplies the confirmation, not the depth of the squash. */
	from { transform: scale(0.95); }
	to   { transform: scale(1); }
}

/* Outside days — Law of Similarity: muted color signals "not this month" */
.cal-day[data-outside] {
	color: var(--muted-foreground);
}

.cal-day[data-outside][data-selected] {
	background: color-mix(in oklch, var(--primary) 60%, transparent);
	color: var(--primary-foreground);
}

/* Disabled */
.cal-day:disabled {
	color: var(--muted-foreground);
	opacity: 0.5;
	pointer-events: none;
	cursor: default;
}

.cal-day:disabled[data-today]::after {
	background: var(--muted-foreground);
}

/*
 * ── Month transition ──
 * Staging + Solid Drawing — directional slide communicates
 * which way in time the user moved, maintaining spatial
 * continuity with the prev/next button positions.
 */

/* Title slides in from navigation direction */
.cal[data-direction="next"] .cal-title {
	animation: cal-slide-next 150ms cubic-bezier(0.22, 1.1, 0.36, 1);
}
.cal[data-direction="prev"] .cal-title {
	animation: cal-slide-prev 150ms cubic-bezier(0.22, 1.1, 0.36, 1);
}

/* Grid rows slide in with staggered delays
   (Follow Through & Overlapping Action — parts settle
   at slightly different times for organic feel) */
.cal[data-direction="next"] .cal-week,
.cal[data-direction="prev"] .cal-week {
	animation-duration: 150ms;
	animation-timing-function: cubic-bezier(0.22, 1.1, 0.36, 1);
	animation-fill-mode: backwards;
}
.cal[data-direction="next"] .cal-week { animation-name: cal-slide-next; }
.cal[data-direction="prev"] .cal-week { animation-name: cal-slide-prev; }

	.cal[data-direction] .cal-week:nth-child(1) { animation-delay: 0ms; }
	.cal[data-direction] .cal-week:nth-child(2) { animation-delay: 10ms; }
	.cal[data-direction] .cal-week:nth-child(3) { animation-delay: 20ms; }
	.cal[data-direction] .cal-week:nth-child(4) { animation-delay: 30ms; }
	.cal[data-direction] .cal-week:nth-child(5) { animation-delay: 40ms; }
	.cal[data-direction] .cal-week:nth-child(6) { animation-delay: 50ms; }

@keyframes cal-slide-next {
	from { opacity: 0; transform: translateX(8px); }
	to   { opacity: 1; transform: translateX(0); }
}
@keyframes cal-slide-prev {
	from { opacity: 0; transform: translateX(-8px); }
	to   { opacity: 1; transform: translateX(0); }
}

/* ── Dark mode ── */
.dark .cal-day:hover:not(:disabled):not([data-selected]) {
	background: color-mix(in oklch, var(--muted) 50%, transparent);
}

.dark .cal-day[data-selected] {
	background: var(--primary);
	color: var(--primary-foreground);
}

.dark .cal-day[data-selected]:hover {
	background: color-mix(in oklch, var(--primary) 80%, transparent);
}

.dark .cal-day[data-today]::after {
	background: var(--primary);
}

.dark .cal-day[data-today][data-selected]::after {
	background: var(--primary-foreground);
}

/* ── Reduced motion — instant state changes, no transform (Timing) ── */
@media (prefers-reduced-motion: reduce) {
	.cal-day {
		transition: none;
	}
	.cal-day:active:not(:disabled) {
		transform: none;
	}
	.cal-day[data-selecting] {
		animation: none;
	}
	.cal[data-direction] .cal-title,
	.cal[data-direction] .cal-week {
		animation: none;
	}
}

/* card.css */
/* Card base */
.card {
	display: flex;
	flex-direction: column;
	gap: 1rem;
	overflow: hidden;
	border-radius: var(--radius-xl);
	background-color: var(--card);
	padding-top: 1rem;
	padding-bottom: 1rem;
	font-size: var(--text-base);
	line-height: 1.25rem;
	color: var(--card-foreground);
	/* Layered resting depth (visual-layered-shadows) — the ring plus
	   three soft layers; --shadow-card flips for dark mode. */
	box-shadow: var(--shadow-card);
	outline: none;
	/* Hover feedback belongs in the 120-180ms band. The shadow is
	   transitioned directly rather than via a pseudo-element overlay:
	   the card needs overflow: hidden to clip full-bleed images to its
	   corners, which would also clip an overlay's shadow. */
	transition-property: box-shadow, transform;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.card:has([data-slot="card-footer"]) {
	padding-bottom: 0;
}

.card:has(> img:first-child) {
	padding-top: 0;
}

.card > img:first-child {
	border-top-left-radius: var(--radius-xl);
	border-top-right-radius: var(--radius-xl);
}

.card > img:last-child {
	border-bottom-left-radius: var(--radius-xl);
	border-bottom-right-radius: var(--radius-xl);
}

/* Focus-visible */
.card:focus-visible {
	box-shadow:
		var(--shadow-card),
		0 0 0 1px var(--ring),
		0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* Interactive card */
.card-interactive {
	cursor: pointer;
}

.card-interactive:hover {
	/* Hover lifts one elevation step (visual-shadow-matches-elevation) */
	box-shadow: var(--shadow-card-hover);
	transform: translateY(-1px);
}

.card-interactive:active,
.card-interactive[data-pressed] {
	transform: translateY(0);
	transition-duration: 100ms;
}

/* Card size: sm */
.card[data-size="sm"] {
	gap: 0.75rem;
	padding-top: 0.75rem;
	padding-bottom: 0.75rem;
}

.card[data-size="sm"]:has([data-slot="card-footer"]) {
	padding-bottom: 0;
}

/* CardHeader */
.card-header {
	display: grid;
	grid-auto-rows: min-content;
	align-items: start;
	gap: 0.25rem;
	border-top-left-radius: var(--radius-xl);
	border-top-right-radius: var(--radius-xl);
	padding-left: 1rem;
	padding-right: 1rem;
}

.card[data-size="sm"] .card-header {
	padding-left: 0.75rem;
	padding-right: 0.75rem;
}

.card-header:has([data-slot="card-action"]) {
	grid-template-columns: 1fr auto;
}

.card-header:has([data-slot="card-description"]) {
	grid-template-rows: auto auto;
}

.card-header.border-b {
	padding-bottom: 1rem;
	border-bottom: 1px solid var(--border);
}

.card[data-size="sm"] .card-header.border-b {
	padding-bottom: 0.75rem;
}

/* CardTitle */
.card-title {
	font-size: var(--text-md);
	line-height: 1.375;
	font-weight: 500;
}

.card[data-size="sm"] .card-title {
	font-size: var(--text-base);
	line-height: 1.25rem;
}

/* CardDescription */
.card-description {
	font-size: var(--text-base);
	color: var(--muted-foreground);
}

/* CardAction */
.card-action {
	grid-column-start: 2;
	grid-row: span 2 / span 2;
	grid-row-start: 1;
	align-self: start;
	justify-self: end;
}

/* CardContent */
.card-content {
	padding-left: 1rem;
	padding-right: 1rem;
}

.card[data-size="sm"] .card-content {
	padding-left: 0.75rem;
	padding-right: 0.75rem;
}

/* CardFooter */
.card-footer {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	border-bottom-left-radius: var(--radius-xl);
	border-bottom-right-radius: var(--radius-xl);
	border-top: 1px solid var(--border);
	background-color: color-mix(in oklch, var(--muted) 50%, transparent);
	padding: 1rem;
}

.card[data-size="sm"] .card-footer {
	padding: 0.75rem;
}

/* Dark mode is covered by the --shadow-card tokens themselves */

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.card {
		transition-duration: 0ms;
	}

	.card-interactive:hover {
		transform: none;
	}

	.card-interactive:active,
	.card-interactive[data-pressed] {
		transform: none;
	}
}

/* carousel.css */
/* Screen reader only — scoped to carousel */
.carousel .sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border-width: 0;
}

/* Carousel base */
.carousel {
	outline: none;
}
.carousel:focus-visible {
	outline: 2px solid var(--ring);
	outline-offset: 2px;
	border-radius: var(--radius-lg);
}

/* Body — positioning context for nav buttons */
.carousel-body {
	position: relative;
}

/* Viewport — clip boundary with cursor hint */
.carousel-viewport {
	overflow: hidden;
	border-radius: var(--radius-lg);
	-webkit-user-select: none;
	user-select: none;
}
/* Tell the browser which axis we own — prevents page scroll fighting the drag */
.carousel[data-orientation="horizontal"] .carousel-viewport {
	touch-action: pan-y pinch-zoom;
}
.carousel[data-orientation="vertical"] .carousel-viewport {
	touch-action: pan-x pinch-zoom;
}
.carousel-viewport[data-draggable] {
	cursor: grab;
}
.carousel-viewport[data-dragging] {
	cursor: grabbing;
}

/*
 * Content track — spring curve overshoots slightly on arrival.
 * During drag [data-dragging] kills transition for 1:1 finger tracking.
 */
.carousel-content {
	display: flex;
	will-change: transform;
	transition: transform 280ms cubic-bezier(0.34, 1.4, 0.64, 1);
}
.carousel[data-orientation="vertical"] .carousel-content {
	flex-direction: column;
}
.carousel-viewport[data-dragging] .carousel-content {
	transition: none;
}

/* Item — sized by JS */
.carousel-item {
	flex: none;
}

/* Navigation buttons */
.carousel .carousel-prev,
.carousel .carousel-next {
	position: absolute;
	border-radius: 9999px;
	touch-action: manipulation;
	z-index: 1;
}

/* Horizontal button positions — outside content */
.carousel[data-orientation="horizontal"] .carousel-prev {
	top: 50%;
	left: -2.5rem;
	transform: translateY(-50%);
}
.carousel[data-orientation="horizontal"] .carousel-next {
	top: 50%;
	right: -2.5rem;
	transform: translateY(-50%);
}
.carousel[data-orientation="horizontal"] .carousel-prev:hover:not(:disabled) {
	transform: translateY(-50%) translateX(-1px);
}
.carousel[data-orientation="horizontal"] .carousel-next:hover:not(:disabled) {
	transform: translateY(-50%) translateX(1px);
}

/* Vertical button positions — outside content */
.carousel[data-orientation="vertical"] .carousel-prev {
	top: -2.5rem;
	left: 50%;
	transform: translateX(-50%) rotate(90deg);
}
.carousel[data-orientation="vertical"] .carousel-next {
	bottom: -2.5rem;
	left: 50%;
	transform: translateX(-50%) rotate(90deg);
}
.carousel[data-orientation="vertical"] .carousel-prev:hover:not(:disabled) {
	transform: translateX(-50%) rotate(90deg) translateX(-1px);
}
.carousel[data-orientation="vertical"] .carousel-next:hover:not(:disabled) {
	transform: translateX(-50%) rotate(90deg) translateX(1px);
}

/* Vertical layout: dots/counter beside viewport without shrinking content */
.carousel[data-orientation="vertical"] {
	position: relative;
}

/* Dot indicators */
.carousel-dots {
	display: flex;
	justify-content: center;
	align-items: center;
	gap: 0.25rem;
	padding-top: 0.75rem;
}
.carousel-dot {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 1.5rem;
	height: 1.5rem;
	border: none;
	padding: 0;
	cursor: pointer;
	background: transparent;
	outline: none;
}
/* Fitts — the visual dot is 8px inside a 24px button; this pushes the
   pointer target to 32px without changing the layout. */
.carousel-dot::after {
	content: "";
	position: absolute;
	inset: -0.25rem;
}
.carousel-dot::before {
	content: "";
	display: block;
	width: 0.5rem;
	height: 0.5rem;
	border-radius: 9999px;
	background-color: var(--border);
	transition:
		width 250ms cubic-bezier(0.34, 1.4, 0.64, 1),
		background-color 200ms ease;
}
.carousel-dot:hover::before {
	background-color: var(--muted-foreground);
}
.carousel-dot:focus-visible {
	outline: 2px solid var(--ring);
	outline-offset: 0;
	border-radius: 9999px;
}
.carousel-dot[data-active]::before {
	width: 1.25rem;
	background-color: var(--foreground);
}

/* Vertical dots: column beside viewport */
.carousel[data-orientation="vertical"] .carousel-dots {
	position: absolute;
	right: -2rem;
	top: 0;
	bottom: 0;
	flex-direction: column;
	justify-content: center;
	padding-top: 0;
}
.carousel[data-orientation="vertical"] .carousel-dot[data-active]::before {
	width: 0.5rem;
	height: 1.25rem;
}

/* Counter */
.carousel-counter {
	text-align: center;
	padding-top: 0.5rem;
	font-size: var(--text-xs);
	line-height: 1rem;
	color: var(--muted-foreground);
	font-variant-numeric: tabular-nums;
}

/* Vertical counter */
.carousel[data-orientation="vertical"] .carousel-counter {
	position: absolute;
	right: -2.5rem;
	top: 0;
	bottom: 0;
	display: flex;
	align-items: center;
	padding-top: 0;
	writing-mode: vertical-lr;
	text-orientation: mixed;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.carousel-content {
		transition: none;
	}
	.carousel-dot::before {
		transition: none;
	}
	.carousel[data-orientation="horizontal"] .carousel-prev:hover:not(:disabled) {
		transform: translateY(-50%);
	}
	.carousel[data-orientation="horizontal"] .carousel-next:hover:not(:disabled) {
		transform: translateY(-50%);
	}
	.carousel[data-orientation="vertical"] .carousel-prev:hover:not(:disabled) {
		transform: translateX(-50%) rotate(90deg);
	}
	.carousel[data-orientation="vertical"] .carousel-next:hover:not(:disabled) {
		transform: translateX(-50%) rotate(90deg);
	}
}

/* Dark mode */
.dark .carousel-dot::before {
	background-color: color-mix(in oklch, var(--foreground) 25%, transparent);
}
.dark .carousel-dot:hover::before {
	background-color: color-mix(in oklch, var(--foreground) 50%, transparent);
}
.dark .carousel-dot[data-active]::before {
	background-color: var(--foreground);
}

/* chart.css */
/* === Chart Container === */
.chart {
	position: relative;
	display: flex;
	flex-direction: column;
	width: 100%;
	font-size: var(--text-xs);
	line-height: 1rem;
}

.chart svg {
	display: block;
	width: 100%;
	height: auto;
	overflow: visible;
}

/* === Grid & Axes === */
.chart-grid-line {
	stroke: var(--border);
	stroke-width: 1;
	stroke-dasharray: 4 4;
	opacity: 0.5;
}

.chart-axis-line {
	stroke: var(--border);
	stroke-width: 1;
}

.chart-axis-tick {
	fill: var(--muted-foreground);
	font-size: var(--text-2xs);
	font-family: inherit;
	/* Tabular figures — axis labels are a column of numbers */
	font-variant-numeric: tabular-nums;
}

/*
 * The one label that must not take the phone's extra pixel. An SVG
 * tick cannot wrap or ellipsize, so on the narrowest axis the growth
 * lands as category names colliding with their neighbours; it holds
 * the desktop step at every width instead.
 */
@media (max-width: 48rem) {
	.chart-axis-tick {
		font-size: 0.6875rem;
	}
}

/* === Bars === */
.chart-bar {
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.chart-bar:hover {
	opacity: 0.8;
}

/* === Keyboard focus ===
   Data marks are one roving tab stop per chart; arrow keys walk them.
   The ring is drawn on the mark itself so the focused datum is obvious
   even when its tooltip is off to one side. */
.chart-hit {
	cursor: crosshair;
}
.chart-bar:focus-visible,
.chart-slice:focus-visible,
.chart-hit:focus-visible {
	outline: 2px solid var(--ring);
	outline-offset: 1px;
}

/* === Lines === */
.chart-line {
	fill: none;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* === Area === */
.chart-area {
	opacity: 0.15;
}

/* === Dots === */
.chart-dot {
	stroke-width: 2;
	stroke: var(--background);
	opacity: 0;
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.chart-dot-active {
	opacity: 1;
}

/* === Pie / Donut === */
.chart-slice {
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
	stroke: var(--background);
	stroke-width: 2;
}
.chart-slice:hover {
	opacity: 0.8;
}
.chart-donut-label {
	fill: var(--foreground);
	font-size: var(--text-2xl);
	font-weight: 600;
	dominant-baseline: central;
	/* Tabular figures — the centre total is a dashboard stat */
	font-variant-numeric: tabular-nums;
}
.chart-donut-sublabel {
	fill: var(--muted-foreground);
	font-size: var(--text-xs);
	dominant-baseline: central;
}

/* === Tooltip === */
.chart-tooltip {
	position: absolute;
	z-index: 50;
	pointer-events: none;
	min-width: 8rem;
	border-radius: var(--radius-lg);
	border: 1px solid color-mix(in oklch, var(--border) 50%, transparent);
	background-color: var(--background);
	padding: 0.375rem 0.625rem;
	font-size: var(--text-xs);
	line-height: 1rem;
	color: var(--foreground);
	/* Popover-tier elevation; the token flips for dark mode */
	box-shadow: var(--shadow-popover);
	/*
	 * Hidden via visibility, not display: display can't transition, so
	 * toggling it in the same frame killed the fade in both directions.
	 * visibility keeps the box measurable for posTip() and is delayed
	 * out so the fade finishes before the tooltip stops hit-testing.
	 */
	visibility: hidden;
	opacity: 0;
	transform: translateY(2px);
	transition: opacity 120ms ease-out, transform 120ms ease-out,
		visibility 0s linear 120ms;
}

.chart-tooltip[data-visible] {
	visibility: visible;
	opacity: 1;
	transform: translateY(0);
	transition: opacity 120ms ease-out, transform 120ms ease-out,
		visibility 0s linear 0s;
}

.chart-tooltip-label {
	font-weight: 500;
	margin-bottom: 0.25rem;
}

.chart-tooltip-row {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.0625rem 0;
}

.chart-tooltip-indicator {
	display: inline-block;
	width: 0.5rem;
	height: 0.5rem;
	flex-shrink: 0;
	border-radius: 2px;
}

.chart-tooltip-name {
	color: var(--muted-foreground);
	flex: 1;
}

.chart-tooltip-value {
	font-weight: 500;
	font-variant-numeric: tabular-nums;
	font-family: var(--font-mono);
}

/* === Legend === */
.chart-legend {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-wrap: wrap;
	gap: 1rem;
	padding-top: 0.75rem;
}

.chart-legend-item {
	display: flex;
	align-items: center;
	gap: 0.375rem;
	font-size: var(--text-xs);
	line-height: 1rem;
	color: var(--muted-foreground);
}

.chart-legend-color {
	width: 0.5rem;
	height: 0.5rem;
	flex-shrink: 0;
	border-radius: 2px;
}

/* === Entrance Animations === */
@keyframes chart-bar-enter {
	from { transform: scaleY(0); }
	to   { transform: scaleY(1); }
}

@keyframes chart-line-enter {
	from { stroke-dashoffset: var(--line-length); }
	to   { stroke-dashoffset: 0; }
}

@keyframes chart-slice-enter {
	from { opacity: 0; transform: scale(0.96); }
	to   { opacity: 1; transform: scale(1); }
}

@keyframes chart-area-enter {
	from { opacity: 0; }
	to   { opacity: 0.15; }
}

.chart[data-animate] .chart-bar {
	/*
	 * transform-box defaults to view-box on SVG elements, which would
	 * resolve "bottom" to the bottom of the whole viewBox — 36px below
	 * the axis, inside the label gutter. fill-box scopes the origin to
	 * each bar so they grow off the baseline instead of up through it.
	 */
	transform-box: fill-box;
	transform-origin: center bottom;
	animation: chart-bar-enter 280ms cubic-bezier(0.34, 1.3, 0.64, 1) both;
	animation-delay: calc(var(--i, 0) * 30ms);
}

/* A bar below the zero line hangs from its top edge, so that is the
   edge it must grow from — "bottom" would detach it from the axis
   for the length of the entrance. */
.chart[data-animate] .chart-bar-neg {
	transform-origin: center top;
}

.chart[data-animate] .chart-line {
	stroke-dasharray: var(--line-length);
	stroke-dashoffset: var(--line-length);
	animation: chart-line-enter 300ms ease-out both;
}

.chart[data-animate] .chart-area {
	opacity: 0;
	animation: chart-area-enter 250ms ease-out 80ms both;
}

.chart[data-animate] .chart-slice {
	/*
	 * Deliberately left on the default view-box: every slice must scale
	 * about the shared pie centre, which renderPie places at the viewBox
	 * centre. fill-box here would burst the slices apart individually.
	 */
	transform-origin: center;
	animation: chart-slice-enter 250ms cubic-bezier(0.34, 1.3, 0.64, 1) both;
	animation-delay: calc(var(--i, 0) * 40ms);
}

/* === Dark Mode === */
.dark .chart-grid-line {
	opacity: 0.3;
}

/* === Reduced Motion === */
@media (prefers-reduced-motion: reduce) {
	.chart[data-animate] .chart-bar,
	.chart[data-animate] .chart-line,
	.chart[data-animate] .chart-area,
	.chart[data-animate] .chart-slice {
		animation: none;
	}
	.chart-bar,
	.chart-dot,
	.chart-slice,
	.chart-tooltip {
		transition: none;
	}
	.chart-area {
		opacity: 0.15;
	}
}

/* checkbox.css */
/* Wrapper */
.checkbox-wrapper {
	position: relative;
	display: inline-flex;
	width: 1.125rem;
	height: 1.125rem;
	flex-shrink: 0;
}

/* Native input: transparent overlay for interaction */
.checkbox-input {
	position: absolute;
	inset: -0.5rem -0.75rem;
	opacity: 0;
	cursor: pointer;
	margin: 0;
	z-index: 1;
}

.checkbox-input:disabled {
	cursor: not-allowed;
}

/* Visual checkbox */
.checkbox-visual {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.125rem;
	height: 1.125rem;
	border-radius: 4px;
	border: 1px solid var(--input);
	background: transparent;
	color: inherit;
	/* Field elevation — a hairline lift off the page */
	box-shadow: var(--shadow-field);
	transition-property: color, background-color, border-color, box-shadow, transform;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Hover on checkbox box (Anticipation — hint before action), matching
   the Radio and Switch treatment */
.checkbox-input:not(:disabled):not(:checked):hover + .checkbox-visual {
	border-color: color-mix(in oklch, var(--primary) 50%, var(--input));
}

/* Pressed feedback */
.checkbox-input:active:not(:disabled) + .checkbox-visual {
	transform: scale(0.97);
	transition-duration: 100ms;
}

/* Focus visible */
.checkbox-input:focus-visible + .checkbox-visual {
	border-color: var(--ring);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* Disabled */
.checkbox-input:disabled + .checkbox-visual {
	opacity: 0.5;
}

/* Disabled via parent group */
[data-disabled="true"] .checkbox-input {
	pointer-events: none;
}
[data-disabled="true"] .checkbox-visual {
	opacity: 0.5;
}

/* Invalid */
.checkbox-input[aria-invalid="true"] + .checkbox-visual {
	border-color: var(--destructive);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

/* Checked — the filled face gets the lit-edge treatment */
.checkbox-input:checked + .checkbox-visual {
	border-color: var(--primary);
	background-color: var(--primary);
	color: var(--primary-foreground);
	box-shadow: var(--highlight-top), var(--shade-bottom), var(--shadow-field);
}

/* Invalid + Checked */
.checkbox-input[aria-invalid="true"]:checked + .checkbox-visual {
	border-color: var(--primary);
}

/* Indicator */
.checkbox-indicator {
	display: grid;
	place-content: center;
	color: currentColor;
	opacity: 1;
	transform: scale(1);
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1),
		transform 150ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.checkbox-indicator svg {
	width: 0.875rem;
	height: 0.875rem;
}

/* Hide indicator when unchecked */
.checkbox-input:not(:checked) + .checkbox-visual .checkbox-indicator {
	opacity: 0;
	transform: scale(0.5);
}

/* Dark mode */
.dark .checkbox-visual {
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}

.dark .checkbox-input:not(:disabled):not(:checked):hover + .checkbox-visual {
	border-color: color-mix(in oklch, var(--primary) 60%, var(--input));
}

.dark .checkbox-input[aria-invalid="true"] + .checkbox-visual {
	border-color: color-mix(in oklch, var(--destructive) 50%, transparent);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}

.dark .checkbox-input:checked + .checkbox-visual {
	background-color: var(--primary);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.checkbox-visual {
		transition-duration: 75ms;
	}
	.checkbox-indicator {
		transition: none;
	}
	.checkbox-input:active:not(:disabled) + .checkbox-visual {
		transform: none;
	}
}

/* collapsible.css */
/*
 * Base. This is a card surface — same --card face, same radius — so it
 * sits at the same height as one instead of flat on the page. The 1px
 * border is gone with the shadow arriving: --shadow-card leads with a
 * hairline ring that draws that edge, and Card itself carries no border
 * for exactly this reason. Keeping both stacked two visible edges.
 */
.collapsible {
	border-radius: var(--radius-lg);
	background-color: var(--card);
	color: var(--card-foreground);
	font-size: var(--text-base);
	line-height: 1.25rem;
	box-shadow: var(--shadow-card);
}

/* Trigger */
.collapsible-trigger {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.5rem;
	padding: 0.75rem 1rem;
	font-weight: 500;
	cursor: pointer;
	user-select: none;
	list-style: none;
	outline: none;
	border-radius: var(--radius-lg);
	transition-property: background-color, border-radius;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Remove default marker */
.collapsible-trigger::-webkit-details-marker {
	display: none;
}
.collapsible-trigger::marker {
	content: "";
}

.collapsible-trigger:hover {
	background-color: var(--muted);
}

.collapsible-trigger:focus-visible {
	box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
}

.collapsible-trigger svg {
	width: 1rem;
	height: 1rem;
	flex-shrink: 0;
	color: var(--muted-foreground);
}

/*
 * The arrow is the whole open/closed readout, so it turns rather than
 * swaps: half a turn on an overshoot curve, which is how a real hinge
 * settles (250ms -- a small state change, and the rotation carries the
 * eye rather than racing it).
 *
 * The turn is on the wrapping span and the selector is [data-slot], not
 * svg[data-slot]: Icon() renders the <svg>, so the attribute can only
 * ever sit on the wrapper -- and matching the wrapper is what lets a
 * trigger that placed its own chevron get the same turn for free.
 */
.collapsible-trigger [data-slot="collapsible-chevron"] {
	display: inline-flex;
	transition: transform 250ms cubic-bezier(0.34, 1.4, 0.64, 1);
}

.collapsible[open] > .collapsible-trigger {
	border-bottom-left-radius: 0;
	border-bottom-right-radius: 0;
}

.collapsible[open] > .collapsible-trigger [data-slot="collapsible-chevron"] {
	transform: rotate(180deg);
}

/* Restore border-radius during close */
.collapsible[data-closing] > .collapsible-trigger {
	border-bottom-left-radius: var(--radius-lg);
	border-bottom-right-radius: var(--radius-lg);
	transition: border-radius 120ms cubic-bezier(0.4, 0, 0.7, 0.2);
}

/* Coming back is half the trip and no overshoot: an exit yields
   attention rather than asking for it, and 120ms is what the content's
   own exit animation gives it before the <details> actually closes. */
.collapsible[data-closing] > .collapsible-trigger [data-slot="collapsible-chevron"] {
	transform: rotate(0deg);
	transition: transform 120ms cubic-bezier(0.4, 0, 0.7, 0.2);
}

/*
 * Content — asymmetric enter/exit (overlapping action).
 * Enter: spring easing with translateY arc (200ms).
 * Exit: snappy ease-in, faster for responsiveness (120ms).
 */
.collapsible-content {
	padding: 0.75rem 1rem;
	border-top: 1px solid var(--border);
	opacity: 1;
	transform: translateY(0);
	transition:
		opacity 200ms cubic-bezier(0.22, 1.1, 0.36, 1),
		transform 200ms cubic-bezier(0.22, 1.1, 0.36, 1);
}

@starting-style {
	.collapsible[open] > .collapsible-content {
		opacity: 0;
		transform: translateY(-4px);
	}
}

/* Exit animation */
.collapsible[data-closing] > .collapsible-content {
	animation: collapsible-out 120ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
	transition: none;
}

@keyframes collapsible-out {
	from { opacity: 1; transform: translateY(0); }
	to   { opacity: 0; transform: translateY(-4px); }
}

/* Dark mode */
.dark .collapsible-trigger:hover {
	background-color: color-mix(in oklch, var(--muted) 50%, transparent);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.collapsible-trigger,
	.collapsible-trigger [data-slot="collapsible-chevron"],
	.collapsible-content {
		transition-duration: 0ms;
	}
	.collapsible[open]:not([data-closing]) > .collapsible-content,
	.collapsible[data-closing] > .collapsible-content {
		animation: none;
	}
}

/* combobox.css */
/* ── Root ── */
.cbox {
	position: relative;
	display: inline-flex;
}

/* ── Input wrapper ── */
.cbox-input-wrap {
	display: inline-flex;
	align-items: center;
	width: fit-content;
	border-radius: var(--radius-lg);
	border: 1px solid var(--input);
	background-color: transparent;
	/* Field elevation — a hairline lift off the page (--shadow-field) */
	box-shadow: var(--shadow-field);
	transition-property: color, background-color, border-color, box-shadow, opacity;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
	/* Same resting height as Input, NativeSelect, SelectTrigger and
	   InputGroup: sibling fields on one row must line up */
	height: 2.25rem;
}

/* Anticipation — hover hints at interactivity before click (Jakob's Law) */
.cbox-input-wrap:hover:not(:has(:disabled)) {
	border-color: color-mix(in oklch, var(--ring) 40%, transparent);
}

/* The ring follows the text input, not any focus inside the wrapper:
   the clear button draws its own ring, and lighting both would put two
   focus indicators on screen at once (staging-one-focal-point). */
.cbox-input-wrap:has(.cbox-input:focus-visible) {
	border-color: var(--ring);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/*
 * Secondary Action — expanded wrapper gets subtle bg tint so the user
 * sees a visual connection between the input and the open panel
 * (Law of Common Region + Working Memory).
 */
.cbox[data-open] .cbox-input-wrap {
	border-color: var(--ring);
	background-color: color-mix(in oklch, var(--muted) 50%, transparent);
}

.cbox-input-wrap:has(:disabled) {
	pointer-events: none;
	opacity: 0.5;
	cursor: not-allowed;
}

.cbox-input-wrap[aria-invalid="true"],
.cbox-input-wrap:has([aria-invalid="true"]) {
	border-color: var(--destructive);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

/* ── Input ── */
.cbox-input {
	flex: 1;
	min-width: 0;
	width: 10rem;
	height: 100%;
	background: transparent;
	border: none;
	outline: none;
	padding: 0 0.625rem;
	/* 1rem until md — a focused control under 16px makes iOS Safari
	   zoom the viewport. Same guard as Input and Textarea. */
	font-size: var(--text-md);
	line-height: 1.5rem;
	color: inherit;
}

@media (min-width: 768px) {
	.cbox-input {
		font-size: var(--text-base);
		line-height: 1.25rem;
	}
}

.cbox-input::placeholder {
	color: var(--muted-foreground);
}

/* ── Trigger button ── */
.cbox-trigger {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	width: 1.5rem;
	height: 1.5rem;
	padding: 0;
	border: none;
	background: transparent;
	color: var(--muted-foreground);
	cursor: pointer;
	border-radius: min(var(--radius-md), 10px);
	transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1),
		transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
	outline: none;
	margin-right: 0.125rem;
}

.cbox-trigger svg {
	width: 1rem;
	height: 1rem;
	pointer-events: none;
}

/* Fitts — 24px boxes; the pseudo-element lifts the pointer target to
   32px without pushing the field taller */
.cbox-trigger,
.cbox-clear {
	position: relative;
}
.cbox-trigger::after,
.cbox-clear::after {
	content: '';
	position: absolute;
	inset: -0.25rem;
}

/* Secondary Action — chevron follows hover to reinforce affordance */
.cbox-trigger:hover {
	color: var(--foreground);
}

/* Squash & Stretch — subtle scale on press (physics-active-state) */
.cbox-trigger:active:not(:disabled) {
	transform: scale(0.97);
	transition-duration: 100ms;
}

/* Chevron rotates when open (Anticipation — confirms the state) */
.cbox[data-open] .cbox-trigger {
	transform: rotate(180deg);
	color: var(--foreground);
}

/* ── Clear button ── */
.cbox-clear {
	display: none;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	width: 1.5rem;
	height: 1.5rem;
	padding: 0;
	border: none;
	background: transparent;
	color: var(--muted-foreground);
	cursor: pointer;
	border-radius: min(var(--radius-md), 10px);
	outline: none;
	margin-right: 0.125rem;
	/* Slow In & Slow Out — fade in when appearing */
	opacity: 0.5;
	transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1),
		opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.cbox-clear svg {
	width: 0.75rem;
	height: 0.75rem;
	pointer-events: none;
}

.cbox-clear:hover {
	color: var(--foreground);
	opacity: 1;
}

/* Keyboard-reachable: it is the only way to clear a committed value,
   so it carries a focus ring of its own */
.cbox-clear:focus-visible {
	color: var(--foreground);
	opacity: 1;
	box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* Squash & Stretch — subtle scale on press (physics-active-state) */
.cbox-clear:active:not(:disabled) {
	transform: scale(0.97);
	transition-duration: 100ms;
}

.cbox[data-has-value] .cbox-clear {
	display: flex;
}

/* Only yield the chevron to the clear button when a clear button is
   actually rendered (ShowClear). Without it, the chevron must stay
   visible so there is always a way to reopen the dropdown. */
.cbox[data-has-value]:has(.cbox-clear) .cbox-trigger {
	display: none;
}

/* ── Content / Popup ── */
.cbox-content {
	position: absolute;
	z-index: 50;
	min-width: 100%;
	max-height: 20rem;
	overflow-y: auto;
	overscroll-behavior: contain;
	border-radius: var(--radius-md);
	background: var(--popover);
	padding: 0.25rem;
	color: var(--popover-foreground);
	font-size: var(--text-base);
	line-height: 1.25rem;
	box-shadow: var(--shadow-popover);
	outline: none;
	/*
	 * Directional slide via custom properties (Solid Drawing).
	 * --cbox-slide drives the directional translate so the panel
	 * emerges FROM the trigger edge, not just fades in place (Staging).
	 * --cbox-base-x holds the alignment offset (e.g. -50% for centering)
	 * and is composed into the keyframes so the base position is never
	 * lost while the animation owns the transform property.
	 */
	--cbox-slide: 0px;
	--cbox-base-x: 0px;
	transform: translateX(var(--cbox-base-x));
}

/* Hidden when closed */
.cbox-content[data-state="closed"] {
	pointer-events: none;
	visibility: hidden;
	opacity: 0;
}

/*
 * Side positioning + directional slide offsets.
 * transform-origin anchors scale to the trigger edge so the panel
 * appears to grow outward — maintaining spatial continuity (Solid Drawing).
 * --cbox-slide drives the directional translate so the panel
 * emerges FROM the input, not just fades in place (Staging).
 */

/* Side: bottom (default) */
.cbox-content[data-side="bottom"] {
	top: calc(100% + 4px);
	transform-origin: top;
	--cbox-slide: -4px;
}
.cbox-content[data-side="top"] {
	bottom: calc(100% + 4px);
	transform-origin: bottom;
	--cbox-slide: 4px;
}

/* Align */
.cbox-content[data-side="bottom"][data-align="start"],
.cbox-content[data-side="top"][data-align="start"] {
	left: 0;
}
.cbox-content[data-side="bottom"][data-align="center"],
.cbox-content[data-side="top"][data-align="center"] {
	left: 50%;
	--cbox-base-x: -50%;
}
.cbox-content[data-side="bottom"][data-align="end"],
.cbox-content[data-side="top"][data-align="end"] {
	right: 0;
}

/*
 * Animations — asymmetric enter/exit with directional slide.
 * Enter: spring easing + directional translate for organic arc (150ms).
 * Exit: snappy accelerate-out, less travel distance than enter so the
 *   leaving panel doesn't compete for attention (Follow Through + Timing).
 */

/* Open (Slow In & Slow Out — spring overshoot on enter) */
.cbox-content[data-state="open"] {
	animation: cbox-in 150ms cubic-bezier(0.22, 1.1, 0.36, 1);
}

/* Close (faster, subtler exit — content yields focus back to input) */
.cbox-content[data-closing] {
	pointer-events: none;
	animation: cbox-out 100ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}

@keyframes cbox-in {
	from {
		opacity: 0;
		transform: translateX(var(--cbox-base-x))
			scale(0.96)
			translateY(var(--cbox-slide));
	}
	to {
		opacity: 1;
		transform: translateX(var(--cbox-base-x))
			scale(1)
			translateY(0);
	}
}

@keyframes cbox-out {
	from {
		opacity: 1;
		transform: translateX(var(--cbox-base-x))
			scale(1)
			translateY(0);
	}
	to {
		opacity: 0;
		transform: translateX(var(--cbox-base-x))
			scale(0.97)
			translateY(calc(var(--cbox-slide) * 0.5));
	}
}

/* ── Group ── */
.cbox-group {
	padding: 0.125rem 0;
}

/* ── Label ── */
.cbox-label {
	padding: 0.25rem 0.5rem;
	font-size: var(--text-xs);
	font-weight: 500;
	color: var(--muted-foreground);
}

/* Chunking — labels after separators get extra top space
   to form visually distinct groups (Law of Proximity) */
.cbox-separator + .cbox-group > .cbox-label:first-child,
.cbox-separator + .cbox-label {
	padding-top: 0.375rem;
}

/* ── Item ── */
.cbox-item {
	position: relative;
	display: flex;
	cursor: default;
	align-items: center;
	gap: 0.375rem;
	/* Concentric with the panel: outer radius minus the 0.25rem pad */
	border-radius: calc(var(--radius-md) - 4px);
	padding: 0.3125rem 2rem 0.3125rem 0.5rem;
	font-size: var(--text-base);
	line-height: 1.25rem;
	outline: none;
	user-select: none;
}

/* Hidden when filtered out */
.cbox-item[data-hidden] {
	display: none;
}

/*
 * Hover fades; keyboard does not. The transition lives on the :hover
 * rule so ArrowUp/ArrowDown moves the highlight instantly.
 */
.cbox-item:hover:not([data-disabled="true"]):not([data-hidden]) {
	background: var(--accent);
	color: var(--accent-foreground);
	transition: background-color 80ms cubic-bezier(0.4, 0, 0.2, 1),
		color 80ms cubic-bezier(0.4, 0, 0.2, 1);
}

.cbox-item[data-highlighted] {
	background: var(--accent);
	color: var(--accent-foreground);
}

/* Selected state */
.cbox-item[data-selected] {
	font-weight: 500;
}

/* Disabled */
.cbox-item[data-disabled="true"] {
	pointer-events: none;
	opacity: 0.5;
}

/* SVG inside items */
.cbox-item svg {
	pointer-events: none;
	flex-shrink: 0;
	width: 1rem;
	height: 1rem;
}

/* ── Item text ── */
.cbox-item-text {
	display: flex;
	flex: 1;
	flex-shrink: 0;
	align-items: center;
	gap: 0.5rem;
	white-space: nowrap;
}

/* ── Item indicator (check) ── */
.cbox-item-indicator {
	pointer-events: none;
	position: absolute;
	right: 0.5rem;
	display: flex;
	align-items: center;
	justify-content: center;
	opacity: 0;
	transform: scale(0.5);
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1),
		transform 150ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.cbox-item[data-selected] .cbox-item-indicator {
	opacity: 1;
	transform: scale(1);
}

.cbox-item-indicator svg {
	width: 0.875rem;
	height: 0.875rem;
}

/* ── Separator ── */
.cbox-separator {
	margin: 0.25rem -0.25rem;
	height: 1px;
	background: var(--border);
}

/* ── Empty ── */
.cbox-empty {
	display: none;
	width: 100%;
	justify-content: center;
	padding: 1.5rem 0.5rem;
	text-align: center;
	font-size: var(--text-base);
	color: var(--muted-foreground);
}

.cbox-content[data-empty] .cbox-empty {
	display: flex;
}

/*
 * Hide groups/separators when all items inside are filtered out.
 * Keeps the panel clean while typing (Law of Praegnanz — simple,
 * orderly form; Cognitive Load — remove noise).
 */
.cbox-group:has([data-slot="combobox-item"]):not(:has([data-slot="combobox-item"]:not([data-hidden]))) {
	display: none;
}
/* Separator before a fully-hidden group */
.cbox-separator:has(+ .cbox-group:not(:has([data-slot="combobox-item"]:not([data-hidden])))) {
	display: none;
}
/* Separator after a fully-hidden group */
.cbox-group:not(:has([data-slot="combobox-item"]:not([data-hidden]))) + .cbox-separator {
	display: none;
}

/* ── Dark mode ── */
.dark .cbox-input-wrap {
	border-color: var(--input);
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}
.dark .cbox-input-wrap:hover:not(:has(:disabled)) {
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
}
.dark .cbox[data-open] .cbox-input-wrap {
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
}
.dark .cbox-input-wrap[aria-invalid="true"],
.dark .cbox-input-wrap:has([aria-invalid="true"]) {
	border-color: color-mix(in oklch, var(--destructive) 50%, transparent);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}
/* ── Reduced motion — instant show/hide, no transform (Timing) ── */
@media (prefers-reduced-motion: reduce) {
	.cbox-input-wrap,
	.cbox-trigger,
	.cbox-clear,
	.cbox-item,
	.cbox-item-indicator {
		transition: none;
	}
	.cbox-trigger:active:not(:disabled),
	.cbox-clear:active:not(:disabled) {
		transform: none;
	}
	.cbox-content[data-state="open"],
	.cbox-content[data-closing] {
		animation: none;
	}
}

/* command.css */
/* ── Command root ── */
.cmd {
	display: flex;
	width: 100%;
	height: 100%;
	flex-direction: column;
	overflow: hidden;
	border-radius: var(--radius-xl);
	background: var(--popover);
	padding: 0.25rem;
	color: var(--popover-foreground);
	/* Popover-tier elevation; dark mode previously had a one-off
	   shadow here while light mode had none — the token covers both */
	box-shadow: var(--shadow-popover);
}

/*
 * ── Command Dialog overrides ──
 * Staging: dialog sits in the upper third so the user's eye
 * doesn't travel far — the command palette is a transient tool,
 * not a destination (Serial Position + Selective Attention).
 */
.cmd-dialog .dialog-panel {
	max-width: calc(100% - 2rem);
	padding: 0;
	overflow: hidden;
	gap: 0;
}

/*
 * Fixed-height shell so the search bar never moves
 * when results filter in/out. The list scrolls internally.
 */
.cmd-dialog .cmd {
	height: 20rem;
	/* The dialog panel owns the elevation */
	box-shadow: none;
}

@media (min-width: 640px) {
	.cmd-dialog .dialog-panel {
		max-width: 28rem;
	}
}

.cmd-dialog[open] {
	align-items: flex-start;
	padding-top: 8vh;
}

/* ── Input wrapper ── */
.cmd-input-wrapper {
	padding: 0.25rem;
	padding-bottom: 0;
}

/*
 * ── Input group (search icon + input) ──
 * Law of Common Region: border + background create a
 * shared surface so icon and input read as one control.
 * Anticipation: border/shadow transitions hint at interactivity
 * before the user types.
 */
.cmd-input-group {
	display: flex;
	height: 2rem;
	align-items: center;
	border-radius: var(--radius-lg);
	border: 1px solid color-mix(in oklch, var(--input) 30%, transparent);
	background: color-mix(in oklch, var(--input) 30%, transparent);
	padding: 0 0.5rem;
	/* border-color only — box-shadow draws the focus ring, and a focus
	   ring must appear instantly rather than fade in over 150ms. */
	transition: border-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.cmd-input-group:focus-within {
	border-color: var(--ring);
	box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* ── Input ── */
.cmd-input {
	flex: 1;
	width: 100%;
	font-size: var(--text-base);
	line-height: 1.25rem;
	background: transparent;
	border: none;
	outline: none;
	color: inherit;
}

.cmd-input::placeholder {
	color: var(--muted-foreground);
}

.cmd-input:disabled {
	cursor: not-allowed;
	opacity: 0.5;
}

/*
 * ── Search icon ──
 * Selective Attention: icon starts muted, transitions to foreground
 * on focus to confirm the input is active (Anticipation).
 */
.cmd-input-icon {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	order: -1;
	padding-right: 0.5rem;
	color: var(--muted-foreground);
	transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.cmd-input-icon svg {
	width: 1rem;
	height: 1rem;
	opacity: 0.5;
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Anticipation: icon wakes up on focus to confirm active state */
.cmd-input-group:focus-within .cmd-input-icon {
	color: var(--foreground);
}

.cmd-input-group:focus-within .cmd-input-icon svg {
	opacity: 1;
}

/* ── List ── */
.cmd-list {
	flex: 1;
	min-height: 0;
	max-height: 18rem;
	overflow-x: hidden;
	overflow-y: auto;
	overscroll-behavior: contain;
	scroll-padding: 0.25rem;
	outline: none;
}

/* ── Empty state ── */
.cmd-empty {
	padding: 1.5rem 0;
	text-align: center;
	font-size: var(--text-base);
	color: var(--muted-foreground);
	display: none;
}

.cmd-empty[data-visible] {
	display: block;
	animation: cmd-empty-in 150ms cubic-bezier(0.33, 1, 0.68, 1);
}

@keyframes cmd-empty-in {
	from { opacity: 0; transform: translateY(-2px); }
	to   { opacity: 1; transform: translateY(0); }
}

/*
 * ── Group ──
 * Chunking (Miller's Law): groups break the list into
 * scannable chunks so users process 2-4 groups instead
 * of N individual items.
 */
.cmd-group {
	overflow: hidden;
	padding: 0.25rem;
	color: var(--foreground);
}

/* Hidden when all items filtered out */
.cmd-group[data-hidden] {
	display: none;
}

/*
 * ── Group heading ──
 * Law of Proximity: extra bottom padding separates the heading
 * from items just enough to read as a label, not an item.
 */
.cmd-group-heading {
	padding: 0.375rem 0.5rem 0.25rem;
	font-size: var(--text-xs);
	font-weight: 500;
	color: var(--muted-foreground);
	letter-spacing: 0.01em;
}

/*
 * ── Item ──
 * Fitts's Law: min-height 2rem ensures comfortable mouse
 * targets; padding gives 32px+ total height per item.
 * Slow In & Slow Out: 80ms bg transition is fast enough
 * to feel instant but smooth enough to avoid flicker.
 */
.cmd-item {
	position: relative;
	display: flex;
	cursor: default;
	align-items: center;
	gap: 0.5rem;
	min-height: 2rem;
	/* Concentric with the panel: the item sits 0.5rem in from the .cmd
	   edge (0.25rem panel pad + 0.25rem group pad), so 0.75rem - 0.5rem */
	border-radius: calc(var(--radius-md) - 4px);
	padding: 0.375rem 0.5rem;
	font-size: var(--text-base);
	line-height: 1.25rem;
	outline: none;
	user-select: none;
	transition: transform 100ms cubic-bezier(0.4, 0, 0.2, 1);
}

/*
 * The highlight fades only when the pointer put it there. Arrow-key
 * navigation lands on items that are not hovered, so it moves the
 * highlight instantly — keyboard must never wait on an animation.
 */
.cmd-item:hover[data-highlighted] {
	transition: background-color 80ms cubic-bezier(0.4, 0, 0.2, 1),
		color 80ms cubic-bezier(0.4, 0, 0.2, 1),
		transform 100ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Hidden when filtered out */
.cmd-item[data-hidden] {
	display: none;
}

/* Highlighted (keyboard / hover) */
.cmd-item[data-highlighted] {
	background: var(--muted);
	color: var(--foreground);
}

/*
 * Squash & Stretch: subtle scale on press gives
 * physical weight to the selection (physics-active-state).
 * 100ms matches button press timing for consistency.
 */
.cmd-item:active:not([data-disabled="true"]) {
	transform: scale(0.98);
	transition-duration: 60ms;
}

/* Disabled */
.cmd-item[data-disabled="true"] {
	pointer-events: none;
	opacity: 0.5;
}

/* SVGs inside items */
.cmd-item svg {
	pointer-events: none;
	flex-shrink: 0;
	width: 1rem;
	height: 1rem;
	color: var(--muted-foreground);
	transition: color 80ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Follow Through: icon color follows highlight for visual unity */
.cmd-item[data-highlighted] svg {
	color: var(--foreground);
}

/*
 * ── Shortcut ──
 * Working Memory: kbd-like styling uses recognition over recall —
 * users see the shortcut and recognise a key combination
 * without reading it as plain text (Von Restorff).
 */
.cmd-shortcut {
	margin-left: auto;
	display: inline-flex;
	align-items: center;
	height: 1.25rem;
	padding: 0 0.25rem;
	border-radius: 0.25rem;
	border: 1px solid var(--border);
	background: color-mix(in oklch, var(--muted) 60%, transparent);
	font-size: var(--text-2xs);
	font-weight: 500;
	font-family: inherit;
	letter-spacing: 0.05em;
	color: var(--muted-foreground);
	transition: color 80ms cubic-bezier(0.4, 0, 0.2, 1),
		border-color 80ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Follow Through: shortcut badge follows highlight state */
.cmd-item[data-highlighted] .cmd-shortcut {
	color: var(--foreground);
	border-color: color-mix(in oklch, var(--foreground) 20%, transparent);
}

/* ── Separator ── */
.cmd-separator {
	margin: 0.25rem -0.25rem;
	height: 1px;
	background: var(--border);
}

/* ── Dark mode ── */
.dark .cmd-input-group {
	border-color: color-mix(in oklch, var(--input) 50%, transparent);
	background: color-mix(in oklch, var(--input) 40%, transparent);
}

.dark .cmd-shortcut {
	background: color-mix(in oklch, var(--muted) 40%, transparent);
	border-color: color-mix(in oklch, var(--foreground) 15%, transparent);
}

/* ── Reduced motion ── */
@media (prefers-reduced-motion: reduce) {
	.cmd-item {
		transition: none;
	}
	/* Must repeat the hover selector: it outranks the bare class
	   above, so listing only .cmd-item would leave it animating. */
	.cmd-item:hover[data-highlighted] {
		transition: none;
	}
	.cmd-item:active:not([data-disabled="true"]) {
		transform: none;
	}
	.cmd-input-group,
	.cmd-input-icon,
	.cmd-input-icon svg,
	.cmd-item svg,
	.cmd-shortcut {
		transition: none;
	}
	.cmd-empty[data-visible] {
		animation: none;
	}
}

/* dark-mode.css */
.dark-mode-toggle {
	position: fixed;
	bottom: 1rem;
	right: 1rem;
	display: flex;
	align-items: center;
	gap: 0.5rem;
	background: var(--card);
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	padding: 0.5rem 0.75rem;
	box-shadow: var(--shadow-raised);
	z-index: 50;
}

/* date-picker.css */
/* ── Date Picker ── */

/* Trigger button — outline style, left-aligned text */
.dp-trigger {
	justify-content: flex-start;
	min-width: 15rem;
	font-weight: 400;
}

/* Placeholder state — muted like input placeholder */
.dp-trigger-placeholder {
	color: var(--muted-foreground);
}

/* Label inside trigger */
[data-slot="dp-label"] {
	flex: 1;
	text-align: left;
}

/* Popover content — auto width to fit calendar */
.dp-trigger + [data-slot="popover-content"] {
	width: auto;
	padding: 0.25rem;
}

/* dialog.css */
/* Dialog: hidden by default, flex when open */
.dialog:not([open]) {
	display: none;
}
.dialog {
	position: fixed;
	inset: 0;
	width: 100%;
	height: 100%;
	max-width: none;
	max-height: none;
	margin: 0;
	padding: 0;
	border: none;
	background: transparent;
	overflow-y: auto;
	overscroll-behavior: contain;
	z-index: 50;
}
.dialog[open] {
	display: flex;
	align-items: safe center;
	justify-content: center;
}

/* Backdrop: shared modal scrim, so dialog and drawer stage alike */
.dialog::backdrop {
	background: var(--backdrop);
	-webkit-backdrop-filter: blur(var(--backdrop-blur));
	backdrop-filter: blur(var(--backdrop-blur));
}

/* Panel: the visible dialog box */
.dialog-panel {
	position: relative;
	width: 100%;
	max-width: calc(100% - 2rem);
	max-height: calc(100% - 2rem);
	margin: auto;
	overflow-y: auto;
	display: grid;
	gap: 1rem;
	border-radius: var(--radius-xl);
	background: var(--background);
	color: var(--foreground);
	padding: 1rem;
	font-size: var(--text-base);
	line-height: 1.25rem;
	/* Top of the elevation scale (visual-shadow-matches-elevation) */
	box-shadow: var(--shadow-modal);
	outline: none;
}

@media (min-width: 640px) {
	.dialog-panel {
		max-width: 24rem;
	}
}

/* Close X button — extended hit area for Fitts's Law (min 44px).
   Radius is concentric with the panel: outer radius minus the
   0.5rem inset (visual-concentric-radius). */
.dialog-close-x {
	position: absolute;
	top: 0.5rem;
	right: 0.5rem;
	border-radius: calc(var(--radius-xl) - 0.5rem);
}
.dialog-close-x::after {
	content: '';
	position: absolute;
	inset: -0.5rem;
}

/* Header */
.dialog-header {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

/* Footer */
.dialog-footer {
	margin: 0 -1rem -1rem -1rem;
	display: flex;
	flex-direction: column-reverse;
	gap: 0.5rem;
	border-radius: 0 0 var(--radius-xl) var(--radius-xl);
	border-top: 1px solid var(--border);
	background: color-mix(in oklch, var(--muted) 50%, transparent);
	padding: 1rem;
}

@media (min-width: 640px) {
	.dialog-footer {
		flex-direction: row;
		justify-content: flex-end;
	}
}

/* Title */
.dialog-title {
	font-size: var(--text-md);
	line-height: 1;
	font-weight: 500;
	margin: 0;
}

/* Description */
.dialog-description {
	font-size: var(--text-base);
	color: var(--muted-foreground);
	margin: 0;
}
.dialog-description a {
	text-decoration: underline;
	text-underline-offset: 3px;
}
.dialog-description a:hover {
	color: var(--foreground);
}

/*
 * Animations — asymmetric enter/exit with overlapping action.
 * Enter: spring easing + slight translateY for organic arc (200ms panel, 150ms backdrop).
 * Exit: snappy ease-in, faster than enter for responsiveness (120ms panel, 100ms backdrop).
 */

/* Open */
.dialog[open] .dialog-panel {
	animation: dialog-panel-in 200ms cubic-bezier(0.22, 1.1, 0.36, 1);
}
.dialog[open]::backdrop {
	animation: dialog-backdrop-in 150ms cubic-bezier(0.33, 1, 0.68, 1);
}

/* Close — pointer-events off so a panel on its way out cannot be
   clicked (the tooltip and context menu do the same) */
.dialog[data-closing] .dialog-panel {
	pointer-events: none;
	animation: dialog-panel-out 120ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}
.dialog[data-closing]::backdrop {
	animation: dialog-backdrop-out 100ms cubic-bezier(0.4, 0, 1, 1) forwards;
}

@keyframes dialog-panel-in {
	from { opacity: 0; transform: scale(0.96) translateY(6px); }
	to   { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes dialog-panel-out {
	from { opacity: 1; transform: scale(1) translateY(0); }
	to   { opacity: 0; transform: scale(0.97) translateY(3px); }
}
@keyframes dialog-backdrop-in {
	from { opacity: 0; }
	to   { opacity: 1; }
}
@keyframes dialog-backdrop-out {
	from { opacity: 1; }
	to   { opacity: 0; }
}


/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.dialog[open] .dialog-panel,
	.dialog[data-closing] .dialog-panel {
		animation: none;
	}
	.dialog[open]::backdrop,
	.dialog[data-closing]::backdrop {
		animation: none;
	}
}

/* drawer.css */
/* Drawer: hidden by default, flex when open */
.drawer:not([open]) {
	display: none;
}
.drawer {
	position: fixed;
	inset: 0;
	width: 100%;
	height: 100%;
	max-width: none;
	max-height: none;
	margin: 0;
	padding: 0;
	border: none;
	background: transparent;
	/* clip, not hidden: a hidden box is still programmatically scrollable,
	   so showModal()'s initial focus scrolls the dialog toward the
	   still-translated panel (right/bottom only — negative-edge overflow
	   isn't scrollable), which fights the slide-in animation. clip makes
	   this box a non-scroll-container, so focus can't scroll it. */
	overflow: hidden;
	overflow: clip;
	overscroll-behavior: contain;
	z-index: 50;
}
.drawer[open] {
	display: flex;
}

/* Direction alignment */
.drawer-bottom[open] {
	align-items: flex-end;
	justify-content: center;
}
.drawer-top[open] {
	align-items: flex-start;
	justify-content: center;
}
.drawer-left[open] {
	align-items: stretch;
	justify-content: flex-start;
}
.drawer-right[open] {
	align-items: stretch;
	justify-content: flex-end;
}

/* Backdrop: shared modal scrim, so drawer and dialog stage alike */
.drawer::backdrop {
	background: var(--backdrop);
	-webkit-backdrop-filter: blur(var(--backdrop-blur));
	backdrop-filter: blur(var(--backdrop-blur));
}

/* Panel: the visible drawer box */
.drawer-panel {
	position: relative;
	display: flex;
	flex-direction: column;
	background: var(--background);
	color: var(--foreground);
	font-size: var(--text-base);
	line-height: 1.25rem;
	outline: none;
	overflow-y: auto;
	/* Top of the elevation scale (visual-shadow-matches-elevation) */
	box-shadow: var(--shadow-modal);
}

/* Bottom panel */
.drawer-bottom .drawer-panel {
	width: 100%;
	max-height: 80vh;
	margin-top: 6rem;
	border-top-left-radius: var(--radius-xl);
	border-top-right-radius: var(--radius-xl);
	border-top: 1px solid var(--border);
}

/* Top panel */
.drawer-top .drawer-panel {
	width: 100%;
	max-height: 80vh;
	margin-bottom: 6rem;
	border-bottom-left-radius: var(--radius-xl);
	border-bottom-right-radius: var(--radius-xl);
	border-bottom: 1px solid var(--border);
}

/* Left panel */
.drawer-left .drawer-panel {
	height: 100%;
	width: 75%;
	max-width: 24rem;
	border-right: 1px solid var(--border);
	border-top-right-radius: var(--radius-xl);
	border-bottom-right-radius: var(--radius-xl);
}

/* Right panel */
.drawer-right .drawer-panel {
	height: 100%;
	width: 75%;
	max-width: 24rem;
	border-left: 1px solid var(--border);
	border-top-left-radius: var(--radius-xl);
	border-bottom-left-radius: var(--radius-xl);
}

/*
 * Handle bar — drag target first, close target second.
 * A handle is the universal "drag me down" signifier, so it drags
 * (see drawerScript) as well as closing on tap; affording a gesture
 * it did not have was the mismatch (ux-jakobs-familiar-patterns).
 * The entire padded region is interactive (data-drawer-close):
 * 16px + 4px bar + 12px = a 32px strip across the full drawer
 * width (ux-fitts-target-size). touch-action: none keeps the drag
 * from being stolen by panel scrolling.
 */
.drawer-handle {
	display: flex;
	justify-content: center;
	padding: 1rem 0 0.75rem;
	flex-shrink: 0;
	cursor: grab;
	touch-action: none;
}
.drawer-handle:active {
	cursor: grabbing;
}
	.drawer-handle span {
		display: block;
		width: 100px;
		height: 4px;
		border-radius: 9999px;
		background-color: var(--muted-foreground);
		opacity: 0.4;
		/* Secondary action: handle scales in with spring after panel arrives */
		transform: scaleX(1);
		transition: transform 200ms cubic-bezier(0.34, 1.4, 0.64, 1) 80ms;
	}

/* Close X button — extended hit area for Fitts's Law (min 44px).
   Radius is concentric with the panel: outer radius minus the
   0.5rem inset (visual-concentric-radius). */
.drawer-close-x {
	position: absolute;
	top: 0.5rem;
	right: 0.5rem;
	border-radius: calc(var(--radius-xl) - 0.5rem);
}
.drawer-close-x::after {
	content: '';
	position: absolute;
	inset: -0.5rem;
}

/* Header — stages attention on title after panel settles */
.drawer-header {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	padding: 1rem;
	/* Overlapping action: content fades in after panel slide */
	opacity: 1;
	transform: translateY(0);
	transition: opacity 150ms ease-out 60ms, transform 150ms ease-out 60ms;
}
.drawer-bottom .drawer-header,
.drawer-top .drawer-header {
	text-align: center;
}
@media (min-width: 768px) {
	.drawer-bottom .drawer-header,
	.drawer-top .drawer-header {
		text-align: left;
	}
}

/* Footer — Law of Common Region: visually distinct action zone */
.drawer-footer {
	margin-top: auto;
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	border-top: 1px solid var(--border);
	background: color-mix(in oklch, var(--muted) 50%, transparent);
	padding: 1rem;
	/* Overlapping action: footer reveals after header */
	opacity: 1;
	transform: translateY(0);
	transition: opacity 150ms ease-out 100ms, transform 150ms ease-out 100ms;
}

/* Title */
.drawer-title {
	font-size: var(--text-md);
	line-height: 1;
	font-weight: 500;
	margin: 0;
}

/* Description */
.drawer-description {
	font-size: var(--text-base);
	color: var(--muted-foreground);
	margin: 0;
}

/*
 * Animations
 *
 * Staging: backdrop leads (150ms), panel follows with spring easing (280ms).
 * Panel slides at full opacity — real drawers don't ghost while moving.
 * Exit: snappy ease-in (150ms panel, 120ms backdrop), faster than enter.
 * Content: @starting-style stagger creates follow-through inside the panel.
 */

/* === Enter: panel slides (spring easing with micro-overshoot) === */
.drawer-bottom[open] .drawer-panel {
	animation: drawer-slide-up-in 280ms cubic-bezier(0.22, 1.12, 0.36, 1);
}
.drawer-top[open] .drawer-panel {
	animation: drawer-slide-down-in 280ms cubic-bezier(0.22, 1.12, 0.36, 1);
}
.drawer-left[open] .drawer-panel {
	animation: drawer-slide-right-in 280ms cubic-bezier(0.22, 1.12, 0.36, 1);
}
.drawer-right[open] .drawer-panel {
	animation: drawer-slide-left-in 280ms cubic-bezier(0.22, 1.12, 0.36, 1);
}

/* Enter: backdrop leads panel (faster, sets the stage) */
.drawer[open]::backdrop {
	animation: drawer-backdrop-in 150ms cubic-bezier(0.33, 1, 0.68, 1);
}

/* === Exit: panel slides out (snappy, no spring) ===
   pointer-events off so a panel on its way out cannot be clicked. */
.drawer[data-closing] .drawer-panel {
	pointer-events: none;
}
.drawer-bottom[data-closing] .drawer-panel {
	animation: drawer-slide-up-out 150ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}
.drawer-top[data-closing] .drawer-panel {
	animation: drawer-slide-down-out 150ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}
.drawer-left[data-closing] .drawer-panel {
	animation: drawer-slide-right-out 150ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}
.drawer-right[data-closing] .drawer-panel {
	animation: drawer-slide-left-out 150ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}

/* Exit: backdrop follows panel (slightly delayed dissolve) */
.drawer[data-closing]::backdrop {
	animation: drawer-backdrop-out 100ms cubic-bezier(0.4, 0, 1, 1) forwards;
}

/* Panel keyframes — pure transform, no opacity (drawers don't ghost) */
@keyframes drawer-slide-up-in {
	from { transform: translateY(100%); }
	to   { transform: translateY(0); }
}
/* Exit starts from wherever the finger left the panel
   (--drawer-drag-y is stamped on touchend), so a flung-away drawer
   continues the drag instead of snapping back to zero first. It is
   0px for every close that did not come from a drag. */
@keyframes drawer-slide-up-out {
	from { transform: translateY(var(--drawer-drag-y, 0px)); }
	to   { transform: translateY(100%); }
}
@keyframes drawer-slide-down-in {
	from { transform: translateY(-100%); }
	to   { transform: translateY(0); }
}
@keyframes drawer-slide-down-out {
	from { transform: translateY(0); }
	to   { transform: translateY(-100%); }
}
@keyframes drawer-slide-right-in {
	from { transform: translateX(-100%); }
	to   { transform: translateX(0); }
}
@keyframes drawer-slide-right-out {
	from { transform: translateX(0); }
	to   { transform: translateX(-100%); }
}
@keyframes drawer-slide-left-in {
	from { transform: translateX(100%); }
	to   { transform: translateX(0); }
}
@keyframes drawer-slide-left-out {
	from { transform: translateX(0); }
	to   { transform: translateX(100%); }
}

/* Backdrop keyframes */
@keyframes drawer-backdrop-in {
	from { opacity: 0; }
	to   { opacity: 1; }
}
@keyframes drawer-backdrop-out {
	from { opacity: 1; }
	to   { opacity: 0; }
}

/*
 * Follow-through: content reveals after panel settles.
 * Header staggered at 60ms, footer at 100ms via transition-delay above.
 * Handle scales in with spring at 80ms (secondary action).
 */
@starting-style {
	.drawer[open] .drawer-header {
		opacity: 0;
		transform: translateY(6px);
	}
	.drawer[open] .drawer-footer {
		opacity: 0;
		transform: translateY(6px);
	}
	.drawer[open] .drawer-handle span {
		transform: scaleX(0.4);
	}
}

/* Dark mode is covered by the --shadow-modal token itself */
.dark .drawer-handle span {
	background-color: var(--muted-foreground);
	opacity: 0.3;
}
.dark .drawer-footer {
	background: color-mix(in oklch, var(--muted) 30%, transparent);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.drawer[open] .drawer-panel,
	.drawer[data-closing] .drawer-panel {
		animation: none;
	}
	.drawer[open]::backdrop,
	.drawer[data-closing]::backdrop {
		animation: none;
	}
	.drawer-header,
	.drawer-footer {
		transition: none;
	}
	.drawer-handle span {
		transition: none;
	}
}

/* empty.css */
/* Empty base */
.empty {
	display: flex;
	width: 100%;
	min-width: 0;
	flex: 1;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 1rem;
	border-radius: var(--radius-xl);
	border: 1px dashed var(--border);
	padding: 1.5rem;
	text-align: center;
	/* balance is a heading treatment; the description opts back out
	   to pretty below (type-text-wrap-balance-headings) */
	text-wrap: balance;
}

/* EmptyHeader */
.empty-header {
	display: flex;
	max-width: 24rem;
	flex-direction: column;
	align-items: center;
	gap: 0.5rem;
}

/* EmptyMedia base */
.empty-media {
	margin-bottom: 0.5rem;
	display: flex;
	flex-shrink: 0;
	align-items: center;
	justify-content: center;
}

.empty-media svg {
	pointer-events: none;
	flex-shrink: 0;
}

/* EmptyMedia variant: icon */
.empty-media-icon {
	display: flex;
	width: 2rem;
	height: 2rem;
	flex-shrink: 0;
	align-items: center;
	justify-content: center;
	border-radius: var(--radius-lg);
	background-color: var(--muted);
	color: var(--foreground);
}

.empty-media-icon svg {
	width: 1rem;
	height: 1rem;
}

/* EmptyTitle */
.empty-title {
	font-size: var(--text-base);
	font-weight: 500;
	letter-spacing: -0.01em;
}

/* EmptyDescription */
.empty-description {
	font-size: var(--text-base);
	line-height: 1.625;
	color: var(--muted-foreground);
	/* Body copy, not a heading — pretty trims orphans instead */
	text-wrap: pretty;
}

.empty-description a {
	text-decoration: underline;
	text-underline-offset: 3px;
}

.empty-description a:hover {
	color: var(--foreground);
}

/* EmptyContent */
.empty-content {
	display: flex;
	width: 100%;
	max-width: 24rem;
	min-width: 0;
	flex-direction: column;
	align-items: center;
	gap: 0.625rem;
	font-size: var(--text-base);
	text-wrap: balance;
}

/* Dark mode needs no overrides here — every color above is a token
   that already flips. */

/* error.css */
/*
 * Peak-end: the error page is the worst page to leave unpolished, so it
 * gets the same Empty-state treatment as any other dead end in the app.
 */
.error-main {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 100dvh;
	padding: 1.5rem;
}

.error-empty {
	max-width: 28rem;
	flex: none;
	border: none;
}

/* Von Restorff — the status code is the one number worth reporting */
.error-code {
	font-size: var(--text-xs);
	line-height: 1rem;
	font-weight: 500;
	font-variant-numeric: tabular-nums;
	letter-spacing: 0.08em;
	color: var(--muted-foreground);
}

/* field.css */
/* ── FieldSet ── */
.field-set {
	display: flex;
	flex-direction: column;
	gap: 1rem;
	border: none;
	margin: 0;
	padding: 0;
}
.field-set:has(> [data-slot="checkbox-group"]),
.field-set:has(> [data-slot="radio-group"]) {
	gap: 0.75rem;
}

/* ── FieldLegend ── */
.field-legend {
	margin-bottom: 0.375rem;
	font-weight: 500;
	text-wrap: balance;
}
.field-legend[data-variant="label"] {
	font-size: var(--text-base);
	line-height: 1.25rem;
}
.field-legend[data-variant="legend"] {
	font-size: var(--text-md);
	line-height: 1.5rem;
}

/* ── FieldGroup ── */
.field-group {
	container-type: inline-size;
	container-name: field-group;
	display: flex;
	width: 100%;
	flex-direction: column;
	gap: 1.25rem;
}
.field-group .field-group {
	gap: 1rem;
}

/* ── Field ── */
.field {
	display: flex;
	width: 100%;
	gap: 0.5rem;
	transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* Invalid: wrapper convention (data-invalid, from the Invalid prop) or
   control convention (aria-invalid on the input, as input/input-group
   style off) — either one lights up the whole field */
.field[data-invalid="true"],
.field:has([aria-invalid="true"]) {
	color: var(--destructive);
}

/* Orientation: vertical */
.field[data-orientation="vertical"] {
	flex-direction: column;
}
.field[data-orientation="vertical"] > * {
	width: 100%;
}
.field[data-orientation="vertical"] > .sr-only {
	width: auto;
}

/* Orientation: horizontal */
.field[data-orientation="horizontal"] {
	flex-direction: row;
	align-items: center;
}
.field[data-orientation="horizontal"]:has(> [data-slot="field-content"]) {
	align-items: flex-start;
}
.field[data-orientation="horizontal"] > [data-slot="field-label"],
.field[data-orientation="horizontal"] > [data-slot="field-title"] {
	flex: 1 1 auto;
}
.field[data-orientation="horizontal"]:has(> [data-slot="field-content"]) [role="checkbox"],
.field[data-orientation="horizontal"]:has(> [data-slot="field-content"]) [role="radio"] {
	margin-top: 1px;
}

/* Orientation: responsive (uses container query) */
.field[data-orientation="responsive"] {
	flex-direction: column;
}
.field[data-orientation="responsive"] > * {
	width: 100%;
}
.field[data-orientation="responsive"] > .sr-only {
	width: auto;
}
@container field-group (min-width: 28rem) {
	.field[data-orientation="responsive"] {
		flex-direction: row;
		align-items: center;
	}
	.field[data-orientation="responsive"] > * {
		width: auto;
	}
	.field[data-orientation="responsive"]:has(> [data-slot="field-content"]) {
		align-items: flex-start;
	}
	.field[data-orientation="responsive"] > [data-slot="field-label"],
	.field[data-orientation="responsive"] > [data-slot="field-title"] {
		flex: 1 1 auto;
	}
	.field[data-orientation="responsive"]:has(> [data-slot="field-content"]) [role="checkbox"],
	.field[data-orientation="responsive"]:has(> [data-slot="field-content"]) [role="radio"] {
		margin-top: 1px;
	}
}

/* ── FieldContent ── */
.field-content {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: 0.125rem;
	line-height: 1.375;
}

/* ── FieldLabel ── */
.field-label {
	display: flex;
	width: fit-content;
	gap: 0.5rem;
	line-height: 1.375;
	font-size: var(--text-base);
	font-weight: 500;
	user-select: none;
	cursor: pointer;
	transition-property: opacity, background-color, border-color;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.field[data-disabled="true"] .field-label {
	pointer-events: none;
	opacity: 0.5;
}
/* Card-style highlight when containing checked inputs. Scoped to the card
   layout below — on a plain inline label the tint has no border or
   padding to sit in and reads as a rectangle glued to the text. */
.field-label:has(> [data-slot="field"]):has(:checked) {
	border-color: color-mix(in oklch, var(--primary) 30%, transparent);
	background-color: color-mix(in oklch, var(--primary) 5%, transparent);
}
/* Card layout when wrapping a nested field */
.field-label:has(> [data-slot="field"]) {
	border-radius: var(--radius-lg);
	border: 1px solid var(--border);
	width: 100%;
	flex-direction: column;
}
.field-label > [data-slot="field"] {
	padding: 0.625rem;
}
/* Dark mode */
.dark .field-label:has(> [data-slot="field"]):has(:checked) {
	border-color: color-mix(in oklch, var(--primary) 20%, transparent);
	background-color: color-mix(in oklch, var(--primary) 10%, transparent);
}

/* ── FieldTitle ── */
.field-title {
	display: flex;
	width: fit-content;
	align-items: center;
	gap: 0.5rem;
	font-size: var(--text-base);
	line-height: 1.375;
	font-weight: 500;
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.field[data-disabled="true"] .field-title {
	opacity: 0.5;
}

/* ── FieldDescription ── */
.field-description {
	text-align: left;
	font-size: var(--text-base);
	line-height: 1.5;
	font-weight: 400;
	color: var(--muted-foreground);
	text-wrap: pretty;
}
.field[data-orientation="horizontal"] .field-description {
	text-wrap: balance;
}
[data-variant="legend"] + .field-description,
[data-variant="label"] + .field-description {
	margin-top: -0.375rem;
}
.field-description:last-child {
	margin-top: 0;
}
.field-description:nth-last-child(2) {
	margin-top: -0.25rem;
}
.field-description a {
	text-decoration: underline;
	text-underline-offset: 4px;
	transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.field-description a:hover {
	color: var(--primary);
}

/* ── FieldSeparator ── */
.field-separator {
	position: relative;
	margin-top: -0.5rem;
	margin-bottom: -0.5rem;
	height: 1.25rem;
	font-size: var(--text-base);
}
.field-separator-line {
	position: absolute;
	left: 0;
	right: 0;
	top: 50%;
	border: none;
	height: 1px;
	background-color: var(--border);
	margin: 0;
}
.field-separator-content {
	position: relative;
	display: block;
	margin-left: auto;
	margin-right: auto;
	width: fit-content;
	background-color: var(--background);
	padding-left: 0.5rem;
	padding-right: 0.5rem;
	color: var(--muted-foreground);
}
.field-separator-content:empty {
	display: none;
}

/* ── FieldError ── */
.field-error {
	font-size: var(--text-base);
	font-weight: 400;
	color: var(--destructive);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.field,
	.field-label,
	.field-title,
	.field-description a {
		transition-duration: 75ms;
	}
}

/* icon.css */
/*
 * --- Icon lighting ---
 * Every closed glyph carries a translucent body under its outline
 * (scripts/icons3d.py). Here it gets lit: a highlight down the
 * top-left face, nothing across the middle, a shade on the
 * bottom-right -- the page's one top-down light, leaning slightly
 * with it. Both alphas are tokens so the pair re-tunes for dark
 * mode, where a body sits on a dark page and needs a brighter top
 * to dome at all.
 *
 * The two transparent middle stops are the same colors as the
 * stops they meet, not a shared `transparent`: gradients
 * interpolate un-premultiplied, so fading white straight into
 * black would drag a gray haze through the midtones.
 */
/*
 * How heavy the body sits. The files carry the light-mode value as
 * an attribute so they still read standing alone (an <img>, a
 * favicon); this hands it back to the theme, and dark mode asks for
 * several times as much -- a tint on a dark page has to be strong
 * enough to dome under the highlight, where the same value on paper
 * is a grey block behind the outline.
 */
.i3d-body {
	opacity: var(--icon-body);
}

#icon-3d .lit {
	stop-opacity: var(--icon-lit);
}

#icon-3d .shade {
	stop-opacity: var(--icon-shade);
}

/*
 * The shadow the glyph throws on the surface under it. drop-shadow
 * follows the alpha channel, so it traces the silhouette -- box-shadow
 * would draw a rectangle around it. Open glyphs (arrows, checks, math
 * signs) have no body to light and this is all the depth they get,
 * which is the honest reading: a wire has no lit face.
 */
.icon {
	filter: drop-shadow(0 1px 1px var(--icon-cast));
}

/* Paint-server carrier: in the document, never on the page. */
.icon-defs {
	position: absolute;
	width: 0;
	height: 0;
	overflow: hidden;
}

/* input-group.css */
/* InputGroup: Base */
.input-group {
	position: relative;
	display: flex;
	height: 2.25rem;
	width: 100%;
	min-width: 0;
	align-items: center;
	border-radius: var(--radius-lg);
	border: 1px solid var(--input);
	cursor: text;
	/* Field elevation — the group is one control (Law of Common Region) */
	box-shadow: var(--shadow-field);
	transition-property: color, background-color, border-color, box-shadow, opacity;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
	outline: none;
}

/* Auto height for textarea */
.input-group:has(> textarea),
.input-group.input-group-has-textarea {
	height: auto;
}

/* Focus ring belongs to the text control only. An embedded
   InputGroupButton keeps its own ring (the --btn-ring slot survives the
   flattening below), so ringing the whole group on any focus-within
   would put two focus indicators on screen at once. */
.input-group:has(.input-group-control:focus-visible),
.input-group:has(.input-group-control-textarea:focus-visible) {
	border-color: var(--ring);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* Invalid styling */
.input-group:has([aria-invalid="true"]) {
	border-color: var(--destructive);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

/* Disabled styling */
.input-group[data-disabled="true"] {
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
	opacity: 0.5;
	pointer-events: none;
}

/* InputGroupAddon: Base */
.input-group-addon {
	display: flex;
	height: auto;
	cursor: text;
	align-items: center;
	justify-content: center;
	gap: 0.5rem;
	padding-top: 0.375rem;
	padding-bottom: 0.375rem;
	font-size: var(--text-base);
	font-weight: 500;
	color: var(--muted-foreground);
	user-select: none;
	transition-property: color;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Secondary action: addon content shifts to foreground on focus */
.input-group:focus-within .input-group-addon {
	color: var(--foreground);
}

/* Keep addon muted on invalid -- don't compete with error signal */
.input-group:has([aria-invalid="true"]) .input-group-addon {
	color: var(--muted-foreground);
}

.input-group[data-disabled="true"] .input-group-addon {
	opacity: 0.5;
}

.input-group-addon svg:not([class*="size-"]) {
	width: 1rem;
	height: 1rem;
}

/* Addon: inline-start */
.input-group-addon-inline-start {
	order: -1;
	padding-left: 0.5rem;
}

/* Addon: inline-end */
.input-group-addon-inline-end {
	order: 999;
	padding-right: 0.5rem;
}


/* Input inside group: remove borders and shadows */
.input-group .input-group-control {
	flex: 1;
	border-radius: 0;
	border: 0;
	background-color: transparent;
	box-shadow: none;
	outline: none;
}

.input-group .input-group-control:focus-visible {
	box-shadow: none;
	border-color: transparent;
}

.input-group .input-group-control:disabled {
	background-color: transparent;
}

.input-group .input-group-control[aria-invalid="true"] {
	box-shadow: none;
}

/* Adjust input padding when addon is present */
.input-group:has(> .input-group-addon-inline-start) > .input-group-control {
	padding-left: 0.375rem;
}

.input-group:has(> .input-group-addon-inline-end) > .input-group-control {
	padding-right: 0.375rem;
}


/* Textarea inside group */
.input-group .input-group-control-textarea {
	flex: 1;
	resize: none;
	border-radius: 0;
	border: 0;
	background-color: transparent;
	padding: 0.5rem 0.625rem;
	box-shadow: none;
	outline: none;
}

.input-group .input-group-control-textarea:focus-visible {
	box-shadow: none;
	border-color: transparent;
}

.input-group .input-group-control-textarea:disabled {
	background-color: transparent;
}

.input-group .input-group-control-textarea[aria-invalid="true"] {
	box-shadow: none;
}

/* InputGroupButton — flat inside the field: the wrapper owns the
   elevation, so the shadow slots are zeroed. Zeroing the slots is the
   whole fix; a box-shadow: none here would also swallow the composed
   focus ring, which has to survive. */
.input-group-btn {
	--btn-shadow: 0 0 rgba(0, 0, 0, 0);
	--btn-shadow-pressed: 0 0 rgba(0, 0, 0, 0);
}

.input-group-btn-size-xs {
	height: 1.5rem;
	gap: 0.25rem;
	border-radius: calc(var(--radius-md) - 3px);
	padding-left: 0.375rem;
	padding-right: 0.375rem;
}

.input-group-btn-size-xs svg:not([class*="size-"]) {
	width: 0.875rem;
	height: 0.875rem;
}

/* Fitts — 24px boxes get a 32px pointer target via a pseudo-element,
   so the addon stays visually compact inside the field */
.btn.input-group-btn-size-xs::after,
.btn.input-group-btn-size-icon-xs::after {
	content: '';
	position: absolute;
	inset: -0.25rem;
}

.input-group-btn-size-sm {
	/* inherits from button sm */
}

.input-group-btn-size-icon-xs {
	width: 1.5rem;
	height: 1.5rem;
	border-radius: calc(var(--radius-md) - 3px);
	padding: 0;
}

.input-group-btn-size-icon-sm {
	width: 2rem;
	height: 2rem;
	padding: 0;
}

/* InputGroupText */
.input-group-text {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	font-size: var(--text-base);
	color: inherit;
}

.input-group-text svg {
	pointer-events: none;
}

.input-group-text svg:not([class*="size-"]) {
	width: 1rem;
	height: 1rem;
}

/* Dark mode */
.dark .input-group {
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}

.dark .input-group[data-disabled="true"] {
	background-color: color-mix(in oklch, var(--input) 80%, transparent);
}

.dark .input-group:has([aria-invalid="true"]) {
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}

.dark .input-group .input-group-control {
	background-color: transparent;
}

.dark .input-group .input-group-control:disabled {
	background-color: transparent;
}

.dark .input-group .input-group-control-textarea {
	background-color: transparent;
}

.dark .input-group .input-group-control-textarea:disabled {
	background-color: transparent;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.input-group,
	.input-group-addon {
		transition-duration: 75ms;
	}
}

/* input.css */
/* Base */
.input {
	height: 2.25rem;
	width: 100%;
	min-width: 0;
	border-radius: var(--radius-lg);
	border: 1px solid var(--input);
	background-color: transparent;
	padding: 0.25rem 0.75rem;
	font-size: var(--text-md);
	line-height: 1.5rem;
	/* Field elevation — a hairline lift off the page (--shadow-field) */
	box-shadow: var(--shadow-field);
	transition-property: color, background-color, border-color, box-shadow, opacity;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
	outline: none;
}

.input::file-selector-button {
	display: inline-flex;
	height: 1.5rem;
	border: 0;
	background-color: transparent;
	font-size: var(--text-base);
	line-height: 1.25rem;
	font-weight: 500;
	color: var(--foreground);
}

.input::placeholder {
	color: var(--muted-foreground);
}

.input:focus-visible {
	border-color: var(--ring);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

.input:disabled {
	pointer-events: none;
	cursor: not-allowed;
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
	opacity: 0.5;
}

.input[aria-invalid="true"] {
	border-color: var(--destructive);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

@media (min-width: 768px) {
	.input {
		font-size: var(--text-base);
		line-height: 1.25rem;
	}
}

/* Dark mode */
.dark .input {
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}
.dark .input:disabled {
	background-color: color-mix(in oklch, var(--input) 80%, transparent);
}
.dark .input[aria-invalid="true"] {
	border-color: color-mix(in oklch, var(--destructive) 50%, transparent);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.input {
		transition-duration: 75ms;
	}
}

/* kbd.css */
/* Kbd
   Keycap affordance: border + bottom shadow mimics a physical key,
   triggering instant recognition (Mental Model / Jakob's Law).
   Distinct from badges via the shadow depth cue (Von Restorff). */
.kbd {
	pointer-events: none;
	display: inline-flex;
	height: 1.25rem;
	width: fit-content;
	min-width: 1.25rem;
	align-items: center;
	justify-content: center;
	gap: 0.25rem;
	border-radius: calc(var(--radius) - 4px);
	border: 1px solid var(--border);
	background-color: var(--muted);
	padding-left: 0.375rem;
	padding-right: 0.375rem;
	font-family: var(--font-sans);
	/* Slashed zero — a keycap must not leave Cmd-0 and Cmd-O ambiguous */
	font-variant-numeric: slashed-zero tabular-nums;
	font-size: var(--text-xs);
	line-height: 1rem;
	font-weight: 500;
	color: var(--muted-foreground);
	user-select: none;
	/* Keycap anatomy: lit top edge, shaded bottom inner edge, a hard
	   1px bottom lip, and a soft drop (visual-layered-shadows) */
	box-shadow:
		var(--highlight-top),
		var(--shade-bottom),
		0 1px 0 0 color-mix(in oklch, var(--foreground) 10%, transparent),
		var(--shadow-field);
}

.kbd svg {
	width: 0.75rem;
	height: 0.75rem;
}

/* Inside tooltip: adapt to dark surface (Cognitive Load — immediate readability) */
[data-slot="tooltip-content"] .kbd {
	border-color: color-mix(in oklch, var(--background) 25%, transparent);
	background-color: color-mix(in oklch, var(--background) 20%, transparent);
	color: var(--background);
	box-shadow: 0 1px 0 0 color-mix(in oklch, var(--background) 15%, transparent);
}

/* Kbd Group (Chunking — related keys grouped with consistent spacing) */
.kbd-group {
	display: inline-flex;
	align-items: center;
	gap: 0.25rem;
}

/* Dark mode — the keycap shadow atoms flip via the elevation tokens */
.dark .kbd {
	border-color: color-mix(in oklch, var(--foreground) 12%, transparent);
}

.dark [data-slot="tooltip-content"] .kbd {
	border-color: color-mix(in oklch, var(--background) 15%, transparent);
	background-color: color-mix(in oklch, var(--background) 10%, transparent);
	box-shadow: 0 1px 0 0 color-mix(in oklch, var(--background) 8%, transparent);
}

/* label.css */
/* Base */
.label {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	font-size: var(--text-base);
	line-height: 1;
	font-weight: 500;
	user-select: none;
	transition-property: color, opacity;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Disabled via parent group */
[data-disabled="true"] .label {
	pointer-events: none;
	opacity: 0.5;
}

/* Disabled via sibling input — either order. The subsequent-sibling pair
   covers input-then-label; the :has() twin covers label-then-input,
   which is the more common markup order. */
.input:disabled ~ .label,
.label:has(~ .input:disabled) {
	cursor: not-allowed;
	opacity: 0.5;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.label {
		transition-duration: 75ms;
	}
}

/* list-card.css */
/* The index card — one thing you can open, on /documentos and /conversas.
   Sized for a thumb: a rail row is a line of text you scan with a mouse a
   foot away, and the same row under a thumb is a 28px target in a wall of
   identical greys. */
.listcards { display: flex; flex-direction: column; gap: 0.5rem; }

/* ListCardMenu wraps the card in two boxes it does not want — the whole card
   is the trigger, so neither has anything of its own to draw. Out of the box
   tree entirely, and the card stays a direct child of the stack. */
.listcard-ctx,
.listcard-ctx > .ctxm-trigger { display: contents; }

.listcard {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	min-height: 3.5rem;
	padding: 0.75rem 0.85rem;
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	background: var(--card);
	box-shadow: var(--shadow-card);
	color: var(--foreground);
	text-decoration: none;
	transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease;
}
.listcard:hover { background: var(--muted); }
/* Pressed: the card takes the finger before the page does. A press is the
   first frame of the navigation, and 120ms is the same press response
   button.css and tabs.css already use — similar things move alike. The
   scale is gentler than a button's 0.97 because this box is the width of
   the screen, and the same ratio there is a lurch rather than a press. */
.listcard:active {
	background: var(--muted);
	transform: scale(0.985);
	transition-duration: 120ms;
}
/* After the rule it undoes, not before it: a media query adds no specificity,
   so this only wins on source order. */
@media (prefers-reduced-motion: reduce) {
	.listcard:active { transform: none; }
}

.listcard-icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	width: 2.25rem;
	height: 2.25rem;
	border-radius: var(--radius-md);
	background: var(--muted);
	color: var(--muted-foreground);
}
.listcard-icon svg { width: 1.15rem; height: 1.15rem; }

.listcard-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.listcard-title {
	font-size: var(--text-md);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
/* When it last changed, which is the only thing a title cannot say. */
.listcard-meta { font-size: var(--text-sm); color: var(--muted-foreground); }

.listcard-caret { display: inline-flex; flex-shrink: 0; color: var(--muted-foreground); }
.listcard-caret svg { width: 1rem; height: 1rem; }

/* menu.css */
/* Context menu: trigger + cursor-positioned content */
/* ── Trigger ── */
.ctxm-trigger {
	user-select: none;
	/* iOS answers a long press on a link with its own callout sheet — the
	   share/copy/preview one — which is the OS competing with this menu for
	   the same gesture. Both properties inherit, so a trigger wrapping a
	   whole card covers the card. */
	-webkit-touch-callout: none;
}

/* ── Content (positioned fixed at cursor) ── */
.ctxm-content {
	position: fixed;
	z-index: 10000;
	min-width: 12rem;
	max-height: calc(100vh - 2rem);
	overflow-x: hidden;
	overflow-y: auto;
	border-radius: var(--radius-md);
	background: var(--popover);
	padding: 0.25rem;
	color: var(--popover-foreground);
	font-size: var(--text-base);
	line-height: 1.25rem;
	box-shadow: var(--shadow-popover);
	outline: none;
	transform-origin: top left;
}

/* Hidden when closed */
.ctxm-content[data-state="closed"] {
	pointer-events: none;
	visibility: hidden;
	opacity: 0;
}

/*
 * timing-no-entrance-context-menu — context menus appear instantly
 * (no entrance animation). Matches native OS behavior (Jakob's Law).
 * Exit-only animation gives feedback that the action was acknowledged.
 */

/* Close animation (exit only) */
.ctxm-content[data-closing] {
	pointer-events: none;
	animation: ctxm-out 100ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}

@keyframes ctxm-out {
	from {
		opacity: 1;
		transform: scale(1);
	}
	to {
		opacity: 0;
		transform: scale(0.95);
	}
}

/* ── Sub content ── */
.ctxm-sub-content {
	position: fixed;
	z-index: 10000;
	min-width: 8rem;
	border-radius: var(--radius-md);
	background: var(--popover);
	padding: 0.25rem;
	color: var(--popover-foreground);
	font-size: var(--text-base);
	line-height: 1.25rem;
	box-shadow: var(--shadow-popover);
	outline: none;
}

.ctxm-sub-content[data-state="closed"] {
	pointer-events: none;
	visibility: hidden;
	opacity: 0;
}

/*
 * No entrance animation — a context menu is already a response to a
 * deliberate invocation, so it appears instantly; its submenus are part
 * of the same menu and follow the same rule
 * (timing-no-entrance-context-menu). The exit still animates: it is
 * feedback that the menu is going away.
 */
.ctxm-sub-content[data-closing] {
	pointer-events: none;
	animation: menu-sub-out 100ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}

.ctxm-sub-content[data-side="left"][data-closing] {
	animation-name: menu-sub-out-left;
}

/* Dark mode is covered by the --shadow-popover token itself
   (item styles + shared sub keyframes live in shared.templ) */

/* ── Reduced motion (content animation only; items/sub-content in shared) ── */
@media (prefers-reduced-motion: reduce) {
	.ctxm-content[data-closing] {
		animation: none;
	}
}

/* Dropdown menu: trigger + anchored content, sides and alignments */
/* ── Root ── */
.ddm {
	position: relative;
	display: inline-flex;
}

/* ── Trigger ── */
.ddm-trigger {
	cursor: pointer;
}

/* ── Content ── */
.ddm-content {
	position: absolute;
	z-index: 50;
	min-width: 12rem;
	border-radius: var(--radius-md);
	/* Same elevation tier as popover and command, so it draws from the
	   same surface tokens — a theme that pulls --popover away from
	   --background must move every floating panel together. */
	background: var(--popover);
	padding: 0.25rem;
	color: var(--popover-foreground);
	font-size: var(--text-base);
	line-height: 1.25rem;
	box-shadow: var(--shadow-popover);
	outline: none;
	/*
	 * --ddm-base-* holds the alignment offset (e.g. -50% for centering).
	 * --ddm-slide-* holds the directional entrance offset per side.
	 * Both are composed into the keyframes so the base position is
	 * never lost while the animation owns the transform property.
	 */
	--ddm-base-x: 0px;
	--ddm-base-y: 0px;
	--ddm-slide-x: 0px;
	--ddm-slide-y: 0px;
	transform: translate(var(--ddm-base-x), var(--ddm-base-y));
}

/* Hidden when closed */
.ddm-content[data-state="closed"] {
	pointer-events: none;
	visibility: hidden;
	opacity: 0;
}

/*
 * Side positioning + directional slide offsets.
 * transform-origin anchors the scale to the trigger edge and
 * --ddm-slide-* starts the panel on the trigger side, so it grows
 * outward FROM the trigger instead of drifting the same direction
 * regardless of where it is anchored (Solid Drawing + Staging).
 * Travel is 2px against the popover 4px: this panel is lighter and
 * 30ms quicker, so it needs less runway.
 */

/* Side: bottom (default) */
.ddm-content[data-side="bottom"] {
	top: calc(100% + 4px);
	transform-origin: top;
	--ddm-slide-y: -2px;
}
.ddm-content[data-side="top"] {
	bottom: calc(100% + 4px);
	transform-origin: bottom;
	--ddm-slide-y: 2px;
}
.ddm-content[data-side="left"] {
	right: calc(100% + 4px);
	top: 0;
	transform-origin: right;
	--ddm-slide-x: 2px;
}
.ddm-content[data-side="right"] {
	left: calc(100% + 4px);
	top: 0;
	transform-origin: left;
	--ddm-slide-x: -2px;
}

/* Align: start (default) */
.ddm-content[data-side="bottom"][data-align="start"],
.ddm-content[data-side="top"][data-align="start"] {
	left: 0;
}
.ddm-content[data-side="bottom"][data-align="center"],
.ddm-content[data-side="top"][data-align="center"] {
	left: 50%;
	--ddm-base-x: -50%;
}
.ddm-content[data-side="bottom"][data-align="end"],
.ddm-content[data-side="top"][data-align="end"] {
	right: 0;
}
.ddm-content[data-side="left"][data-align="start"],
.ddm-content[data-side="right"][data-align="start"] {
	top: 0;
}
.ddm-content[data-side="left"][data-align="center"],
.ddm-content[data-side="right"][data-align="center"] {
	top: 50%;
	--ddm-base-y: -50%;
}
.ddm-content[data-side="left"][data-align="end"],
.ddm-content[data-side="right"][data-align="end"] {
	top: auto;
	bottom: 0;
}

/* Open animation */
.ddm-content[data-state="open"] {
	animation: ddm-in 150ms cubic-bezier(0.22, 1.1, 0.36, 1);
}

/* Close animation */
.ddm-content[data-closing] {
	pointer-events: none;
	animation: ddm-out 100ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}

@keyframes ddm-in {
	from {
		opacity: 0;
		transform: translate(var(--ddm-base-x), var(--ddm-base-y))
			scale(0.95)
			translate(var(--ddm-slide-x), var(--ddm-slide-y));
	}
	to {
		opacity: 1;
		transform: translate(var(--ddm-base-x), var(--ddm-base-y))
			scale(1);
	}
}

/* Exit covers half the entrance distance — the leaving panel should
   yield attention, not draw a second trip across the screen. */
@keyframes ddm-out {
	from {
		opacity: 1;
		transform: translate(var(--ddm-base-x), var(--ddm-base-y))
			scale(1);
	}
	to {
		opacity: 0;
		transform: translate(var(--ddm-base-x), var(--ddm-base-y))
			scale(0.95)
			translate(
				calc(var(--ddm-slide-x) * 0.5),
				calc(var(--ddm-slide-y) * 0.5)
			);
	}
}

/* ── Sub content ── */
.ddm-sub {
	position: relative;
}

.ddm-sub-content {
	position: absolute;
	z-index: 50;
	min-width: 8rem;
	border-radius: var(--radius-md);
	background: var(--popover);
	padding: 0.25rem;
	color: var(--popover-foreground);
	font-size: var(--text-base);
	line-height: 1.25rem;
	box-shadow: var(--shadow-popover);
	outline: none;
}

.ddm-sub-content[data-state="closed"] {
	pointer-events: none;
	visibility: hidden;
	opacity: 0;
}

/* Sub positioning */
.ddm-sub-content[data-side="right"] {
	left: 100%;
	top: -0.1875rem;
}
.ddm-sub-content[data-side="left"] {
	right: 100%;
	top: -0.1875rem;
}

.ddm-sub-content[data-state="open"] {
	animation: menu-sub-in 150ms cubic-bezier(0.22, 1.1, 0.36, 1);
}
.ddm-sub-content[data-closing] {
	pointer-events: none;
	animation: menu-sub-out 100ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}

.ddm-sub-content[data-side="left"][data-state="open"] {
	animation-name: menu-sub-in-left;
}
.ddm-sub-content[data-side="left"][data-closing] {
	animation-name: menu-sub-out-left;
}

/* Dark mode is covered by the --shadow-popover token itself
   (item styles + shared sub keyframes live in shared.templ) */

/* ── Reduced motion (content animation only; items/sub-content in shared) ── */
@media (prefers-reduced-motion: reduce) {
	.ddm-content[data-state="open"],
	.ddm-content[data-closing] {
		animation: none;
	}
}

/* Items, submenus and separators, shared by both menus */
/* ── Menu item base (item, checkbox/radio item, sub-trigger) ── */
.ddm-item, .ctxm-item,
.ddm-checkbox-item, .ctxm-checkbox-item,
.ddm-radio-item, .ctxm-radio-item,
.ddm-sub-trigger, .ctxm-sub-trigger {
	position: relative;
	display: flex;
	cursor: default;
	align-items: center;
	gap: 0.375rem;
	/* Concentric with the panel: outer radius minus the 0.25rem pad */
	border-radius: calc(var(--radius-md) - 4px);
	padding: 0.3125rem 0.5rem;
	font-size: var(--text-base);
	line-height: 1.25rem;
	outline: none;
	user-select: none;
}

/*
 * The highlight fades only when the pointer put it there. Arrow keys
 * land on items the pointer is nowhere near, and keyboard navigation
 * must never wait on an animation — so the transition is scoped to
 * the hovered item and keyboard moves swap instantly.
 */
.ddm-item:hover[data-highlighted], .ctxm-item:hover[data-highlighted],
.ddm-checkbox-item:hover[data-highlighted], .ctxm-checkbox-item:hover[data-highlighted],
.ddm-radio-item:hover[data-highlighted], .ctxm-radio-item:hover[data-highlighted],
.ddm-sub-trigger:hover[data-highlighted], .ctxm-sub-trigger:hover[data-highlighted] {
	transition: background-color 80ms cubic-bezier(0.4, 0, 0.2, 1),
		color 80ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Highlighted state (keyboard/hover) */
.ddm-item[data-highlighted], .ctxm-item[data-highlighted],
.ddm-checkbox-item[data-highlighted], .ctxm-checkbox-item[data-highlighted],
.ddm-radio-item[data-highlighted], .ctxm-radio-item[data-highlighted],
.ddm-sub-trigger[data-highlighted], .ctxm-sub-trigger[data-highlighted] {
	background: var(--muted);
	color: var(--foreground);
}

/* Highlighted — icons follow the highlight color */
.ddm-item[data-highlighted] svg, .ctxm-item[data-highlighted] svg,
.ddm-checkbox-item[data-highlighted] svg, .ctxm-checkbox-item[data-highlighted] svg,
.ddm-radio-item[data-highlighted] svg, .ctxm-radio-item[data-highlighted] svg,
.ddm-sub-trigger[data-highlighted] svg, .ctxm-sub-trigger[data-highlighted] svg {
	color: var(--foreground);
}
.ddm-item[data-variant="destructive"][data-highlighted] svg,
.ctxm-item[data-variant="destructive"][data-highlighted] svg {
	color: var(--destructive);
}

/* Destructive variant */
.ddm-item[data-variant="destructive"],
.ctxm-item[data-variant="destructive"] {
	color: var(--destructive);
}
.ddm-item[data-variant="destructive"][data-highlighted],
.ctxm-item[data-variant="destructive"][data-highlighted] {
	background: color-mix(in oklch, var(--destructive) 10%, transparent);
	color: var(--destructive);
}

/* Inset */
.ddm-item[data-inset="true"], .ctxm-item[data-inset="true"],
.ddm-checkbox-item[data-inset="true"], .ctxm-checkbox-item[data-inset="true"],
.ddm-radio-item[data-inset="true"], .ctxm-radio-item[data-inset="true"],
.ddm-sub-trigger[data-inset="true"], .ctxm-sub-trigger[data-inset="true"],
.ddm-label[data-inset="true"], .ctxm-label[data-inset="true"] {
	padding-left: 1.75rem;
}

/* Disabled */
.ddm-item[data-disabled="true"], .ctxm-item[data-disabled="true"],
.ddm-checkbox-item[data-disabled="true"], .ctxm-checkbox-item[data-disabled="true"],
.ddm-radio-item[data-disabled="true"], .ctxm-radio-item[data-disabled="true"],
.ddm-sub-trigger[data-disabled="true"], .ctxm-sub-trigger[data-disabled="true"] {
	pointer-events: none;
	opacity: 0.5;
}

/* SVG inside items */
.ddm-item svg, .ctxm-item svg,
.ddm-checkbox-item svg, .ctxm-checkbox-item svg,
.ddm-radio-item svg, .ctxm-radio-item svg,
.ddm-sub-trigger svg, .ctxm-sub-trigger svg {
	pointer-events: none;
	flex-shrink: 0;
	width: 1rem;
	height: 1rem;
}

/* ── Checkbox / radio item indicator ── */
.ddm-checkbox-item, .ctxm-checkbox-item,
.ddm-radio-item, .ctxm-radio-item {
	padding-right: 2rem;
}

.ddm-item-indicator, .ctxm-item-indicator {
	pointer-events: none;
	position: absolute;
	right: 0.5rem;
	display: flex;
	align-items: center;
	justify-content: center;
	opacity: 0;
	transform: scale(0.5);
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1),
		transform 150ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.ddm-checkbox-item[data-checked="true"] .ddm-item-indicator,
.ctxm-checkbox-item[data-checked="true"] .ctxm-item-indicator,
.ddm-radio-item[data-checked="true"] .ddm-item-indicator,
.ctxm-radio-item[data-checked="true"] .ctxm-item-indicator {
	opacity: 1;
	transform: scale(1);
}

.ddm-item-indicator svg, .ctxm-item-indicator svg {
	width: 0.875rem;
	height: 0.875rem;
}

/* ── Sub trigger chevron ── */
.ddm-sub-trigger, .ctxm-sub-trigger {
	padding-right: 0.375rem;
}

/* Secondary Action — chevron hints at submenu direction, rotates on open */
.ddm-sub-trigger > svg:last-child,
.ctxm-sub-trigger > svg:last-child {
	margin-left: auto;
	transition: transform 150ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.ddm-sub-trigger[data-open="true"] > svg:last-child,
.ctxm-sub-trigger[data-open="true"] > svg:last-child {
	transform: rotate(90deg);
}
.ddm-sub-trigger[data-open="true"],
.ctxm-sub-trigger[data-open="true"] {
	background: var(--muted);
	color: var(--foreground);
}

/* ── Label ── */
.ddm-label, .ctxm-label {
	padding: 0.25rem 0.5rem;
	font-size: var(--text-xs);
	font-weight: 500;
	color: var(--muted-foreground);
}
/* Chunking — labels after a separator get extra top space */
.ddm-separator + .ddm-label,
.ctxm-separator + .ctxm-label {
	padding-top: 0.375rem;
}

/* ── Separator ── */
.ddm-separator, .ctxm-separator {
	margin: 0.25rem -0.25rem;
	height: 1px;
	background: var(--border);
}

/* ── Shortcut ── */
.ddm-shortcut, .ctxm-shortcut {
	margin-left: auto;
	font-size: var(--text-xs);
	letter-spacing: 0.1em;
	color: var(--muted-foreground);
}
.ddm-item[data-highlighted] .ddm-shortcut,
.ctxm-item[data-highlighted] .ctxm-shortcut,
.ddm-checkbox-item[data-highlighted] .ddm-shortcut,
.ctxm-checkbox-item[data-highlighted] .ctxm-shortcut,
.ddm-radio-item[data-highlighted] .ddm-shortcut,
.ctxm-radio-item[data-highlighted] .ctxm-shortcut,
.ddm-sub-trigger[data-highlighted] .ddm-shortcut,
.ctxm-sub-trigger[data-highlighted] .ctxm-shortcut {
	color: var(--foreground);
}
.ddm-item[data-variant="destructive"][data-highlighted] .ddm-shortcut,
.ctxm-item[data-variant="destructive"][data-highlighted] .ctxm-shortcut {
	color: var(--destructive);
}

/* ── Dark mode — destructive highlight ── */
.dark .ddm-item[data-variant="destructive"][data-highlighted],
.dark .ctxm-item[data-variant="destructive"][data-highlighted] {
	background: color-mix(in oklch, var(--destructive) 20%, transparent);
}

/* ── Sub-content enter/exit keyframes (shared) ── */
@keyframes menu-sub-in {
	from { opacity: 0; transform: translateX(-4px) scale(0.97); }
	to { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes menu-sub-out {
	from { opacity: 1; transform: translateX(0) scale(1); }
	to { opacity: 0; transform: translateX(-4px) scale(0.97); }
}
@keyframes menu-sub-in-left {
	from { opacity: 0; transform: translateX(4px) scale(0.97); }
	to { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes menu-sub-out-left {
	from { opacity: 1; transform: translateX(0) scale(1); }
	to { opacity: 0; transform: translateX(4px) scale(0.97); }
}

/* ── Reduced motion — items and sub-content ── */
@media (prefers-reduced-motion: reduce) {
	.ddm-item, .ctxm-item,
	.ddm-checkbox-item, .ctxm-checkbox-item,
	.ddm-radio-item, .ctxm-radio-item,
	.ddm-sub-trigger, .ctxm-sub-trigger,
	.ddm-sub-trigger > svg:last-child,
	.ctxm-sub-trigger > svg:last-child,
	.ddm-item-indicator, .ctxm-item-indicator {
		transition: none;
	}
	/* Must repeat the hover selectors: they outrank the bare class
	   above, so listing only the base would leave them animating. */
	.ddm-item:hover[data-highlighted], .ctxm-item:hover[data-highlighted],
	.ddm-checkbox-item:hover[data-highlighted], .ctxm-checkbox-item:hover[data-highlighted],
	.ddm-radio-item:hover[data-highlighted], .ctxm-radio-item:hover[data-highlighted],
	.ddm-sub-trigger:hover[data-highlighted], .ctxm-sub-trigger:hover[data-highlighted] {
		transition: none;
	}
	.ddm-sub-content[data-state="open"], .ctxm-sub-content[data-state="open"],
	.ddm-sub-content[data-closing], .ctxm-sub-content[data-closing] {
		animation: none;
	}
}

/* native-select.css */
/* Base wrapper */
.native-select-wrapper {
	position: relative;
	width: fit-content;
}
.native-select-wrapper:has(select:disabled) {
	opacity: 0.5;
}

/* Base select */
.native-select {
	height: 2.25rem;
	width: 100%;
	min-width: 0;
	appearance: none;
	border-radius: var(--radius-lg);
	border: 1px solid var(--input);
	background-color: transparent;
	/* Field elevation — a hairline lift off the page (--shadow-field) */
	box-shadow: var(--shadow-field);
	padding: 0.25rem 2rem 0.25rem 0.625rem;
	/* 1rem until md — a focused control under 16px makes iOS Safari
	   zoom the viewport. Same guard as Input and Textarea. */
	font-size: var(--text-md);
	line-height: 1.5rem;
	transition-property: color, background-color, border-color, box-shadow, opacity;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
	outline: none;
	user-select: none;
}

.native-select:hover:not(:disabled) {
	border-color: color-mix(in oklch, var(--ring) 40%, transparent);
}

.native-select:focus-visible {
	border-color: var(--ring);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

.native-select:disabled {
	pointer-events: none;
	cursor: not-allowed;
}

.native-select[aria-invalid="true"] {
	border-color: var(--destructive);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

/* Size: sm */
.native-select[data-size="sm"] {
	height: 2rem;
	padding-top: 0.125rem;
	padding-bottom: 0.125rem;
	border-radius: min(var(--radius-md), 10px);
}

@media (min-width: 768px) {
	.native-select {
		font-size: var(--text-base);
		line-height: 1.25rem;
	}
}

/* Chevron icon */
.native-select-icon {
	position: absolute;
	top: 50%;
	right: 0.625rem;
	transform: translateY(-50%);
	pointer-events: none;
	user-select: none;
	color: var(--muted-foreground);
	display: flex;
	align-items: center;
	justify-content: center;
	transition-property: color, transform;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.native-select-icon svg {
	width: 1rem;
	height: 1rem;
}

/* Chevron reacts to hover and focus (secondary action / anticipation) */
.native-select-wrapper:has(select:hover:not(:disabled)) .native-select-icon {
	color: var(--foreground);
}
/* Focus brightens the chevron but does NOT rotate it: rotation means
   "open" everywhere else in the library, and tabbing through a form
   must not animate a control that has not been opened. */
.native-select-wrapper:has(select:focus-visible) .native-select-icon {
	color: var(--foreground);
}

/* Dark mode */
.dark .native-select {
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}
.dark .native-select:hover {
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
}
.dark .native-select[aria-invalid="true"] {
	border-color: color-mix(in oklch, var(--destructive) 50%, transparent);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.native-select,
	.native-select-icon {
		transition-duration: 75ms;
	}
}

/* nav-skeleton.css */
/* Page skeletons — see components/nav-skeleton.go for why they exist and
   nav-skeleton.js for when they are painted.

   Almost nothing is laid out here: each skeleton is the real page's root
   element wearing the real page's classes, so .doclist, .listcard,
   .buscar-bar and .perfil-head do the arranging exactly as they do for the
   content that replaces them. What is left is the grey bars. */

/* Nothing here is pressable and nothing here is text: a skeleton must not
   take a tap that the page under it would have taken, and it must not be
   selectable by a drag that was aimed at the words that are coming. */
.skel {
	pointer-events: none;
	user-select: none;
}

/* --- The transition itself ---

   A page change used to be three hard cuts inside one frame — page,
   skeleton, page — which on a fast answer is a strobe rather than a
   transition. Both halves of the change now arrive on the same fade, so
   there is one motion instead of three cuts, and it scales itself to the
   wait: a 30ms answer is a single soft crossfade in which the skeleton
   barely registers, while a slow one is a skeleton that settles, sits, and
   resolves into the page.

   Four numbers, and each is a rule rather than a taste:
     - 200ms — a page change is a small state change (180-260ms), and it is
       user-initiated, so it may not exceed 300ms.
     - ease-out — this is an entrance. Arrive fast, settle gently. (An exit
       would be ease-in; nothing here exits, see below.)
     - from 0.35, not from 0 — the press has to answer *now*. Starting at
       zero means the screen is blank on the frame the finger lifts, and it
       means the skeleton→content swap blinks through empty on the way. At
       a third of the way up, the change is visible immediately and neither
       end of it ever passes through nothing.
     - opacity alone, no rise — a `transform` on the content region makes it
       the containing block for every `position: fixed` inside it, and the
       document editor's keyboard toolbar is one of those. Not worth 6px.

   No exit animation, deliberately: the outgoing page is destroyed by the
   runtime's own swap, and animating it out would mean holding the previous
   page's DOM somewhere to fade — client state this app does without. A true
   crossfade (the skeleton as an absolutely-positioned overlay over the
   content region, surviving the swap) was considered and dropped for the
   same reason plus one worse: an overlay that fails to be removed leaves
   the whole app behind a pane. The skeleton already lands on the content's
   own pixel rows, which is most of what a crossfade would have bought. */
@keyframes page-in {
	from { opacity: 0.35; }
	to { opacity: 1; }
}
#main-content[data-page-in] {
	animation: page-in 200ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
	#main-content[data-page-in] {
		animation: none;
	}
}

/* One line of text, waiting.
   A bar drawn the height of the *bar* is not the height of the *line*: a
   line box is its font size times its leading, and a stack of six cards
   built out of bars that are only as tall as their ink is nearly fifty
   pixels shorter than the list that replaces it. So the box is the real
   line box, and the ink inside it is held to ~0.9em by transparent borders
   the fill is clipped out of — the bar keeps its slim look and the card
   keeps its true height. Two custom properties are the whole vocabulary:
   --skel-size is the step on the type scale (so a phone re-tunes with it)
   and --skel-lead is that element's line-height.
   `.skeleton.x` and never `.x`, here and below: a tie is settled by which
   file the bundler concatenated first, and nav-skeleton.css comes before
   skeleton.css alphabetically. Out-specify, do not out-order. */
.skeleton.skel-line {
	--skel-size: var(--text-md);
	--skel-lead: 1.5;
	--skel-radius: 999px;
	--skel-band: calc((var(--skel-size) * var(--skel-lead) - var(--skel-size) * 0.9) / 2);
	height: calc(var(--skel-size) * var(--skel-lead));
	border-top: var(--skel-band) solid transparent;
	border-bottom: var(--skel-band) solid transparent;
	/* Paint the fill inside the transparent borders, not under them. The
	   shimmer already lands there: ::after's inset is the padding box. */
	background-clip: padding-box;
	/* Stated on the border box, so the fill ends up with exactly
	   --skel-radius once the border is taken off it. */
	border-radius: calc(var(--skel-radius) + var(--skel-band));
}

/* --- Index cards (/documentos, /conversas) --- */

/* The card's box comes from list-card.css. Only the caret gutter has to be
   re-stated: the real one is sized by the chevron inside it, and there is
   no chevron here — a grey one would be drawing a control that is not
   there yet. */
.skel-caret {
	width: 1rem;
}
.skel-card .listcard-icon {
	/* .listcard-icon is already 2.25rem of --muted with the right radius;
	   the Skeleton class only adds the sweep over it. */
	display: block;
}
.skeleton.skel-card-title {
	width: 58%;
}
.skeleton.skel-card-meta {
	--skel-size: var(--text-sm);
	width: 30%;
}
/* A list of identical rows reads as a loading graphic; a list of rows that
   are almost the same reads as titles. Three lengths, cycled — the variation
   belongs here and not in the markup, which stays a plain loop. */
.skel-card:nth-child(3n + 1) .skel-card-title { width: 64%; }
.skel-card:nth-child(3n + 2) .skel-card-title { width: 43%; }
.skel-card:nth-child(3n) .skel-card-title { width: 77%; }
.skel-card:nth-child(2n) .skel-card-meta { width: 22%; }

/* --- /buscar --- */

/* .buscar-label: --text-sm, margin 0.25rem 0 0.35rem, padding 0 0.6rem. */
.skeleton.skel-buscar-label {
	--skel-size: var(--text-sm);
	width: 4.5rem;
	margin: 0.25rem 0.6rem 0.35rem;
}
.skeleton.skel-buscar-icon {
	width: 1.05rem;
	height: 1.05rem;
	flex-shrink: 0;
	border-radius: var(--radius-md);
}
.skeleton.skel-buscar-title {
	width: 52%;
}
.skel-item:nth-child(3n + 1) .skel-buscar-title { width: 61%; }
.skel-item:nth-child(3n) .skel-buscar-title { width: 40%; }

/* --- /perfil --- */

/* TypographyH1: --text-4xl over 1.1, and .perfil-head h1 owns the 0.25rem
   under it. A pill at that height would read as a button, so this one keeps
   the block radius. */
.skeleton.skel-perfil-name {
	--skel-size: var(--text-4xl);
	--skel-lead: 1.1;
	--skel-radius: var(--radius-md);
	width: 11rem;
	margin-bottom: 0.25rem;
}
/* TypographyMuted: --text-base over 1.45. */
.skeleton.skel-perfil-mail {
	--skel-size: var(--text-base);
	--skel-lead: 1.45;
	width: 15rem;
}
/* .field: a column with a 0.5rem gap. */
.skel-perfil-form {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}
/* .label: --text-base over 1. */
.skeleton.skel-perfil-label {
	--skel-size: var(--text-base);
	--skel-lead: 1;
	width: 13rem;
}
/* The textarea's resting height, not its rows="8": textarea.css gives it
   `field-sizing: content` over a 4rem floor, so an empty one is the floor
   plus its border — which is what the real form opens at, and therefore
   what keeps Salvar from jumping when it lands. */
.skeleton.skel-perfil-box {
	height: calc(4rem + 2px);
	border-radius: var(--radius-lg);
}
/* .field-description: --text-base over 1.5. */
.skeleton.skel-perfil-hint {
	--skel-size: var(--text-base);
	width: 70%;
}
/* Button, size default: 2.25rem tall, --radius-lg. .perfil-actions puts 1rem
   between the description and the button; 0.5rem of it is the form's own gap
   above. */
.skeleton.skel-perfil-btn {
	width: 4.5rem;
	height: 2.25rem;
	margin-top: 0.5rem;
	border-radius: var(--radius-lg);
}

/* --- /documentos/{id} --- */

.skeleton.skel-doc-icon {
	width: 1.6rem;
	height: 1.6rem;
	flex-shrink: 0;
	border-radius: var(--radius-md);
}
.skeleton.skel-doc-title {
	--skel-size: var(--text-4xl);
	--skel-lead: 1.1;
	--skel-radius: var(--radius-md);
	width: 55%;
}
/* Prose: full-measure lines, and a short one every fourth with the gap of a
   paragraph break under it — a block of equal bars is a barcode, and a
   paragraph ends mid-line. */
.skeleton.skel-prose:nth-child(4n) {
	width: 68%;
	margin-bottom: 1.1rem;
}

/* pagination.css */
/* Pagination nav */
.pagination {
	margin-left: auto;
	margin-right: auto;
	display: flex;
	width: 100%;
	justify-content: center;
}

/* Content list — tight gap keeps the page run reading as one group
   (ux-proximity-grouping); the targets themselves carry the size */
.pagination-content {
	display: flex;
	align-items: center;
	gap: 0.25rem;
	list-style: none;
	padding: 0;
	margin: 0;
}

/* Link base */
.pagination-link {
	text-decoration: none;
	cursor: pointer;
	/* Tabular figures — page numbers keep an even rhythm and the
	   active page does not shift width when it goes bold */
	font-variant-numeric: tabular-nums;
}

/*
 * Active page.
 *
 * The current page is the one item in the row that CANNOT be clicked,
 * so it must not carry the strongest press affordance. A raised face
 * (border + elevation) inverted the signal: the dead element looked
 * the most interactive, and every live target looked flat beside it
 * (ux-similarity-consistency, visual-shadow-matches-elevation).
 *
 * A tinted chip is not available either — --muted, --accent and
 * --secondary are all the same value as the ghost hover wash, so a
 * chip would render identically to simply hovering a neighbour.
 *
 * So the current page is marked rather than raised: full-strength
 * foreground, heavier weight, and a rule underneath that no hover
 * state can produce. Three stacked cues, zero elevation
 * (ux-von-restorff-emphasis).
 */
.pagination-link[data-active="true"] {
	font-weight: 600;
	color: var(--foreground);
	pointer-events: none;
	cursor: default;
	/* Belt and braces: never inherit a raised face from .btn */
	background-color: transparent;
	border-color: transparent;
	--btn-shadow: 0 0 rgba(0, 0, 0, 0);
	--btn-shadow-pressed: 0 0 rgba(0, 0, 0, 0);
}

/* The marker itself — a pseudo-element, not an extra node
   (pseudo-over-dom-node); .btn is already position: relative. */
.pagination-link[data-active="true"]::after {
	content: '';
	position: absolute;
	left: 50%;
	/* Anchored to the centre, not the bottom edge: the box grows to
	   2.75rem on touch, and a bottom-anchored rule would drift away
	   from the digits it belongs to. */
	top: 50%;
	width: 1.125rem;
	height: 2px;
	transform: translate(-50%, 0.625rem);
	border-radius: 9999px;
	background-color: var(--primary);
}

/* Disabled boundary state — Cognitive Load: prevent impossible actions */
.pagination-link[aria-disabled="true"] {
	pointer-events: none;
	opacity: 0.5;
	cursor: default;
}

/* Previous / Next */
.pagination-prev {
	padding-left: 0.375rem;
}
.pagination-next {
	padding-right: 0.375rem;
}

/* Chevron anticipation — 12 Principles: hint at direction */
.pagination-prev svg,
.pagination-next svg {
	transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.pagination-prev:not([aria-disabled="true"]):hover svg {
	transform: translateX(-2px);
}
.pagination-next:not([aria-disabled="true"]):hover svg {
	transform: translateX(2px);
}

/* Text hidden on small screens */
@media (max-width: 639px) {
	.pagination-text {
		display: none;
	}
}
@media (min-width: 640px) {
	.pagination-text {
		display: block;
	}
}

/*
 * Mobile touch targets — Fitts's Law: min 44px on touch devices.
 * Prev/Next drop their label at this width, so they become icon-only
 * buttons and need the same bump as the numbers sitting beside them;
 * without it the two boundary controls end up the smallest targets in
 * the row. Their asymmetric padding also goes away with the label,
 * or the lone caret sits off-center.
 */
@media (max-width: 639px) {
	.pagination-link.btn-size-icon {
		width: 2.75rem;
		height: 2.75rem;
	}
	.pagination-prev,
	.pagination-next {
		width: 2.75rem;
		height: 2.75rem;
		padding-left: 0;
		padding-right: 0;
	}
}

/* Ellipsis — Selective Attention: muted to signal non-interactive */
.pagination-ellipsis {
	display: flex;
	width: 2.25rem;
	height: 2.25rem;
	align-items: center;
	justify-content: center;
	color: var(--muted-foreground);
}

.pagination-ellipsis svg {
	width: 1rem;
	height: 1rem;
}

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

/* Dark mode — the marker rides --primary and --foreground, which flip
   themselves; only the disabled wash needs a nudge. */
.dark .pagination-link[aria-disabled="true"] {
	opacity: 0.4;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.pagination-prev svg,
	.pagination-next svg {
		transition: none;
	}
	.pagination-prev:hover svg,
	.pagination-next:hover svg {
		transform: none;
	}
}

/* popover.css */
/* ── Root ── */
.popover {
	position: relative;
	display: inline-flex;
}

/* ── Trigger ── */
.popover-trigger {
	cursor: pointer;
}

/* ── Content ── */
.popover-content {
	position: absolute;
	z-index: 50;
	width: 18rem;
	display: flex;
	flex-direction: column;
	gap: 0.625rem;
	border-radius: var(--radius-lg);
	background: var(--popover);
	padding: 0.625rem;
	color: var(--popover-foreground);
	font-size: var(--text-base);
	line-height: 1.25rem;
	box-shadow: var(--shadow-popover);
	outline: none;
	/*
	 * --popover-base-* holds the alignment offset (e.g. -50% for centering).
	 * --popover-slide-* holds the directional animation offset per-side.
	 * Both are composed into the keyframes so the base position is never lost.
	 */
	--popover-base-x: 0px;
	--popover-base-y: 0px;
	--popover-slide-x: 0px;
	--popover-slide-y: 0px;
	transform: translate(var(--popover-base-x), var(--popover-base-y));
}

/* Hidden when closed */
.popover-content[data-state="closed"] {
	pointer-events: none;
	visibility: hidden;
	opacity: 0;
}

/*
 * Side positioning + directional slide offsets.
 * transform-origin anchors scale to the trigger edge so the panel
 * appears to grow outward — maintaining spatial continuity (Solid Drawing).
 * --popover-slide-* drives the directional translate so the panel
 * emerges FROM the trigger, not just fades in place (Staging).
 */

/* Side: bottom (default) */
.popover-content[data-side="bottom"] {
	top: calc(100% + 4px);
	transform-origin: top;
	--popover-slide-y: -4px;
}
.popover-content[data-side="top"] {
	bottom: calc(100% + 4px);
	transform-origin: bottom;
	--popover-slide-y: 4px;
}
.popover-content[data-side="left"] {
	right: calc(100% + 4px);
	top: 0;
	transform-origin: right;
	--popover-slide-x: 4px;
}
.popover-content[data-side="right"] {
	left: calc(100% + 4px);
	top: 0;
	transform-origin: left;
	--popover-slide-x: -4px;
}

/* Align */
.popover-content[data-side="bottom"][data-align="start"],
.popover-content[data-side="top"][data-align="start"] {
	left: 0;
}
.popover-content[data-side="bottom"][data-align="center"],
.popover-content[data-side="top"][data-align="center"] {
	left: 50%;
	--popover-base-x: -50%;
}
.popover-content[data-side="bottom"][data-align="end"],
.popover-content[data-side="top"][data-align="end"] {
	right: 0;
}
.popover-content[data-side="left"][data-align="start"],
.popover-content[data-side="right"][data-align="start"] {
	top: 0;
}
.popover-content[data-side="left"][data-align="center"],
.popover-content[data-side="right"][data-align="center"] {
	top: 50%;
	--popover-base-y: -50%;
}
.popover-content[data-side="left"][data-align="end"],
.popover-content[data-side="right"][data-align="end"] {
	top: auto;
	bottom: 0;
}

/*
 * Fixed anchoring — for a popover inside a clipping ancestor (the sidebar's
 * scroll region clips x as soon as it scrolls y). The panel is taken out of
 * the trigger's containing block and placed from the trigger rect in JS, so
 * nothing can clip it. JS writes left/top/right/bottom inline; the base
 * offsets are zeroed here because the alignment is already in those numbers.
 */
.popover-content[data-popover-anchor="fixed"] {
	position: fixed;
	z-index: 10000;
	--popover-base-x: 0px;
	--popover-base-y: 0px;
}

/*
 * Animations — asymmetric enter/exit with directional slide.
 * Enter: spring easing + directional translate for organic arc (180ms).
 *   Slightly longer than dropdown (150ms) because popovers carry richer
 *   content — the extra 30ms lets the eye settle (Timing + Staging).
 * Exit: snappy accelerate-out, less travel distance than enter so the
 *   leaving panel doesn't compete for attention (Follow Through + Timing).
 */

/* Open (Slow In & Slow Out — spring overshoot on enter) */
.popover-content[data-state="open"] {
	animation: popover-in 180ms cubic-bezier(0.22, 1.1, 0.36, 1);
}

/* Close (faster, subtler exit — content yields focus back to trigger) */
.popover-content[data-closing] {
	pointer-events: none;
	animation: popover-out 120ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}

@keyframes popover-in {
	from {
		opacity: 0;
		transform: translate(var(--popover-base-x), var(--popover-base-y))
			scale(0.96)
			translate(var(--popover-slide-x), var(--popover-slide-y));
	}
	to {
		opacity: 1;
		transform: translate(var(--popover-base-x), var(--popover-base-y))
			scale(1);
	}
}

@keyframes popover-out {
	from {
		opacity: 1;
		transform: translate(var(--popover-base-x), var(--popover-base-y))
			scale(1);
	}
	to {
		opacity: 0;
		transform: translate(var(--popover-base-x), var(--popover-base-y))
			scale(0.97)
			translate(
				calc(var(--popover-slide-x) * 0.5),
				calc(var(--popover-slide-y) * 0.5)
			);
	}
}

/* ── Header ── */
.popover-header {
	display: flex;
	flex-direction: column;
	gap: 0.125rem;
	font-size: var(--text-base);
}

/* ── Title ── */
.popover-title {
	font-weight: 500;
}

/* ── Description ── */
.popover-description {
	color: var(--muted-foreground);
}

/* ── Reduced motion — instant show/hide, no transform (Timing) ── */
@media (prefers-reduced-motion: reduce) {
	.popover-content[data-state="open"],
	.popover-content[data-closing] {
		animation: none;
	}
}

/* progress.css */
/* Keyframes */
@keyframes progress-shimmer {
	100% {
		transform: translateX(100%);
	}
}

@keyframes progress-indeterminate {
	0% {
		left: -40%;
		width: 40%;
	}
	50% {
		left: 20%;
		width: 50%;
	}
	100% {
		left: 100%;
		width: 40%;
	}
}

/* Base */
.progress {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
}

/* Track */
.progress-track {
	position: relative;
	display: flex;
	height: 0.375rem;
	width: 100%;
	align-items: center;
	overflow: hidden;
	border-radius: 9999px;
	background-color: var(--muted);
	/* The track is a recessed well the fill travels along */
	box-shadow: var(--shadow-well);
}

/* Indicator */
.progress-indicator {
	position: relative;
	height: 100%;
	border-radius: 9999px;
	background-color: var(--primary);
	/* Lit top edge lifts the fill out of the well */
	box-shadow: var(--highlight-top);
	overflow: hidden;
	/*
	 * Linear, deliberately. Progress is the one place easing is wrong:
	 * the bar represents real work, so its motion must stay 1:1 with
	 * the value it reports (easing-linear-only-progress).
	 */
	transition: width 300ms linear;
}

/* Shimmer – secondary action communicating "in motion" */
.progress-indicator::after {
	content: '';
	position: absolute;
	inset: 0;
	transform: translateX(-100%);
	background: linear-gradient(
		90deg,
		transparent 0%,
		color-mix(in oklch, var(--primary-foreground) 15%, transparent) 40%,
		color-mix(in oklch, var(--primary-foreground) 25%, transparent) 60%,
		transparent 100%
	);
	/* 1.5s matches the skeleton shimmer and the indeterminate slide —
	   the same effect must not run at two different speeds */
	animation: progress-shimmer 1.5s ease-in-out infinite;
}

/* Complete: stop shimmer, settle into solid fill (peak-end rule) */
.progress[data-state="complete"] .progress-indicator::after {
	animation: none;
	display: none;
}

/* Indeterminate: own sliding animation (staging) */
.progress[data-state="indeterminate"] .progress-indicator {
	position: absolute;
	width: 40%;
	animation: progress-indeterminate 1.5s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}

.progress[data-state="indeterminate"] .progress-indicator::after {
	animation: none;
	display: none;
}

/* Label */
.progress-label {
	font-size: var(--text-base);
	line-height: 1.25rem;
	font-weight: 500;
}

/* Value */
.progress-value {
	margin-left: auto;
	font-size: var(--text-base);
	line-height: 1.25rem;
	color: var(--muted-foreground);
	font-variant-numeric: tabular-nums;
}

/* Dark mode – subtler shimmer against dark backgrounds */
.dark .progress-indicator::after {
	background: linear-gradient(
		90deg,
		transparent 0%,
		color-mix(in oklch, var(--primary-foreground) 8%, transparent) 40%,
		color-mix(in oklch, var(--primary-foreground) 12%, transparent) 60%,
		transparent 100%
	);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.progress-indicator {
		transition-duration: 75ms;
	}

	.progress-indicator::after {
		animation: none;
		display: none;
	}

	.progress[data-state="indeterminate"] .progress-indicator {
		animation: none;
		width: 50%;
		left: 25%;
	}
}

/* pwa-nav.css */
/* --- Reload (installed only) --- */
/* The bottom bar's five slots are the app's own navigation; reload lives in
   the account menu instead. In a browser tab the chrome already has one, so
   the row is hidden unless the app is running installed. */
.account-reload { display: none; }
html[data-standalone] .account-reload { display: flex; }
/* Spins while the reload it kicked off is in flight — the old page stays
   on screen until the new one paints, so without it the tap looks lost. */
.reload-icon { display: inline-flex; flex-shrink: 0; }
.is-reloading .reload-icon { animation: ptr-spin 700ms linear infinite; }

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

/* No pull-to-refresh of our own, and none from the browser either: an
   overscroll at the top of a page must not reload it. */
.content-panel { overscroll-behavior-y: contain; }

@media (prefers-reduced-motion: reduce) {
	.is-reloading .reload-icon { animation: none; }
}

/* radio-group.css */
/* Radio Group */
.radio-group {
	display: grid;
	width: 100%;
	gap: 0.25rem;
}

/* Radio Label */
.radio-label {
	display: inline-flex;
	width: fit-content;
	align-items: center;
	gap: 0.5rem;
	padding: 0.25rem 0;
	cursor: pointer;
}

.radio-label-text {
	font-size: var(--text-base);
	line-height: 1.25rem;
	font-weight: 500;
	user-select: none;
}

/* Wrapper */
.radio-wrapper {
	position: relative;
	display: inline-flex;
	width: 1.125rem;
	height: 1.125rem;
	flex-shrink: 0;
}

/* Native input: transparent overlay for interaction */
.radio-input {
	position: absolute;
	inset: -0.5rem -0.75rem;
	opacity: 0;
	cursor: pointer;
	margin: 0;
	z-index: 1;
}

.radio-input:disabled {
	cursor: not-allowed;
}

/* Visual radio */
.radio-visual {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.125rem;
	height: 1.125rem;
	border-radius: 50%;
	border: 1px solid var(--input);
	background: transparent;
	color: inherit;
	/* Field elevation — a hairline lift off the page */
	box-shadow: var(--shadow-field);
	transition: border-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
		background-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
		color 100ms cubic-bezier(0.4, 0, 0.2, 1),
		box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1),
		transform 100ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Hover on radio circle (Anticipation — hint before action) */
.radio-input:not(:disabled):not(:checked):hover + .radio-visual {
	border-color: color-mix(in oklch, var(--primary) 50%, var(--input));
}

/* Pressed feedback */
.radio-input:active:not(:disabled) + .radio-visual {
	transform: scale(0.97);
	transition-duration: 100ms;
}

/* Focus visible */
.radio-input:focus-visible + .radio-visual {
	border-color: var(--ring);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* Disabled */
.radio-input:disabled + .radio-visual {
	opacity: 0.5;
}

/* Disabled label row */
.radio-label:has(.radio-input:disabled) {
	cursor: not-allowed;
	pointer-events: none;
}
.radio-label:has(.radio-input:disabled) .radio-label-text {
	opacity: 0.5;
}

/* Invalid */
.radio-input[aria-invalid="true"] + .radio-visual {
	border-color: var(--destructive);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

/* Checked — background fills first (Staging); lit-edge on the face */
.radio-input:checked + .radio-visual {
	border-color: var(--primary);
	background-color: var(--primary);
	color: var(--primary-foreground);
	box-shadow: var(--highlight-top), var(--shade-bottom), var(--shadow-field);
}

/* Invalid + Checked */
.radio-input[aria-invalid="true"]:checked + .radio-visual {
	border-color: var(--primary);
}

/* Indicator */
.radio-indicator {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 1.125rem;
	height: 1.125rem;
}

/* Dot — delayed pop-in after background fill (Follow Through) */
.radio-dot {
	display: block;
	width: 0.5rem;
	height: 0.5rem;
	border-radius: 50%;
	background-color: var(--primary-foreground);
	opacity: 1;
	transform: scale(1);
	transition: opacity 120ms 40ms cubic-bezier(0.4, 0, 0.2, 1),
		transform 200ms 40ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Hide dot when unchecked — scale from 0 for a satisfying pop (Appeal) */
.radio-input:not(:checked) + .radio-visual .radio-dot {
	opacity: 0;
	transform: scale(0);
	transition-delay: 0ms;
}

/* Dark mode */
.dark .radio-visual {
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}

.dark .radio-input:not(:disabled):not(:checked):hover + .radio-visual {
	border-color: color-mix(in oklch, var(--primary) 60%, var(--input));
}

.dark .radio-input[aria-invalid="true"] + .radio-visual {
	border-color: color-mix(in oklch, var(--destructive) 50%, transparent);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}

.dark .radio-input:checked + .radio-visual {
	background-color: var(--primary);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.radio-visual {
		transition-duration: 75ms;
	}
	.radio-dot {
		transition: none;
	}
	.radio-input:active:not(:disabled) + .radio-visual {
		transform: none;
	}
}

/* resizable.css */
/* Resizable Panel Group */
.resizable-group {
	display: flex;
	height: 100%;
	width: 100%;
}
.resizable-group-vertical {
	flex-direction: column;
}

/* Resizable Panel */
.resizable-panel {
	overflow: hidden;
	flex-shrink: 0;
	flex-grow: 0;
}

/* Resizable Handle
   [Fitts's Law] 16px hit area via ::after for comfortable targeting
   [timing-under-300ms] 150ms transitions for hover/active feedback
   [easing-entrance-ease-out] ease-out for state appearance */
.resizable-handle {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	width: 1px;
	background-color: var(--border);
	outline: none;
	cursor: col-resize;
	touch-action: none;
	user-select: none;
	transition: background-color 150ms ease-out;
}

/* [Fitts's Law] Generous invisible hit area -- 16px wide */
.resizable-handle::after {
	content: "";
	position: absolute;
	inset: 0;
	left: 50%;
	width: 16px;
	transform: translateX(-50%);
}

.resizable-handle:focus-visible {
	background-color: var(--ring);
	box-shadow: 0 0 0 2px color-mix(in oklch, var(--ring) 30%, transparent);
}

/* [Aesthetic-Usability] Soft intermediate hover before full active */
.resizable-handle:hover {
	background-color: color-mix(in oklch, var(--primary) 50%, transparent);
}

/* [physics-active-state] Active drag gets full primary color */
.resizable-handle.resizable-handle-active {
	background-color: var(--primary);
	transition-duration: 50ms;
}

/* [Flow] Boundary feedback -- hitting min/max is normal operation, not
   an error, so the handle dims back toward its resting border color
   ("stuck") instead of flashing the destructive red used for
   irreversible actions elsewhere in the kit. */
.resizable-handle.resizable-handle-at-limit {
	background-color: color-mix(in oklch, var(--primary) 35%, var(--border));
	transition-duration: 100ms;
}

/* Horizontal group handle (separator between top/bottom) */
.resizable-handle-vertical {
	width: 100%;
	height: 1px;
	cursor: row-resize;
}
.resizable-handle-vertical::after {
	content: "";
	position: absolute;
	inset: 0;
	top: 50%;
	left: 0;
	width: 100%;
	height: 16px;
	transform: translateY(-50%);
}

/* Dragging state: change cursor globally */
body.resizable-dragging-col {
	cursor: col-resize !important;
	user-select: none;
}
body.resizable-dragging-row {
	cursor: row-resize !important;
	user-select: none;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.resizable-handle {
		transition: none;
	}
}

/* Dark mode */
.dark .resizable-handle:hover {
	background-color: color-mix(in oklch, var(--primary) 60%, transparent);
}
.dark .resizable-handle.resizable-handle-active {
	background-color: var(--primary);
}

/* select.css */
/* ── Root ── */
.sel {
	position: relative;
	display: inline-flex;
}

/* ── Trigger ── */
.sel-trigger {
	display: inline-flex;
	width: fit-content;
	align-items: center;
	justify-content: space-between;
	gap: 0.375rem;
	border-radius: var(--radius-lg);
	border: 1px solid var(--input);
	background-color: transparent;
	/* Field elevation — a hairline lift off the page (--shadow-field) */
	box-shadow: var(--shadow-field);
	padding: 0.5rem 0.5rem 0.5rem 0.625rem;
	font-size: var(--text-base);
	line-height: 1.25rem;
	white-space: nowrap;
	transition-property: color, background-color, border-color, box-shadow, opacity, transform;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
	outline: none;
	user-select: none;
	cursor: pointer;
	height: 2.25rem;
}

/* Anticipation — hover hints at interactivity before click (Jakob's Law) */
.sel-trigger:hover:not(:disabled) {
	border-color: color-mix(in oklch, var(--ring) 40%, transparent);
}

/* Squash & Stretch — shared press spec with Button, Checkbox, Radio,
   Switch, Tabs and Calendar: scale(0.97) over 100ms (timing-consistent) */
.sel-trigger:active:not(:disabled) {
	transform: scale(0.97);
	transition-duration: 100ms;
}

.sel-trigger:focus-visible {
	border-color: var(--ring);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/*
 * Secondary Action — expanded trigger gets subtle bg tint so the user
 * sees a visual connection between the trigger and the open panel
 * (Law of Common Region + Working Memory).
 */
.sel-trigger[aria-expanded="true"] {
	border-color: var(--ring);
	background-color: color-mix(in oklch, var(--muted) 50%, transparent);
}

.sel-trigger:disabled {
	pointer-events: none;
	opacity: 0.5;
	cursor: not-allowed;
}

.sel-trigger[aria-invalid="true"] {
	border-color: var(--destructive);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

/* Size: sm */
.sel-trigger-sm {
	height: 2rem;
	border-radius: min(var(--radius-md), 10px);
}

/* ── Trigger icon ── */
.sel-trigger-icon {
	display: flex;
	align-items: center;
	justify-content: center;
	pointer-events: none;
	color: var(--muted-foreground);
	flex-shrink: 0;
	transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1),
		color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.sel-trigger-icon svg {
	width: 1rem;
	height: 1rem;
}

/* Secondary Action — chevron follows hover/focus to reinforce affordance */
.sel-trigger:hover:not(:disabled) .sel-trigger-icon {
	color: var(--foreground);
}

/* Chevron rotates when open (Anticipation in reverse — confirms the state) */
.sel-trigger[aria-expanded="true"] .sel-trigger-icon {
	transform: rotate(180deg);
	color: var(--foreground);
}

/* ── Value ── */
.sel-value {
	display: flex;
	flex: 1;
	align-items: center;
	gap: 0.375rem;
	text-align: left;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* Placeholder — shown via CSS when no value is selected.
   JS replaces the pseudo-element with real text on selection. */
.sel-value[data-placeholder]:empty::before {
	content: attr(data-placeholder);
	color: var(--muted-foreground);
}

/* ── Content ── */
.sel-content {
	position: absolute;
	z-index: 50;
	min-width: 100%;
	max-height: 20rem;
	overflow-y: auto;
	overscroll-behavior: contain;
	border-radius: var(--radius-md);
	background: var(--popover);
	padding: 0.25rem;
	color: var(--popover-foreground);
	font-size: var(--text-base);
	line-height: 1.25rem;
	box-shadow: var(--shadow-popover);
	outline: none;
	/*
	 * --sel-base-x holds the alignment offset (e.g. -50% for centering).
	 * --sel-slide holds the directional animation offset per-side.
	 * Both are composed into the keyframes so the base position is never lost.
	 */
	--sel-base-x: 0px;
	--sel-slide: 0px;
	transform: translateX(var(--sel-base-x));
}

/* Hidden when closed */
.sel-content[data-state="closed"] {
	pointer-events: none;
	visibility: hidden;
	opacity: 0;
}

/*
 * Side positioning + directional slide offsets.
 * transform-origin anchors scale to the trigger edge so the panel
 * appears to grow outward — maintaining spatial continuity (Solid Drawing).
 * --sel-slide drives the directional translate so the panel
 * emerges FROM the trigger, not just fades in place (Staging).
 */

/* Side: bottom (default) */
.sel-content[data-side="bottom"] {
	top: calc(100% + 4px);
	transform-origin: top;
	--sel-slide: -4px;
}
.sel-content[data-side="top"] {
	bottom: calc(100% + 4px);
	transform-origin: bottom;
	--sel-slide: 4px;
}

/* Align */
.sel-content[data-side="bottom"][data-align="start"],
.sel-content[data-side="top"][data-align="start"] {
	left: 0;
}
.sel-content[data-side="bottom"][data-align="center"],
.sel-content[data-side="top"][data-align="center"] {
	left: 50%;
	--sel-base-x: -50%;
}
.sel-content[data-side="bottom"][data-align="end"],
.sel-content[data-side="top"][data-align="end"] {
	right: 0;
}

/*
 * Animations — asymmetric enter/exit with directional slide.
 * Enter: spring easing + directional translate for organic arc (150ms).
 * Exit: snappy accelerate-out, less travel distance than enter so the
 *   leaving panel doesn't compete for attention (Follow Through + Timing).
 */

/* Open (Slow In & Slow Out — spring overshoot on enter) */
.sel-content[data-state="open"] {
	animation: sel-in 150ms cubic-bezier(0.22, 1.1, 0.36, 1);
}

/* Close (faster, subtler exit — content yields focus back to trigger) */
.sel-content[data-closing] {
	pointer-events: none;
	animation: sel-out 100ms cubic-bezier(0.4, 0, 0.7, 0.2) forwards;
}

@keyframes sel-in {
	from {
		opacity: 0;
		transform: translateX(var(--sel-base-x))
			scale(0.96)
			translateY(var(--sel-slide));
	}
	to {
		opacity: 1;
		transform: translateX(var(--sel-base-x))
			scale(1);
	}
}

@keyframes sel-out {
	from {
		opacity: 1;
		transform: translateX(var(--sel-base-x))
			scale(1);
	}
	to {
		opacity: 0;
		transform: translateX(var(--sel-base-x))
			scale(0.97)
			translateY(calc(var(--sel-slide) * 0.5));
	}
}

/* ── Group ── */
.sel-group {
	padding: 0.125rem 0;
}

/* ── Label ── */
.sel-label {
	padding: 0.25rem 0.5rem;
	font-size: var(--text-xs);
	font-weight: 500;
	color: var(--muted-foreground);
}

/* Chunking — labels after separators get extra top space
   to form visually distinct groups (Law of Proximity) */
.sel-separator + .sel-group > .sel-label:first-child,
.sel-separator + .sel-label {
	padding-top: 0.375rem;
}

/* ── Item ── */
.sel-item {
	position: relative;
	display: flex;
	cursor: default;
	align-items: center;
	gap: 0.375rem;
	/* Concentric with the panel: outer radius minus the 0.25rem pad */
	border-radius: calc(var(--radius-md) - 4px);
	padding: 0.3125rem 2rem 0.3125rem 0.5rem;
	font-size: var(--text-base);
	line-height: 1.25rem;
	outline: none;
	user-select: none;
}

/*
 * Hover fades; keyboard does not. The transition lives on the :hover
 * rule so pointer highlighting stays smooth while ArrowUp/ArrowDown
 * moves the highlight instantly — keyboard navigation must never wait
 * on an animation.
 */
.sel-item:hover:not([data-disabled="true"]) {
	background: var(--accent);
	color: var(--accent-foreground);
	transition: background-color 80ms cubic-bezier(0.4, 0, 0.2, 1),
		color 80ms cubic-bezier(0.4, 0, 0.2, 1);
}

.sel-item[data-highlighted] {
	background: var(--accent);
	color: var(--accent-foreground);
}

/* Selected state */
.sel-item[data-selected] {
	font-weight: 500;
}

/* Disabled */
.sel-item[data-disabled="true"] {
	pointer-events: none;
	opacity: 0.5;
}

/* SVG inside items */
.sel-item svg {
	pointer-events: none;
	flex-shrink: 0;
	width: 1rem;
	height: 1rem;
}

/* ── Item text ── */
.sel-item-text {
	display: flex;
	flex: 1;
	flex-shrink: 0;
	align-items: center;
	gap: 0.5rem;
	white-space: nowrap;
}

/* ── Item indicator (check) ── */
.sel-item-indicator {
	pointer-events: none;
	position: absolute;
	right: 0.5rem;
	display: flex;
	align-items: center;
	justify-content: center;
	opacity: 0;
	transform: scale(0.5);
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1),
		transform 150ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.sel-item[data-selected] .sel-item-indicator {
	opacity: 1;
	transform: scale(1);
}

.sel-item-indicator svg {
	width: 0.875rem;
	height: 0.875rem;
}

/* ── Separator ── */
.sel-separator {
	margin: 0.25rem -0.25rem;
	height: 1px;
	background: var(--border);
}

/* ── Dark mode ── */
.dark .sel-trigger {
	border-color: var(--input);
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}
.dark .sel-trigger:hover:not(:disabled) {
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
}
.dark .sel-trigger[aria-expanded="true"] {
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
}
.dark .sel-trigger[aria-invalid="true"] {
	border-color: color-mix(in oklch, var(--destructive) 50%, transparent);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}
/* ── Reduced motion — instant show/hide, no transform (Timing) ── */
@media (prefers-reduced-motion: reduce) {
	.sel-trigger,
	.sel-trigger-icon,
	.sel-item,
	.sel-item-indicator {
		transition: none;
	}
	.sel-trigger:active:not(:disabled) {
		transform: none;
	}
	.sel-content[data-state="open"],
	.sel-content[data-closing] {
		animation: none;
	}
}

/* separator.css */
.separator {
	flex-shrink: 0;
	background-color: var(--border);
	transition: background-color 150ms ease;
}

.separator-horizontal {
	height: 1px;
	width: 100%;
}

.separator-vertical {
	width: 1px;
	align-self: stretch;
}

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

/* sidebar.css */
.shell {
	display: flex;
	flex-direction: column;
	height: 100vh;
	height: 100dvh; /* phones: track the real viewport, not the URL-bar one */
	overflow: hidden;
	position: relative;
}

/* Floating expand button for a collapsed desktop rail. */
.shell-expand-btn {
	position: absolute;
	top: 0.6rem;
	left: 0.6rem;
	z-index: 50;
	display: none; /* shown only while collapsed */
	background: var(--card);
	border: 1px solid var(--border);
	box-shadow: var(--shadow-control);
}
.shell-expand-btn:hover { background: var(--muted); }
.shell[data-sidebar-collapsed] .shell-expand-btn { display: inline-flex; }

/* The phone tab bar (shown only under 48rem, where the rail is gone).
   Two classes, not one: the bar IS a Tabs root, so `.bbar` alone only ties
   with `.tabs { display: flex }` — and a tie is settled by bundle order,
   which this app's styles must never depend on. */
.tabs.bbar { display: none; }
/* The code button, likewise: on desktop the rail carries the row. */
.shell-code-fab { display: none; }
/* The account menu is the rail's popover laid flat; on desktop the popover
   is the one that renders, so this stays out of the way. */
.account-menu { display: none; }

/* Deliberately no loading indicator on a navigation, and deliberately no
   crossfade either. Wrapping the swap in a View Transition was tried and
   removed: the runtime is shared across this lineage and a transition it
   starts is opt-out (the browser's own 250ms root crossfade runs with no
   CSS at all), so it made every app of the lineage fade whether or not it
   had asked. A hairline sweeping the top edge went the same way: what it
   reports — "a request is in flight" — the pressed tab already says by
   lighting instantly. Spinners and skeletons are worse still; both stand
   in place of content the reader can still use. If a route is slow enough
   to need one, the route is the thing to fix. */

/* Body: [fixed sidebar | content] */
.shell-body {
	flex: 1;
	min-height: 0;
	display: flex;
}
/* Fixed rail. Must not clip: the account popover escapes the rail. A
   stacking context above the content panel keeps it on top. */
.sidebar-panel {
	width: 16rem;
	flex-shrink: 0;
	background: var(--card);
	border-right: 1px solid var(--border);
	overflow: visible;
	position: relative;
	z-index: 45;
	/* Expand is an entrance: arrive fast, settle gently (ease-out). The
	   collapsed state below overrides the curve with an ease-in exit. */
	transition: width 240ms cubic-bezier(0, 0, 0.2, 1);
}
.content-panel {
	flex: 1;
	min-width: 0;
	overflow-y: auto;
	display: flex;
	flex-direction: column;
	position: relative;
}

.sidebar {
	display: flex;
	flex-direction: column;
	height: 100%;
	overflow: visible;
	gap: 1.5rem;
	/* Fixed width so the rail slides/clips away as the panel narrows
	   instead of reflowing its rows to nothing during the collapse. */
	width: 16rem;
	/* Fade the contents a touch faster than the width so the rail is gone
	   before its sliver would look half-empty. */
	transition: opacity 160ms ease-out;
}

/* Head: logo (left) + collapse / close (right), one line. */
.sidebar-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 0.5rem 0.75rem 0;
	flex-shrink: 0;
}
.sidebar-head-actions { display: flex; align-items: center; gap: 0.25rem; }
.sidebar-logo { display: inline-flex; align-items: center; text-decoration: none; }

/* Brand mark. */
.brand-mark { display: inline-flex; align-items: center; }
.brand-mark-icon { display: inline-flex; color: var(--primary); }
.brand-mark-icon svg { width: 1.35rem; height: 1.35rem; }

/* Scroll region — the labelled sections stack here, 16px apart. */
.sidebar-scroll {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	padding: 0 0.75rem;
	display: flex;
	flex-direction: column;
	gap: 24px; /* 24px between sections */
}

/* Main items — always visible (not collapsible). */
.sidebar-primary {
	display: flex;
	flex-direction: column;
	gap: 1px; /* little spacing between items of the same section */
}

/* Collapsible section: a small grey header that brightens on hover, over
   an items container that animates to zero height when collapsed. */
.sb-section { display: flex; flex-direction: column; }
.sb-section-head {
	display: flex;
	align-items: center;
	gap: 0.3rem;
	width: 100%;
	height: 24px;
	padding: 0 0.4rem;
	border: 0;
	background: none;
	font-family: inherit;
	font-size: var(--text-xs);
	font-weight: 500;
	color: var(--muted-foreground);
	cursor: pointer;
	transition: color 150ms ease;
}
.sb-section-head:hover { color: var(--foreground); }
.sb-section-title {
	flex: 1;
	min-width: 0;
	text-align: left;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.sb-section-caret { display: inline-flex; flex-shrink: 0; }
.sb-section-caret svg { width: 0.7rem; height: 0.7rem; transition: transform 200ms ease; }
.sb-section[data-collapsed] .sb-section-caret svg { transform: rotate(-90deg); }
/* 0fr↔1fr grid rows give a JS-free height animation. */
.sb-section-body {
	display: grid;
	grid-template-rows: 1fr;
	transition: grid-template-rows 200ms ease;
}
.sb-section[data-collapsed] .sb-section-body { grid-template-rows: 0fr; }
.sb-section-items {
	min-height: 0;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	gap: 1px; /* little spacing between items of the same section */
}

/* Rail item — Linear style: a bit grey at rest, foreground on hover and
   active, no background in any state. 28px tall. */
.sidebar-item {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	width: 100%;
	height: 28px;
	padding: 0 0.4rem;
	border: 0;
	border-radius: var(--radius-md);
	background: none;
	font-family: inherit;
	font-size: var(--text-base);
	color: var(--muted-foreground);
	text-align: left;
	text-decoration: none;
	cursor: pointer;
	transition: color 150ms ease;
}
.sidebar-item:hover { color: var(--foreground); }
.sidebar-item-active { color: var(--foreground); }
.sidebar-item-icon { display: inline-flex; flex-shrink: 0; color: currentColor; }
.sidebar-item-icon svg { width: 1rem; height: 1rem; }
.sidebar-item-label {
	flex: 1;
	min-width: 0;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* Optical centering for every label in the rail. `align-items: center`
   centres the *line box*, but Manrope is ascent-heavy (1.045em over
   0.294em) and Blink rounds both to whole pixels, so the baseline lands
   half a pixel below where cap-height centring wants it -- and half a
   pixel snaps to a whole one when the glyphs rasterise, which is the
   "labels sit under their icons" everyone can see. A pixel of margin
   under the label pays it back: a flex row centres the label's *margin*
   box, so 1px of margin lifts the text by half of one, and the cap band
   ends up sharing a centre with the 16px icon beside it. Not the icon's
   problem to solve -- it sits on whole pixels and must stay there. */
.sidebar-item-label,
.sidebar-thread-label,
.sidebar-account-name {
	margin-bottom: 1px;
}

/* Document rows live in the primary nav, so they inherit .sidebar-item
   wholesale; this block adds the row-level 3-dots positioning, the tree
   (caret + collapsible children) and the "Mais" overflow row. */
.sidebar-docs { display: flex; flex-direction: column; gap: 1px; }
.sidebar-doc-node { display: flex; flex-direction: column; gap: 1px; }
.sidebar-doc-kids { display: flex; flex-direction: column; gap: 1px; }
/* Collapsed is the default: a branch opens because someone opened it
   (or is standing inside it — see the auto-expand in sidebarScript). */
.sidebar-doc-node:not([data-open]) > .sidebar-doc-kids { display: none; }
/* Tree indent. The row carries --doc-indent inline (docRowPad) rather than
   a padding, so a restyling of the same row is free to pad it evenly and
   step the container right instead — which is what the /documentos cards
   do (pages/documentos_lista.css). */
.sidebar-doc-row { padding-left: var(--doc-indent, 0.4rem); }
.sidebar-doc-ctx { display: block; position: relative; }
/* The trigger wraps only the 3-dots, so the button anchors to the row. */
.sidebar-doc-ctx .ctxm-trigger { display: contents; }
/* Leave room for the button so a long title does not run under it. */
.sidebar-doc-ctx:hover .sidebar-item-label { padding-right: 1.25rem; }

/* Caret: a folder's expand control, sitting exactly over the icon slot.
   On desktop it swaps in on hover (the icon yields); on touch, where
   nothing hovers, a folder always shows the caret in the icon's place —
   the affordance beats the pictogram. */
.sidebar-doc-caret {
	position: absolute;
	top: 50%;
	left: var(--doc-indent, 0.4rem);
	transform: translateY(-50%);
	display: none;
	align-items: center;
	justify-content: center;
	width: 1rem;
	height: 1.2rem;
	padding: 0;
	border: 0;
	border-radius: var(--radius-sm);
	background: var(--card);
	color: var(--muted-foreground);
	cursor: pointer;
}
.sidebar-doc-caret svg { width: 0.8rem; height: 0.8rem; transition: transform 150ms ease; }
.sidebar-doc-caret:hover { color: var(--foreground); }
.sidebar-doc-node[data-open] > .sidebar-doc-ctx .sidebar-doc-caret svg { transform: rotate(90deg); }
.sidebar-doc-ctx:hover .sidebar-doc-caret,
.sidebar-doc-caret:focus-visible { display: inline-flex; }
@media (hover: none) { .sidebar-doc-caret { display: inline-flex; } }

/* Drag: the moving row dims; a drop lands before/after (a 2px slot rule)
   or inside (the row lights up like a hover). */
.sidebar-doc-row[data-hm-busy],
.sidebar-doc-dragging { opacity: 0.45; }
.sidebar-doc-row { position: relative; }
.sidebar-doc-row.sb-drop-into { background: var(--muted); border-radius: var(--radius-md); }
.sidebar-doc-row.sb-drop-before::before,
.sidebar-doc-row.sb-drop-after::after {
	content: "";
	position: absolute;
	left: 0.4rem;
	right: 0.4rem;
	height: 2px;
	border-radius: 1px;
	background: var(--primary);
}
.sidebar-doc-row.sb-drop-before::before { top: -1.5px; }
.sidebar-doc-row.sb-drop-after::after { bottom: -1.5px; }

/* "Mais" is a popover trigger, so it needs the row layout spelled out —
   PopoverTrigger renders a button, not an anchor. */
.sidebar-more-pop { display: flex; }
button.sidebar-more { width: 100%; }
.sidebar-more-menu { max-height: 60vh; overflow-y: auto; }
.sidebar-more-label {
	min-width: 0;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* Shared popover menu (account). Items match the sidebar items' radius so
   nothing reads square. */
.sidebar-menu {
	width: auto;
	min-width: 12rem;
	padding: 0.3rem;
	gap: 0.125rem;
}
.sidebar-menu-form { margin: 0; display: flex; }
.sidebar-menu-item {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	width: 100%;
	min-height: 2.25rem;
	padding: 0.45rem 0.55rem;
	border: 0;
	border-radius: var(--radius-md);
	background: none;
	color: var(--popover-foreground);
	font-family: inherit;
	font-size: var(--text-base);
	text-align: left;
	text-decoration: none;
	cursor: pointer;
	transition: background-color 120ms ease;
}
.sidebar-menu-item:hover,
.sidebar-menu-item:focus-visible {
	background: var(--accent);
	outline: none;
}
.sidebar-menu-item svg { width: 1rem; height: 1rem; flex-shrink: 0; }

/* Dark-mode toggle inside the account popover: moon + "Dark mode" in
   light mode, sun + "Light mode" in dark mode. */
.sidebar-dark-light { display: inline-flex; align-items: center; }
.sidebar-dark-dark { display: none; align-items: center; }
.dark .sidebar-dark-light { display: none; }
.dark .sidebar-dark-dark { display: inline-flex; }

/* Recent conversation rows — same Linear treatment as items, with a
   hover-only 3-dots menu button pinned to the row's right edge. */
.sidebar-threads { display: flex; flex-direction: column; gap: 1px; }
.sidebar-thread-ctx { display: block; position: relative; }
/* The trigger wraps only the 3-dots button; it stays out of the flow so
   the button anchors to the row container (.sidebar-thread-ctx). */
.sidebar-thread-ctx .ctxm-trigger { display: contents; }
.sidebar-thread {
	display: flex;
	align-items: center;
	height: 28px;
	padding: 0 0.4rem;
	border-radius: var(--radius-md);
	font-size: var(--text-base);
	color: var(--muted-foreground);
	text-decoration: none;
	cursor: pointer;
	transition: color 150ms ease;
}
.sidebar-thread:hover { color: var(--foreground); }
.sidebar-thread-active { color: var(--foreground); }
.sidebar-thread-label {
	flex: 1;
	min-width: 0;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.sidebar-thread-menu-btn {
	position: absolute;
	top: 50%;
	right: 0.2rem;
	transform: translateY(-50%);
	display: none;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	padding: 0;
	border: 0;
	border-radius: var(--radius-md);
	/* Solid rail background masks the label's end so long titles do not
	   run under the button (no shadow — a soft glow read as an out-of-
	   place blob on hover). */
	background: var(--card);
	color: var(--muted-foreground);
	cursor: pointer;
}
.sidebar-thread-menu-btn:hover { color: var(--foreground); }
.sidebar-thread-menu-btn svg { width: 1rem; height: 1rem; }
/* 3-dots is visible only on row hover (plus keyboard focus / touch). */
.sidebar-thread-ctx:hover .sidebar-thread-menu-btn,
.sidebar-thread-menu-btn:focus-visible { display: inline-flex; }
@media (hover: none) { .sidebar-thread-menu-btn { display: inline-flex; } }

/* "Mostrar mais" — the same 28px row as a thread, one notch quieter so
   it reads as a control rather than another conversation. */
.sidebar-thread-more {
	display: flex;
	align-items: center;
	gap: 0.3rem;
	width: 100%;
	height: 28px;
	padding: 0 0.4rem;
	border: 0;
	border-radius: var(--radius-md);
	background: none;
	font-family: inherit;
	font-size: var(--text-sm);
	color: var(--muted-foreground);
	text-align: left;
	cursor: pointer;
	transition: color 150ms ease, opacity 150ms ease;
}
.sidebar-thread-more:hover { color: var(--foreground); }
.sidebar-thread-more svg { width: 0.85rem; height: 0.85rem; flex-shrink: 0; }
/* In flight: dim and stop taking clicks. hypermedia.js marks the control
   that started a request and disables it until the answer lands. */
.sidebar-thread-more[data-hm-busy] { opacity: 0.55; cursor: default; }

/* Account row pinned to the bottom of the rail — name only, no icon,
   grey until hovered. */
.sidebar-account {
	flex-shrink: 0;
	padding: 0.5rem 0.75rem;
	border-top: 1px solid var(--border);
}
.sidebar-account-pop { display: flex; width: 100%; }
.sidebar-account-trigger {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	width: 100%;
	height: 2.75rem;
	padding: 0 0.5rem;
	border: 0;
	border-radius: var(--radius-md);
	background: none;
	color: var(--foreground);
	text-align: left;
	cursor: pointer;
	transition: background-color 120ms ease;
}
.sidebar-account-trigger:hover { background: var(--muted); }
.sidebar-account-trigger .avatar { flex-shrink: 0; }
.sidebar-account-fb {
	background: var(--primary);
	color: var(--primary-foreground);
	font-weight: 600;
}
.sidebar-account-name {
	flex: 1;
	min-width: 0;
	font-size: var(--text-base);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.sidebar-account-caret { display: inline-flex; color: var(--muted-foreground); flex-shrink: 0; }
.sidebar-account-caret svg { width: 0.9rem; height: 0.9rem; }
.sidebar-account-menu { min-width: 13rem; }

/* --- Collapsible sidebar (the ChatGPT/Claude rail) ---
   Collapsed, the panel narrows to nothing (animated) and the way back is
   the floating expand button. Width — not display:none — so collapse and
   expand can be animated. */
.shell[data-sidebar-collapsed] .sidebar-panel {
	width: 0;
	overflow: hidden;
	border-right-color: transparent;
	/* Collapse is an exit: build momentum before departure (ease-in). */
	transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
}
.shell[data-sidebar-collapsed] .sidebar { opacity: 0; }
.shell[data-sidebar-collapsed] .shell-expand-btn {
	animation: shell-expand-in 200ms cubic-bezier(0, 0, 0.2, 1);
}
@keyframes shell-expand-in {
	from { opacity: 0; transform: scale(0.85); }
	to { opacity: 1; transform: scale(1); }
}

/* Content region (what a navigation replaces) */
.shell-content {
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
	position: relative;
	/* Clip (not scroll): keeps a wide/open popup or long row from creating
	   a horizontal scrollbar into empty space. */
	overflow-x: clip;
}

@media (max-width: 48rem) {
	/* Phone shell: content edge to edge, navigation floating over it.
	   The rail does not render here at all — its three jobs are three
	   screens (/documentos, /conversas, /perfil) the tab bar leads to.
	   It stays in the DOM (display:none) because #sidebar-docs and
	   #sidebar-threads are fragment targets and the server sends them
	   without knowing the viewport; they land on a hidden element and
	   the screen that cares asks for itself again. */
	.shell-expand-btn { display: none !important; }
	.sidebar-panel { display: none; }
	.content-panel { width: 100%; padding-top: env(safe-area-inset-top); }

	/* --- The glass tab bar ---
	   A capsule floating clear of the bottom edge, not a bar welded to
	   it: on iOS the home indicator owns the last few millimetres of the
	   screen, and a control sitting under it is a control you swipe the
	   app away trying to press. --bbar-lift is that clearance plus the
	   safe area; --bbar-space is what the content has to keep free.
	   Glass, not paint: the capsule is translucent and blurs what passes
	   behind it, so scrolling shows through and the bar reads as a layer
	   above the page rather than a slice cut out of it. */
	.shell {
		--bbar-h: 3.5rem;
		/* The clearance is over and above the safe area, not instead of it:
		   the home indicator sits inside its own inset, and a capsule that
		   only just clears it still reads as touching. Nudged 0.55 → 0.75 →
		   0.875 → 1.25rem, each time because it still looked close on an
		   iPhone. */
		--bbar-lift: calc(env(safe-area-inset-bottom) + 1.25rem);
		--bbar-space: calc(var(--bbar-h) + var(--bbar-lift) + 0.5rem);
	}
	/* Specificity, not bundle order: every rule below has to outrank the
	   Tabs component's own (.tabs[data-orientation] > .tabs-list and
	   friends), and a style in this app must not depend on which file
	   the bundler concatenated first. Hence `.shell .bbar.tabs > …`. */
	.shell .bbar.tabs {
		position: fixed;
		left: 0;
		right: 0;
		bottom: var(--bbar-lift);
		z-index: 50;
		display: flex;
		flex-direction: row;
		justify-content: center;
		gap: 0;
		padding: 0 0.75rem;
		/* The gutter around the capsule is page, not bar — presses fall
		   through to whatever is under it. */
		pointer-events: none;
	}
	.shell .bbar.tabs > .bbar-list {
		pointer-events: auto;
		width: 100%;
		max-width: 24rem;
		height: var(--bbar-h);
		padding: 0.3rem;
		border-radius: 999px;
		/* Two layers make the glass: a wash of the page colour thin
		   enough to see through, and the blur that turns whatever is
		   behind it into colour rather than shapes. saturate is what
		   keeps the content underneath from going grey the way a plain
		   blur leaves it. */
		background: color-mix(in oklch, var(--background) 70%, transparent);
		-webkit-backdrop-filter: blur(20px) saturate(180%);
		backdrop-filter: blur(20px) saturate(180%);
		border: 1px solid color-mix(in oklch, var(--border) 80%, transparent);
		box-shadow: var(--shadow-popover);
	}
	/* The rim: one hairline of page-light along the top edge, which is
	   what stops a translucent capsule reading as a smudge. */
	.shell .bbar .bbar-list::before {
		content: "";
		position: absolute;
		inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: var(--highlight-top);
	}
	.shell .bbar .bbar-tab {
		height: 100%;
		flex: 1;
		padding: 0;
		border-radius: 999px;
		color: var(--muted-foreground);
	}
	.shell .bbar .bbar-tab svg { width: 1.4rem; height: 1.4rem; }
	/* The indicator is the pill that slides between tabs — the Tabs
	   component's own surface, rounded to the capsule. Neutral, like
	   every other tab strip in the app: the bar says where you are, it
	   does not need to be the loudest thing on the screen. */
	.shell .bbar .tabs-indicator {
		border: 0;
		border-radius: 999px;
		background: var(--background);
		box-shadow: var(--shadow-control);
	}
	/* Active: the glyph reads on the pill — both while the indicator is
	   the pill and in the no-JS fallback where the trigger is. */
	.shell .bbar .bbar-tab[data-active] {
		border-color: transparent;
		background: var(--background);
		color: var(--foreground);
		box-shadow: var(--shadow-control);
	}
	/* Dark: only the face changes. The page colour is already what the
	   glass is made of, so an opaque pill of it disappears — a lifted
	   surface carries it instead. The shadow stays, because the bevel is
	   the thing that says the pill sits on top of the bar. Spelled out
	   rather than left to the cascade: tabs.css has dark rules of equal
	   weight that would otherwise flatten it back. */
	.dark .shell .bbar .tabs-indicator {
		border: 0;
		border-radius: 999px;
		background: var(--accent);
		box-shadow: var(--shadow-control);
	}
	.dark .shell .bbar .bbar-tab[data-active] {
		border-color: transparent;
		background: var(--accent);
		color: var(--foreground);
		box-shadow: var(--shadow-control);
	}
	.shell .bbar .tabs-list[data-animated] .bbar-tab[data-active],
	.dark .shell .bbar .tabs-list[data-animated] .bbar-tab[data-active] {
		background: transparent;
		box-shadow: none;
	}

	/* --- The code button ---
	   The tab bar's counterweight: same glass, opposite corner. Code mode
	   is not one of the five places the app is, so it gets no tab; but on
	   a phone the rail that used to carry it does not render, which left
	   it two navigations deep behind /perfil. A circle in the corner
	   furthest from the thumb is the honest place for it — reachable in
	   one press, never pressed by accident.
	   Top-*right* and not left: the left edge is the back swipe's
	   (pwa-nav.js), and a control sitting in the gesture's runway is a
	   control that eats it. It shows on nova conversa and on /code and
	   nowhere else, which is what keeps the corner the page's everywhere
	   else — /documentos and /conversas both put a button right there. */
	/* data-show, not a render-time branch: the shell is never re-rendered by
	   a navigation, so which screen we are on is the client's answer (see
	   sidebar.js). Absent = the other twenty screens, where this corner
	   belongs to the page. */
	.shell-code-fab[data-show] {
		position: fixed;
		top: calc(env(safe-area-inset-top) + 0.6rem);
		right: 0.75rem;
		z-index: 50;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 2.25rem;
		height: 2.25rem;
		border-radius: 999px;
		color: var(--muted-foreground);
		background: color-mix(in oklch, var(--background) 70%, transparent);
		-webkit-backdrop-filter: blur(20px) saturate(180%);
		backdrop-filter: blur(20px) saturate(180%);
		border: 1px solid color-mix(in oklch, var(--border) 80%, transparent);
		box-shadow: var(--shadow-popover);
	}
	/* The same hairline rim the capsule has: what keeps a translucent
	   surface from reading as a smudge. */
	.shell-code-fab::before {
		content: "";
		position: absolute;
		inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: var(--highlight-top);
	}
	.shell-code-fab svg { width: 1.15rem; height: 1.15rem; }
	/* Amber on /code, and solid rather than tinted: a code turn runs Claude
	   Code on this box with permissions skipped, and the button that opened
	   that door is the only thing left saying so — the banner that used to
	   say it in words is gone. --warning is the same value in both themes,
	   so there is no dark variant to keep in step. */
	.shell-code-fab-active {
		color: var(--warning-foreground);
		background: var(--warning);
		border-color: color-mix(in oklch, var(--warning) 70%, transparent);
	}

	/* Room for it — on the content region, not on the scroller. The
	   scroller keeps running to the screen edge, so a long page slides
	   *behind* the capsule (which is the whole point of glass) while this
	   padding keeps its last row from ending underneath it. On the chat,
	   whose transcript is its own scroller filling this region, the same
	   padding lifts the sticky composer clear of the bar instead. */
	.shell-content { padding-bottom: var(--bbar-space); }

	/* The keyboard owns the bottom edge while something is being typed:
	   nav can wait, and the space over the keyboard cannot be spent on
	   it. The reserved room goes with the bar — otherwise the composer
	   floats an inch above the keyboard over nothing. Instant, no
	   animation — focus changes are high-frequency
	   (none-high-frequency). */
	.shell:has(#main-content :is(input, textarea, [contenteditable]:not([contenteditable="false"])):focus) .bbar {
		display: none;
	}
	.shell:has(#main-content :is(input, textarea, [contenteditable]:not([contenteditable="false"])):focus) .shell-content {
		padding-bottom: 0;
	}
}
@media (prefers-reduced-motion: reduce) {
	.sidebar-panel,
	.sidebar,
	.sb-section-body,
	.sb-section-caret svg { transition: none; }
	.shell[data-sidebar-collapsed] .shell-expand-btn { animation: none; }
}

/* --- Account menu on the account screen (phones only) ---
   The rail's popover body, laid flat on /perfil. On desktop the popover is
   the one that renders and this stays hidden; on a phone there is no rail,
   so this is the only door to Modo escuro, Primitivos and Sair. */
@media (max-width: 48rem) {
	.account-menu {
		display: flex;
		flex-direction: column;
		gap: 0.15rem;
		padding-top: 1.5rem;
		border-top: 1px solid var(--border);
	}
	.account-menu .sidebar-menu-form { width: 100%; }
	.account-menu .sidebar-menu-item {
		min-height: 3.25rem;
		padding: 0.9rem 0.85rem;
		border-radius: var(--radius-lg);
		color: var(--foreground);
		font-size: var(--text-md);
	}
	.account-menu .sidebar-menu-item:active { background: var(--muted); }
	.account-menu .sidebar-menu-item svg { width: 1.15rem; height: 1.15rem; }
}

/* skeleton.css */
@keyframes skeleton-shimmer {
	100% {
		transform: translateX(100%);
	}
}

.skeleton {
	position: relative;
	overflow: hidden;
	border-radius: var(--radius-md);
	background-color: var(--muted);
}

.skeleton::after {
	content: '';
	position: absolute;
	inset: 0;
	transform: translateX(-100%);
	background: linear-gradient(
		90deg,
		transparent 0%,
		color-mix(in oklch, var(--background) 40%, transparent) 40%,
		color-mix(in oklch, var(--background) 60%, transparent) 60%,
		transparent 100%
	);
	animation: skeleton-shimmer 1.5s ease-in-out infinite;
}

.skeleton-circle {
	border-radius: 9999px;
}

@media (prefers-reduced-motion: reduce) {
	.skeleton::after {
		animation: none;
		display: none;
	}
	.skeleton {
		opacity: 0.7;
	}
}

.dark .skeleton::after {
	background: linear-gradient(
		90deg,
		transparent 0%,
		color-mix(in oklch, var(--foreground) 5%, transparent) 40%,
		color-mix(in oklch, var(--foreground) 8%, transparent) 60%,
		transparent 100%
	);
}

/* slider.css */
/* Base */
.slider {
	position: relative;
	display: flex;
	width: 100%;
	touch-action: none;
	user-select: none;
	align-items: center;
	height: 1.25rem;
}

.slider-disabled {
	opacity: 0.5;
	pointer-events: none;
}

/* Native input: transparent overlay for interaction
   Fitts's Law — extend vertically beyond visual bounds for easier targeting */
.slider-input {
	position: absolute;
	inset: -0.5rem 0;
	width: 100%;
	opacity: 0;
	cursor: pointer;
	margin: 0;
	z-index: 2;
}

.slider-input:disabled {
	cursor: not-allowed;
}

/* Track */
.slider-track {
	position: relative;
	width: 100%;
	height: 0.25rem;
	overflow: hidden;
	border-radius: 9999px;
	background-color: var(--muted);
	/* The track is a recessed well the thumb travels in */
	box-shadow: var(--shadow-well);
}

/* Range (filled portion) */
.slider-range {
	position: absolute;
	height: 100%;
	border-radius: 9999px;
	background-color: var(--primary);
	/* No width transition: the thumb is positioned with `left`, which is
	   not transitioned, so easing the fill desynced the two halves of a
	   single value. Keyboard steps should land instantly anyway. */
}

/* Thumb — spring return for satisfying settle after drag */
.slider-thumb {
	position: absolute;
	width: 0.75rem;
	height: 0.75rem;
	border-radius: 9999px;
	border: 1px solid var(--ring);
	background-color: var(--background);
	/* The thumb floats above the well it slides in */
	box-shadow: var(--shadow-thumb);
	transform: translateX(-50%);
	pointer-events: none;
	z-index: 1;
	transition:
		box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1),
		transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Hover — anticipation: soft ring hints at interactivity */
.slider:not(.slider-disabled):hover .slider-thumb {
	box-shadow: var(--shadow-thumb), 0 0 0 4px color-mix(in oklch, var(--ring) 20%, transparent);
}

/* Focus-visible — accessible ring, stronger than hover */
.slider-input:focus-visible ~ .slider-thumb {
	box-shadow: var(--shadow-thumb), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* Active — peak engagement: scale + wider ring (staging) */
.slider-input:active:not(:disabled) ~ .slider-thumb {
	box-shadow: var(--shadow-thumb), 0 0 0 5px color-mix(in oklch, var(--ring) 30%, transparent);
	transform: translateX(-50%) scale(1.05);
	transition-duration: 100ms;
}

/* Dragging — disable transitions for zero-latency direct manipulation */
.slider-dragging .slider-range,
.slider-dragging .slider-thumb {
	transition: none;
}

/* Dark mode */
.dark .slider-track {
	background-color: color-mix(in oklch, var(--muted) 80%, transparent);
}

.dark .slider-thumb {
	background-color: white;
	border-color: var(--ring);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.slider-range {
		transition: none;
	}
	.slider-thumb {
		transition: none;
	}
	.slider-input:active:not(:disabled) ~ .slider-thumb {
		transform: translateX(-50%);
	}
}

/* sound-settings.css */
.sound-settings {
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
	width: 100%;
	max-width: 20rem;
}

.sound-settings-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem;
}

/* The slider needs room; the label and readout bracket it */
.sound-settings-volume .slider {
	flex: 1;
}

/* Muted state reads as inactive rather than disappearing */
.sound-settings[data-muted] .sound-settings-volume {
	opacity: 0.5;
}

.sound-settings-value {
	min-width: 2.5rem;
	text-align: right;
	font-size: var(--text-xs);
	line-height: 1rem;
	/* Tabular figures — the readout must not jitter as it counts */
	font-variant-numeric: tabular-nums;
	color: var(--muted-foreground);
}

/* spinner.css */
@keyframes spin {
	from { transform: rotate(0deg); }
	to { transform: rotate(360deg); }
}
.spinner {
	width: 1rem;
	height: 1rem;
	animation: spin 1s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
	.spinner {
		animation-duration: 2s;
	}
}

/* switch.css */
/* Wrapper */
.switch-wrapper {
	position: relative;
	display: inline-flex;
	flex-shrink: 0;
}

/* Size: default */
.switch-size-default {
	width: 32px;
	height: 18.4px;
}

/* Size: sm */
.switch-size-sm {
	width: 24px;
	height: 14px;
}
/* Fitts — the shared overlay inset leaves the sm track at a 30px
   effective height; widen it just for this size to clear 32px */
.switch-size-sm .switch-input {
	inset: -0.5625rem -0.75rem;
}

/* Native input: transparent overlay for interaction */
.switch-input {
	position: absolute;
	inset: -0.5rem -0.75rem;
	opacity: 0;
	cursor: pointer;
	margin: 0;
	z-index: 1;
}

.switch-input:disabled {
	cursor: not-allowed;
}

/* Track — secondary action: bg fills quickly while thumb slides */
.switch-track {
	display: inline-flex;
	width: 100%;
	height: 100%;
	align-items: center;
	border-radius: 9999px;
	border: 1px solid transparent;
	background-color: var(--input);
	/* The track is a recessed well the thumb travels in */
	box-shadow: var(--shadow-well);
	transition:
		background-color 120ms cubic-bezier(0.4, 0, 0.2, 1),
		border-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
		box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1),
		transform 100ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Hover: Anticipation — hint before action */
.switch-input:not(:disabled):not(:checked):hover + .switch-track {
	background-color: color-mix(in oklch, var(--input) 80%, var(--foreground));
}

.switch-input:not(:disabled):checked:hover + .switch-track {
	background-color: color-mix(in oklch, var(--primary) 80%, transparent);
}

/* Pressed feedback */
.switch-input:active:not(:disabled) + .switch-track {
	transform: scale(0.97);
	transition-duration: 100ms;
}

/* Track: checked */
.switch-input:checked + .switch-track {
	background-color: var(--primary);
}

/* Track: focus-visible */
.switch-input:focus-visible + .switch-track {
	border-color: var(--ring);
	box-shadow: var(--shadow-well), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* Track: disabled */
.switch-input:disabled + .switch-track {
	cursor: not-allowed;
	opacity: 0.5;
}

/* Track: invalid */
.switch-input[aria-invalid="true"] + .switch-track {
	border-color: var(--destructive);
	box-shadow: var(--shadow-well), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

/* Thumb — primary action: spring slide draws attention (Staging) */
.switch-thumb {
	pointer-events: none;
	display: block;
	border-radius: 9999px;
	background-color: var(--background);
	/* The thumb floats above the well it slides in */
	box-shadow: var(--shadow-thumb);
	transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Thumb: default size */
.switch-size-default .switch-thumb {
	width: 1rem;
	height: 1rem;
}

/* Thumb: sm size */
.switch-size-sm .switch-thumb {
	width: 0.75rem;
	height: 0.75rem;
}

/* Thumb: unchecked */
.switch-input:not(:checked) + .switch-track .switch-thumb {
	transform: translateX(0);
}

/* Thumb: checked */
.switch-input:checked + .switch-track .switch-thumb {
	transform: translateX(calc(100% - 2px));
}

/* Dark mode */
.dark .switch-track {
	background-color: color-mix(in oklch, var(--input) 80%, transparent);
}

.dark .switch-input:not(:disabled):not(:checked):hover + .switch-track {
	background-color: var(--input);
}

.dark .switch-input:checked + .switch-track {
	background-color: var(--primary);
}

.dark .switch-input:checked + .switch-track .switch-thumb {
	background-color: var(--primary-foreground);
}

.dark .switch-input:not(:checked) + .switch-track .switch-thumb {
	background-color: var(--foreground);
}

.dark .switch-input[aria-invalid="true"] + .switch-track {
	border-color: color-mix(in oklch, var(--destructive) 50%, transparent);
	box-shadow: var(--shadow-well), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.switch-track {
		transition-duration: 75ms;
	}
	.switch-thumb {
		transition: none;
	}
	.switch-input:active:not(:disabled) + .switch-track {
		transform: none;
	}
}

/* table.css */
/* Container — the static frame the edge fades hang off. It must not be the
   scroller: an absolutely positioned fade inside a scroll container is
   part of the scrolled content and drifts away from the edge, so the
   scrolling lives on .tbl-scroll instead. */
.tbl-container {
	position: relative;
	width: 100%;
}
.tbl-scroll {
	width: 100%;
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
	/* Set a max-height on .tbl-container (via Class) to make this the
	   vertical scroller too — that is what lets the header pin. */
	max-height: inherit;
}

/* Edge fades hint at the content clipped either side (Selective Attention) */
.tbl-container::before,
.tbl-container::after {
	content: "";
	position: absolute;
	top: 0;
	bottom: 0;
	z-index: 2;
	width: 2rem;
	pointer-events: none;
	opacity: 0;
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.tbl-container::before {
	left: 0;
	background: linear-gradient(to right, var(--background), transparent);
}
.tbl-container::after {
	right: 0;
	background: linear-gradient(to left, var(--background), transparent);
}
.tbl-container.has-overflow-left::before,
.tbl-container.has-overflow-right::after {
	opacity: 1;
}

/* Table */
.tbl {
	width: 100%;
	caption-side: bottom;
	font-size: var(--text-base);
	line-height: 1.25rem;
	border-collapse: collapse;
	/* Tabular figures — digits in a column must align and must not
	   reflow the cell when a value changes (Manrope ships tnum) */
	font-variant-numeric: tabular-nums;
}

/* Header — pins to the top once .tbl-scroll has a constrained height, and
   carries more visual weight either way (Working Memory, Make Structure
   Obvious) */
.tbl-header {
	position: sticky;
	top: 0;
	z-index: 1;
	background-color: var(--background);
	transition: box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.tbl-header tr {
	border-bottom: 2px solid var(--border);
}
/* Soft shade only while rows are actually passing beneath the header —
   an unscrolled header sits flat in the page and casts nothing */
.tbl-container.has-overflow-top .tbl-header {
	box-shadow: var(--shadow-field);
}

/* Body — zebra striping for scanability (Chunking, Law of Similarity) */
.tbl-body tr:nth-child(even) {
	background-color: color-mix(in oklch, var(--muted) 25%, transparent);
}
.tbl-body tr:last-child {
	border-bottom: 0;
}

/* Footer */
.tbl-footer {
	border-top: 2px solid var(--border);
	background-color: color-mix(in oklch, var(--muted) 50%, transparent);
	font-weight: 500;
}
.tbl-footer > tr:last-child {
	border-bottom: 0;
}

/* Row — hover/focus/selected (Staging, Selective Attention) */
.tbl-row {
	border-bottom: 1px solid var(--border);
	transition-property: background-color, color, box-shadow;
	transition-duration: 120ms;
	transition-timing-function: cubic-bezier(0.2, 0, 0.2, 1);
}
.tbl-body .tbl-row:hover {
	background-color: color-mix(in oklch, var(--muted) 50%, transparent);
}
.tbl-row:focus-visible {
	outline: none;
	box-shadow: inset 3px 0 0 var(--ring);
	background-color: color-mix(in oklch, var(--muted) 40%, transparent);
}
.tbl-row[data-state="selected"] {
	background-color: var(--muted);
	box-shadow: inset 3px 0 0 var(--primary);
}

/* Head cell — muted color to distinguish from data (Visual Hierarchy) */
.tbl-head {
	height: 2.75rem;
	padding-left: 0.5rem;
	padding-right: 0.5rem;
	text-align: left;
	vertical-align: middle;
	font-weight: 600;
	font-size: var(--text-xs);
	line-height: 1rem;
	letter-spacing: 0.025em;
	white-space: nowrap;
	color: var(--muted-foreground);
}
.tbl-head:has([role="checkbox"]) {
	padding-right: 0;
}

/* Data cell */
.tbl-cell {
	padding: 0.625rem;
	vertical-align: middle;
	white-space: nowrap;
}
.tbl-cell:has([role="checkbox"]) {
	padding-right: 0;
}

/* Caption */
.tbl-caption {
	margin-top: 1rem;
	font-size: var(--text-base);
	line-height: 1.25rem;
	color: var(--muted-foreground);
}

/* Dark mode zebra adjustment */
.dark .tbl-body tr:nth-child(even) {
	background-color: color-mix(in oklch, var(--muted) 15%, transparent);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.tbl-row,
	.tbl-header,
	.tbl-container::before,
	.tbl-container::after {
		transition-duration: 75ms;
	}
}

/* tabs.css */
/* Root */
.tabs {
	display: flex;
	gap: 0.5rem;
}
.tabs[data-orientation="horizontal"] {
	flex-direction: column;
}
/* vertical = row (default flex-direction) */

/* List — base (Law of Common Region: shared container groups triggers) */
.tabs-list {
	position: relative;
	display: inline-flex;
	width: fit-content;
	align-items: center;
	justify-content: center;
	border-radius: var(--radius-lg);
	padding: 3px;
	color: var(--muted-foreground);
}
.tabs[data-orientation="horizontal"] > .tabs-list {
	height: 2.5rem;
}
.tabs[data-orientation="vertical"] > .tabs-list {
	height: fit-content;
	flex-direction: column;
}

/* List — default variant */
.tabs-list-default {
	background-color: var(--muted);
}

/* List — line variant */
.tabs-list-line {
	gap: 0.25rem;
	background-color: transparent;
	border-radius: 0;
}

/*
 * Sliding indicator — Staging (primary animation draws the eye to
 * the state change) + Solid Drawing (spatial continuity across the bar).
 * Hidden until JS positions it; triggers carry their own fallback styles.
 */
.tabs-indicator {
	position: absolute;
	top: 0;
	left: 0;
	pointer-events: none;
	opacity: 0;
	z-index: 0;
}

/* JS-enhanced: indicator visible, transition enabled */
.tabs-list[data-animated] .tabs-indicator {
	opacity: 1;
	/* Follow Through — spring curve creates slight overshoot on arrival */
	transition:
		transform 250ms cubic-bezier(0.34, 1.56, 0.64, 1),
		width 250ms cubic-bezier(0.34, 1.56, 0.64, 1),
		height 250ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Default variant indicator — background pill slides behind triggers */
.tabs-list-default .tabs-indicator {
	border-radius: var(--radius-md);
	background-color: var(--background);
	box-shadow: var(--shadow-raised);
}

/* Line variant indicator — thin bar (Staging: singular focal point) */
/* Horizontal: underline at bottom of list */
.tabs[data-orientation="horizontal"] .tabs-list-line .tabs-indicator {
	top: auto;
	bottom: 0;
	left: 0;
	height: 2px;
	border-radius: 1px;
	background-color: var(--foreground);
}
/* Vertical: sideline at right of list */
.tabs[data-orientation="vertical"] .tabs-list-line .tabs-indicator {
	top: 0;
	left: auto;
	right: 0;
	width: 2px;
	border-radius: 1px;
	background-color: var(--foreground);
}

/* Trigger — base (Fitts's Law: adequate padding for comfortable targets) */
.tabs-trigger {
	position: relative;
	z-index: 1;
	display: inline-flex;
	height: calc(100% - 1px);
	flex: 1;
	align-items: center;
	justify-content: center;
	gap: 0.375rem;
	border-radius: var(--radius-md);
	border: 1px solid transparent;
	padding: 0.25rem 0.75rem;
	font-size: var(--text-base);
	line-height: 1.25rem;
	font-weight: 500;
	white-space: nowrap;
	color: color-mix(in oklch, var(--foreground) 60%, transparent);
	background: none;
	cursor: pointer;
	outline: none;
	/* A trigger is an <a> when it carries an Href — it must not read as
	   body text with a link in it. */
	text-decoration: none;
	/* Slow In & Slow Out — eased color/bg transitions.
	   box-shadow and border-color are deliberately absent: together they
	   draw the focus ring, and a focus ring must appear instantly. Fading
	   only one of the two made two thirds of the ring snap and the rest
	   drift in over 150ms. */
	transition-property: color, background-color, transform;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.tabs[data-orientation="vertical"] .tabs-trigger {
	width: 100%;
	justify-content: flex-start;
}

/* Anticipation — hover hint before committing to the action */
.tabs-trigger:hover:not(:disabled):not([aria-disabled="true"]) {
	color: var(--foreground);
}
.tabs-list-line .tabs-trigger:hover:not(:disabled):not([aria-disabled="true"]):not([data-active]) {
	background-color: color-mix(in oklch, var(--muted) 50%, transparent);
}

/* Pressed feedback — Squash and Stretch: subtle scale communicates weight */
.tabs-trigger:active:not(:disabled):not([aria-disabled="true"]) {
	transform: scale(0.97);
	transition-duration: 100ms;
}

.tabs-trigger:focus-visible {
	border-color: var(--ring);
	box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
	outline: 1px solid var(--ring);
}
.tabs-trigger:disabled,
.tabs-trigger[aria-disabled="true"] {
	pointer-events: none;
	opacity: 0.5;
}
.tabs-trigger svg {
	pointer-events: none;
	flex-shrink: 0;
}
.tabs-trigger svg:not([class*='size-']) {
	width: 1rem;
	height: 1rem;
}

/*
 * Active trigger — no-JS fallback (Cognitive Load: works without scripts).
 * When JS enhances with [data-animated], the indicator handles the visual
 * so trigger bg/shadow are removed to avoid doubling.
 */

/* Default variant: active trigger fallback. Same token as the indicator
   it stands in for, so the no-JS pill and the animated one are the same
   surface at the same height. */
.tabs-list-default .tabs-trigger[data-active] {
	background-color: var(--background);
	color: var(--foreground);
	box-shadow: var(--shadow-raised);
}
.tabs-list-default[data-animated] .tabs-trigger[data-active] {
	background-color: transparent;
	box-shadow: none;
}

/* Line variant: active trigger */
.tabs-list-line .tabs-trigger {
	background-color: transparent;
}
.tabs-list-line .tabs-trigger[data-active] {
	background-color: transparent;
	color: var(--foreground);
	box-shadow: none;
}

/* Line underline/sideline via ::after — no-JS fallback */
.tabs-trigger::after {
	content: "";
	position: absolute;
	background-color: var(--foreground);
	opacity: 0;
	transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.tabs[data-orientation="horizontal"] .tabs-trigger::after {
	left: 0;
	right: 0;
	bottom: -5px;
	height: 2px;
}
.tabs[data-orientation="vertical"] .tabs-trigger::after {
	top: 0;
	bottom: 0;
	right: -4px;
	width: 2px;
}
.tabs-list-line .tabs-trigger[data-active]::after {
	opacity: 1;
}

/* JS-enhanced: indicator replaces ::after */
[data-animated] .tabs-trigger::after {
	display: none;
}

/*
 * Content — Secondary Action: subtle fade-in supports the main indicator
 * slide without stealing focus.
 */
.tabs-content {
	flex: 1;
	font-size: var(--text-base);
	line-height: 1.25rem;
	outline: none;
}
.tabs-content:not([data-active]) {
	display: none;
}
.tabs-content[data-active] {
	animation: tabs-content-in 150ms cubic-bezier(0, 0, 0.2, 1);
}

/* Timing — secondary action is shorter and subtler than the primary slide */
@keyframes tabs-content-in {
	from {
		opacity: 0;
		transform: translateY(2px);
	}
	to {
		opacity: 1;
		transform: translateY(0);
	}
}

/* Dark mode */
.dark .tabs-trigger {
	color: var(--muted-foreground);
}
.dark .tabs-trigger:hover:not(:disabled):not([aria-disabled="true"]) {
	color: var(--foreground);
}
/* Dark mode draws the pill with a border instead of a shadow — the
   indicator does the same two rules down, and the fallback has to agree
   with it or the token ring doubles the border. */
.dark .tabs-list-default .tabs-trigger[data-active] {
	border-color: var(--border);
	background-color: var(--background);
	color: var(--foreground);
	box-shadow: none;
}
.dark .tabs-list-default[data-animated] .tabs-trigger[data-active] {
	border-color: transparent;
	background-color: transparent;
}
.dark .tabs-list-default .tabs-indicator {
	border: 1px solid var(--border);
	background-color: var(--background);
	box-shadow: none;
}
.dark .tabs-list-line .tabs-trigger[data-active] {
	border-color: transparent;
	background-color: transparent;
	color: var(--foreground);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.tabs-trigger {
		transition-duration: 0ms;
	}
	.tabs-trigger:active:not(:disabled) {
		transform: none;
	}
	.tabs-trigger::after {
		transition-duration: 0ms;
	}
	.tabs-list[data-animated] .tabs-indicator {
		transition: none;
	}
	.tabs-content[data-active] {
		animation: none;
	}
}

/* textarea.css */
/* Base */
.textarea {
	display: flex;
	field-sizing: content;
	min-height: 4rem;
	width: 100%;
	border-radius: var(--radius-lg);
	border: 1px solid var(--input);
	background-color: transparent;
	padding: 0.5rem 0.625rem;
	font-size: var(--text-md);
	line-height: 1.5rem;
	/* Field elevation — a hairline lift off the page (--shadow-field) */
	box-shadow: var(--shadow-field);
	transition-property: color, background-color, border-color, box-shadow, opacity;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
	outline: none;
	resize: vertical;
}

.textarea::placeholder {
	color: var(--muted-foreground);
}

.textarea:focus-visible {
	border-color: var(--ring);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

.textarea:disabled {
	pointer-events: none;
	cursor: not-allowed;
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
	opacity: 0.5;
}

.textarea[aria-invalid="true"] {
	border-color: var(--destructive);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

@media (min-width: 768px) {
	.textarea {
		font-size: var(--text-base);
		line-height: 1.25rem;
	}
}

/* Dark mode */
.dark .textarea {
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}
.dark .textarea:disabled {
	background-color: color-mix(in oklch, var(--input) 80%, transparent);
}
.dark .textarea[aria-invalid="true"] {
	border-color: color-mix(in oklch, var(--destructive) 50%, transparent);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.textarea {
		transition-duration: 75ms;
	}
}

/* time-picker.css */
/* ── Root ── */
.tp {
	display: inline-flex;
	align-items: center;
	gap: 0.125rem;
	user-select: none;
}

/* ── Field wrapper ── */
.tp-field {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.125rem;
	width: 2rem;
}

/* ── Label ── */
.tp-label {
	font-size: var(--text-2xs);
	font-weight: 500;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--muted-foreground);
	line-height: 1;
	white-space: nowrap;
}

/* ── Input ── */
.tp-input {
	width: 2rem;
	height: 2rem;
	border-radius: var(--radius-md);
	border: 1px solid var(--input);
	background-color: transparent;
	/* Field elevation — a hairline lift off the page (--shadow-field) */
	box-shadow: var(--shadow-field);
	text-align: center;
	font-size: var(--text-base);
	font-weight: 500;
	font-family: inherit;
	font-variant-numeric: tabular-nums;
	color: var(--foreground);
	outline: none;
	padding: 0;
	transition-property: border-color, box-shadow, background-color;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.tp-input::placeholder {
	color: var(--muted-foreground);
	font-weight: 400;
}

.tp-input:focus-visible {
	border-color: var(--ring);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

.tp-input:disabled {
	pointer-events: none;
	cursor: not-allowed;
	opacity: 0.5;
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
}

.tp-input[aria-invalid="true"] {
	border-color: var(--destructive);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);
}

/* ── Separator ── */
.tp-separator {
	font-size: var(--text-md);
	font-weight: 500;
	color: var(--foreground);
	line-height: 1;
	padding-top: 0.75rem;
}

/* ── AM/PM toggle ── */
.tp-period {
	width: 2rem;
	height: 2rem;
	margin-top: 0.75rem;
	border-radius: var(--radius-md);
	border: 1px solid var(--input);
	background-color: transparent;
	/* Field elevation — a hairline lift off the page (--shadow-field) */
	box-shadow: var(--shadow-field);
	font-size: var(--text-xs);
	font-weight: 600;
	font-family: inherit;
	text-transform: uppercase;
	/* Uppercase needs the extra tracking back, like the sibling label */
	letter-spacing: 0.05em;
	color: var(--foreground);
	cursor: pointer;
	outline: none;
	padding: 0;
	transition-property: border-color, box-shadow, background-color, color;
	transition-duration: 150ms;
	transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.tp-period:hover {
	background-color: var(--muted);
}

.tp-period:focus-visible {
	border-color: var(--ring);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

.tp-period:disabled {
	pointer-events: none;
	opacity: 0.5;
}

/* ── Dark mode ── */
.dark .tp-input {
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}
.dark .tp-input:disabled {
	background-color: color-mix(in oklch, var(--input) 80%, transparent);
}
.dark .tp-input[aria-invalid="true"] {
	border-color: color-mix(in oklch, var(--destructive) 50%, transparent);
	box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent);
}
.dark .tp-period {
	background-color: color-mix(in oklch, var(--input) 30%, transparent);
}
.dark .tp-period:hover {
	background-color: color-mix(in oklch, var(--input) 50%, transparent);
}

/* ── Reduced motion ── */
@media (prefers-reduced-motion: reduce) {
	.tp-input,
	.tp-period {
		transition-duration: 75ms;
	}
}

/* toast.css */
/* Toaster container */
.toaster {
	position: fixed;
	bottom: 1rem;
	right: 1rem;
	z-index: 100;
	display: flex;
	flex-direction: column-reverse;
	gap: 0.5rem;
	max-width: 24rem;
	width: 100%;
	pointer-events: none;
}

@media (max-width: 480px) {
	.toaster {
		left: 1rem;
		right: 1rem;
		max-width: none;
	}
}

/* Toast item */
.toast {
	pointer-events: auto;
	position: relative;
	display: flex;
	align-items: center;
	gap: 0.5rem;
	width: 100%;
	border-radius: var(--radius-lg);
	border: 1px solid var(--border);
	background-color: var(--popover);
	color: var(--popover-foreground);
	padding: 0.75rem 2.25rem 0.75rem 1rem;
	font-size: var(--text-base);
	line-height: 1.25rem;
	/* Popover-tier elevation; the token flips for dark mode */
	box-shadow: var(--shadow-popover);
	animation: toast-enter 250ms cubic-bezier(0.22, 1.1, 0.36, 1);
	touch-action: pan-x;
	user-select: none;
	will-change: transform, opacity;
	transition: transform 150ms cubic-bezier(0.33, 1, 0.68, 1);
}

.toast[data-swiping] {
	transition: none;
}

.toast[data-dismissing] {
	animation: toast-exit 200ms cubic-bezier(0.33, 0, 0.67, 0) forwards;
}

.toast[data-swipe-dismiss] {
	animation: toast-swipe-exit 150ms cubic-bezier(0.33, 0, 0.67, 0) forwards;
}

/* When title is present, align icon to top */
.toast:has(.toast-title) {
	align-items: flex-start;
}

/* Toast icon */
.toast > svg:first-child {
	flex-shrink: 0;
	width: 1rem;
	height: 1rem;
}

.toast:has(.toast-title) > svg:first-child {
	translate: 0 0.125rem;
}

/* Toast content */
.toast-content {
	flex: 1;
	min-width: 0;
}

.toast-title {
	font-weight: 500;
}

.toast-title + .toast-description {
	margin-top: 0.125rem;
}

.toast-description {
	color: var(--muted-foreground);
}

/*
 * Toast action button — a subdued inline affordance ("Undo"), not a
 * primary button: the toast is already an interruption, so the action
 * must not compete with the message for attention. Outline face on a
 * transparent ground, and 2rem tall so it clears the 32px minimum
 * (ux-fitts-target-size) even though it reads as quiet.
 */
.toast-action {
	flex-shrink: 0;
	/* .toast:has(.toast-title) top-aligns the row; the action still
	   belongs on the text baseline block, not pinned to the title. */
	align-self: center;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 2rem;
	padding: 0 0.625rem;
	/* The close button is absolutely positioned and overlaps the last
	   0.25rem of the content box; clear it so it cannot cover the
	   action or steal taps from its right edge. */
	margin-right: 0.375rem;
	border-radius: var(--radius-md);
	border: 1px solid var(--border);
	background-color: transparent;
	color: var(--foreground);
	font-family: inherit;
	font-size: var(--text-sm);
	font-weight: 500;
	line-height: 1;
	white-space: nowrap;
	cursor: pointer;
	transition: background-color 150ms, border-color 150ms;
}

.toast-action:hover {
	background-color: var(--muted);
}

.toast-action:focus-visible {
	outline: none;
	border-color: var(--ring);
	box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* Toast close button */
.toast-close {
	position: absolute;
	right: 0.25rem;
	width: 2.25rem;
	height: 2.25rem;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: var(--radius-lg);
	border: none;
	background: transparent;
	color: var(--muted-foreground);
	cursor: pointer;
	opacity: 0.4;
	transition: opacity 150ms, background-color 150ms;
}

.toast-close svg {
	width: 0.75rem;
	height: 0.75rem;
}

.toast:hover .toast-close {
	opacity: 1;
}

.toast-close:hover {
	background-color: var(--muted);
	opacity: 1;
}

/* Variant: success */
.toast-success > svg:first-child {
	color: var(--success);
}

/* Variant: info */
.toast-info > svg:first-child {
	color: var(--info);
}

/* Variant: warning */
.toast-warning {
	background-color: color-mix(in oklch, var(--warning) 5%, var(--popover));
}
.toast-warning > svg:first-child {
	color: var(--warning);
}

/* Variant: error */
.toast-error {
	background-color: color-mix(in oklch, var(--destructive) 5%, var(--popover));
	border-color: color-mix(in oklch, var(--destructive) 25%, var(--border));
}
.toast-error > svg:first-child {
	color: var(--destructive);
}

/*
 * Animations — asymmetric enter/exit.
 * Enter: spring overshoot + translateY for organic arc (250ms).
 * Exit: gentle fade + scale-down, faster than enter (200ms).
 * Swipe-exit: fast follow-through in swipe direction (150ms).
 */

@keyframes toast-enter {
	from {
		opacity: 0;
		transform: translateY(12px) scale(0.96);
	}
	to {
		opacity: 1;
		transform: translateY(0) scale(1);
	}
}

@keyframes toast-exit {
	0% {
		opacity: 1;
		transform: translateY(0) scale(1);
	}
	100% {
		opacity: 0;
		transform: translateY(8px) scale(0.96);
	}
}

/*
 * Swipe exit starts from wherever the finger left the toast
 * (--toast-swipe-x / --toast-swipe-o are stamped on touchend), so the
 * dismissal continues the drag instead of snapping back to zero first.
 */
@keyframes toast-swipe-exit {
	from {
		opacity: var(--toast-swipe-o, 1);
		transform: translateX(var(--toast-swipe-x, 0px));
	}
	to {
		opacity: 0;
		transform: translateX(110%);
	}
}

/* Dark mode */
.dark .toast-warning {
	background-color: color-mix(in oklch, var(--warning) 8%, var(--popover));
}

.dark .toast-error {
	background-color: color-mix(in oklch, var(--destructive) 10%, var(--popover));
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.toast {
		animation: none;
		transition: none;
	}
	.toast[data-dismissing],
	.toast[data-swipe-dismiss] {
		animation: none;
		opacity: 0;
	}
}

/* tooltip.css */
/* Base */
.tooltip {
	position: relative;
	display: inline-flex;
	--tooltip-delay: 400ms;
}

.tooltip-trigger {
	display: inline-flex;
	align-items: center;
}

.tooltip-content {
	position: absolute;
	z-index: 50;
	display: inline-flex;
	width: max-content;
	max-width: 20rem;
	align-items: center;
	gap: 0.375rem;
	border-radius: var(--radius-md);
	background-color: var(--foreground);
	padding: 0.375rem 0.75rem;
	font-size: var(--text-xs);
	line-height: 1rem;
	font-weight: 500;
	color: var(--background);
	/* Popover-tier elevation — matters most in dark mode, where the
	   light chip needs the shadow to sit above the page */
	box-shadow: var(--shadow-popover);
	pointer-events: none;
	opacity: 0;
	visibility: hidden;
	/* Exit: fast accelerate-out, no delay (Timing + Slow In & Slow Out) */
	transition:
		opacity 100ms cubic-bezier(0.4, 0, 1, 1),
		transform 100ms cubic-bezier(0.4, 0, 1, 1),
		visibility 0s linear 100ms;
}

/* Show on hover — delayed to filter accidental triggers (Jakob's Law) */
.tooltip:hover > .tooltip-content {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
	/* Enter: decelerate-in after delay (Staging + Selective Attention) */
	transition:
		opacity 150ms cubic-bezier(0, 0, 0.2, 1) var(--tooltip-delay),
		transform 150ms cubic-bezier(0, 0, 0.2, 1) var(--tooltip-delay),
		visibility 0s linear var(--tooltip-delay);
}

/* Show on keyboard focus — no delay for intentional action (Doherty Threshold) */
.tooltip:focus-within > .tooltip-content {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
	transition:
		opacity 150ms cubic-bezier(0, 0, 0.2, 1),
		transform 150ms cubic-bezier(0, 0, 0.2, 1),
		visibility 0s linear 0s;
}

/* Dismissed via Escape — instant hide, no transition (Cognitive Load) */
.tooltip[data-tooltip-dismissed] > .tooltip-content {
	opacity: 0 !important;
	visibility: hidden !important;
	transition: none !important;
}

/* Side: top (default) */
.tooltip-content[data-side="top"] {
	bottom: 100%;
	left: 50%;
	transform: translateX(-50%) translateY(0.25rem) scale(0.95);
	margin-bottom: 0.25rem;
}
.tooltip:hover > .tooltip-content[data-side="top"],
.tooltip:focus-within > .tooltip-content[data-side="top"] {
	transform: translateX(-50%) translateY(0) scale(1);
}

/* Side: bottom */
.tooltip-content[data-side="bottom"] {
	top: 100%;
	left: 50%;
	transform: translateX(-50%) translateY(-0.25rem) scale(0.95);
	margin-top: 0.25rem;
}
.tooltip:hover > .tooltip-content[data-side="bottom"],
.tooltip:focus-within > .tooltip-content[data-side="bottom"] {
	transform: translateX(-50%) translateY(0) scale(1);
}

/* Side: left */
.tooltip-content[data-side="left"] {
	right: 100%;
	top: 50%;
	transform: translateY(-50%) translateX(0.25rem) scale(0.95);
	margin-right: 0.25rem;
}
.tooltip:hover > .tooltip-content[data-side="left"],
.tooltip:focus-within > .tooltip-content[data-side="left"] {
	transform: translateY(-50%) translateX(0) scale(1);
}

/* Side: right */
.tooltip-content[data-side="right"] {
	left: 100%;
	top: 50%;
	transform: translateY(-50%) translateX(-0.25rem) scale(0.95);
	margin-left: 0.25rem;
}
.tooltip:hover > .tooltip-content[data-side="right"],
.tooltip:focus-within > .tooltip-content[data-side="right"] {
	transform: translateY(-50%) translateX(0) scale(1);
}

/* Arrow base */
/*
 * Arrow — purely decorative, so it is a pseudo-element rather than a
 * real node in every tooltip instance.
 */
.tooltip-content::after {
	content: '';
	position: absolute;
	width: 0.5rem;
	height: 0.5rem;
	background-color: var(--foreground);
	border-radius: 1px;
	transform: rotate(45deg);
}

/* Arrow: top */
.tooltip-content[data-side="top"]::after {
	bottom: -0.25rem;
	left: 50%;
	margin-left: -0.25rem;
}

/* Arrow: bottom */
.tooltip-content[data-side="bottom"]::after {
	top: -0.25rem;
	left: 50%;
	margin-left: -0.25rem;
}

/* Arrow: left */
.tooltip-content[data-side="left"]::after {
	right: -0.25rem;
	top: 50%;
	margin-top: -0.25rem;
}

/* Arrow: right */
.tooltip-content[data-side="right"]::after {
	left: -0.25rem;
	top: 50%;
	margin-top: -0.25rem;
}

/* Reduced motion — instant show/hide, keep delay to prevent noise */
@media (prefers-reduced-motion: reduce) {
	.tooltip-content {
		transition-duration: 0ms;
	}
	.tooltip:hover > .tooltip-content {
		transition:
			opacity 0ms linear var(--tooltip-delay),
			transform 0ms linear var(--tooltip-delay),
			visibility 0s linear var(--tooltip-delay);
	}
	.tooltip:focus-within > .tooltip-content {
		transition-duration: 0ms;
	}
}

/* typography.css */
/* --- Typography --- */

/*
 * Headings lead with unitless line-heights: the scale shrinks on a
 * phone (--text-* in base.templ) and a leading pinned in rem would
 * stay behind, leaving a 28px title in a 40px line box.
 */

/* H1 */
.typo-h1 {
	scroll-margin-top: 5rem;
	font-size: var(--text-4xl);
	line-height: 1.1;
	font-weight: 800;
	letter-spacing: -0.025em;
	text-wrap: balance;
	color: var(--foreground);
}

/* H2 */
.typo-h2 {
	scroll-margin-top: 5rem;
	border-bottom: 1px solid var(--border);
	padding-bottom: 0.5rem;
	font-size: var(--text-3xl);
	line-height: 1.2;
	font-weight: 600;
	letter-spacing: -0.025em;
	color: var(--foreground);
	transition: border-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* H3 */
.typo-h3 {
	scroll-margin-top: 5rem;
	font-size: var(--text-2xl);
	line-height: 1.3;
	font-weight: 600;
	letter-spacing: -0.025em;
	color: var(--foreground);
}

/* H4 */
.typo-h4 {
	scroll-margin-top: 5rem;
	font-size: var(--text-xl);
	line-height: 1.4;
	font-weight: 600;
	letter-spacing: -0.025em;
	color: var(--foreground);
}

/* Paragraph */
.typo-p {
	line-height: 1.75;
	color: var(--foreground);
	/* Protects the last line from a lone orphan word */
	text-wrap: pretty;
}
.typo-p + .typo-p {
	margin-top: 1.5rem;
}

/* Blockquote */
.typo-blockquote {
	margin-top: 1.5rem;
	border-left: 2px solid var(--border);
	padding-left: 1.5rem;
	font-style: italic;
	color: var(--foreground);
	transition: border-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Table */
.typo-table-wrapper {
	margin-top: 1.5rem;
	margin-bottom: 1.5rem;
	width: 100%;
	overflow-y: auto;
}
.typo-table-wrapper table {
	width: 100%;
}
.typo-table-wrapper thead tr {
	margin: 0;
	border-bottom: 1px solid var(--border);
	padding: 0;
}
.typo-table-wrapper th {
	border-bottom: 1px solid var(--border);
	padding: 0.5rem 1rem;
	text-align: left;
	font-weight: 700;
	color: var(--foreground);
}
.typo-table-wrapper td {
	padding: 0.5rem 1rem;
	text-align: left;
	color: var(--foreground);
	/* Tabular figures — numeric cells align down the column */
	font-variant-numeric: tabular-nums;
}
.typo-table-wrapper tbody tr {
	border-bottom: 1px solid var(--border);
	transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.typo-table-wrapper tbody tr:last-child {
	border-bottom: 0;
}
.typo-table-wrapper tbody tr:hover {
	background-color: var(--muted);
}

/* List */
.typo-list {
	margin-top: 1.5rem;
	margin-bottom: 1.5rem;
	margin-left: 1.5rem;
	list-style-type: disc;
}
.typo-list > li {
	margin-top: 0.5rem;
	color: var(--foreground);
}

/* Inline Code */
.typo-code {
	position: relative;
	border-radius: var(--radius-md);
	background-color: var(--muted);
	padding-left: 0.3rem;
	padding-right: 0.3rem;
	padding-top: 0.2rem;
	padding-bottom: 0.2rem;
	font-family: var(--font-mono);
	font-size: var(--text-base);
	font-weight: 600;
	color: var(--foreground);
}

/* Lead */
.typo-lead {
	font-size: var(--text-xl);
	line-height: 1.45;
	color: var(--muted-foreground);
}

/* Large */
.typo-large {
	font-size: var(--text-lg);
	line-height: 1.5;
	font-weight: 600;
	color: var(--foreground);
}

/* Small */
.typo-small {
	font-size: var(--text-base);
	line-height: 1;
	font-weight: 500;
	color: var(--foreground);
}

/* Muted */
.typo-muted {
	font-size: var(--text-base);
	line-height: 1.45;
	color: var(--muted-foreground);
}

/* Dark mode — rules and borders ride the tokens, which flip themselves;
   only the row hover needs softening against the darker surface. */
.dark .typo-table-wrapper tbody tr:hover {
	background-color: color-mix(in oklch, var(--muted) 50%, transparent);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
	.typo-h2,
	.typo-blockquote,
	.typo-table-wrapper tbody tr {
		transition-duration: 75ms;
	}
}

/* admin.css */
.admin { flex: 1; min-height: 0; overflow-y: auto; padding: 1.5rem 1rem 3rem; }
.admin > * { width: 100%; max-width: 40rem; margin-left: auto; margin-right: auto; }
.admin-head { margin-bottom: 1.5rem; }
.admin-head h1 { margin-bottom: 0.25rem; }
/* One card per readout, stacked. Matches div to div so the extracted
   <style> block sitting at the top of .admin is not spaced as a card. */
.admin > div + div { margin-top: 1rem; }

.admin-limit { display: flex; flex-direction: column; gap: 0.4rem; }
.admin-limit + .admin-limit { margin-top: 1.15rem; }
.admin-limit-top { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.admin-limit-label { font-size: var(--text-base); font-weight: 500; color: var(--foreground); }
.admin-limit-pct { font-size: var(--text-base); font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
.admin-limit-pct[data-level="warning"] { color: var(--warning); }
.admin-limit-pct[data-level="critical"] { color: var(--destructive); }
.admin-limit-reset { font-size: var(--text-sm); color: var(--muted-foreground); }
.admin-limit-bar[data-level="warning"] .progress-indicator { background: var(--warning); }
.admin-limit-bar[data-level="critical"] .progress-indicator { background: var(--destructive); }

.admin-note { margin-top: 1.15rem; color: var(--muted-foreground); }
.admin-note code { font-size: var(--text-xs); }

/* The balance leads the card: what is left is the number being looked
   for, and the bar underneath is only context for how fast it is going. */
.admin-balance { display: flex; align-items: baseline; gap: 0.5rem; margin-bottom: 0.65rem; }
.admin-balance-value { font-size: var(--text-3xl); font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; color: var(--foreground); }
.admin-balance-value[data-level="warning"] { color: var(--warning); }
.admin-balance-value[data-level="critical"] { color: var(--destructive); }
.admin-balance-caption { font-size: var(--text-base); color: var(--muted-foreground); }

/* buscar.css */
/* The search screen: the field held at the top, the results scrolling
   under it, the glass tab bar where it always is.

   Not `position: sticky` — the screen is a flex column that fills the
   content region and the list is its own scroller, the same shape the chat
   uses for transcript + composer. Sticky would depend on which ancestor
   counts as the scrollport; this depends on nothing. */
.buscar {
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
	width: 100%;
	max-width: 48rem;
	margin: 0 auto;
}

.buscar-bar {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin: 0.75rem 1rem;
	padding: 0 0.75rem;
	height: 2.75rem;
	border-radius: 999px;
	background: var(--muted);
	border: 1px solid transparent;
	transition: border-color 150ms ease, background-color 150ms ease;
}
.buscar-bar:focus-within {
	background: var(--background);
	border-color: var(--ring);
}
.buscar-bar-icon { display: inline-flex; flex-shrink: 0; color: var(--muted-foreground); }
.buscar-bar-icon svg { width: 1.05rem; height: 1.05rem; }

.buscar-input {
	flex: 1;
	min-width: 0;
	border: 0;
	outline: 0;
	background: transparent;
	color: var(--foreground);
	font-family: inherit;
	/* 1rem or iOS zooms the page on focus. */
	font-size: var(--text-md);
}
.buscar-input::placeholder { color: var(--muted-foreground); }
/* The type-ahead is the search; the native affordances are a second one. */
.buscar-input::-webkit-search-decoration,
.buscar-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

/* Shown only once there is something to clear, off the field's own state. */
.buscar-clear {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	width: 1.4rem;
	height: 1.4rem;
	padding: 0;
	border: 0;
	border-radius: 999px;
	background: var(--muted-foreground);
	color: var(--background);
	cursor: pointer;
}
.buscar-clear svg { width: 0.7rem; height: 0.7rem; }
.buscar-input:placeholder-shown ~ .buscar-clear { display: none; }

.buscar-results {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	padding: 0 0.75rem 1.5rem;
	display: flex;
	flex-direction: column;
	gap: 0.1rem;
}
.buscar-label {
	margin: 0.25rem 0 0.35rem;
	padding: 0 0.6rem;
	font-size: var(--text-sm);
	font-weight: 600;
	color: var(--muted-foreground);
}
.buscar-item {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 0.15rem;
	min-height: 3.25rem;
	padding: 0.7rem 0.6rem;
	border-radius: var(--radius-lg);
	text-decoration: none;
	color: var(--foreground);
	cursor: pointer;
	transition: background-color 120ms ease, transform 120ms ease;
}
.buscar-item:hover, .buscar-item:focus-visible { background: var(--muted); outline: none; }
/* Pressed: the same press response as the index card it is a sibling of
   (list-card.css), because a result row and a card do the same job — open
   the thing named on it. */
.buscar-item:active { background: var(--muted); transform: scale(0.985); }
@media (prefers-reduced-motion: reduce) {
	.buscar-item:active { transform: none; }
}
.buscar-item-head { display: flex; align-items: center; gap: 0.55rem; min-width: 0; }
.buscar-item-icon { display: inline-flex; flex-shrink: 0; color: var(--muted-foreground); }
.buscar-item-icon svg { width: 1.05rem; height: 1.05rem; }
.buscar-item-title { font-size: var(--text-md); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Indented to the title, past the icon gutter, so the snippet reads as
   belonging to the row above it rather than starting a new one. */
.buscar-item-snippet {
	padding-left: 1.6rem;
	font-size: var(--text-sm);
	color: var(--muted-foreground);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.buscar-empty { padding: 2rem 1rem; text-align: center; font-size: var(--text-base); color: var(--muted-foreground); }

@media (max-width: 48rem) {
	/* This screen brings its own scroller, so the room for the glass tab
	   bar has to be inside it. The shell reserves that room as padding on
	   #main-content, which for a page that scrolls as a whole is right —
	   the list slides behind the capsule and its last row still clears it.
	   Here it would end the scroller above the capsule instead, and
	   nothing would ever pass behind the glass. */
	.shell-content:has(> .buscar) { padding-bottom: 0; }
	.buscar-results { padding-bottom: calc(1rem + var(--bbar-space)); }
	/* The bar goes away while the field has focus (the keyboard owns that
	   edge), and the room reserved for it has to go with it or the list
	   ends in a hole above the keyboard. */
	.buscar:has(.buscar-input:focus) .buscar-results { padding-bottom: 1rem; }
}

/* chat.css */
.chat {
	/* One curve for every hover/press in the chat, matching the 150ms
	   used across components so chat controls feel like the rest. */
	--chat-transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
	display: flex;
	flex-direction: column;
	flex: 1;
	min-height: 0;
	/* The AppShell content panel owns the viewport height; the chat just
	   fills it (mirrors the /ai demo, not the standalone base chat). */
	overflow-y: auto;
	/* Guard, not a layout tool: the transcript renders untrusted model
	   markdown, and overflow-y:auto forces the x axis to auto too — so one
	   over-wide node turns the whole conversation into a sideways drag.
	   Anything genuinely wide (code, tables) carries its own scroller. */
	overflow-x: hidden;
	text-wrap: pretty;
}
.chat-notice {
	margin: auto;
	max-width: 32rem;
	padding: 2rem;
	text-align: center;
	color: var(--muted-foreground);
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.75rem;
}
.chat-notice svg { width: 2rem; height: 2rem; }
.chat-notice code { font-family: var(--font-mono); background: var(--muted); padding: 0.1em 0.35em; border-radius: 0.3em; }
.chat-transcript {
	flex: 1;
	padding: 1.5rem 1rem 1rem;
	display: flex;
	flex-direction: column;
	gap: 1.25rem;
}
.chat-transcript > * { width: 100%; max-width: 48rem; margin: 0 auto; }
.chat-empty { margin: auto; text-align: center; padding: 2rem 1rem; color: var(--muted-foreground); max-width: 30rem; }
.chat-empty-icon { display: inline-flex; color: var(--primary); margin-bottom: 0.75rem; }
.chat-empty-icon svg { width: 2.5rem; height: 2.5rem; }
.chat-empty-lead { text-wrap: balance; }
/* Destinations wear the outline button's own classes — no local copy of
   its shape, shadow, hover and focus to drift out of step. Only the row
   that holds them is local. */
.chat-starters { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.5rem; margin-top: 1.25rem; }

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

/* Messages */
.msg { display: flex; gap: 0.75rem; }
.msg-user { justify-content: flex-end; }
.msg-user .msg-col { display: flex; flex-direction: column; align-items: flex-end; gap: 0.25rem; max-width: 80%; }
.msg-user .msg-bubble {
	background: var(--muted);
	color: var(--foreground);
	padding: 0.75rem 1rem;
	border-radius: 1.1rem 1.1rem 0.25rem 1.1rem;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
	font-size: var(--text-md);
}
.msg-queue { display: inline-flex; align-items: center; gap: 0.25rem; font-size: var(--text-2xs); color: var(--muted-foreground); font-family: var(--font-mono); }
.msg-queue svg { width: 0.75rem; height: 0.75rem; }
.msg-queued .msg-bubble { opacity: 0.55; }
.msg-assistant { align-items: flex-start; }
.msg-body { min-width: 0; flex: 1; }
.msg-text { overflow-wrap: anywhere; font-family: var(--font-sans); font-size: var(--text-md); line-height: 1.65; padding-top: 0.25rem; }
.msg-text.msg-text-plain { white-space: pre-wrap; }

.msg-worked { display: flex; align-items: center; gap: 0.5rem; color: var(--muted-foreground); font-family: var(--font-mono); font-size: var(--text-2xs); margin-bottom: -0.5rem; }
.msg-worked svg { width: 0.75rem; height: 0.75rem; }
.msg-worked::after { content: ""; flex: 1; border-top: 1px solid var(--border); }

/* Markdown content */
.msg-text > :first-child { margin-top: 0; }
.msg-text > :last-child { margin-bottom: 0; }
.msg-text p, .msg-text ul, .msg-text ol, .msg-text pre, .msg-text blockquote, .msg-text table, .msg-text hr { margin: 0.6em 0; }
.msg-text ul, .msg-text ol { padding-left: 1.3em; }
.msg-text li + li { margin-top: 0.2em; }
.msg-text h1, .msg-text h2, .msg-text h3, .msg-text h4 { font-weight: 700; line-height: 1.3; margin: 0.8em 0 0.4em; text-wrap: balance; }
.msg-text h1 { font-size: 1.25em; }
.msg-text h2 { font-size: 1.15em; }
.msg-text h3, .msg-text h4 { font-size: 1.05em; }
.msg-text a { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
.msg-text code { font-family: var(--font-mono); font-size: 0.85em; background: var(--muted); padding: 0.1em 0.35em; border-radius: 0.3em; }
.msg-text pre { background: var(--muted); padding: 0.75em 0.9em; border-radius: var(--radius); overflow-x: auto; }
.msg-text pre code { background: none; padding: 0; }
.msg-text blockquote { border-left: 3px solid var(--border); padding-left: 0.8em; color: var(--muted-foreground); }
/* A table is the one prose block that must not be squeezed to fit: .msg-text
   wraps anywhere, so a crammed column breaks words letter by letter and the
   text reads vertically. Scroll it instead — the same trade components/Table
   makes (nowrap cells behind a scroller), applied to the <table> itself
   because goldmark gives us no wrapper element to hang a scroller on. */
.msg-text table {
	display: block;
	width: max-content;
	max-width: 100%;
	overflow-x: auto;
	border-collapse: collapse;
	font-size: 0.9em;
}
.msg-text th, .msg-text td { border: 1px solid var(--border); padding: 0.35em 0.6em; text-align: left; white-space: nowrap; }
.msg-text .run-md { display: block; }
.msg-text .run-md:empty { display: none; }
.msg-text .run-md > :first-child { margin-top: 0; }
.msg-text .run-md > :last-child { margin-bottom: 0; }

/* Ordered stream: text + tool chips */
.msg-stream { display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; }
.msg-stream > .msg-text { width: 100%; }
.tool-chip { display: inline-flex; align-items: center; gap: 0.5rem; align-self: flex-start; max-width: 100%; font-size: var(--text-sm); color: var(--muted-foreground); }
.tool-chip-icon { display: inline-flex; flex-shrink: 0; }
.tool-chip-icon svg { width: 0.875rem; height: 0.875rem; }
/* The label is a shell command or a file path — it has no natural ceiling.
   Without min-width:0 it refuses to shrink, blows past the chip's max-width
   and makes the whole transcript draggable sideways. Truncate instead. */
.tool-chip-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; color: var(--foreground); }
.tool-chip-count { flex-shrink: 0; margin-left: -0.25rem; padding: 0 0.3rem; border-radius: 999px; background: var(--muted); font-size: var(--text-2xs); font-weight: 600; font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
.tool-chip-active { animation: chat-chip-pulse 1.3s ease-in-out infinite; }
@keyframes chat-chip-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
@media (prefers-reduced-motion: reduce) { .tool-chip-active { animation: none; } }

/* Reasoning panel: a Collapsible stripped of its card. The component
   ships as a bordered surface, which is right for a page section and
   wrong here -- reasoning sits inside an answer, so it reads at tool-chip
   weight and only the open body gets a rule to sit against. */
.msg-think.collapsible { align-self: stretch; width: 100%; border: 0; background: transparent; border-radius: var(--radius); }
.msg-think .msg-think-trigger { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.125rem 0.25rem; border-radius: var(--radius); font-size: var(--text-sm); font-weight: 500; color: var(--muted-foreground); }
.msg-think .msg-think-trigger:hover, .dark .msg-think .msg-think-trigger:hover { background: transparent; color: var(--foreground); }
.msg-think .msg-think-label { display: inline-flex; align-items: center; gap: 0.4rem; min-width: 0; }
.msg-think .msg-think-label svg { width: 0.875rem; height: 0.875rem; flex-shrink: 0; }
.msg-think .msg-think-caret { display: inline-flex; flex-shrink: 0; }
.msg-think .msg-think-caret svg { width: 0.75rem; height: 0.75rem; transition: transform 250ms cubic-bezier(0.34, 1.4, 0.64, 1); }
.msg-think[open] .msg-think-caret svg { transform: rotate(180deg); }
.msg-think[data-closing] .msg-think-caret svg { transform: rotate(0deg); transition: transform 120ms cubic-bezier(0.4, 0, 0.7, 0.2); }
/* The body is the raw stream: verbatim, wrapped, and bounded. A model can
   think for pages, and an unbounded panel would push the answer out of
   the viewport the moment someone opened it. */
.msg-think .msg-think-body {
	margin: 0.25rem 0 0 0.4rem;
	padding: 0 0 0 0.75rem;
	border-top: 0;
	border-left: 2px solid var(--border);
	max-height: 18rem;
	overflow-y: auto;
	overscroll-behavior: contain;
	white-space: pre-wrap;
	overflow-wrap: anywhere;
	font-size: var(--text-sm);
	line-height: 1.55;
	color: var(--muted-foreground);
}
/* Live: the label shimmers like the standalone thinking indicator, so a
   collapsed panel still says "this is happening right now". */
.msg-think-live .msg-think-text {
	background: linear-gradient(100deg, var(--muted-foreground) 40%, var(--foreground) 50%, var(--muted-foreground) 60%);
	background-size: 250% 100%;
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	color: transparent;
	animation: msg-shine 1.8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
	.msg-think-caret svg { transition: none; }
	.msg-think-live .msg-think-text { animation: none; color: var(--muted-foreground); -webkit-text-fill-color: var(--muted-foreground); }
}

/* Turn-end chip */
.chat-chip { display: inline-flex; align-items: center; gap: 0.5rem; align-self: flex-start; min-height: 2rem; padding: 0.25rem 0.75rem; border: 1px solid var(--primary); border-radius: 999px; background: color-mix(in oklab, var(--primary) 8%, transparent); font-size: var(--text-sm); font-weight: 500; color: var(--primary); text-decoration: none; transition: background-color var(--chat-transition), transform var(--chat-transition); }
.chat-chip:hover { background: color-mix(in oklab, var(--primary) 16%, transparent); }
.chat-chip:active { transform: scale(0.97); transition-duration: 120ms; }
.chat-chip svg { width: 0.875rem; height: 0.875rem; }

/* Message actions — copy (both roles) and edit (own message only) */
.msg-copy, .msg-edit { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; padding: 0; border: 0; border-radius: var(--radius); background: transparent; color: var(--muted-foreground); cursor: pointer; opacity: 0; transition: opacity var(--chat-transition), background-color var(--chat-transition), color var(--chat-transition), transform var(--chat-transition); }
.msg-assistant .msg-copy { margin-top: 0.25rem; }
.msg-actions { display: flex; align-items: center; gap: 0.125rem; }
.msg-assistant:hover .msg-copy,
.msg-user:hover .msg-copy, .msg-user:hover .msg-edit,
.msg-copy:focus-visible, .msg-edit:focus-visible { opacity: 1; }
.msg-copy:hover, .msg-edit:hover { background: var(--muted); color: var(--foreground); }
.msg-copy:active, .msg-edit:active { transform: scale(0.97); transition-duration: 120ms; }
.msg-copy svg, .msg-edit svg { width: 0.875rem; height: 0.875rem; }
.msg-copy-done { display: none; color: var(--primary); }
.msg-copy.is-copied .msg-copy-idle { display: none; }
.msg-copy.is-copied .msg-copy-done { display: inline-flex; }
.msg-copy.is-copied { opacity: 1; }
@media (hover: none) { .msg-copy, .msg-edit { opacity: 1; } }
/* An edit rewrites the transcript from that message down, so it is offered
   only when the chat is idle and the row exists on the server. */
.chat-running .msg-edit, .msg-user:not([data-msg-id]) .msg-edit { display: none; }

/* Editing your own message: the bubble becomes the composer for it. The
   column drops its 80% ceiling so the field is not typed into sideways. */
.msg-editing .msg-bubble, .msg-editing .msg-actions { display: none; }
.msg-user.msg-editing .msg-col { max-width: 100%; width: 100%; align-items: stretch; }
.msg-edit-box { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; }
.msg-edit-input.textarea { display: block; min-height: 2.75rem; max-height: 40vh; overflow-y: auto; field-sizing: fixed; resize: none; background: var(--card); }
.msg-edit-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 0.5rem; }
.msg-edit-hint { margin-right: auto; font-size: var(--text-2xs); color: var(--muted-foreground); }
.msg-edit-box.msg-edit-busy { opacity: 0.6; pointer-events: none; }

/* Retry button */
.msg-retry { display: inline-flex; align-items: center; gap: 0.5rem; align-self: flex-start; min-height: 2rem; margin-top: 0.5rem; padding: 0.25rem 0.75rem; border: 1px solid var(--border); border-radius: 999px; background: var(--card); color: var(--foreground); font-size: var(--text-sm); font-weight: 500; cursor: pointer; transition: background-color var(--chat-transition), transform var(--chat-transition); }
.msg-retry:hover { background: var(--muted); }
.msg-retry:active { transform: scale(0.97); transition-duration: 120ms; }
.msg-retry svg { width: 0.875rem; height: 0.875rem; }

/* Attachment chip */
.composer-attachments:empty { display: none; }
.composer-attachments { width: 100%; max-width: 48rem; margin: 0 auto 0.5rem; display: flex; flex-wrap: wrap; gap: 0.5rem; }
.attachment-chip { display: inline-flex; align-items: center; gap: 0.5rem; max-width: 100%; padding: 0.25rem 0.5rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--muted); font-size: var(--text-sm); color: var(--foreground); }
.attachment-chip svg { width: 0.875rem; height: 0.875rem; flex-shrink: 0; color: var(--muted-foreground); }
.attachment-chip-meta { color: var(--muted-foreground); }
.attachment-chip-remove { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 1rem; height: 1rem; padding: 0; border: 0; background: transparent; color: var(--muted-foreground); cursor: pointer; transition: color var(--chat-transition), transform var(--chat-transition); }
/* Bleed takes the 1rem glyph to a 2rem hit target without moving it (Fitts) */
.attachment-chip-remove::before { content: ""; position: absolute; inset: -0.5rem; }
.attachment-chip-remove:hover { color: var(--foreground); }
.attachment-chip-remove:active { transform: scale(0.97); transition-duration: 120ms; }

/* Uploaded-file chips — in the composer (with a remove button) and on sent
   user messages. Images render as thumbnails; other files as a labeled pill. */
.composer-files:empty { display: none; }
.composer-files { width: 100%; max-width: 48rem; margin: 0 auto 0.5rem; display: flex; flex-wrap: wrap; gap: 0.5rem; }
.msg-files { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: flex-end; }
.file-chip { position: relative; display: inline-flex; align-items: center; gap: 0.5rem; max-width: 15rem; padding: 0.375rem 0.5rem; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--card); box-shadow: var(--shadow-field); font-size: var(--text-sm); color: var(--foreground); }
.file-chip svg { width: 1.1rem; height: 1.1rem; flex-shrink: 0; color: var(--muted-foreground); }
.file-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-chip-meta { flex-shrink: 0; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.file-chip-thumb { width: 2.25rem; height: 2.25rem; border-radius: var(--radius); object-fit: cover; flex-shrink: 0; background: var(--muted); }
.file-chip-uploading { opacity: 0.6; }
.file-chip-spinner { width: 1rem; height: 1rem; border: 2px solid var(--border); border-top-color: var(--primary); border-radius: 999px; animation: file-chip-spin 0.6s linear infinite; }
@keyframes file-chip-spin { to { transform: rotate(360deg); } }
.file-chip-remove { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 1.1rem; height: 1.1rem; padding: 0; border: 0; background: transparent; color: var(--muted-foreground); cursor: pointer; transition: color var(--chat-transition), transform var(--chat-transition); }
.file-chip-remove svg { width: 0.9rem; height: 0.9rem; }
.file-chip-remove::before { content: ""; position: absolute; inset: -0.5rem; }
.file-chip-remove:hover { color: var(--foreground); }
.file-chip-remove:active { transform: scale(0.97); transition-duration: 120ms; }
.file-attachment { display: inline-flex; align-items: center; gap: 0.5rem; max-width: 15rem; text-decoration: none; color: var(--foreground); }
.file-attachment-image img { display: block; max-width: 15rem; max-height: 15rem; border-radius: var(--radius-md); border: 1px solid var(--border); object-fit: cover; }
.file-attachment-doc { padding: 0.5rem 0.625rem; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--card); box-shadow: var(--shadow-field); font-size: var(--text-sm); }
.file-attachment-doc svg { width: 1.25rem; height: 1.25rem; flex-shrink: 0; color: var(--muted-foreground); }
.file-attachment-doc:hover { background: var(--muted); }
.file-attachment-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.composer-attach { flex-shrink: 0; color: var(--muted-foreground); }
.composer-attach:hover { color: var(--foreground); }
.composer-attach svg { width: 1.4rem; height: 1.4rem; }
.composer-box.composer-dragover { border-color: var(--ring); box-shadow: var(--shadow-field), 0 0 0 3px color-mix(in oklab, var(--ring) 30%, transparent); }

/* Typing indicator (three dots) */
/* "Thinking" indicator: a status line whose text shimmers while the model
   works (a bright band sweeps across via a text-clipped gradient). The
   text reflects what the model is doing ("Machinando…" placeholder). */
.msg-thinking { display: inline-flex; align-items: center; padding: 0.5rem 0.25rem; }
.msg-thinking-text {
	font-size: var(--text-md);
	font-weight: 500;
	background: linear-gradient(100deg, var(--muted-foreground) 40%, var(--foreground) 50%, var(--muted-foreground) 60%);
	background-size: 250% 100%;
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	color: transparent;
	animation: msg-shine 1.8s linear infinite;
}
@keyframes msg-shine { from { background-position: 150% 0; } to { background-position: -150% 0; } }
@media (prefers-reduced-motion: reduce) {
	.msg-thinking-text { animation: none; color: var(--muted-foreground); -webkit-text-fill-color: var(--muted-foreground); }
}

/* Composer */
.chat-composer { position: sticky; bottom: 0; padding: 0.75rem 1rem 1rem; background: var(--background); }
/* Outer radius derives from the inner one plus the padding, so the box and
   the send button stay concentric if either value changes. */
.composer-box { --composer-pad: 0.5rem; --composer-inner-radius: 0.75rem; width: 100%; max-width: 48rem; margin: 0 auto; display: flex; flex-direction: column; gap: 0.5rem; border: 1px solid var(--input); border-radius: calc(var(--composer-inner-radius) + var(--composer-pad)); padding: var(--composer-pad); background: var(--card); transition: border-color var(--chat-transition), box-shadow var(--chat-transition); }
.composer-box:focus-within { border-color: var(--ring); box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 30%, transparent); }
/* One line at rest, growing a line at a time up to 40vh. The height is
   JS-driven (autosize), so the textarea must size from `height`: `flex: 1`
   here made flex-basis 0 win over it and pinned the box at its min-height,
   and `display: flex` (the base .textarea) makes scrollHeight unreadable.
   field-sizing stays `fixed` so a browser that supports content-sizing
   does not fight the same measurement. */
.composer-input.textarea { display: block; flex: 0 0 auto; border: 0; outline: 0; resize: none; overflow-y: auto; field-sizing: fixed; background: transparent; color: var(--foreground); font-size: var(--text-md); line-height: 1.5; min-height: 2.5rem; max-height: 40vh; border-radius: 0; padding: 0.5rem; }
.composer-input.textarea:focus-visible { box-shadow: none; }
/* Toolbar row under the textarea: model picker on the left, send on the
   right. When no picker renders, the send button stays right (Serial
   Position — the primary action holds its expected corner). */
.composer-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.composer-actions-left { display: inline-flex; align-items: center; flex-shrink: 0; }
.composer-send { flex-shrink: 0; }
.composer-send:disabled { opacity: 0.4; cursor: not-allowed; }
.composer-icon { display: inline-flex; }
.composer-icon-send svg { width: 1.2rem; height: 1.2rem; }
.composer-icon-stop { display: none; }
.composer-icon-stop svg { width: 0.9rem; height: 0.9rem; fill: currentColor; }
.composer-stop-mode .composer-icon-send { display: none; }
.composer-stop-mode .composer-icon-stop { display: inline-flex; }
/* Model picker: a ghost trigger that reads as secondary chrome until
   hovered/opened (Von Restorff reserves emphasis for the send button). */
.composer-model { flex-shrink: 1; min-width: 0; }
.composer-model-trigger { display: inline-flex; align-items: center; gap: 0.25rem; height: 2.25rem; max-width: 100%; padding: 0 0.5rem; border: 0; border-radius: var(--radius-md); background: transparent; color: var(--muted-foreground); font-size: var(--text-sm); font-weight: 500; line-height: 1; cursor: pointer; transition: background-color var(--chat-transition), color var(--chat-transition); }
.composer-model-trigger:hover, .composer-model[data-slot="dropdown-menu"] .composer-model-trigger[aria-expanded="true"] { background: var(--muted); color: var(--foreground); }
.composer-model-trigger:active { transform: scale(0.97); }
.composer-model-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.composer-model-caret { display: inline-flex; flex-shrink: 0; opacity: 0.7; }
.composer-model-caret svg { width: 0.85rem; height: 0.85rem; }
@media (prefers-reduced-motion: reduce) { .composer-model-trigger:active { transform: none; } }

/* Composer actions: mic + send share the right corner. The mic shows only
   when the input is empty and voice input is configured (mirrors ChatGPT);
   any typed text swaps it back to send. */
.composer-actions { display: inline-flex; align-items: center; gap: 0.5rem; margin-left: auto; min-width: 0; }
.composer-mic { flex-shrink: 0; color: var(--muted-foreground); }
.composer-mic:hover { color: var(--foreground); }
.composer-mic svg { width: 1.25rem; height: 1.25rem; }
.composer-recording-mode .composer-mic { display: none; }

/* Recording overlay: covers the textarea + toolbar while capturing, so the
   composer becomes a live waveform with stop/send actions. */
.composer-recording { display: none; align-items: center; gap: 0.5rem; padding: 0.25rem; }
.composer-recording-mode .composer-input,
.composer-recording-mode .composer-toolbar { display: none; }
.composer-recording-mode .composer-recording { display: flex; }
.composer-rec-main { flex: 1; min-width: 0; display: flex; align-items: center; gap: 0.5rem; height: 2.25rem; padding: 0 0.5rem; }
.composer-rec-time { flex-shrink: 0; font-family: var(--font-mono); font-size: var(--text-sm); font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
.composer-rec-cancel { flex-shrink: 0; color: var(--muted-foreground); }
.composer-rec-cancel:hover { color: var(--foreground); }
.composer-rec-stop, .composer-rec-send { flex-shrink: 0; }
.composer-rec-stop { color: var(--muted-foreground); }
.composer-rec-stop:hover { color: var(--foreground); }
.composer-rec-cancel svg, .composer-rec-stop svg { width: 1rem; height: 1rem; }

/* Waveform: a scrolling history of the mic level, one bar per column, the
   newest at the right (set from the analyser via --wave-scale on each
   bar). Bars need their own height -- the row centers rather than
   stretches them, and an empty span with no height scales to nothing. A
   resting idle pulse plays until the first column lands. */
.composer-waveform { flex: 1; min-width: 0; display: flex; align-items: center; justify-content: center; gap: 2px; height: 1.35rem; overflow: hidden; }
.composer-wave-bar { flex: 1; max-width: 3px; min-width: 2px; height: 100%; border-radius: 999px; background: var(--primary); transform: scaleY(var(--wave-scale, 0.08)); transform-origin: center; transition: transform 90ms linear; }
.composer-recording-mode .composer-wave-bar { animation: composer-wave-idle 1.1s ease-in-out infinite; }
.composer-recording-mode.composer-wave-live .composer-wave-bar { animation: none; }
@keyframes composer-wave-idle { 0%, 100% { transform: scaleY(0.15); } 50% { transform: scaleY(0.4); } }

/* Transcribing state: swap the waveform + timer for a spinner while the
   clip uploads, and lock the actions so a double-tap cannot double-send. */
.composer-rec-loading { flex: 1; min-width: 0; display: none; align-items: center; gap: 0.5rem; height: 100%; padding: 0 0.5rem; color: var(--muted-foreground); font-size: var(--text-base); }
.composer-rec-loading svg { width: 1rem; height: 1rem; }
.composer-rec-busy .composer-waveform, .composer-rec-busy .composer-rec-time { display: none; }
.composer-rec-busy .composer-rec-loading { display: flex; }
.composer-rec-busy .composer-rec-stop, .composer-rec-busy .composer-rec-send, .composer-rec-busy .composer-rec-cancel { opacity: 0.5; pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
	.composer-recording-mode .composer-wave-bar { animation: none; }
	.composer-wave-bar { transition: none; }
}

/* conversas.css */
/* The conversations index. The cards are components/list-card.css — the
   same ones /documentos lists — so what is left here is the page around
   them. */
.convlist {
	width: 100%;
	max-width: 48rem;
	margin: 0 auto;
	padding: 1.5rem 1rem 2rem;
	display: flex;
	flex-direction: column;
	gap: 1.25rem;
}
.convlist-empty { color: var(--muted-foreground); font-size: var(--text-base); }

.convlist-more {
	display: flex;
	align-items: center;
	gap: 0.35rem;
	min-height: 3.25rem;
	padding: 0.9rem 0.85rem;
	border: 0;
	border-radius: var(--radius-lg);
	background: none;
	font-family: inherit;
	font-size: var(--text-base);
	color: var(--muted-foreground);
	text-align: left;
	cursor: pointer;
	transition: color 150ms ease, opacity 150ms ease;
}
.convlist-more:hover { color: var(--foreground); }
.convlist-more svg { width: 0.9rem; height: 0.9rem; flex-shrink: 0; }
/* In flight: dim and stop taking clicks (hypermedia.js marks the control
   that started the request). */
.convlist-more[data-hm-busy] { opacity: 0.55; cursor: default; }

/* documento.css */
.doc {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	padding: 1.5rem 1rem 5rem;
}
/* One reading column, centred. Prose measure beats full width: a table
   can scroll inside it, a paragraph cannot un-stretch. */
.doc > * {
	width: 100%;
	max-width: 46rem;
	margin-left: auto;
	margin-right: auto;
}

/* Head */
.doc-head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 1rem;
	margin-bottom: 1.75rem;
}
.doc-head-main { display: flex; align-items: center; gap: 0.4rem; min-width: 0; flex: 1; }
.doc-head-icon { color: var(--muted-foreground); flex-shrink: 0; }
.doc-head-icon svg { width: 1.6rem; height: 1.6rem; }
.doc-title { margin-bottom: 0; text-wrap: balance; min-width: 0; flex: 1; outline: none; }
.doc-head-actions { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
.doc-share { display: flex; align-items: center; gap: 0.5rem; }
.doc-share-label { font-size: var(--text-sm); color: var(--muted-foreground); cursor: pointer; }

/* Breadcrumb — quiet by design: it is wayfinding, not content, so it
   whispers above the title and disappears entirely on a root document
   (the empty nav keeps the id on the page for the move fragment). */
.doc-crumbs {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.15rem;
	min-height: 0;
	margin-bottom: 0.35rem;
	font-size: var(--text-sm);
}
.doc-crumbs:empty { margin-bottom: 0; }
.doc-crumb {
	display: inline-flex;
	align-items: center;
	gap: 0.3rem;
	max-width: 14rem;
	padding: 0.1rem 0.3rem;
	border-radius: var(--radius-sm);
	color: var(--muted-foreground);
	text-decoration: none;
	transition: color 120ms ease, background-color 120ms ease;
}
.doc-crumb:hover { color: var(--foreground); background: var(--muted); }
.doc-crumb svg { width: 0.85rem; height: 0.85rem; flex-shrink: 0; }
.doc-crumb-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.doc-crumb-sep { color: var(--muted-foreground); opacity: 0.5; }

/* Children — the pages filed inside this one, below the prose. Rows
   read like the rail's: quiet links, the document's own icon. */
.doc-children { margin-top: 2.5rem; padding-top: 0.75rem; border-top: 1px solid var(--border); }
.doc-children-rows { display: flex; flex-direction: column; gap: 1px; }
.doc-child {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	width: 100%;
	min-height: 2rem;
	padding: 0.25rem 0.4rem;
	border: 0;
	border-radius: var(--radius-md);
	background: none;
	font-family: inherit;
	font-size: var(--text-base);
	color: var(--foreground);
	text-align: left;
	text-decoration: none;
	cursor: pointer;
	transition: background-color 120ms ease;
	position: relative;
}
.doc-child:hover { background: var(--muted); }
.doc-child-icon { display: inline-flex; flex-shrink: 0; color: var(--muted-foreground); }
.doc-child-icon svg { width: 1rem; height: 1rem; }
.doc-child-label { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.doc-child-new { color: var(--muted-foreground); }
.doc-child-new:hover { color: var(--foreground); }
/* Drag: the moving row dims; the drop slot is a 2px rule at the edge
   the row would land on. */
.doc-child-dragging { opacity: 0.45; }
.doc-child-over-before::before,
.doc-child-over-after::after {
	content: "";
	position: absolute;
	left: 0.4rem;
	right: 0.4rem;
	height: 2px;
	border-radius: 1px;
	background: var(--primary);
}
.doc-child-over-before::before { top: -1px; }
.doc-child-over-after::after { bottom: -1px; }

/* Body: blocks stack with the rhythm of a document, not of a form. The gap
   is small because a paragraph carries its own margin; a heading and a
   list item should sit close to what they belong to. */
.doc-body { display: flex; flex-direction: column; gap: 0.15rem; }
.doc-blk { position: relative; min-width: 0; }
.doc-blk-data { margin: 0.9rem 0; }

/* Sections. The bar is the line variant — a document is a page, not a
   control panel, and the pill variant would read as a widget dropped
   into the prose. */
.doc-preamble { margin-bottom: 1.25rem; }
.doc-preamble:empty { margin-bottom: 0; }
.doc-tabs { gap: 1.25rem; }
.doc-tabs > .tabs-list { align-self: flex-start; }

/* Text blocks. Each is one line-level element; the markers of lists are
   drawn in their own cell so the editable region holds text only.

   Reading size, not the UI size: a document is prose first, so it asks
   for --text-md and gains the phone extra pixel with it. And the model
   writes the text, so the column has to survive whatever it writes: a
   long url or a run the browser cannot break lands inside the measure
   instead of over the page edge. */
.doc-line {
	outline: none;
	min-height: 1.5em;
	font-size: var(--text-md);
	overflow-wrap: break-word;
}
.doc-h1, .doc-h2, .doc-h3 {
	font-weight: 600;
	line-height: 1.25;
	text-wrap: balance;
	margin: 1.4rem 0 0.35rem;
}
.doc-blk:first-child .doc-h1,
.doc-blk:first-child .doc-h2,
.doc-blk:first-child .doc-h3 { margin-top: 0; }
.doc-h1 { font-size: var(--text-3xl); letter-spacing: -0.02em; }
.doc-h2 { font-size: var(--text-xl); letter-spacing: -0.015em; }
.doc-h3 { font-size: var(--text-lg); }
p.doc-line { margin: 0; padding: 0.1rem 0; line-height: 1.7; text-wrap: pretty; }

.doc-li { display: flex; align-items: flex-start; gap: 0.5rem; padding: 0.1rem 0; }
.doc-li-mark {
	flex: 0 0 auto;
	min-width: 1.1rem;
	line-height: 1.7;
	color: var(--muted-foreground);
	user-select: none;
	text-align: right;
}
.doc-li-num { font-variant-numeric: tabular-nums; }
.doc-li .doc-line { flex: 1; min-width: 0; line-height: 1.7; }

/* A todo. The box occupies the marker cell a bullet would, so a checklist
   nests, indents and aligns with every other list in the document without
   a second layout. */
.doc-todo-box {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 1.15rem;
	height: 1.15rem;
	/* Optically centred on the first line's x-height rather than its box:
	   at line-height 1.7 a top-aligned square sits visibly high. */
	margin-top: 0.29rem;
	padding: 0;
	border: 1.5px solid var(--border);
	border-radius: 0.3rem;
	background: none;
	color: transparent;
	cursor: pointer;
	transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}
/* The tick has to be tappable with a thumb, and an 18px square is not.
   The target grows past the box without the box growing with it. It stops
   short of the text (0.5rem away, and the text has its own job) and short
   of half the gap to the item above: a list line is ~29px tall, so a 44px
   target would overlap its neighbour's and the wrong task would tick. */
.doc-todo-box::after {
	content: "";
	position: absolute;
	inset: -0.45rem -0.35rem;
}
.doc-todo-box svg { width: 0.8rem; height: 0.8rem; }
.doc-todo-box:hover { border-color: var(--muted-foreground); }
.doc-blk[data-done] .doc-todo-box {
	background: var(--primary);
	border-color: var(--primary);
	color: var(--primary-foreground);
}
/* Done is legible as done at a glance, and still readable — a finished
   item is history, not litter. */
.doc-blk[data-done] .doc-todo .doc-line {
	color: var(--muted-foreground);
	text-decoration: line-through;
	text-decoration-thickness: 1px;
	text-decoration-color: color-mix(in oklab, var(--muted-foreground) 60%, transparent);
}

.doc-quote {
	margin: 0.2rem 0;
	padding-left: 0.9rem;
	border-left: 2px solid var(--border);
	color: var(--muted-foreground);
	line-height: 1.7;
}
.doc-code {
	margin: 0.3rem 0;
	padding: 0.8rem;
	border-radius: var(--radius-md);
	background: var(--muted);
	overflow-x: auto;
}
.doc-code .doc-line {
	font-family: var(--font-mono);
	font-size: var(--text-base);
	line-height: 1.6;
	white-space: pre-wrap;
	word-break: break-word;
}
.doc-divider { padding: 0.7rem 0; }
.doc-divider-rule { display: block; border-top: 1px solid var(--border); }

/* Inline marks inside a line. */
.doc-line a { color: var(--primary); text-underline-offset: 0.2em; }
.doc-line strong { font-weight: 600; }
.doc-line code {
	font-family: var(--font-mono);
	font-size: 0.85em;
	padding: 0.1rem 0.3rem;
	border-radius: var(--radius);
	background: var(--muted);
}

/* Stat — the number is the block, so it carries the weight. */
.doc-stat-body { display: flex; flex-direction: column; gap: 0.15rem; }
.doc-stat-label {
	font-size: var(--text-sm);
	color: var(--muted-foreground);
}
.doc-stat-value {
	font-size: var(--text-3xl);
	font-weight: 600;
	letter-spacing: -0.02em;
	font-variant-numeric: tabular-nums;
}
.doc-stat-empty { font-size: var(--text-xl); color: var(--muted-foreground); }

/* A one-series chart needs no legend: the card title already names the
   series, and a legend repeating it is a second label for one thing. A
   pie/donut keeps its legend — there the entries are the categories. */
.doc-chart-solo .chart-legend { display: none; }

/* Table */
.doc-table { min-width: 0; }
.doc-table-frame { max-height: 32rem; }
.doc-num { text-align: right; font-variant-numeric: tabular-nums; }
.doc-table-foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem;
	padding-top: 0.6rem;
}
.doc-table-count { font-size: var(--text-xs); }
.doc-pager { margin-top: 0.35rem; }
.doc-table-empty { padding: 2rem 1rem; }
/* The region dims while its replacement is in flight, so a slow query
   reads as loading instead of as a table that ignored the click. */
.doc-table-region[data-loading] { opacity: 0.55; transition: opacity 120ms ease 90ms; }

/* A cell reads as text until touched (Fitts, without the chrome). */
.doc-cell {
	display: block;
	width: 100%;
	cursor: text;
	border-radius: var(--radius);
}
.doc-num .doc-cell { text-align: right; }
.doc-cell-ro { cursor: default; }
.doc-cell:not(.doc-cell-ro):hover {
	box-shadow: 0 0 0 3px var(--muted);
	background: var(--muted);
}
.doc-cell:focus-visible {
	outline: none;
	box-shadow: 0 0 0 2px var(--ring);
}
.doc-cell-blank { color: var(--muted-foreground); }

/* Image */
.doc-figure { margin: 0; }
.doc-figure img {
	display: block;
	width: 100%;
	height: auto;
	max-width: 100%;
	border-radius: var(--radius-lg);
	border: 1px solid var(--border);
}
.doc-figure figcaption {
	margin-top: 0.5rem;
	font-size: var(--text-sm);
	color: var(--muted-foreground);
	text-align: center;
}

/* Doc — another page's link, sitting in the body where the author put it.
   The same quiet row the child list draws, so a page reads the same
   wherever it appears. */
.doc-ref { display: block; }
.doc-ref-row {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	min-height: 2rem;
	padding: 0.25rem 0.4rem;
	border-radius: var(--radius-md);
	color: var(--foreground);
	text-decoration: none;
	transition: background-color 120ms ease;
}
a.doc-ref-row:hover { background: var(--muted); }
.doc-ref-row .icon {
	width: 1rem;
	height: 1rem;
	flex: none;
	color: var(--muted-foreground);
}
.doc-ref-label {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-weight: 500;
}
.doc-ref-gone { color: var(--muted-foreground); }
.doc-ref-gone .doc-ref-label { font-weight: 400; font-style: italic; }

/* File — an attachment reads as one row, not as content. */
.doc-file { display: block; }
.doc-file-row {
	display: flex;
	align-items: center;
	gap: 0.65rem;
	padding: 0.7rem 0.85rem;
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	background: var(--card);
	color: inherit;
	text-decoration: none;
	transition: background-color 150ms ease, border-color 150ms ease;
}
a.doc-file-row:hover { background: var(--muted); border-color: var(--ring); }
.doc-file-row .icon { width: 1.35rem; height: 1.35rem; flex: none; }
.doc-file-name {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-size: var(--text-sm);
}
.doc-file-meta {
	flex: none;
	font-size: var(--text-xs);
	color: var(--muted-foreground);
	font-variant-numeric: tabular-nums;
}
.doc-file-gone { opacity: 0.7; }
.doc-file-caption {
	display: block;
	margin-top: 0.4rem;
	font-size: var(--text-sm);
	color: var(--muted-foreground);
}

/* A block still uploading: present so the caret has an anchor, greyed so
   nobody mistakes it for saved. serialize() skips it. */
.doc-blk[data-pending] { opacity: 0.6; }
.doc-blk[data-pending] .doc-file-row { border-style: dashed; }

/* Fold — the Collapsible component, with no content inside it: what it
   holds are the blocks after it in the body. So everything here is about
   turning a card into a *seam* — the line where one part of the document
   ends and the next is waiting. A rule above it and a heading's weight
   without a heading's size: a fold is structure, and structure must not
   compete with the ## that is content.

   Out-specified, never out-ordered (.collapsible.doc-fold beats
   .collapsible on specificity): the bundle is concatenated alphabetically
   and no style here may depend on which file landed first. */
.doc-blk[data-type="collapse"] { margin: 0; }
.collapsible.doc-fold {
	margin: 0.6rem 0 0.1rem;
	border-radius: 0;
	background: none;
	box-shadow: none;
	border-top: 1px solid var(--border);
	font-size: inherit;
	line-height: inherit;
}
.doc-blk:first-child .collapsible.doc-fold { margin-top: 0; border-top: 0; }
/* The caret sits where a list marker would and the label runs from there,
   so a fold reads like the outline entry it is. The row is the target — a
   1rem glyph is not a thumb — and it bleeds a little past the measure so
   the hover tint has somewhere to be without moving the text.

   The padding is what makes the row a *thumb* target: full width by
   44px, Apple's floor, which the old 0.6/0.2rem pair missed by ten
   pixels. It is deliberately not symmetric — a header belongs to what
   comes under it, so the space above the name (the gap from the rule) is
   larger than the space below it. Growing the box rather than bleeding a
   pseudo-element past it is the choice here because the extra area would
   otherwise land on the line above or the first block the fold holds,
   and a tap meant for the caret would tick somebody's todo. */
.collapsible-trigger.doc-fold-trigger {
	justify-content: flex-start;
	gap: 0.45rem;
	padding: 0.85rem 0.4rem 0.55rem;
	margin: 0 -0.4rem;
	border-radius: var(--radius-md);
	font-size: var(--text-md);
	font-weight: 600;
}
.collapsible.doc-fold[open] > .doc-fold-trigger svg { transform: rotate(90deg); }
.doc-fold-label {
	flex: 1 1 auto;
	min-width: 0;
	line-height: 1.4;
	outline: none;
	overflow-wrap: break-word;
}
/* The name is editable, but only while it is *being renamed* — pressing a
   fold is what the header is for, and a word that takes a caret is a word
   that swallows the press. So it inherits the trigger's cursor and its
   refusal to be selected until the editor arms it, and only then does it
   turn back into text you can type into. */
.doc-fold-label[contenteditable="true"] {
	user-select: text;
	cursor: text;
}
/* Unlike a block's placeholder this one shows even unfocused: a fold with no
   name is a bar with nothing on it, and nothing to press for. */
.doc-fold-label:empty::before {
	content: attr(data-placeholder);
	font-weight: 400;
	color: var(--muted-foreground);
}
/* Closed, the count is the only honest thing the bar can say about itself.
   Open, the content says it — kept in the layout so opening does not shift
   the label under the finger that just tapped it. */
.doc-fold-count {
	flex: none;
	font-size: var(--text-sm);
	font-weight: 400;
	color: var(--muted-foreground);
	font-variant-numeric: tabular-nums;
	transition: opacity 140ms ease;
}
.collapsible.doc-fold[open] .doc-fold-count { opacity: 0; }
.doc-blk[data-hide] { display: none; }

/* What a fold holds is indented under it, by exactly the width of the
   caret and its gap — so the blocks line up with the name, the way a
   sub-entry lines up under its chapter. The membership is real but it is
   also invisible otherwise: these are siblings in the DOM (that is the
   whole marker design), so with nothing shifted an open fold's list ended
   where the document's left edge is and read as prose that merely happens
   to sit below a bar. The indent is the only thing on screen saying where
   the section starts and, more importantly, where it stops. */
.doc-blk[data-fold] { padding-left: 1.45rem; }

/* No script, so nothing is closed: <details> would still open, but what it
   holds are siblings that only a script un-hides, and a caret that reveals
   nothing is worse than no caret. Everything is simply there. */
@media (scripting: none) {
	.doc-blk[data-hide] { display: revert; }
	.collapsible-trigger.doc-fold-trigger { pointer-events: none; }
	.collapsible-trigger.doc-fold-trigger > svg { display: none; }
}

@media (max-width: 48rem) {
	.doc { padding: 1rem 0.85rem 4rem; }
	.doc-head { flex-direction: column; gap: 0.75rem; }
	/* The icon rides the title's *first* line: a title long enough to wrap
	   would otherwise float it down to the middle of the block. */
	.doc-head-main { align-items: flex-start; }
	.doc-head-icon { margin-top: calc((var(--text-4xl) * 1.1 - 2.25rem) / 2); }
	/* Stacked, the actions get the full column: sharing on the left edge,
	   save state and delete on the right. Without the reorder the save
	   indicator's reserved width sits first and indents everything after it
	   by 4.5rem of nothing. */
	.doc-head-actions { align-self: stretch; }
	.doc-head-actions > .doc-saved { order: 1; margin-left: auto; }
	.doc-head-actions > .btn { order: 2; }
}
@media (prefers-reduced-motion: reduce) {
	.doc-th, .doc-size { transition: none; }
	.doc-fold-count { transition: none; }
}

/* documento_editor.css */
/* An empty line still has to be tappable and has to say what it is for.
   The placeholder shows only on the focused block: a document full of
   grey hint text reads as a broken form, not as a page. */
.doc-line:empty::before,
.doc-line[data-empty]::before {
	content: attr(data-placeholder);
	color: var(--muted-foreground);
	pointer-events: none;
	opacity: 0;
}
.doc-blk[data-focus] .doc-line:empty::before,
.doc-blk[data-focus] .doc-line[data-empty]::before { opacity: 1; }
.doc-title:empty::before {
	content: attr(data-placeholder);
	color: var(--muted-foreground);
}

/* The gutter is a desktop-only affordance. hover:hover keeps it off
   touch entirely, where it would just eat 3rem of a narrow column. */
.doc-gutter {
	position: absolute;
	top: 0.15rem;
	left: -3.4rem;
	display: none;
	gap: 0.05rem;
	opacity: 0;
	transition: opacity 120ms ease;
}
@media (hover: hover) and (pointer: fine) {
	.doc-gutter { display: flex; }
}
.doc-blk:hover > .doc-gutter,
.doc-blk[data-focus] > .doc-gutter,
.doc-gutter:focus-within { opacity: 1; }
.doc-gutter-btn { color: var(--muted-foreground); width: 1.5rem; height: 1.5rem; }
.doc-handle { cursor: grab; touch-action: none; }
.doc-blk[data-dragging] { opacity: 0.4; }

/* A picked block. Tint only — no outline and no padding: adjacent selected
   blocks have to read as one continuous run (which is what a range
   selection is), and anything that changes the box would make the document
   jump the moment you select. */
.doc-blk[data-selected] {
	border-radius: 3px;
	background: color-mix(in oklab, var(--primary) 16%, transparent);
}
/* In selection mode the lines are not editable, so the browser paints no
   caret and no text selection — the tint is the only selection on screen. */
.doc[data-blocksel] .doc-line::selection { background: transparent; }

/* Off-screen, unpainted, and never scrolled to: it exists to own the focus
   so copy/cut/paste events have somewhere to fire. */
.doc-selhost {
	position: fixed;
	top: 0;
	left: 0;
	width: 1px;
	height: 1px;
	padding: 0;
	border: 0;
	opacity: 0;
	overflow: hidden;
	pointer-events: none;
	white-space: pre;
}

.doc-selcount {
	flex: 0 0 auto;
	padding: 0 0.4rem;
	font-size: var(--text-sm);
	font-variant-numeric: tabular-nums;
	color: var(--muted-foreground);
	white-space: nowrap;
}
.doc-selbar-danger { color: var(--destructive); }

/* A pasted data block before its real markup arrives from the server. It is
   saved (unlike a pending upload) — the placeholder is only what stands in
   for a chart nobody can draw in the browser. */
.doc-specph {
	padding: 0.7rem 0.85rem;
	border: 1px dashed var(--border);
	border-radius: var(--radius);
	color: var(--muted-foreground);
	font-size: var(--text-sm);
}

.doc-drop-line {
	position: fixed;
	height: 2px;
	background: var(--primary);
	border-radius: 2px;
	pointer-events: none;
	display: none;
	z-index: 55;
}
.doc-drop-line[data-on] { display: block; }

/* Save state. It has to be legible and it has to be ignorable: an
   indicator that flashes on every keystroke becomes the thing you watch
   instead of the thing you wrote. */
.doc-saved {
	font-size: var(--text-xs);
	color: var(--muted-foreground);
	min-width: 4.5rem;
	text-align: right;
	transition: opacity 200ms ease;
}
.doc-saved[data-state="idle"] { opacity: 0; }
.doc-saved[data-state="saving"] { opacity: 0.7; }
.doc-saved[data-state="saved"] { opacity: 0.7; }
.doc-saved[data-state="error"] { opacity: 1; color: var(--destructive); }

.doc-conflict { margin-bottom: 1rem; }
.doc-conflict-btn { margin-top: 0.6rem; }

/* Keyboard bar. Fixed to the bottom and lifted by the visual viewport
   offset, because on iOS the keyboard does not resize the layout
   viewport — bottom:0 alone would leave it underneath the keys. */
.doc-kbar {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 60;
	background: var(--popover);
	border-top: 1px solid var(--border);
	box-shadow: var(--shadow-popover);
	padding: 0.3rem 0.4rem;
	padding-bottom: max(0.3rem, env(safe-area-inset-bottom));
	will-change: transform;
}
.doc-kbar-scroll {
	display: flex;
	align-items: center;
	gap: 0.15rem;
	overflow-x: auto;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}
.doc-kbar-scroll::-webkit-scrollbar { display: none; }
/* 44px targets. A formatting bar you have to aim at is a bar you stop
   using. */
.doc-kbar-btn {
	flex: 0 0 auto;
	min-width: 2.75rem;
	height: 2.75rem;
	color: var(--foreground);
}
.doc-kbar-btn[data-on] { background: var(--muted); }
.doc-kbar-btn svg { width: 1.15rem; height: 1.15rem; }
.doc-kbar-sep {
	flex: 0 0 auto;
	width: 1px;
	height: 1.5rem;
	margin: 0 0.25rem;
	background: var(--border);
}

/* Menus: one look for the block menu, the slash menu, the link editor
   and the icon picker. They are positioned by JS against the caret or
   the control that opened them. */
.doc-menu {
	position: fixed;
	z-index: 70;
	min-width: 13rem;
	max-height: min(24rem, 60vh);
	overflow-y: auto;
	padding: 0.3rem;
	border: 1px solid var(--border);
	border-radius: var(--radius-lg);
	background: var(--popover);
	color: var(--popover-foreground);
	box-shadow: var(--shadow-popover);
}
.doc-menu[hidden] { display: none; }
.doc-menu-group { display: flex; flex-direction: column; gap: 0.05rem; }
.doc-menu-label {
	padding: 0.4rem 0.5rem 0.25rem;
	font-size: var(--text-2xs);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--muted-foreground);
}
.doc-menu-sep { height: 1px; margin: 0.3rem 0; background: var(--border); }
.doc-menu-item {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	width: 100%;
	min-height: 2.5rem;
	padding: 0.4rem 0.5rem;
	border: 0;
	border-radius: var(--radius);
	background: none;
	color: inherit;
	font: inherit;
	font-size: var(--text-base);
	text-align: left;
	cursor: pointer;
}
.doc-menu-item svg { width: 1rem; height: 1rem; flex-shrink: 0; color: var(--muted-foreground); }
.doc-menu-item:hover, .doc-menu-item[data-active] { background: var(--muted); }
.doc-menu-item[data-on] { color: var(--primary); }
.doc-menu-item-main { display: flex; flex-direction: column; min-width: 0; }
.doc-menu-hint { font-size: var(--text-xs); color: var(--muted-foreground); }
.doc-menu-danger { color: var(--destructive); }
.doc-menu-danger svg { color: var(--destructive); }

.doc-slash { min-width: 15rem; }
.doc-linkbox { min-width: 17rem; padding: 0.5rem; }
.doc-link-input { width: 100%; }
.doc-link-btns { display: flex; justify-content: flex-end; gap: 0.35rem; margin-top: 0.45rem; }

.doc-iconpicker {
	display: grid;
	grid-template-columns: repeat(8, 2.25rem);
	gap: 0.1rem;
	min-width: 0;
	padding: 0.45rem;
}
.doc-icon-opt {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 2.25rem;
	height: 2.25rem;
	border: 0;
	border-radius: var(--radius);
	background: none;
	color: var(--muted-foreground);
	cursor: pointer;
}
.doc-icon-opt:hover { background: var(--muted); color: var(--foreground); }
.doc-icon-on { background: var(--muted); color: var(--primary); }
.doc-icon-opt svg { width: 1.15rem; height: 1.15rem; }

@media (max-width: 30rem) {
	.doc-iconpicker { grid-template-columns: repeat(6, 2.25rem); }
}
@media (prefers-reduced-motion: reduce) {
	.doc-gutter, .doc-saved { transition: none; }
}

/* documento_recipe.css */
/* Recipe block — the Cooking for Engineers table.

   The whole value of this layout is that the eye tracks a row rightwards and
   a column downwards without counting anything, so the styling has exactly
   one job: make the cell boundaries unmistakable and get out of the way.
   That means real gridlines (a data table earns its zebra striping from
   scanning long lists of like rows; here every cell is a different shape and
   striping would fight the spans), and nothing else.

   Every rule is scoped under .doc-recipe rather than written flat. The block
   reuses components/table.go, so .tbl / .tbl-head / .tbl-cell / .tbl-row are
   already on these elements — and a flat .doc-recipe-do would tie with
   .tbl-cell and be decided by which file the bundle concatenated last. The
   extra ancestor is what makes these win on specificity instead of order. */

.doc-recipe {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
}

.doc-recipe .doc-recipe-table {
	width: 100%;
	table-layout: auto;
	/* separate + zero spacing, not collapse, for the same reason the data
	   table does it: a collapsed border belongs to the table and stays put
	   when a sticky cell scrolls out from under it, so the left column would
	   lose its edge exactly when it is the only thing holding the reader's
	   place. The cost is that each cell draws two sides and the table draws
	   the other two, or every internal line would be 2px. */
	border-collapse: separate;
	border-spacing: 0;
	border-top: 1px solid var(--border);
	border-left: 1px solid var(--border);
	font-size: var(--text-base);
	/* Quantities are the one place digits line up down a column. */
	font-variant-numeric: tabular-nums;
}

/* A grid, not a list of rows: every cell carries its own outline, because a
   cell spanning four ingredients is only legible if its box is. */
.doc-recipe .doc-recipe-table th,
.doc-recipe .doc-recipe-table td {
	border-right: 1px solid var(--border);
	border-bottom: 1px solid var(--border);
	padding: 0.5rem 0.625rem;
	text-align: left;
	white-space: normal;
	overflow-wrap: break-word;
}

/* Ingredients. Sticky so the left column survives a sideways scroll on a
   phone — a step you can read while the thing it applies to has scrolled off
   is exactly the disorientation this layout removes. */
.doc-recipe .doc-recipe-ing {
	position: sticky;
	left: 0;
	z-index: 1;
	min-width: 8.5rem;
	max-width: 14rem;
	height: auto;
	background-color: var(--background);
	font-size: var(--text-base);
	font-weight: 500;
	letter-spacing: normal;
	line-height: 1.4;
	color: var(--foreground);
	vertical-align: top;
}

/* Steps sit centred in their span, vertically and horizontally: a cell
   covering four ingredients has to read as belonging to all four, and text
   pinned to its top corner reads as belonging to the first one. */
.doc-recipe .doc-recipe-do {
	min-width: 7rem;
	vertical-align: middle;
	text-align: center;
	text-wrap: balance;
	line-height: 1.4;
	background-color: color-mix(in oklch, var(--muted) 30%, transparent);
}
.dark .doc-recipe .doc-recipe-do {
	background-color: color-mix(in oklch, var(--muted) 22%, transparent);
}

/* The wait before an ingredient is used. Bordered like everything else so the
   grid stays square, but empty and unshaded — nothing happens here. */
.doc-recipe .doc-recipe-gap {
	padding: 0;
	background-color: transparent;
}

.doc-recipe .doc-recipe-note {
	font-size: var(--text-xs);
	color: var(--muted-foreground);
}

/* The reusable table's row chrome is wrong for a grid of merged cells: zebra
   striping paints half a spanning cell, and the hover highlight follows a
   <tr> that a step cell does not belong to. */
.doc-recipe .doc-recipe-table .tbl-body tr:nth-child(even),
.dark .doc-recipe .doc-recipe-table .tbl-body tr:nth-child(even),
.doc-recipe .doc-recipe-table .tbl-body .tbl-row:hover {
	background-color: transparent;
}
.doc-recipe .doc-recipe-table .tbl-row {
	border-bottom: 0;
	transition: none;
}

/* Phone: the table is allowed to be wider than the screen and scroll, because
   squeezing five columns into 360px is what turns a step into one word per
   line. The sticky ingredient column is what makes that scroll survivable. */
@media (max-width: 48rem) {
	.doc-recipe .doc-recipe-table {
		font-size: var(--text-xs);
	}
	.doc-recipe .doc-recipe-table th,
	.doc-recipe .doc-recipe-table td {
		padding: 0.4rem 0.5rem;
	}
	.doc-recipe .doc-recipe-ing {
		min-width: 7rem;
		max-width: 9.5rem;
		font-size: var(--text-xs);
	}
	.doc-recipe .doc-recipe-do {
		min-width: 5.5rem;
	}
}

/* documento_table.css */
/* The first column stays put while the rest scrolls. On a phone it is the
   only thing that keeps a wide table legible: without it you scroll right
   and lose track of which row you are reading. */
.doc-table-frame table { border-collapse: separate; border-spacing: 0; }
.doc-table-frame th:first-child,
.doc-table-frame td:first-child {
	position: sticky;
	left: 0;
	z-index: 2;
	background: var(--background);
}
.doc-table-frame thead th { position: sticky; top: 0; z-index: 3; background: var(--background); }
.doc-table-frame thead th:first-child { z-index: 4; }

/* A header is two controls now: the label sorts, the caret opens the
   column menu. Neither looks like a control at rest. */
.doc-table-frame thead th[data-col] {
	user-select: none;
	position: relative;
	padding-right: 1.35rem;
}
.doc-th {
	display: inline-flex;
	align-items: center;
	gap: 0.25rem;
	color: inherit;
	text-decoration: none;
	border-radius: var(--radius);
	cursor: pointer;
	transition: color 150ms ease;
}
.doc-num .doc-th { flex-direction: row-reverse; }
.doc-th:hover { color: var(--foreground); }
.doc-th:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--ring); }
.doc-th svg { width: 0.7rem; height: 0.7rem; flex: none; }
/* The sorted column is the one thing in the header that gets emphasis —
   an arrow on every column would be six arrows saying nothing. */
.doc-th[data-sort] { color: var(--foreground); font-weight: 600; }

/* The menu trigger is a hover affordance on a mouse and long-press on
   touch, so it never spends width in a phone-width header at rest. */
.doc-th-menu {
	position: absolute;
	top: 50%;
	right: 0.15rem;
	transform: translateY(-50%);
	display: grid;
	place-items: center;
	width: 1.1rem;
	height: 1.1rem;
	padding: 0;
	border: 0;
	border-radius: var(--radius);
	background: none;
	color: var(--muted-foreground);
	cursor: pointer;
	opacity: 0;
	transition: opacity 120ms ease;
}
.doc-th-menu svg { width: 0.65rem; height: 0.65rem; }
th:hover > .doc-th-menu,
.doc-th-menu:focus-visible { opacity: 1; }
.doc-th-menu:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--ring); }
/* No hover on touch, and long-press is the road there. A permanently
   drawn caret in every header is width a phone does not have. */
@media (pointer: coarse) {
	.doc-th-menu { display: none; }
	.doc-table-frame thead th[data-col] { padding-right: 0.5rem; }
}

/* The bar above the table. It never swaps — see docTable. */
.doc-table-bar {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	flex-wrap: wrap;
	padding-bottom: 0.55rem;
}
.doc-search { flex: 1 1 12rem; max-width: 22rem; }
.doc-search-clear {
	display: grid;
	place-items: center;
	color: var(--muted-foreground);
}
.doc-search-clear:hover { color: var(--foreground); }
.doc-search-clear svg { width: 0.85rem; height: 0.85rem; }

.doc-sizes { display: flex; align-items: center; gap: 0.15rem; }
.doc-sizes-label { font-size: var(--text-xs); margin-right: 0.15rem; }
.doc-size {
	padding: 0.15rem 0.4rem;
	border-radius: var(--radius);
	color: var(--muted-foreground);
	font-size: var(--text-xs);
	font-variant-numeric: tabular-nums;
	text-decoration: none;
	transition: color 150ms ease, background-color 150ms ease;
}
.doc-size:hover { color: var(--foreground); background: var(--muted); }
.doc-size[data-on="true"] { color: var(--primary); background: var(--muted); font-weight: 600; }

.doc-row-add { color: var(--muted-foreground); }
.doc-row-add:hover { color: var(--foreground); }

/* Inline cell editor. It replaces the cell in place and keeps the column
   width, so the table does not jump when one opens. */
.doc-cell-input {
	width: 100%;
	min-width: 4rem;
	padding: 0.15rem 0.3rem;
	border: 1px solid var(--ring);
	border-radius: var(--radius);
	background: var(--background);
	color: inherit;
	font: inherit;
	font-size: var(--text-base);
	outline: none;
}
.doc-num .doc-cell-input { text-align: right; }

/* Row sheet: the phone path. Comfortable spacing, full-width controls,
   nothing to aim at. */
.doc-row-body { max-height: 65vh; overflow-y: auto; padding: 0 0.25rem 0.5rem; }
.doc-sheet { display: flex; flex-direction: column; gap: 0.85rem; }
.doc-sheet-field { display: flex; flex-direction: column; gap: 0.35rem; }
.doc-sheet-field input { width: 100%; }
.doc-sheet-actions {
	display: flex;
	justify-content: flex-start;
	margin-top: 0.4rem;
	padding-top: 0.75rem;
	border-top: 1px solid var(--border);
}
.doc-sheet-del { color: var(--destructive); }

/* Column menu */
.doc-colmenu { min-width: 15rem; }
.doc-col-name { width: 100%; margin-bottom: 0.4rem; }
.doc-col-types { display: flex; flex-wrap: wrap; gap: 0.25rem; }
.doc-col-type {
	flex: 1 1 auto;
	padding: 0.35rem 0.5rem;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: none;
	color: var(--muted-foreground);
	font: inherit;
	font-size: var(--text-xs);
	cursor: pointer;
}
.doc-col-type[data-on] {
	border-color: var(--primary);
	color: var(--primary);
	background: var(--muted);
}

/* documentos_lista.css */
/* The documents index. The cards themselves are components/list-card.css —
   shared with /conversas — so what is left here is the page around them: a
   stack of top-level documents, no heading and no nesting. */
.doclist {
	width: 100%;
	max-width: 48rem;
	margin: 0 auto;
	padding: 1.5rem 1rem 2rem;
	display: flex;
	flex-direction: column;
	gap: 1.25rem;
}
.doclist-empty { color: var(--muted-foreground); font-size: var(--text-base); }

/* perfil.css */
.perfil { flex: 1; min-height: 0; overflow-y: auto; padding: 1.5rem 1rem 3rem; }
.perfil > * { width: 100%; max-width: 40rem; margin-left: auto; margin-right: auto; }
.perfil-head { margin-bottom: 1.5rem; }
.perfil-head h1 { margin-bottom: 0.25rem; }
.perfil-familia { display: flex; align-items: flex-start; gap: 0.75rem; margin-bottom: 1.5rem; padding: 0.875rem 1rem; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--muted); box-shadow: var(--shadow-well); }
.perfil-familia svg { width: 1.25rem; height: 1.25rem; flex-shrink: 0; margin-top: 0.1rem; color: var(--muted-foreground); }
.perfil-familia p { margin: 0; }
.perfil-familia strong { color: var(--foreground); font-weight: 500; }
.perfil-actions { display: flex; align-items: center; gap: 0.75rem; margin-top: 1rem; }
.perfil-status { min-height: 1.25rem; }
.perfil-saved { display: inline-flex; align-items: center; gap: 0.3rem; font-size: var(--text-base); color: var(--success); animation: perfil-fade 200ms ease-out; }
.perfil-saved svg { width: 1rem; height: 1rem; }
@keyframes perfil-fade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .perfil-saved { animation: none; } }

.perfil-push { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 2.5rem; padding-top: 2rem; border-top: 1px solid var(--border); }
.perfil-push-title { font-size: var(--text-lg); }

/* primitivos.css */
/* The primitives page. Every specimen inside a card is real document markup,
   so this file styles the frame around the specimens and nothing inside
   them — a rule here that reached a .doc-* class would be this page
   quietly disagreeing with the documents it is describing. */
.prims {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	padding: 1.5rem 1rem 4rem;
}
.prims > * {
	width: 100%;
	max-width: 46rem;
	margin-left: auto;
	margin-right: auto;
}

.prims-head { margin-bottom: 2rem; }
.prims-head h1 { margin-bottom: 0.35rem; }

/* The index is wayfinding, so it reads as a row of quiet chips rather than
   a list of links. It wraps instead of scrolling: on a phone two lines of
   six chips is legible, a horizontal scroller hides half of them. */
.prims-index {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
	margin-top: 1rem;
}
.prims-chip {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	padding: 0.3rem 0.65rem;
	border-radius: 999px;
	background: var(--muted);
	font-size: var(--text-sm);
	font-weight: 500;
	color: var(--muted-foreground);
	text-decoration: none;
	transition: background-color 150ms ease, color 150ms ease;
}
.prims-chip:hover { background: var(--accent); color: var(--foreground); }
.prims-chip svg { width: 0.85rem; height: 0.85rem; }

/* Sections. The heading and the note are reached by descent, not by a class
   of their own: naming a class on a Typography component replaces the
   component's (h.Merge means "caller overrides"), and typo-h2 is exactly
   the scale a section heading wants. */
.prims-sec { margin-bottom: 2.5rem; }
.prims-sec > .typo-h2 { margin-bottom: 0.6rem; }
.prims-sec > .typo-muted { margin-bottom: 1.25rem; }
.prims-sec > .card + .card { margin-top: 1rem; }

/* One specimen. The render leads and the source follows: the thing itself
   is what someone came to see, and the markdown is the caption. */
.prim-body { display: flex; flex-direction: column; gap: 0.9rem; }
.prim-render {
	padding: 0.9rem 1rem;
	border-radius: var(--radius-lg);
	background: color-mix(in oklab, var(--muted) 45%, transparent);
}
/* A data block brings its own surface — a card for a chart or a number, a
   frame for a table, a border for an image or a file — so it gets no second
   one underneath. The tint is for prose, which has none of its own. It also
   has to go for the table specifically: the first column and the header row
   are sticky and paint --background, which over a tinted panel reads as a
   highlighted column. */
.prim-render:has(.doc-blk-data) {
	padding: 0;
	background: none;
}
/* The specimens are read, never edited, so the caret never appears in one
   and a text cursor over the line would promise otherwise. */
.prim-render .doc-line { cursor: default; }

/* A table's first column and header row are sticky, so they paint an opaque
   colour to let the rest scroll under them — and that colour is the page's,
   which inside a card is a shade off. Repaint them with the card's face:
   dropping the background instead would make the columns overlap while
   scrolling a narrow screen sideways. */
.prim-render .doc-table-frame th,
.prim-render .doc-table-frame td:first-child { background: var(--card); }

/* Source. Mono, small, scrollable sideways — a long JSON line must not
   widen the page on a phone. */
.prim-srcwrap { display: flex; flex-direction: column; gap: 0.3rem; }
.prim-srclabel {
	font-size: var(--text-2xs);
	font-weight: 500;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--muted-foreground);
}
.prim-src {
	margin: 0;
	overflow-x: auto;
	padding: 0.7rem 0.85rem;
	border-radius: var(--radius-md);
	background: var(--muted);
	font-family: var(--font-mono);
	font-size: var(--text-xs);
	line-height: 1.55;
	color: var(--foreground);
	white-space: pre;
	tab-size: 2;
}
.prim-src code { font-family: inherit; }

/* A folded spec sits inside a card already, so it drops the card face and
   keeps only the divider — two stacked surfaces read as a mistake. */
.prim-fold { background: none; box-shadow: none; border-radius: var(--radius-md); }
.prim-fold > .collapsible-trigger { padding: 0.5rem 0; font-size: var(--text-sm); }
.prim-fold > .collapsible-trigger:hover { background: none; color: var(--foreground); }
.prim-fold > .collapsible-content { padding: 0 0 0.25rem; }
.prim-fold-label {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	color: var(--muted-foreground);
	font-weight: 500;
}
.prim-fold-label svg { width: 0.9rem; height: 0.9rem; }
.prim-fold-caret { display: inline-flex; flex-shrink: 0; }
.prim-fold-caret svg { width: 0.75rem; height: 0.75rem; color: var(--muted-foreground); transition: transform 250ms cubic-bezier(0.34, 1.4, 0.64, 1); }
.prim-fold[open] .prim-fold-caret svg { transform: rotate(180deg); }

/* Charts: two up where there is room, one per row on a phone. The type
   name sits above each card because that string is what you ask for. */
.prim-charts { display: grid; gap: 0.75rem; }
.prim-chart { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }
.prim-chart-type {
	font-family: var(--font-mono);
	font-size: var(--text-2xs);
	color: var(--muted-foreground);
}
.prim-chart .doc-blk { margin: 0; }

.prim-stats { display: grid; gap: 0.75rem; }
.prim-stats .doc-blk { margin: 0; }

.prim-grid { display: grid; gap: 0.75rem; }
.prim-mini-body { display: flex; align-items: flex-start; gap: 0.75rem; }
.prim-mini-icon { display: inline-flex; color: var(--muted-foreground); flex-shrink: 0; }
.prim-mini-icon svg { width: 1.15rem; height: 1.15rem; }
.prim-mini-text .typo-small { display: block; margin-bottom: 0.25rem; }

/* Closing block: the sentences that produce everything above. */
.prims-asks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.prims-asks li {
	display: flex;
	align-items: flex-start;
	gap: 0.6rem;
	padding: 0.65rem 0.85rem;
	border-radius: var(--radius-lg);
	background: color-mix(in oklab, var(--muted) 45%, transparent);
	font-size: var(--text-base);
	line-height: 1.5;
}
.prims-asks svg { width: 0.9rem; height: 0.9rem; flex-shrink: 0; margin-top: 0.25rem; color: var(--muted-foreground); }

@media (min-width: 48rem) {
	.prims { padding: 2rem 2rem 5rem; }
	.prim-charts { grid-template-columns: 1fr 1fr; }
	.prim-stats { grid-template-columns: repeat(3, 1fr); }
	.prim-grid { grid-template-columns: 1fr 1fr; }
}

