/*
 * components.css — the shared component set.
 *
 * DR-4 asks for a single consistent component set, because the previous build
 * accumulated several competing button styles and it showed. So: ONE button,
 * three variants, two sizes. One card. One pill. If a screen needs a button
 * that is not here, that is a design conversation, not a new class.
 *
 * Every component takes its values from tokens.css. No hex codes below this
 * line.
 *
 * @package Uloominate
 */

/* ============================================================= BUTTON == */

.ulm-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--ulm-space-2);
	height: 48px;
	padding-inline: var(--ulm-space-6);
	border: 1px solid transparent;
	border-radius: var(--ulm-radius-sm);
	font-family: var(--ulm-font);
	font-size: var(--ulm-text-base);
	font-weight: var(--ulm-fw-semibold);
	line-height: 1;
	text-align: center;
	white-space: nowrap;
	cursor: pointer;
	transition:
		transform var(--ulm-duration-fast) var(--ulm-ease),
		box-shadow var(--ulm-duration-fast) ease,
		background-color var(--ulm-duration-fast) ease,
		border-color var(--ulm-duration-fast) ease;
}

.ulm-btn:disabled,
.ulm-btn[aria-disabled="true"] {
	opacity: .55;
	cursor: not-allowed;
	transform: none !important;
	box-shadow: none !important;
}

/* --- variants ------------------------------------------------------- */

.ulm-btn--primary {
	background: var(--ulm-primary);
	color: var(--ulm-ink-invert);
}

.ulm-btn--primary:hover:not(:disabled) {
	color: var(--ulm-ink-invert);
	transform: translateY(-2px);
	box-shadow: var(--ulm-shadow-lift);
}

.ulm-btn--secondary {
	background: var(--ulm-surface);
	color: var(--ulm-deep);
	border-color: var(--ulm-border-strong);
}

.ulm-btn--secondary:hover:not(:disabled) {
	color: var(--ulm-deep);
	transform: translateY(-2px);
	box-shadow: var(--ulm-shadow-md);
}

/* Gold. The design uses it for Login and the vendor entry point — a third
   emphasis level between primary and secondary, not a fourth button. */
.ulm-btn--gold {
	background: var(--ulm-gold);
	color: var(--ulm-deep);
}

.ulm-btn--gold:hover:not(:disabled) {
	color: var(--ulm-deep);
	transform: translateY(-2px);
	box-shadow: var(--ulm-shadow-md);
}

.ulm-btn--ghost {
	background: transparent;
	color: var(--ulm-primary);
	padding-inline: var(--ulm-space-3);
}

.ulm-btn--ghost:hover:not(:disabled) {
	background: var(--ulm-tint-mint);
	color: var(--ulm-deep);
}

.ulm-btn--danger {
	background: var(--ulm-danger);
	color: var(--ulm-ink-invert);
}

.ulm-btn--danger:hover:not(:disabled) {
	color: var(--ulm-ink-invert);
	transform: translateY(-2px);
}

/* --- sizes ---------------------------------------------------------- */

.ulm-btn--lg {
	height: 68px;             /* the hero CTA height in the Figma file */
	height: 58px;             
	padding-inline: var(--ulm-space-7);
	font-size: var(--ulm-text-lg);
	border-radius: var(--ulm-radius-sm);
}

.ulm-btn--sm {
	height: 36px;
	padding-inline: var(--ulm-space-4);
	font-size: var(--ulm-text-sm);
}

.ulm-btn--block {
	width: 100%;
}

.ulm-btn--icon {
	width: 44px;
	height: 44px;
	padding: 0;
	border-radius: var(--ulm-radius-pill);
}

/* =============================================================== CARD == */

/*
 * The Figma file gives cards no border and no shadow. Both are added here,
 * deliberately understated: a white card on a white section is otherwise
 * invisible in a browser (deviation D-3).
 */
.ulm-card {
	position: relative;
	display: flex;
	flex-direction: column;
	background: var(--ulm-surface);
	border: 1px solid var(--ulm-border);
	border-radius: var(--ulm-radius-lg);
	box-shadow: var(--ulm-shadow-sm);
	overflow: hidden;
	transition:
		transform var(--ulm-duration-base) var(--ulm-ease),
		box-shadow var(--ulm-duration-base) ease;
}

.ulm-card--interactive:hover {
	transform: translateY(-4px);
	box-shadow: var(--ulm-shadow-md);
}

.ulm-card__media {
	position: relative;
	aspect-ratio: 4 / 3;
	background: var(--ulm-surface-alt);
	overflow: hidden;
}

.ulm-card__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform var(--ulm-duration-slow) var(--ulm-ease);
}

.ulm-card--interactive:hover .ulm-card__media img {
	transform: scale(1.04);
}

.ulm-card__body {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--ulm-space-2);
	padding: var(--ulm-space-4);
}

.ulm-card__title {
	font-size: var(--ulm-text-base);
	font-weight: var(--ulm-fw-medium);
	line-height: var(--ulm-leading-snug);
	color: var(--ulm-deep);
	/* Two lines, then ellipsis. The seed deliberately includes a
	   160-character title so this gets exercised during the build. */
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.ulm-card__meta {
	font-size: var(--ulm-text-2xs);
	color: var(--ulm-ink-muted);
}

.ulm-card__footer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--ulm-space-3);
	margin-top: auto;
	padding-top: var(--ulm-space-2);
}

/* ============================================================== PRICE == */

.ulm-price {
	display: inline-flex;
	align-items: baseline;
	gap: var(--ulm-space-2);
	font-family: var(--ulm-font-numeric);
}

.ulm-price__now {
	font-size: var(--ulm-text-lg);
	font-weight: var(--ulm-fw-bold);
	color: var(--ulm-ink-alt);
}

.ulm-price__was {
	font-size: var(--ulm-text-sm);
	color: var(--ulm-ink-faint);
	text-decoration: line-through;
}

.ulm-price--free .ulm-price__now {
	color: var(--ulm-primary);
}

.ulm-discount {
	display: inline-flex;
	align-items: center;
	padding: 2px var(--ulm-space-2);
	background: var(--ulm-danger);
	color: var(--ulm-ink-invert);
	border-radius: var(--ulm-radius-xs);
	font-family: var(--ulm-font);
	font-size: var(--ulm-text-2xs);
	font-weight: var(--ulm-fw-semibold);
	letter-spacing: .02em;
}

/* ============================================================= RATING == */

.ulm-rating {
	display: inline-flex;
	align-items: center;
	gap: var(--ulm-space-1);
}

.ulm-rating__stars {
	display: inline-flex;
	color: var(--ulm-star);
}

.ulm-rating__stars svg + svg {
	margin-inline-start: 1px;
}

.ulm-rating__star--empty {
	color: var(--ulm-track);
}

.ulm-rating__count {
	font-family: var(--ulm-font-numeric);
	font-size: var(--ulm-text-2xs);
	color: var(--ulm-ink-faint);
}

/* Distribution bars on the product page (FR-7.7). */
.ulm-rating-bar {
	display: grid;
	grid-template-columns: auto 1fr auto;
	align-items: center;
	gap: var(--ulm-space-3);
	font-size: var(--ulm-text-sm);
}

.ulm-rating-bar__track {
	height: 8px;
	background: var(--ulm-track);
	border-radius: var(--ulm-radius-pill);
	overflow: hidden;
}

.ulm-rating-bar__fill {
	display: block;
	height: 100%;
	background: var(--ulm-star);
	background: rgb(58, 131, 107);
	border-radius: inherit;
}

/* =============================================================== PILL == */

.ulm-pill {
	display: inline-flex;
	align-items: center;
	gap: var(--ulm-space-1);
	padding: var(--ulm-space-1) var(--ulm-space-3);
	border-radius: var(--ulm-radius-pill);
	font-size: var(--ulm-text-2xs);
	font-weight: var(--ulm-fw-medium);
	line-height: 1.6;
	white-space: nowrap;
}

.ulm-pill--success { background: var(--ulm-success-bg); color: var(--ulm-success); }
.ulm-pill--warning { background: var(--ulm-warning-bg); color: var(--ulm-warning); }
.ulm-pill--danger  { background: var(--ulm-danger-bg);  color: var(--ulm-danger); }
.ulm-pill--info    { background: var(--ulm-info-bg);    color: var(--ulm-info); }
.ulm-pill--muted   { background: var(--ulm-surface-alt); color: var(--ulm-ink-muted); }

/* Category and subject tags on the product page. */
.ulm-tag {
	display: inline-flex;
	align-items: center;
	padding: var(--ulm-space-1) var(--ulm-space-3);
	background: var(--ulm-tint-mint);
	color: var(--ulm-deep);
	border-radius: var(--ulm-radius-sm);
	font-size: var(--ulm-text-2xs);
	font-weight: var(--ulm-fw-medium);
}

/* ============================================================= FORMS == */

.ulm-field {
	display: flex;
	flex-direction: column;
	gap: var(--ulm-space-2);
}

.ulm-field__label {
	font-size: var(--ulm-text-sm);
	font-weight: var(--ulm-fw-medium);
	color: var(--ulm-deep);
}

.ulm-field__required {
	color: var(--ulm-danger);
}

.ulm-input,
.ulm-select,
.ulm-textarea {
	width: 100%;
	min-height: 48px;
	padding: var(--ulm-space-3) var(--ulm-space-4);
	background: var(--ulm-surface);
	border: 1px solid var(--ulm-border-input);
	border-radius: var(--ulm-radius-sm);
	font-size: var(--ulm-text-base);
	color: var(--ulm-ink);
	transition: border-color var(--ulm-duration-fast) ease, box-shadow var(--ulm-duration-fast) ease;
}

.ulm-input::placeholder,
.ulm-textarea::placeholder {
	color: var(--ulm-ink-faint);
}

.ulm-input:focus,
.ulm-select:focus,
.ulm-textarea:focus {
	outline: none;
	border-color: var(--ulm-primary);
	box-shadow: 0 0 0 3px rgba(58, 131, 107, .14);
}

.ulm-textarea {
	min-height: 120px;
	line-height: var(--ulm-leading-normal);
	resize: vertical;
}

.ulm-select {
	appearance: none;
	padding-inline-end: var(--ulm-space-10);
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%2329483C' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: right var(--ulm-space-4) center;
}

/* Field-level error, matching the API contract's 422 `fields` shape. */
.ulm-field--error .ulm-input,
.ulm-field--error .ulm-select,
.ulm-field--error .ulm-textarea {
	border-color: var(--ulm-danger);
}

.ulm-field__error {
	display: flex;
	align-items: center;
	gap: var(--ulm-space-1);
	font-size: var(--ulm-text-xs);
	color: var(--ulm-danger);
}

.ulm-field__help {
	font-size: var(--ulm-text-xs);
	color: var(--ulm-ink-muted);
}

.ulm-checkbox {
	display: flex;
	align-items: flex-start;

	gap: var(--ulm-space-3);
	cursor: pointer;
	font-size: var(--ulm-text-sm);
	line-height: var(--ulm-leading-normal);
}

.ulm-checkbox input,
.ulm-checkbox .ulm-mfield input[type="checkbox"] {
	flex: 0 0 auto;
	width: 20px;
	height: 20px !important;
	margin-top: 2px;
	accent-color: var(--ulm-primary);
	cursor: pointer;
}

/* Search field in the header. */
.ulm-search {
	display: flex;
	align-items: center;
	gap: var(--ulm-space-2);
	height: 48px;
	padding-inline: var(--ulm-space-4);
	background: var(--ulm-surface);
	border: 1px solid var(--ulm-border);
	border-radius: var(--ulm-radius-pill);
}

.ulm-search input {
	flex: 1;
	min-width: 0;
	border: 0;
	background: transparent;
	font-size: var(--ulm-text-sm);
}

.ulm-search input:focus {
	outline: none;
}

/* ============================================================ STATES == */

/*
 * Empty, loading and error states do not exist anywhere in the Figma file
 * (gap G-4). Designed here from the same tokens: an icon, one explanatory
 * line, one action. Never a dead end.
 */
.ulm-empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--ulm-space-4);
	padding: var(--ulm-space-16) var(--ulm-space-6);
	text-align: center;
}

.ulm-empty__icon {
	display: grid;
	place-items: center;
	width: 72px;
	height: 72px;
	background: var(--ulm-tint-mint);
	color: var(--ulm-primary);
	border-radius: var(--ulm-radius-pill);
}

.ulm-empty__title {
	font-size: var(--ulm-text-xl);
	color: var(--ulm-deep);
}

.ulm-empty__body {
	max-width: 46ch;
	color: var(--ulm-ink-muted);
}

/* Skeletons, not spinners — a skeleton tells you what is coming. */
.ulm-skeleton {
	background: linear-gradient(
		90deg,
		var(--ulm-surface-alt) 25%,
		#e9ecef 37%,
		var(--ulm-surface-alt) 63%
	);
	background-size: 400% 100%;
	border-radius: var(--ulm-radius-sm);
	animation: ulm-shimmer 1.4s ease infinite;
}

@keyframes ulm-shimmer {
	from { background-position: 100% 50%; }
	to   { background-position: 0 50%; }
}

.ulm-skeleton--text { height: 1em; margin-block: .25em; }
.ulm-skeleton--title { height: 1.6em; width: 70%; }
.ulm-skeleton--media { aspect-ratio: 4 / 3; border-radius: 0; }

/* Inline notice — used for decline reasons, cap warnings, held states. */
.ulm-notice {
	display: flex;
	gap: var(--ulm-space-3);
	padding: var(--ulm-space-4) var(--ulm-space-5);
	border-radius: var(--ulm-radius-md);
	border-inline-start: 4px solid;
	font-size: var(--ulm-text-sm);
	line-height: var(--ulm-leading-normal);
}

.ulm-notice--info    { background: var(--ulm-info-bg);    border-color: var(--ulm-info);    color: var(--ulm-deep); }
.ulm-notice--success { background: var(--ulm-success-bg); border-color: var(--ulm-success); color: var(--ulm-deep); }
.ulm-notice--warning { background: var(--ulm-warning-bg); border-color: var(--ulm-warning); color: var(--ulm-deep); }
.ulm-notice--danger  { background: var(--ulm-danger-bg);  border-color: var(--ulm-danger);  color: var(--ulm-deep); }

.ulm-notice__action {
	margin-top: 6px;
}

.ulm-notice__action a {
	font-family: var(--ulm-font-m3);
	font-size: 13px;
	font-weight: var(--ulm-fw-semibold);
	color: currentColor;
	text-decoration: underline;
	text-underline-offset: 3px;
}

.ulm-notice__title {
	font-weight: var(--ulm-fw-semibold);
}

/* ============================================================== TOAST == */

.ulm-toasts {
	position: fixed;
	inset-block-end: var(--ulm-space-6);
	inset-inline-end: var(--ulm-space-6);
	z-index: var(--ulm-z-toast);
	display: flex;
	flex-direction: column;
	gap: var(--ulm-space-3);
	pointer-events: none;
}

.ulm-toast {
	display: flex;
	align-items: center;
	gap: var(--ulm-space-3);
	min-width: 280px;
	max-width: 420px;
	padding: var(--ulm-space-4) var(--ulm-space-5);
	background: var(--ulm-deep);
	color: var(--ulm-ink-invert);
	border-radius: var(--ulm-radius-md);
	box-shadow: var(--ulm-shadow-lg);
	font-size: var(--ulm-text-sm);
	pointer-events: auto;
	animation: ulm-rise var(--ulm-duration-base) var(--ulm-ease) both;
}

.ulm-toast--danger { background: var(--ulm-danger); }

/* ========================================================= BREADCRUMB == */

.ulm-breadcrumb {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--ulm-space-2);
	font-size: var(--ulm-text-sm);
	color: var(--ulm-ink-muted);
}

.ulm-breadcrumb a { color: var(--ulm-ink-muted); }
.ulm-breadcrumb a:hover { color: var(--ulm-primary); }

.ulm-breadcrumb__sep { color: var(--ulm-track); }

.ulm-breadcrumb [aria-current="page"] {
	color: var(--ulm-deep);
	font-weight: var(--ulm-fw-medium);
}

/* ========================================================= PAGINATION == */

/* Drawn exactly as the Figma browse page shows it: 1 2 3 4 … 48 with arrows. */
.ulm-pagination {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--ulm-space-2);
	font-family: var(--ulm-font-numeric);
}

.ulm-pagination__item {
	display: grid;
	place-items: center;
	min-width: 40px;
	height: 40px;
	padding-inline: var(--ulm-space-2);
	border: 1px solid var(--ulm-border);
	border-radius: var(--ulm-radius-sm);
	color: var(--ulm-deep);
	font-size: var(--ulm-text-sm);
	font-weight: var(--ulm-fw-medium);
	transition: background-color var(--ulm-duration-fast) ease, border-color var(--ulm-duration-fast) ease;
}

.ulm-pagination__item:hover {
	background: var(--ulm-tint-mint);
	border-color: var(--ulm-primary);
	color: var(--ulm-deep);
}

.ulm-pagination__item[aria-current="page"] {
	background: var(--ulm-primary);
	border-color: var(--ulm-primary);
	color: var(--ulm-ink-invert);
}

.ulm-pagination__ellipsis {
	color: var(--ulm-ink-faint);
	padding-inline: var(--ulm-space-1);
}

/* ============================================================= FACETS == */

.ulm-facets {
	display: flex;
	flex-direction: column;
	gap: var(--ulm-space-6);
}

.ulm-facet {
	border-bottom: 1px solid var(--ulm-border);
	padding-bottom: var(--ulm-space-5);
}

.ulm-facet:last-child { border-bottom: 0; }

.ulm-facet__toggle {
	display: flex;
	align-items: center;
	justify-content: space-between;
	width: 100%;
	gap: var(--ulm-space-3);
	font-size: var(--ulm-text-base);
	font-weight: var(--ulm-fw-semibold);
	color: var(--ulm-deep);
	text-align: start;
}

.ulm-facet__toggle svg {
	flex: 0 0 auto;
	transition: transform var(--ulm-duration-fast) ease;
}

.ulm-facet__toggle[aria-expanded="false"] svg {
	transform: rotate(-90deg);
}

.ulm-facet__body {
	display: flex;
	flex-direction: column;
	gap: var(--ulm-space-3);
	margin-top: var(--ulm-space-4);
}

.ulm-facet__body[hidden] { display: none; }

.ulm-facet__option {
	display: flex;
	align-items: center;
	gap: var(--ulm-space-3);
	font-size: var(--ulm-text-sm);
	color: var(--ulm-ink);
	cursor: pointer;
}

.ulm-facet__option input {
	width: 18px;
	height: 18px;
	accent-color: var(--ulm-primary);
	cursor: pointer;
}

/*
 * A sub-category, indented under its parent. The rule sits on the depth custom
 * property rather than on a chain of `--child --child` classes, so the third
 * level (Maths and ELA both go that deep) needs no new class.
 */
.ulm-facet__option--child {
	padding-inline-start: calc(var(--ulm-facet-depth, 1) * 22px);
	font-size: var(--ulm-text-xs, 13px);
	color: var(--ulm-ink-muted);
}

.ulm-facet__option--child input {
	width: 15px;
	height: 15px;
}

/*
 * A category with sub-categories: the checkbox and its disclosure share a row.
 *
 * The chevron is its own button rather than part of the label, because
 * expanding a category and filtering by it are different intentions. One
 * control doing both would make it impossible to look inside a category
 * without also narrowing the results to it.
 */
.ulm-facet__branch {
	display: flex;
	flex-direction: column;
	gap: var(--ulm-space-2, 6px);
}

.ulm-facet__row {
	display: flex;
	align-items: center;
	gap: var(--ulm-space-2, 6px);
}

.ulm-facet__row > .ulm-facet__option {
	flex: 1 1 auto;
	min-width: 0;
}

.ulm-facet__expand {
	display: grid;
	flex: 0 0 auto;
	place-items: center;
	width: 26px;
	height: 26px;
	padding: 0;
	border: 1px solid transparent;
	border-radius: 6px;
	background: none;
	color: var(--ulm-ink-muted);
	cursor: pointer;
	transition: background var(--ulm-duration-fast) ease,
		color var(--ulm-duration-fast) ease,
		transform var(--ulm-duration-fast) ease;
}

.ulm-facet__expand:hover {
	background: var(--ulm-tint-mint);
	color: var(--ulm-deep);
}

.ulm-facet__expand:focus-visible {
	border-color: var(--ulm-primary);
	color: var(--ulm-deep);
}

/* Points right when closed, down when open — the affordance in the design. */
.ulm-facet__expand[aria-expanded="true"] {
	transform: rotate( 90deg );
	color: var(--ulm-primary);
}

.ulm-facet__children {
	display: flex;
	flex-direction: column;
	gap: var(--ulm-space-2, 6px);
}

/* base.css sets display on generic elements, and an author rule beats the
 * browser's own [hidden] — so it has to be re-stated or nothing ever closes. */
.ulm-facet__children[hidden] {
	display: none;
}

.ulm-facet__count {
	margin-inline-start: auto;
	font-family: var(--ulm-font-numeric);
	font-size: var(--ulm-text-2xs);
	color: var(--ulm-ink-faint);
}

/*
 * The category facet used to be a bounded, scrolling box: ninety options, all
 * of them visible, ran the sidebar several screens past the results it is
 * meant to be filtering.
 *
 * The disclosures solved that at the source — collapsed, the list is the nine
 * top-level categories — so the cap and its scrollbar are gone. A nested
 * scroll region inside a page that already scrolls is a thing readers lose
 * their place in, and it was only ever paying for a problem that no longer
 * exists. Opening several branches makes the sidebar tall; that is the
 * reader's own doing, and the page scrolls.
 */
.ulm-facet[data-facet="category"] .ulm-facet__body {
	padding-inline-end: 4px;
}


/* ========================================================= PRICE RANGE == */

/*
 * Two native range inputs laid on top of one track.
 *
 * Custom-drawn handles were the alternative and were rejected: these are
 * keyboard operable, announced as sliders with their values, and get the
 * platform's own touch targets — none of which comes free with divs and
 * pointer events. The cost is that both inputs cover the same area, so pointer
 * events are turned off on the input and back on for its thumb alone,
 * otherwise the upper one would swallow every click meant for the lower.
 */
.ulm-range {
	display: flex;
	flex-direction: column;
	gap: 10px;
	padding-top: 2px;
}

.ulm-range__readout {
	display: flex;
	justify-content: space-between;
	font-family: var(--ulm-font-numeric);
	font-size: var(--ulm-text-sm, 14px);
	font-weight: 600;
	color: var(--ulm-deep);
}

.ulm-range__ends {
	display: flex;
	justify-content: space-between;
	font-family: var(--ulm-font-numeric);
	font-size: var(--ulm-text-2xs, 11px);
	color: var(--ulm-ink-faint);
}

.ulm-range__track {
	position: relative;
	height: 22px;
}

/* The unfilled bar. */
.ulm-range__track::before {
	content: "";
	position: absolute;
	inset-inline: 0;
	top: 50%;
	height: 4px;
	margin-top: -2px;
	border-radius: 999px;
	background: var(--ulm-track, #c9c9c9);
}

/* The selected span, positioned by the script. */
.ulm-range__fill {
	position: absolute;
	top: 50%;
	height: 4px;
	margin-top: -2px;
	border-radius: 999px;
	background: var(--ulm-primary);
	inset-inline-start: 0;
	inline-size: 100%;
}

.ulm-range__track input[type="range"] {
	position: absolute;
	inset-inline: 0;
	top: 0;
	width: 100%;
	height: 22px;
	margin: 0;
	appearance: none;
	background: none;
	pointer-events: none;
}

.ulm-range__track input[type="range"]::-webkit-slider-thumb {
	appearance: none;
	pointer-events: auto;
	width: 18px;
	height: 18px;
	border: 2px solid var(--ulm-surface);
	border-radius: 50%;
	background: var(--ulm-primary);
	box-shadow: 0 1px 4px rgb(0 0 0 / 28%);
	cursor: grab;
}

.ulm-range__track input[type="range"]::-moz-range-thumb {
	pointer-events: auto;
	width: 18px;
	height: 18px;
	border: 2px solid var(--ulm-surface);
	border-radius: 50%;
	background: var(--ulm-primary);
	box-shadow: 0 1px 4px rgb(0 0 0 / 28%);
	cursor: grab;
}

.ulm-range__track input[type="range"]:focus-visible::-webkit-slider-thumb {
	outline: 2px solid var(--ulm-primary);
	outline-offset: 2px;
}

.ulm-range__track input[type="range"]:focus-visible::-moz-range-thumb {
	outline: 2px solid var(--ulm-primary);
	outline-offset: 2px;
}

/* =============================================================== TABS == */

.ulm-tabs {
	display: flex;
	gap: var(--ulm-space-6);
	border-bottom: 1px solid var(--ulm-border);
	overflow-x: auto;
	scrollbar-width: none;
}

.ulm-tabs::-webkit-scrollbar { display: none; }

.ulm-tab {
	position: relative;
	padding-block: var(--ulm-space-4);
	color: var(--ulm-ink-muted);
	font-size: var(--ulm-text-base);
	font-weight: var(--ulm-fw-medium);
	white-space: nowrap;
	border-bottom: 2px solid transparent;
	margin-bottom: -1px;
	transition: color var(--ulm-duration-fast) ease, border-color var(--ulm-duration-fast) ease;
}

.ulm-tab:hover { color: var(--ulm-deep); }

.ulm-tab[aria-selected="true"] {
	color: var(--ulm-primary);
	border-bottom-color: var(--ulm-primary);
}

.ulm-tab__count {
	margin-inline-start: var(--ulm-space-2);
	font-family: var(--ulm-font-numeric);
	font-size: var(--ulm-text-2xs);
	color: var(--ulm-ink-faint);
}

/* ============================================================ AVATAR == */

.ulm-avatar {
	display: grid;
	place-items: center;
	flex: 0 0 auto;
	width: var(--size, 40px);
	height: var(--size, 40px);
	background: var(--ulm-tint-mint);
	color: var(--ulm-primary);
	border-radius: var(--ulm-radius-pill);
	font-size: calc(var(--size, 40px) * .38);
	font-weight: var(--ulm-fw-semibold);
	overflow: hidden;
}

.ulm-avatar img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* ======================================================== STAT TILE == */

.ulm-stat {
	display: flex;
	flex-direction: column;
	gap: var(--ulm-space-2);
	padding: var(--ulm-space-5);
	background: var(--ulm-surface);
	border: 1px solid var(--ulm-border);
	border-radius: var(--ulm-radius-md);
}

.ulm-stat__label {
	font-size: var(--ulm-text-sm);
	color: var(--ulm-ink-muted);
}

.ulm-stat__value {
	font-family: var(--ulm-font-numeric);
	font-size: var(--ulm-text-3xl);
	font-weight: var(--ulm-fw-semibold);
	line-height: 1.1;
	color: var(--ulm-deep);
}

.ulm-stat__delta {
	font-size: var(--ulm-text-xs);
	font-weight: var(--ulm-fw-medium);
}

.ulm-stat__delta--up   { color: var(--ulm-success); }
.ulm-stat__delta--down { color: var(--ulm-danger); }

/* ======================================================= WISHLIST BTN == */

.ulm-wish {
	position: absolute;
	inset-block-start: var(--ulm-space-3);
	inset-inline-end: var(--ulm-space-3);
	display: grid;
	place-items: center;
	width: 38px;
	height: 38px;
	background: rgba(255, 255, 255, .92);
	color: var(--ulm-ink-muted);
	border-radius: var(--ulm-radius-pill);
	box-shadow: var(--ulm-shadow-sm);
	transition: color var(--ulm-duration-fast) ease, transform var(--ulm-duration-fast) var(--ulm-ease);
}

.ulm-wish:hover { transform: scale(1.08); color: var(--ulm-danger); }

.ulm-wish[aria-pressed="true"] {
	color: var(--ulm-danger);
}

.ulm-wish[aria-pressed="true"] svg {
	fill: currentColor;
}


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

/*
 * ulm_field()'s markup. Shared rather than screen-specific: the same component
 * renders the auth screens, the resource editor, coupon creation, subscription
 * billing, the customer profile and the contact form. It lived in auth.css and
 * every form outside the auth flow was unstyled as a result.
 */

.ulm-mfield {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.ulm-mfield__label {
	font-family: var(--ulm-font-m3);
	font-size: 12px;
	color: var(--ulm-m3-outline);
}

.ulm-mfield__required { color: var(--ulm-danger); }

.ulm-mfield input,
.ulm-mfield select,
.ulm-mfield textarea {
	height: 52px;
	padding: 0 14px;
	border: 1px solid #CAC4D0;
	border-radius: 8px;
	background: var(--ulm-surface);
	font-family: var(--ulm-font);
	font-size: 15px;
	color: var(--ulm-ink);
	transition: border-color var(--ulm-duration-fast) ease, box-shadow var(--ulm-duration-fast) ease;
}

.ulm-mfield textarea {
	height: auto;
	min-height: 120px;
	padding: 12px 14px;
	line-height: 1.6;
	resize: vertical;
}

/* ------------------------------------------------- password reveal -- */

/*
 * The wrapper exists only on fields that opted into the show/hide button, so
 * it must not change how the input sits. `display: flex` with the input on
 * `flex: 1` reproduces the full-width block the input had as a direct child of
 * the column-flex .ulm-mfield.
 */
.ulm-mfield__control {
	position: relative;
	display: flex;
	align-items: stretch;
}

.ulm-mfield__control input {
	flex: 1 1 auto;
	min-width: 0;
	/* Room for the button, so a long password never runs under it. */
	padding-inline-end: 46px;
}

.ulm-mfield__reveal {
	position: absolute;
	inset-block: 1px;
	inset-inline-end: 1px;
	display: grid;
	place-items: center;
	width: 44px;
	padding: 0;
	color: var(--ulm-ink-muted);
	cursor: pointer;
	background: none;
	border: 0;
	/* Inside the input's rounded corner, so the focus ring is not clipped. */
	border-radius: 0 7px 7px 0;
}

.ulm-mfield__reveal:hover { color: var(--ulm-primary); }

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

.ulm-mfield input:focus,
.ulm-mfield select:focus,
.ulm-mfield textarea:focus {
	outline: none;
	border-color: var(--ulm-primary);
	box-shadow: 0 0 0 3px rgba(58, 131, 107, .14);
}

/* ------------------------------------------------ Stripe payment element -- */

/*
 * Why a hidden input decides what Stripe's card fields look like.
 *
 * The card number, expiry and CVC are rendered by Stripe inside an iframe, so
 * no stylesheet of ours can reach them. Instead the gateway reads the computed
 * style off one element on the page and sends it to Stripe as an Appearance —
 * border, radius, padding, font, colours, shadow — and Stripe rebuilds its
 * fields to match.
 *
 * Which element depends on the screen, and that is the whole bug:
 *
 *   checkout           #billing_first_name — a real field, already themed, so
 *                      the card fields there have always matched.
 *   payment methods    #wc-stripe-hidden-style-input — an off-screen probe the
 *   / add card         gateway prints purely to be measured.
 *
 * Nothing styled the probe. It was an unstyled <input>, so Stripe was handed
 * the browser's defaults — square corners and a 2px inset border — and drew the
 * card fields to match, next to the theme's own 8px-radius fields. Giving the
 * probe the same styling as `.ulm-mfield input` is what fixes it: these values
 * are not decoration here, they are the payload.
 *
 * Keep this in step with `.ulm-mfield input` above. Deliberately not written as
 * a shared selector: that rule styles fields somebody looks at, and this one
 * styles a measurement, and merging them invites someone to "tidy up" the
 * off-screen input out of existence.
 *
 * It must stay rendered. `display: none` would leave several of these
 * properties without a used value to read, and Stripe would fall back to its
 * own defaults again. The gateway already hides it with inline
 * `position:absolute; opacity:0; pointer-events:none`, so it costs no layout
 * and cannot be tabbed into.
 */
#wc-stripe-hidden-style-input {
	height: 52px;
	padding: 0 14px;
	border: 1px solid #CAC4D0;
	border-radius: 8px;
	background: var(--ulm-surface);
	font-family: var(--ulm-font);
	font-size: 15px;
	color: var(--ulm-ink);
	box-shadow: none;
	outline: none;
}

/*
 * The box Stripe mounts into, on the screens that are not the checkout.
 *
 * Same reasoning as the checkout rule in cart.css: the gateway prints this
 * container whether or not it has anything to mount, and an empty one drew a
 * stray outlined strip under the saved-card list.
 */
.wc-stripe-upe-element:empty { display: none; }

.ulm-mfield--error input,
.ulm-mfield--error select,
.ulm-mfield--error textarea {
	border-color: var(--ulm-danger);
}

/* Picture fields — store logo and banner.
 *
 * The frame is the control. The file input is inside it, hidden by clipping
 * rather than display:none so it keeps its place in the tab order, and the
 * whole frame is a <label> pointing at it — so a click anywhere opens the
 * picker with no script involved.
 *
 * The shape is not decoration. A logo is drawn as a circle everywhere it
 * appears on the storefront, so it is picked as a circle here; cropping
 * surprises belong before the upload, not after it.
 */
.ulm-imgfield {
	gap: 8px;
	margin: 10px 0 18px;
}

.ulm-imgfield__frame {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
	cursor: pointer;
	background: var(--ulm-surface-alt);
	border: 1px dashed #CAC4D0;
	color: var(--ulm-ink-faint);
	transition: border-color var(--ulm-duration-fast) ease, box-shadow var(--ulm-duration-fast) ease;
}

.ulm-imgfield--circle .ulm-imgfield__frame {
	width: 140px;
	height: 140px;
	border-radius: 50%;
}

.ulm-imgfield--wide .ulm-imgfield__frame {
	width: 100%;
	height: 150px;
	border-radius: var(--ulm-radius-lg);
}

.ulm-imgfield__frame:hover {
	border-color: var(--ulm-primary);
}

/* The input is invisible but focusable, so the ring has to be drawn here. */
.ulm-imgfield__frame:focus-within {
	border-color: var(--ulm-primary);
	box-shadow: 0 0 0 3px rgba(58, 131, 107, .14);
}

.ulm-imgfield__img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * `hidden` has to be re-stated here.
 *
 * base.css opens with `img, svg, video, canvas { display: block }`, and an
 * author rule beats the browser's own `[hidden] { display: none }` no matter
 * how specific that is. So an empty <img hidden> stayed in the layout: it took
 * `width: 100%` of the frame, pushed the camera placeholder out to the right
 * edge, and drew the browser's broken-image glyph in the corner. That is the
 * mangled picker in the screenshot, and it is a reset problem rather than
 * anything to do with these fields.
 */
.ulm-imgfield__img[hidden],
.ulm-imgfield__empty[hidden] {
	display: none;
}

.ulm-imgfield__empty {
	display: flex;
	align-items: center;
	justify-content: center;
}

/* Revealed on hover or keyboard focus, over whatever is behind it. */
.ulm-imgfield__overlay {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 3px;
	opacity: 0;
	background: rgba(17, 98, 84, .62);
	color: #fff;
	transition: opacity var(--ulm-duration-fast) ease;
}

.ulm-imgfield__frame:hover .ulm-imgfield__overlay,
.ulm-imgfield__frame:focus-within .ulm-imgfield__overlay {
	opacity: 1;
}

.ulm-imgfield__action {
	font-family: var(--ulm-font-m3);
	font-size: 12px;
	letter-spacing: .01em;
}

/* Clipped, not display:none — a display:none input cannot take focus, which
 * would put this field out of reach of the keyboard entirely. */
.ulm-imgfield .ulm-imgfield__input {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	border: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
}

/*
 * A field's own error message. It was 12px grey-weight red under a help line
 * of the same size, which read as a second help line — vendors were resubmitting
 * the store-setup wizard without ever seeing why it had bounced. Now it is a
 * step larger than the help text, semibold, and carries a marker so the eye
 * lands on it while scanning the column of fields.
 */
.ulm-mfield__error {
	display: flex;
	align-items: flex-start;
	gap: 6px;
	margin-top: 6px;
	font-family: var(--ulm-font-m3);
	font-size: 13px;
	font-weight: var(--ulm-fw-semibold);
	line-height: 1.45;
	color: var(--ulm-danger);
}

.ulm-mfield__error::before {
	content: "!";
	flex: 0 0 auto;
	width: 15px;
	height: 15px;
	margin-top: 1px;
	border-radius: 50%;
	background: var(--ulm-danger);
	color: #fff;
	font-size: 11px;
	font-weight: var(--ulm-fw-bold, 700);
	line-height: 15px;
	text-align: center;
}

.ulm-mfield__error[hidden] { display: none; }

/*
 * A supporting link under a field — the Terms and Conditions beside the
 * agreement checkbox on vendor registration.
 *
 * Indented to the checkbox's text rather than to its box, so it reads as part
 * of what is being agreed to instead of as a stray line under the form. The
 * 20px is the box width from `.ulm-checkbox input`, plus that rule's
 * `--ulm-space-3` gap.
 */
.ulm-mfield__aside {
	margin-top: 6px;
	margin-inline-start: calc(20px + var(--ulm-space-3));
	font-size: 13px;
	line-height: 1.5;
}

.ulm-mfield__asidelink {
	display: inline-flex;
	gap: 5px;
	align-items: center;
	font-weight: var(--ulm-fw-semibold);
	color: var(--ulm-primary);
	text-decoration: underline;
	text-underline-offset: 2px;
}

.ulm-mfield__asidelink:hover,
.ulm-mfield__asidelink:focus-visible { color: var(--ulm-deep); }

/* The arrow says "new window" without a second line of text saying so — the
   screen-reader wording is in the markup. */
.ulm-mfield__asidelink svg { flex: 0 0 auto; }

/*
 * The whole field, not only its outline, is tinted while it is wrong. A red
 * 1px border on a white input is invisible next to three correct fields.
 */
.ulm-mfield--error input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.ulm-mfield--error select,
.ulm-mfield--error textarea {
	background: var(--ulm-danger-bg);
}

.ulm-mfield--error .ulm-mfield__label { color: var(--ulm-danger); }

/* ---------------------------------------------------- form error summary -- */

/*
 * Everything wrong with a submission, gathered where the submit button is.
 * Sized as a block rather than a line: it has to survive being read by someone
 * whose eye is already on the button and about to click it again.
 */
.ulm-formalert {
	display: flex;
	align-items: flex-start;
	gap: var(--ulm-space-3);
	padding: var(--ulm-space-4) var(--ulm-space-5);
	border: 1px solid var(--ulm-danger);
	border-inline-start-width: 4px;
	border-radius: var(--ulm-radius-md);
	background: var(--ulm-danger-bg);
	color: var(--ulm-deep);
	font-size: var(--ulm-text-sm);
	line-height: var(--ulm-leading-normal);
}

/* Focused from script after a failed post — the ring would say nothing here. */
.ulm-formalert:focus { outline: none; }

.ulm-formalert__icon {
	flex: 0 0 auto;
	margin-top: 1px;
	line-height: 0;
	color: var(--ulm-danger);
}

.ulm-formalert__body { min-width: 0; }

.ulm-formalert__title {
	font-weight: var(--ulm-fw-semibold);
	color: var(--ulm-danger);
}

/*
 * The bullets are dropped for a single message — one bulleted item reads as a
 * list with the rest missing.
 */
.ulm-formalert__list {
	margin: 6px 0 0;
	padding-inline-start: 18px;
	list-style: disc;
}

.ulm-formalert__list:has(> li:only-child) {
	padding-inline-start: 0;
	list-style: none;
}

.ulm-formalert__list li + li { margin-top: 4px; }

.ulm-formalert__list a {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
}

.ulm-formalert__list a:hover,
.ulm-formalert__list a:focus-visible { color: var(--ulm-danger); }

/* So a followed link does not land the field under a sticky header. */
.ulm-mfield { scroll-margin-top: var(--ulm-space-6, 24px); }

.ulm-mfield__help {
	font-family: var(--ulm-font-m3);
	font-size: 12px;
	color: var(--ulm-m3-outline);
}

/*
 * The notice's leading icon. ulm_notice() has always emitted this element;
 * without a rule the icon sat on the text baseline and pushed the first line
 * of copy out of alignment with the rest.
 */
.ulm-notice__icon {
	flex: 0 0 auto;
	margin-top: 1px;
	line-height: 0;
	color: currentcolor;
}


/* ------------------------------------------------------------ plan picker -- */

/*
 * Choosing a tier. Shared, because it appears during vendor registration and
 * again on the subscription screen — two different areas, two different screen
 * stylesheets, one component.
 */

.ulm-planpick {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr));
	gap: 12px;
}

.ulm-planpick__option {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 6px;
	padding: 16px;
	border: 2px solid var(--ulm-border);
	border-radius: 12px;
	cursor: pointer;
	transition: border-color var(--ulm-duration-fast) ease, background var(--ulm-duration-fast) ease;
}

.ulm-planpick__option:hover { border-color: var(--ulm-border-strong); }

/* The whole tile is the control; the radio itself is visually hidden but
   still focusable, so keyboard and screen-reader users get the real thing. */
.ulm-planpick__option input {
	position: absolute;
	opacity: 0;
	pointer-events: none;
}

.ulm-planpick__option:has(input:checked) {
	border-color: var(--ulm-primary);
	background: var(--ulm-tint-mint);
}

.ulm-planpick__option:has(input:focus-visible) {
	outline: 2px solid var(--ulm-primary);
	outline-offset: 3px;
}

.ulm-planpick__name {
	font-weight: var(--ulm-fw-semibold);
	font-size: 15px;
	color: var(--ulm-deep);
}

.ulm-planpick__price {
	font-family: var(--ulm-font-numeric);
	font-weight: var(--ulm-fw-bold);
	font-size: 20px;
	color: var(--ulm-teal);
}

.ulm-planpick__blurb {
	font-size: 12.5px;
	line-height: 1.5;
	color: var(--ulm-ink-muted);
}

/* --- row variant (A-03 Register Vendor) --------------------------------- */

/*
 * The grid of tiles above is V-11 Subscription, where the plans are being
 * compared. A-03 is a different question — you are picking one line out of a
 * list while filling in a form beside it — so the design stacks them as full
 * width rows with a real radio dot showing. Same markup, same class names, one
 * modifier; the two screens looked identical before and neither matched.
 */
.ulm-planpick--rows {
	grid-template-columns: minmax(0, 1fr);
	gap: 10px;
}

.ulm-planpick--rows .ulm-planpick__option {
	display: grid;
	grid-template-columns: 18px minmax(0, 1fr);
	align-items: start;
	column-gap: 12px;
	row-gap: 3px;
	padding: 14px;
	border-width: 1.6px;
}

/* The dot is drawn here, so it stops being the hidden control it is in the
   tile variant and becomes the thing you actually see. */
.ulm-planpick--rows .ulm-planpick__option input {
	position: static;
	opacity: 1;
	pointer-events: auto;
	appearance: none;
	-webkit-appearance: none;
	grid-row: 1 / span 3;
	width: 18px;
	height: 18px;
	margin: 2px 0 0;
	border: 1.8px solid var(--ulm-border-strong);
	border-radius: 50%;
	background: var(--ulm-surface);
	cursor: pointer;
	padding: 0;
}

.ulm-planpick--rows .ulm-planpick__option input:checked {
	border-color: var(--ulm-primary);
	background: var(--ulm-primary);
	box-shadow: inset 0 0 0 3px var(--ulm-surface);
}

/* Name and price share a baseline; the blurb runs full width under both. */
.ulm-planpick--rows .ulm-planpick__name {
	grid-column: 2;
	align-self: baseline;
}

.ulm-planpick--rows .ulm-planpick__price {
	grid-column: 2;
	font-size: 14px;
	align-self: baseline;
}

.ulm-planpick--rows .ulm-planpick__blurb {
	grid-column: 2;
	font-size: 13px;
}

.ulm-planpick--rows .ulm-planpick__note {
	grid-column: 2;
	font-size: 12px;
	line-height: 1.5;
	color: var(--ulm-warning);
}

/*
 * Publishers is shown but not selectable (OI-3 — it is arranged by agreement).
 * Shown rather than hidden because a school or publisher needs to see that the
 * tier exists and who to ask; disabled rather than a link because picking it
 * here would promise terms nobody has negotiated.
 */
.ulm-planpick--rows .ulm-planpick__option--locked {
	opacity: .72;
	cursor: not-allowed;
}

.ulm-planpick--rows .ulm-planpick__option--locked input { cursor: not-allowed; }

.ulm-planpick--rows .ulm-planpick__option--locked:hover { border-color: var(--ulm-border); }

@media (min-width: 560px) {
	.ulm-planpick--rows .ulm-planpick__option {
		grid-template-columns: 18px auto minmax(0, 1fr);
		column-gap: 10px;
	}

	.ulm-planpick--rows .ulm-planpick__option input { grid-row: 1 / span 2; }

	.ulm-planpick--rows .ulm-planpick__price { grid-column: 3; justify-self: start; }

	.ulm-planpick--rows .ulm-planpick__blurb,
	.ulm-planpick--rows .ulm-planpick__note { grid-column: 2 / span 2; }
}


/* --------------------------------------------------- purchased-item row -- */

/*
 * One resource in a list: thumbnail, title and meta, then an action. It shows
 * up in the cart, on the order confirmation, in account orders and in account
 * downloads — four screens across three areas. It lived in cart.css, so the
 * account screens rendered it with no rules at all.
 *
 * The `ulm-cart__` prefix is kept rather than renamed: the class appears in
 * five templates and a rename buys tidier names at the cost of a migration
 * that could silently miss one.
 */



.ulm-cart__lines,
.ulm-checkout__main,
.ulm-confirm__downloads {
	display: flex;
	flex-direction: column;
	gap: 18px;
	min-width: 0;
}


/* --------------------------------------------------------------- line -- */

.ulm-cart__line,
.ulm-confirm__item {
	display: flex;
	flex-wrap: wrap;
	gap: 16px;
	align-items: center;
	padding: 18px 20px;
	border-bottom: 1px solid var(--ulm-border, #edf0ee);
}


.ulm-cart__line:last-child { border-bottom: 0; }


.ulm-cart__thumb {
	flex: 0 0 92px;
	width: 92px;
	height: 68px;
	overflow: hidden;
	background: var(--ulm-surface-alt, #f3f4f6);
	border-radius: 9px;
}


.ulm-cart__thumb img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}


.ulm-cart__lineinfo {
	display: flex;
	flex: 1 1 220px;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
}


.ulm-cart__linetitle {
	font-size: 14.5px;
	font-weight: 600;
	line-height: 1.45;
	color: var(--ulm-deep);
	text-decoration: none;
}


.ulm-cart__linetitle:hover { color: var(--ulm-primary); }


.ulm-cart__linemeta {
	font-size: 12.5px;
	line-height: 1.6;
	color: var(--ulm-ink-muted);
}


.ulm-cart__gone {
	font-size: 12.5px;
	font-weight: 600;
	color: var(--ulm-danger);
}


.ulm-cart__lineside {
	display: flex;
	flex: 0 0 auto;
	flex-direction: column;
	gap: 8px;
	align-items: flex-end;
}


.ulm-cart__lineprice {
	font-family: var(--ulm-font-numeric);
	font-size: 16px;
	font-weight: 700;
	color: var(--ulm-ink-alt, #2b2b2b);
}

	.ulm-cart__lineside {
		flex-direction: row;
		gap: 14px;
		align-items: center;
		width: 100%;
		justify-content: space-between;
	}


	.ulm-cart__thumb {
		flex-basis: 68px;
		width: 68px;
		height: 52px;
	}

/* ------------------------------------------------------------ status pill -- */

/*
 * A status as a coloured pill. Used across the admin console and the customer
 * account — it was defined in admin.css, so every pill on an account screen
 * was unstyled text.
 */



/* ------------------------------------------------------------- pills -- */

.ulm-apill {
	display: inline-flex;
	gap: 6px;
	align-items: center;
	padding: 4px 10px;
	font-size: 11.5px;
	font-weight: 600;
	white-space: nowrap;
	border-radius: 9999px;
}


/*
 * Two names for the live state. Post_Statuses::moderation_status() returns
 * "approved" and that is what the catalogue passes in; other screens pass the
 * raw post status "publish". Both mean on sale, so both get the rule rather
 * than one of them silently rendering an unstyled pill.
 */
.ulm-apill--publish,
.ulm-apill--approved   { color: var(--ulm-primary, #3a836b); background: var(--ulm-tint-header, #eaf7f1); }

.ulm-apill--pending     { color: var(--ulm-gold-ink, #b9913e); background: var(--ulm-tint-peach, #fff2de); }

.ulm-apill--declined    { color: var(--ulm-danger, #c0392b); background: #fdf1ef; }

.ulm-apill--draft       { color: var(--adm-muted); background: var(--adm-line-soft); }

.ulm-apill--unpublished { color: var(--adm-plum); background: var(--adm-plum-tint); }

.ulm-apill--suspended   { color: var(--ulm-danger, #c0392b); background: #fdf1ef; }

.ulm-apill--info        { color: var(--adm-info); background: var(--adm-info-tint); }

/* ---------------------------------------------------------- field row -- */

/* Two or three fields side by side. Used by the resource editor and by the
 * customer profile, which are in different areas. */



.ulm-editor__row {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
	gap: 14px;
}

/* The body class. base.css styles the `body` element directly; this exists so
 * the hook is real rather than decorative. */
.ulm-body {
	min-height: 100%;
}


/* A small forward link — "Full reports →", "View receipt →". Used on admin
 * panels and on the account order card, so it belongs with the components. */

.ulm-apanel__link {
	font-size: 13px;
	font-weight: 600;
}


/* ------------------------------------------------------- button loading -- */

/*
 * Shown while a form is waiting on the server. The ring is drawn in
 * currentColor with one side missing, so it works on every button variant
 * without a per-variant colour.
 *
 * Under prefers-reduced-motion the site-wide rule in base.css stops this
 * turning, which is the right outcome — the state is also carried by the label
 * swap and aria-busy, so nothing depends on the animation to be understood.
 */
.ulm-btn__spinner {
	display: none;
	flex: 0 0 18px;
	width: 18px;
	height: 18px;
	border: 2.4px solid currentColor;
	border-top-color: transparent;
	border-radius: 50%;
	opacity: .9;
	animation: ulm-spin .8s linear infinite;
}

.ulm-btn.is-loading .ulm-btn__spinner { display: block; }

.ulm-btn.is-loading {
	cursor: progress;
	opacity: .82;
}

/* The press animation on a button that is already working reads as a second
   submit; there is nothing to acknowledge until the answer arrives. */
.ulm-btn.is-loading:active { transform: none; }

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

/* --------------------------------------------------- add to cart busy -- */

/*
 * The working state of every Add to Cart / Get on the site.
 *
 * Keyed to the data attribute rather than to a class, because this is one
 * component wearing two skins — the buy box on a resource page
 * (`.ulm-pdp__cta`) and the quick-add on a card (`.ulm-pcard__add`) — and they
 * live in two different screen stylesheets. Written per screen, one of them
 * would eventually get the spinner and the other would not.
 *
 * The icon gives way to the spinner and the words stay where they are.
 * Swapping the label too would change the button's width mid-request, so the
 * one moment the user is watching it is the one moment it moves.
 */
[data-ulm-add-to-cart] .ulm-addcart__spinner { display: none; }

[data-ulm-add-to-cart].is-busy > svg { display: none; }

[data-ulm-add-to-cart].is-busy .ulm-addcart__spinner {
	display: block;
	flex: 0 0 auto;
	width: 16px;
	height: 16px;
	border: 2.2px solid currentColor;
	border-top-color: transparent;
	border-radius: 50%;
	opacity: .9;
	animation: ulm-spin .8s linear infinite;
}

/* The plus on a card is 14px, so its spinner matches rather than nudging the
   label across by two pixels as it appears. */
.ulm-pcard__add.is-busy .ulm-addcart__spinner {
	width: 14px;
	height: 14px;
	border-width: 2px;
}

[data-ulm-add-to-cart].is-busy {
	cursor: progress;
	opacity: .9;
}

/* A press animation on a button that is already working reads as a second
   submit; there is nothing to acknowledge until the answer arrives. */
[data-ulm-add-to-cart].is-busy:active { transform: none; }

/*
 * With motion reduced the spinner stops turning but still replaces the icon,
 * so the button still says "working" — it says it by changing rather than by
 * moving. A state that only exists as an animation disappears entirely for
 * anyone who has asked for less of it.
 */
@media (prefers-reduced-motion: reduce) {
	[data-ulm-add-to-cart].is-busy .ulm-addcart__spinner { animation: none; }
}

/* ------------------------------------------------------------ sort control -- */

/*
 * "Sort by" above a product grid. It appears on Browse and again on a vendor's
 * storefront — two screens with two stylesheets — so it lived in browse.css
 * and the storefront rendered it as an unstyled select.
 */



.ulm-browse__sort {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	height: 41px;
	padding: 0 16px;
	border: 1px solid var(--ulm-border);
	border-radius: 8px;
	background: var(--ulm-surface);
	cursor: pointer;
	/* Pinned right even when no chips are present. */
	margin-inline-start: auto;
}


.ulm-browse__sort-label,
.ulm-browse__sort select {
	font-family: var(--ulm-font-numeric);
	font-weight: var(--ulm-fw-semibold);
	font-size: 14px;
	line-height: 21px;
	color: var(--ulm-ink);
}


.ulm-browse__sort select {
	border: none;
	background: transparent;
	cursor: pointer;
	outline: none;
	padding: 0;
}


	.ulm-browse__sort {
		margin-inline-start: 0;
		justify-content: space-between;
	}


/* ------------------------------------------------------- file dropzone -- */

/*
 * A bare <input type="file"> is the browser's own widget: a grey button whose
 * label and font are the operating system's, sitting next to unstyled text.
 * Beside 52px outlined fields it reads as the one control the design forgot,
 * which is exactly what it was.
 *
 * So the real input is taken out of the flow (not `display:none` — that makes
 * it unfocusable) and a label wraps the visible button, which is all a file
 * input needs to be operable by click, Enter and Space.
 */
.ulm-filedrop {
	display: flex;
	align-items: center;
	gap: 14px;
	flex-wrap: wrap;
	padding: 16px;
	border: 1.6px dashed var(--ulm-border-strong);
	border-radius: 12px;
	background: var(--ulm-surface-alt, #FCFDFC);
	transition: border-color var(--ulm-duration-fast) ease, background var(--ulm-duration-fast) ease;
}

/* Dragging a file anywhere over the zone, and while a file is chosen. */
.ulm-filedrop.is-dragging {
	border-color: var(--ulm-primary);
	border-style: solid;
	background: var(--ulm-tint-mint);
}

.ulm-filedrop.is-filled { border-style: solid; }

.ulm-mfield--error .ulm-filedrop { border-color: var(--ulm-danger); }

.ulm-filedrop__icon {
	display: grid;
	place-items: center;
	flex: 0 0 42px;
	width: 42px;
	height: 42px;
	border-radius: 10px;
	background: var(--ulm-tint-mint);
	color: var(--ulm-primary);
}

.ulm-filedrop__text {
	flex: 1 1 200px;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.ulm-filedrop__name {
	font-weight: var(--ulm-fw-semibold);
	font-size: 13.5px;
	color: var(--ulm-deep);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.ulm-filedrop__hint {
	font-size: 12px;
	line-height: 1.5;
	color: var(--ulm-ink-muted);
}

.ulm-filedrop__cta {
	position: relative;
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	height: 42px;
	padding: 0 18px;
	border: 1px solid var(--ulm-border-strong);
	border-radius: 8px;
	background: var(--ulm-surface);
	font-size: 13.5px;
	font-weight: var(--ulm-fw-semibold);
	color: var(--ulm-primary);
	cursor: pointer;
	transition: background var(--ulm-duration-fast) ease;
}

.ulm-filedrop__cta:hover { background: var(--ulm-tint-mint); }

/* Off-screen rather than hidden: a display:none input cannot be focused, and
   the keyboard route to the file picker is focusing it. */
.ulm-filedrop__input {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
	opacity: 0;
}

.ulm-filedrop__input:focus-visible + span,
.ulm-filedrop__cta:has(.ulm-filedrop__input:focus-visible) {
	outline: 2px solid var(--ulm-primary);
	outline-offset: 2px;
}

/* --------------------------------------------------------- image grid -- */

.ulm-imagegrid {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
}

.ulm-imagegrid__item {
	position: relative;
	flex: 0 0 108px;
	width: 108px;
	height: 130px;
	border-radius: 9px;
	overflow: hidden;
	background: var(--ulm-surface-alt, #F3F4F6);
	border: 2px solid var(--ulm-border);
}

/*
 * The cover is whichever tile is first, so the badge is selected rather than
 * set. Nothing has to re-label the tiles when one is removed — the CSS already
 * knows which one leads.
 */
.ulm-imagegrid__item:first-child { border-color: var(--ulm-primary); }

.ulm-imagegrid__badge {
	position: absolute;
	inset-inline: 0;
	bottom: 0;
	display: none;
	padding: 2px;
	background: var(--ulm-primary);
	color: var(--ulm-ink-invert);
	font-size: 9.5px;
	font-weight: var(--ulm-fw-bold);
	text-align: center;
	letter-spacing: .05em;
	text-transform: uppercase;
}

.ulm-imagegrid__item:first-child .ulm-imagegrid__badge { display: block; }

.ulm-imagegrid__item img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.ulm-imagegrid__remove {
	position: absolute;
	top: 4px;
	right: 4px;
	display: grid;
	place-items: center;
	width: 22px;
	height: 22px;
	border: none;
	border-radius: 50%;
	background: rgba(18, 18, 18, .62);
	color: var(--ulm-ink-invert);
	cursor: pointer;
	transition: background var(--ulm-duration-fast) ease;
}

.ulm-imagegrid__remove:hover { background: var(--ulm-danger); }

.ulm-imagegrid__add {
	position: relative;
	flex: 0 0 108px;
	width: 108px;
	height: 130px;
	display: grid;
	place-items: center;
	border: 1.6px dashed var(--ulm-border-strong);
	border-radius: 9px;
	background: var(--ulm-surface-alt, #FCFDFC);
	color: var(--ulm-primary);
	cursor: pointer;
	transition: background var(--ulm-duration-fast) ease, border-color var(--ulm-duration-fast) ease;
}

.ulm-imagegrid__add:hover {
	background: var(--ulm-tint-mint);
	border-color: var(--ulm-primary);
}

/* Five is the cap (FR-5.4); past it the tile goes rather than sitting there
   inviting a click that would be refused. */
.ulm-imagegrid__add[hidden] { display: none; }

/* ---------------------------------------------------------- file list -- */

/*
 * The files already on a resource, and — in the buyer's account — the files a
 * purchase entitles them to. One component for both: a vendor checking what
 * they attached and a buyer collecting it are looking at the same list, and
 * two that drifted would disagree about what the resource contains.
 */
.ulm-filelist {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.ulm-filelist:empty { display: none; }

.ulm-filelist__row {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 10px 12px;
	border: 1px solid var(--ulm-border);
	border-radius: 10px;
	background: var(--ulm-surface);
}

.ulm-filelist__row--link {
	color: var(--ulm-deep);
	transition: border-color var(--ulm-duration-fast) ease, background var(--ulm-duration-fast) ease;
}

.ulm-filelist__row--link:hover {
	border-color: var(--ulm-primary);
	background: var(--ulm-tint-mint);
	color: var(--ulm-deep);
}

.ulm-filelist__icon {
	display: grid;
	place-items: center;
	flex: 0 0 34px;
	width: 34px;
	height: 34px;
	border-radius: 8px;
	background: var(--ulm-tint-mint);
	color: var(--ulm-primary);
}

.ulm-filelist__text {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 1px;
}

.ulm-filelist__name {
	font-size: 13.5px;
	font-weight: var(--ulm-fw-semibold);
	color: var(--ulm-deep);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.ulm-filelist__meta {
	font-size: 12px;
	color: var(--ulm-ink-muted);
}

/* Not yet saved — the file is chosen but has not been uploaded, and saying so
   is the difference between "this is attached" and "this will be". */
.ulm-filelist__row--pending {
	border-style: dashed;
	border-color: var(--ulm-primary);
	background: var(--ulm-tint-mint);
}

.ulm-filelist__remove {
	flex: 0 0 auto;
	display: grid;
	place-items: center;
	width: 28px;
	height: 28px;
	border: none;
	border-radius: 8px;
	background: transparent;
	color: var(--ulm-ink-muted);
	cursor: pointer;
	transition: background var(--ulm-duration-fast) ease, color var(--ulm-duration-fast) ease;
}

.ulm-filelist__remove:hover {
	background: var(--ulm-danger-bg);
	color: var(--ulm-danger);
}

.ulm-filelist--download { margin-top: 4px; }

/* --- the downloads list --------------------------------------------------
 *
 * In components.css rather than in a screen stylesheet because two screens
 * draw it: the customer account (C-02) and the vendor dashboard (V-13), which
 * load account.css and vendor.css respectively and never both. These rules
 * lived under `.ulm-acct__*` in account.css, which meant the vendor copy of
 * the same markup arrived unstyled — a flat stack of borderless rows.
 *
 * The `.ulm-acct__*` originals stay where they are: orders, wishlist, reviews
 * and following still use them, and those screens are the account's alone.
 */
.ulm-downloads__search {
	display: flex;
	gap: 8px;
	align-items: center;
}

.ulm-downloads__search input[type="search"] {
	height: 40px;
	padding: 0 12px;
	font-family: inherit;
	font-size: 13.5px;
	color: var(--ulm-ink);
	background: #fff;
	border: 1px solid var(--ulm-border, #edf0ee);
	border-radius: 8px;
}

/* The vendor shell has no header slot for a search box, so the screen puts
   one above the list instead. Harmless where it is not used. */
.ulm-downloads__head {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	justify-content: flex-end;
	margin-bottom: 16px;
}

.ulm-downloads__list {
	display: flex;
	flex-direction: column;
	gap: 14px;
}

.ulm-downloads__list > .ulm-cart__line {
	background: var(--ulm-surface, #fff);
	border: 1px solid var(--ulm-border, #edf0ee);
	border-radius: 14px;
}

.ulm-downloads__pager {
	display: flex;
	flex-wrap: wrap;
	gap: 14px;
	align-items: center;
	justify-content: center;
	margin-top: 24px;
}

/* --- category nav dropdowns ---------------------------------------------
 *
 * Opened on hover for a mouse and on click/keyboard for everything else. The
 * hover rule is CSS-only so the menu works before the script runs; the script
 * only exists to make it reachable without a pointer.
 */
/*
 * `position: relative`: the panel hangs from the item that opens it.
 *
 * It was briefly positioned against the whole strip so it could span the
 * header, which is a mega-menu — the wrong shape for lists this short. A column
 * of seven names stretched across 1440px puts the last of them a screen away
 * from the word that opened it.
 */
.ulm-header__nav-item {
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: 2px;
}

.ulm-header__nav-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 2px;
	border: 0;
	background: none;
	/*
	 * Stated, not inherited. The toggle is a sibling of the link rather than a
	 * child of it, so `inherit` reached the header and landed on the browser
	 * default — a black chevron on the green bar.
	 */
	color: rgba(255, 255, 255, .88);
	cursor: pointer;
	transition: transform var(--ulm-duration-fast) ease, color var(--ulm-duration-fast) ease;
}

.ulm-header__nav-toggle:hover,
.ulm-header__nav-item:hover .ulm-header__nav-toggle {
	color: var(--ulm-ink-invert);
}

.ulm-header__nav-item:hover .ulm-header__nav-toggle,
.ulm-header__nav-toggle[aria-expanded="true"] { transform: rotate( 180deg ); }

/*
 * The panel, full width of the header.
 *
 * Visibility is opacity and `visibility`, never the `hidden` attribute. base.css
 * enforces `[hidden] { display: none !important }` site-wide — correct for every
 * other panel, and fatal here, because `!important` outranks `:hover`. With the
 * attribute in place this menu could only ever be opened by a click, which is
 * exactly the wrong interaction for a category bar.
 */
.ulm-header__dropdown {
	position: absolute;
	top: 100%;
	inset-inline-start: 0;
	z-index: 60;

	/*
	 * One name per row, in a column no wider than a name needs.
	 *
	 * A fixed width rather than `max-content`: several of these wrap onto a
	 * second line, and sizing to content would give every category a panel of a
	 * different width — the menu would appear to jump as the pointer moved
	 * along the bar.
	 */
	display: flex;
	flex-direction: column;
	width: 320px;
	max-height: min( 70vh, 520px );
	overflow-y: auto;
	padding: 12px;
	margin-top: 10px;
	background: var(--ulm-surface);
	border: 1px solid #E6EFEA;
	border-radius: 14px;
	box-shadow: 0 1px 2px rgba(18, 18, 18, .06), 0 22px 44px -24px rgba(41, 72, 60, .5);

	opacity: 0;
	visibility: hidden;
	pointer-events: none;

	/*
	 * Closing waits; opening does not.
	 *
	 * The delay is on the closed state, which is the state being transitioned
	 * *to* when the pointer leaves — so the panel holds for a moment rather
	 * than vanishing under a hand that is still on its way to it. The open rule
	 * below cancels the delay, so arriving on a category is still instant.
	 */
	transition: opacity var(--ulm-duration-fast) ease, visibility var(--ulm-duration-fast) ease;
	transition-delay: .35s;
}

/*
 * The bridge across the gap.
 *
 * The 10px between a category and its panel belongs to neither of them, so the
 * pointer crossing it left `:hover` entirely and the panel shut halfway through
 * the journey. This fills that strip with an invisible target.
 *
 * It hangs off the NAV ITEM, not off the panel. On the panel it was a `::before`
 * sitting at `top: -12px` — outside the panel's own box — and the panel carries
 * `overflow-y: auto` for its `max-height`. Overflow clips a pseudo-element the
 * same as any other content, so the bridge was being cut away and never received
 * the pointer at all. The nav item does no clipping, so here it survives.
 *
 * Only while the item is engaged: a permanent invisible strip under every
 * category would swallow clicks meant for the page beneath the bar.
 */
.ulm-header__nav-item:hover::after,
.ulm-header__nav-item:focus-within::after,
.ulm-header__nav-item.is-open::after {
	content: "";
	position: absolute;
	inset-inline-start: 0;
	top: 100%;
	z-index: 59;
	width: 320px;
	height: 14px;
}

/* Flipped panels are anchored right, so their bridge has to be too. */
.ulm-header__nav-item:nth-last-child(-n+3):hover::after,
.ulm-header__nav-item:nth-last-child(-n+3):focus-within::after,
.ulm-header__nav-item:nth-last-child(-n+3).is-open::after {
	inset-inline-start: auto;
	inset-inline-end: 0;
}

/*
 * Hover and focus open it with no script at all; `.is-open` is what the script
 * adds for a tap or a keyboard, so the two paths cannot disagree about state.
 */
.ulm-header__nav-item:hover > .ulm-header__dropdown,
.ulm-header__nav-item:focus-within > .ulm-header__dropdown,
.ulm-header__nav-item.is-open > .ulm-header__dropdown {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
	transition-delay: 0s;
}

/*
 * The last categories in the strip sit close to the right edge, and a panel
 * anchored to their leading edge would hang off the page. These flip to hang
 * from their trailing edge instead.
 */
.ulm-header__nav-item:nth-last-child(-n+3) .ulm-header__dropdown {
	inset-inline-start: auto;
	inset-inline-end: 0;
}

/*
 * A panel the script measured as running off the right of the window. Same
 * outcome as the nth-last-child flip above, reached by measurement rather than
 * by counting positions — which is the only way to be right about a strip that
 * wraps and a panel whose width depends on its contents.
 */
.ulm-header__nav-item.is-clamped .ulm-header__dropdown {
	inset-inline-start: auto;
	inset-inline-end: 0;
}

.ulm-header__nav-item.is-clamped:hover::after,
.ulm-header__nav-item.is-clamped:focus-within::after,
.ulm-header__nav-item.is-clamped.is-open::after {
	inset-inline-start: auto;
	inset-inline-end: 0;
}

/*
 * Two panels open at once.
 *
 * The close transition is delayed .35s so the panel does not vanish under a
 * hand still travelling towards it. Moving along the bar from one category to
 * the next inherited that grace period, so the panel being left was still
 * fading while its neighbour opened instantly — two 320px panels overlapping,
 * which is what it looked like.
 *
 * While the pointer is anywhere on the bar, every item that is NOT the one
 * under it closes immediately. Leaving the bar altogether is unaffected, so the
 * grace period still does its job.
 */
.ulm-header__nav:hover .ulm-header__nav-item:not(:hover):not(.is-open) > .ulm-header__dropdown {
	transition-delay: 0s;
	opacity: 0;
	visibility: hidden;
	pointer-events: none;
}

.ulm-header__dropdown-link {
	display: flex;
	align-items: center;
	/* The count that sat on the right is gone; the name is the whole row. */
	padding: 9px 11px;
	border-radius: 8px;
	font-size: 14px;
	line-height: 1.3;
	color: var(--ulm-ink);
	text-decoration: none;
}

.ulm-header__dropdown-link:hover,
.ulm-header__dropdown-link:focus-visible {
	background: var(--ulm-tint-mint);
	color: var(--ulm-deep);
}

.ulm-header__dropdown-link[aria-current="page"] {
	background: var(--ulm-tint-mint);
	font-weight: var(--ulm-fw-semibold);
	color: var(--ulm-deep);
}

.ulm-header__dropdown-link--all {
	font-weight: var(--ulm-fw-semibold);
	color: var(--ulm-primary);
}

/*
 * Grouped panels — Math, ELA, Science, Social Studies.
 *
 * Their sub-categories are shelves ("Elementary math", "By topic"), not
 * destinations, so the panel shows the subjects underneath each one. One shelf
 * stays a single column; two or more become columns, which is the only way
 * three grade bands of eight subjects each fit on a screen.
 */
.ulm-header__dropdown--wide {
	width: min(700px, calc(100vw - 32px));
}

.ulm-header__dropcols {
	display: grid;
	grid-template-columns: 1fr;
	gap: 4px 12px;
}

.ulm-header__dropdown--wide .ulm-header__dropcols {
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

.ulm-header__dropcol {
	display: flex;
	flex-direction: column;
	min-width: 0;
}

/*
 * The shelf's own name. A heading in weight and colour, a link in behaviour —
 * it is a real category archive, and a heading that cannot be clicked hides a
 * page the buyer can reach from everywhere else.
 */
.ulm-header__dropcol-title {
	padding: 9px 11px 5px;
	font-size: 12px;
	font-weight: var(--ulm-fw-semibold);
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--ulm-ink-muted);
	text-decoration: none;
}

.ulm-header__dropcol-title:hover,
.ulm-header__dropcol-title:focus-visible {
	color: var(--ulm-primary);
}

.ulm-header__dropcol-title[aria-current="page"] {
	color: var(--ulm-primary);
}

/* Space between one column's last subject and the next shelf, when the panel
   is narrow enough that the columns stack. */
.ulm-header__dropcol + .ulm-header__dropcol .ulm-header__dropcol-title {
	padding-top: 12px;
}

/* A wide panel hanging off a category near the right edge runs off the page. */
@media (max-width: 1180px) {
	.ulm-header__dropdown--wide {
		width: min(560px, calc(100vw - 32px));
	}
}
/* On a touch screen there is no hover to open with, and no room for a panel
 * that floats. The children sit inline under their parent instead. */
@media (max-width: 860px) {
	.ulm-header__dropdown {
		position: static;
		min-width: 0;
		margin-top: 4px;
		box-shadow: none;
	}
}


/* --------------------------------------------------------- label/value row -- */

/*
 * "Payouts pending    $231.88" — a label on the left, a figure on the right.
 * Used across the admin console and on the vendor subscription screen, so it
 * belongs here rather than in either screen's stylesheet.
 */



.ulm-ameter__head,
.ulm-arow {
	display: flex;
	gap: 10px;
	align-items: baseline;
	justify-content: space-between;
}


.ulm-ameter__name,
.ulm-arow__label {
	font-size: 13px;
	color: var(--adm-muted);
}


.ulm-ameter__count,
.ulm-arow__value {
	font-family: var(--ulm-font-numeric);
	font-size: 14px;
	font-weight: 700;
	color: var(--adm-ink);
}


.ulm-arows {
	display: flex;
	flex-direction: column;
	gap: 10px;
}


.ulm-arow__value--warn   { color: var(--ulm-gold-ink, #b9913e); }

.ulm-arow__value--danger { color: var(--ulm-danger, #c0392b); }

/* --- reporting a resource ------------------------------------------------
 *
 * Quiet on purpose. A prominent Report button on a marketplace tells every
 * browsing buyer there is probably something here worth reporting; this has to
 * be findable by somebody who has seen a problem and unnoticed by everyone else.
 */
.ulm-pdp__report {
	margin: 14px 0 0;
	text-align: center;
}

.ulm-pdp__report-link {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 4px 2px;
	border: 0;
	background: none;
	font-family: var(--ulm-font-m3);
	font-size: 12.5px;
	color: var(--ulm-ink-faint);
	text-decoration: none;
	cursor: pointer;
}

.ulm-pdp__report-link:hover,
.ulm-pdp__report-link:focus-visible {
	color: var(--ulm-danger);
}

.ulm-pdp__report-link--done {
	color: var(--ulm-primary);
	cursor: default;
}

.ulm-report {
	width: min(94vw, 460px);
	padding: 0;
	border: 0;
	border-radius: 16px;
	background: var(--ulm-surface);
	color: var(--ulm-ink);
	box-shadow: 0 24px 60px -28px rgba(41, 72, 60, .55);
}

/* The theme's first modal, so this sets the convention: a neutral dark
 * overlay. A green-tinted one read as part of the page rather than as
 * something covering it, and a backdrop's whole job is to say "the page is
 * behind this now". */
.ulm-report::backdrop {
	background: rgba(18, 18, 18, .58);
}

.ulm-report__form {
	display: flex;
	flex-direction: column;
	gap: 14px;
	padding: 24px;
}

.ulm-report__title {
	margin: 0;
	font-size: 19px;
	font-weight: var(--ulm-fw-semibold);
	color: var(--ulm-deep);
}

.ulm-report__lede,
.ulm-report__foot {
	margin: 0;
	font-size: 13px;
	line-height: 1.6;
	color: var(--ulm-ink-muted);
}

.ulm-report__foot {
	font-size: 12px;
	color: var(--ulm-ink-faint);
}

.ulm-report__reasons {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: 0;
	border: 0;
	margin: 0;
}

.ulm-report__legend {
	padding: 0 0 6px;
	font-family: var(--ulm-font-m3);
	font-size: 12px;
	color: var(--ulm-m3-outline);
}

.ulm-report__reason {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	padding: 9px 10px;
	border-radius: 8px;
	font-size: 14px;
	line-height: 1.4;
	cursor: pointer;
}

/* Hover is a hint; selection is a statement. The tint says "you could pick
 * this", the solid green says "this is what you picked" — the two must not
 * look alike or a reporter cannot tell whether their choice registered. */
.ulm-report__reason:hover,
.ulm-report__reason:focus-within {
	background: var(--ulm-tint-mint);
}

.ulm-report__reason:has( input:checked ) {
	background: var(--ulm-primary);
	color: var(--ulm-ink-invert);
	font-weight: var(--ulm-fw-semibold);
}

.ulm-report__reason:has( input:checked ):hover {
	background: var(--ulm-deep);
}

.ulm-report__reason input {
	accent-color: var(--ulm-primary);
}

.ulm-report__reason:has( input:checked ) input {
	accent-color: var(--ulm-ink-invert);
}

.ulm-report__reason input {
	margin-top: 2px;
	flex: none;
}

.ulm-report__detail {
	display: flex;
	flex-direction: column;
	gap: 5px;
}

.ulm-report__label {
	font-family: var(--ulm-font-m3);
	font-size: 12px;
	color: var(--ulm-m3-outline);
}

.ulm-report__detail textarea {
	padding: 10px 12px;
	border: 1px solid #CAC4D0;
	border-radius: 8px;
	font-family: var(--ulm-font);
	font-size: 14px;
	line-height: 1.6;
	resize: vertical;
}

.ulm-report__detail textarea:focus {
	outline: none;
	border-color: var(--ulm-primary);
	box-shadow: 0 0 0 3px rgba(58, 131, 107, .14);
}

.ulm-report__error {
	margin: 0;
	font-family: var(--ulm-font-m3);
	font-size: 12.5px;
	color: var(--ulm-danger);
}

.ulm-report__error[hidden] { display: none; }

.ulm-report__actions {
	display: flex;
	justify-content: flex-end;
	gap: 10px;
}

/* ================================================== confirm dialog == */

/*
 * Shared by every consequential action in the moderation console.
 *
 * A native <dialog>, so the backdrop, Escape, focus trapping and the aria
 * semantics come from the browser rather than from us getting all four right.
 *
 * `.ulm-cdialog`, NOT `.ulm-confirm`. That name already belongs to the order
 * confirmation page (P-08, styled in screens/cart.css), and the two collided:
 * this block's `width: min(92vw, 460px)` was sizing the whole thank-you page,
 * so its 366px receipt column had 230px of content box to sit in. The result
 * was a page squeezed into a 460px ribbon with the receipt and review cards
 * overflowing across the downloads beside them.
 *
 * It was invisible from either file. Nothing here mentions the order page and
 * nothing there mentions a dialog; components.css simply loads first and the
 * page never restated `width`, so there was nothing to override. Renaming the
 * dialog rather than the page because a `<dialog>` is the smaller, more
 * contained thing, and because "confirm" describes a whole screen better than
 * it describes a modal.
 *
 * The script binds on `data-ulm-confirm-*` attributes, not on these classes,
 * so the rename does not touch app.js.
 */
.ulm-cdialog {
	width: min(92vw, 460px);
	padding: 0;
	border: 0;
	border-radius: 16px;
	background: var(--ulm-surface);
	box-shadow: 0 24px 60px -20px rgb(41 72 60 / 45%);
}

.ulm-cdialog::backdrop {
	background: rgb(18 42 34 / 45%);
}

.ulm-cdialog__inner {
	display: flex;
	flex-direction: column;
	gap: 12px;
	padding: 26px 26px 22px;
}

.ulm-cdialog__title {
	margin: 0;
	font-weight: var(--ulm-fw-semibold);
	font-size: 19px;
	line-height: 1.3;
	color: var(--ulm-deep);
}

.ulm-cdialog__body {
	margin: 0;
	font-size: 14.5px;
	line-height: 1.65;
	color: var(--ulm-ink-muted);
	text-wrap: pretty;
}

/*
 * Confirm sits on the right, cancel on its left. Cancel is first in the DOM so
 * it takes focus when the dialog opens — an accidental Enter should not approve
 * a store.
 */
.ulm-cdialog__actions {
	display: flex;
	gap: 10px;
	justify-content: flex-end;
	margin-top: 6px;
}

@media (max-width: 420px) {
	.ulm-cdialog__actions { flex-direction: column-reverse; }
	.ulm-cdialog__actions .ulm-btn { width: 100%; }
}

/* ================================================ dashboard nav drawer == */

/*
 * The narrow-viewport navigation for all three dashboards.
 *
 * Vendor, administration and the customer account keep their navigation on
 * screen permanently at desk width. Below their breakpoint it becomes an
 * off-canvas drawer: a button at the top-left of the page slides the panel in
 * from that edge, over a scrim.
 *
 * The three panels themselves stay in their own stylesheets — they are a dark
 * green sidebar, a near-black sidebar and a light strip, and merging them here
 * would mean undoing two of the three. What is shared is the chrome around
 * them: the trigger, the dismiss, the scrim and the body lock. Written once
 * because "a drawer opens from the left" is one behaviour, not three.
 *
 * Both controls are `display: none` here and switched on inside each screen's
 * media query, because the width at which each dashboard runs out of room is
 * its own: the vendor and admin sidebars are ~268px of permanent chrome and
 * give up at 1024px, while the account strip survives to 768px.
 */

.ulm-dashnav__toggle,
.ulm-dashnav__close {
	display: none;
	place-items: center;
	flex: 0 0 auto;
	width: 42px;
	height: 42px;
	padding: 0;
	font: inherit;
	color: inherit;
	background: none;
	border: 0;
	border-radius: 10px;
	cursor: pointer;
	transition: background var(--ulm-duration-fast, .15s) ease;
}

.ulm-dashnav__toggle:hover,
.ulm-dashnav__toggle:focus-visible { background: rgb(0 0 0 / 6%); }

/* The close sits on a dark sidebar in two of the three shells, so it tints
   with white; the account drawer overrides this to a dark ink. */
.ulm-dashnav__close:hover,
.ulm-dashnav__close:focus-visible { background: rgb(255 255 255 / 14%); }

/*
 * The scrim is keyed on the open class rather than on a media query, so it
 * cannot outlive the drawer: the script clears `is-navopen` when the viewport
 * crosses back above the breakpoint, and the scrim goes with it.
 */
.ulm-dashnav__scrim { display: none; }

.is-navopen > .ulm-dashnav__scrim {
	position: fixed;
	inset: 0;
	z-index: 125;
	display: block;
	background: rgb(18 24 22 / 55%);
	animation: ulm-dashnav-fade .2s ease;
}

@keyframes ulm-dashnav-fade {
	from { opacity: 0; }
	to   { opacity: 1; }
}

/* The page behind a drawer must not scroll under the finger that is aiming
   at the panel. Lifted when the drawer closes, and by the script if the
   viewport widens past the breakpoint while it is open. */
body.ulm-dashnav-open { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
	.is-navopen > .ulm-dashnav__scrim { animation: none; }
}

/* ======================================================= MESSAGE SELLER == */

/*
 * The "Message seller" control (FR-4.9). One component, two placements: the
 * buy column on a resource page, where the panel opens in normal flow, and the
 * storefront identity row, where it opens as a card over the page because that
 * row is a flex line pulled up across the banner and anything expanding inside
 * it would shove the store name sideways.
 *
 * The trigger is a <summary>, so the panel opens with no JavaScript. That costs
 * two lines of marker removal and buys a control that cannot silently do
 * nothing.
 */

.ulm-msgseller {
	position: relative;
}

.ulm-msgseller--block,
.ulm-msgseller--inline {
	width: 100%;
}

.ulm-msgseller__wrap {
	position: relative;
}

/* A summary is a list item by default in every engine, with its own marker. */
.ulm-msgseller__btn {
	list-style: none;
	user-select: none;
}

/*
 * Two rules, not one selector list. A browser that does not recognise
 * ::-webkit-details-marker drops the whole list, and Firefox would then still
 * be drawing a triangle inside the button.
 */
.ulm-msgseller__btn::-webkit-details-marker {
	display: none;
}

.ulm-msgseller__btn::marker {
	content: "";
}

/* Focus goes on the summary itself, so it must be visible on the button. */
.ulm-msgseller__btn:focus-visible {
	outline: 2px solid var(--ulm-primary);
	outline-offset: 2px;
}

/* --- the panel ---------------------------------------------------------- */

.ulm-msgseller__panel {
	margin-top: var(--ulm-space-3);
	padding: var(--ulm-space-4);
	border: 1px solid var(--ulm-border-strong);
	border-radius: var(--ulm-radius-md);
	background: var(--ulm-surface);
}

/*
 * Over the page rather than in it, for the storefront row. Right-aligned so a
 * card wider than its trigger opens inwards instead of off the edge of a phone.
 */
.ulm-msgseller--popover .ulm-msgseller__panel {
	position: absolute;
	z-index: 30;
	inset-inline-end: 0;
	inline-size: min(360px, calc(100vw - 32px));
	margin-top: var(--ulm-space-2);
	box-shadow: var(--ulm-shadow-lg);
}

.ulm-msgseller__lede {
	margin-bottom: var(--ulm-space-3);
	font-size: var(--ulm-text-sm);
	line-height: var(--ulm-leading-normal);
	color: var(--ulm-ink-muted);
}

.ulm-msgseller__form textarea {
	display: block;
	width: 100%;
	min-height: 96px;
	padding: 10px 12px;
	border: 1px solid var(--ulm-border-input);
	border-radius: var(--ulm-radius-sm);
	background: var(--ulm-surface);
	font-family: inherit;
	font-size: var(--ulm-text-sm);
	line-height: var(--ulm-leading-normal);
	color: var(--ulm-ink);
	resize: vertical;
}

.ulm-msgseller__form textarea:focus-visible {
	outline: 2px solid var(--ulm-primary);
	outline-offset: 1px;
}

.ulm-msgseller__actions {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--ulm-space-3);
	margin-top: var(--ulm-space-3);
}

.ulm-msgseller__note {
	font-size: 12px;
	line-height: 1.4;
	color: var(--ulm-ink-muted);
}

/*
 * Sized like a real message, not like a caption. This is the only thing
 * standing between a buyer and a form that appears to have swallowed what they
 * typed, so it gets weight and a marker rather than a thin red line.
 */
.ulm-msgseller__error {
	display: flex;
	align-items: flex-start;
	gap: 6px;
	margin-bottom: var(--ulm-space-3);
	font-size: 13px;
	font-weight: var(--ulm-fw-semibold);
	line-height: 1.45;
	color: var(--ulm-danger);
}

.ulm-msgseller__error::before {
	content: "!";
	flex: 0 0 auto;
	width: 15px;
	height: 15px;
	margin-top: 1px;
	border-radius: 50%;
	background: var(--ulm-danger);
	color: #fff;
	font-size: 11px;
	font-weight: 700;
	line-height: 15px;
	text-align: center;
}

.ulm-msgseller__error[hidden] { display: none; }

/* What the script leaves in place of the panel once a message has landed. */
.ulm-msgseller__sent {
	margin-top: var(--ulm-space-2);
}

@media (max-width: 520px) {
	/* No room to hang a card off the edge of a 360px row. */
	.ulm-msgseller--popover .ulm-msgseller__panel {
		position: static;
		inline-size: auto;
		box-shadow: none;
	}

	.ulm-msgseller__actions {
		flex-direction: column;
		align-items: stretch;
	}
}

/* ====================================================== MESSAGE THREAD == */

/*
 * One buyer↔vendor conversation (FR-4.9).
 *
 * These rules lived in screens/vendor.css while the only place a thread was
 * rendered was V-08. There are two now — the vendor's inbox and the buyer's
 * account — and the two areas load different stylesheets, so the buyer's copy
 * arrived with the markup styled and none of the rules. It is a component, so
 * it lives with the components.
 *
 * The card is part of the component rather than borrowed from the surrounding
 * screen's panel class, for the same reason: a thread has to look like a thread
 * in both places without either screen knowing about it.
 */

.ulm-message {
	display: flex;
	flex-direction: column;
	gap: 16px;
	padding: 20px;
	background: var(--ulm-surface);
	border: 1px solid var(--ulm-border);
	border-radius: 14px;
}

.ulm-message + .ulm-message {
	margin-top: 16px;
}

.ulm-message__head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: 12px;
}

.ulm-message__title {
	margin: 0;
	font-weight: var(--ulm-fw-semibold);
	font-size: 16px;
	line-height: 1.4;
	letter-spacing: normal;
	color: var(--ulm-deep);
}

/* Who it is with, when, and which resource it is about. */
.ulm-message__meta {
	margin-top: 4px;
	font-size: 14px;
	color: var(--ulm-ink-muted);
}

.ulm-message__meta a {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 3px;
}

.ulm-message__body {
	font-size: 14.5px;
	line-height: 1.65;
	color: var(--ulm-ink);
}

.ulm-message__thread {
	display: flex;
	flex-direction: column;
	gap: 12px;
	padding-inline-start: 16px;
	border-inline-start: 3px solid var(--ulm-border);
}

.ulm-message__reply {
	font-size: 14px;
	line-height: 1.6;
	color: var(--ulm-ink);
}

.ulm-message__who {
	display: flex;
	align-items: baseline;
	gap: 8px;
	font-size: 13px;
	margin-bottom: 2px;
}

.ulm-message__form {
	display: flex;
	flex-direction: column;
	gap: 10px;
	align-items: flex-start;
}

.ulm-message__form textarea {
	width: 100%;
	padding: 12px 14px;
	border: 1px solid var(--ulm-border-input);
	border-radius: 10px;
	font-family: var(--ulm-font);
	font-size: 14px;
	line-height: 1.6;
	resize: vertical;
}

.ulm-message__form textarea:focus {
	outline: none;
	border-color: var(--ulm-primary);
	box-shadow: 0 0 0 3px rgba(58, 131, 107, .14);
}

/* The date beside a reply's author. Quieter than the reply itself. */
.ulm-message__when {
	font-size: 13px;
	color: var(--ulm-ink-muted);
}
