/*
 * product-foundation.css — Phase 8 design-system foundation
 * ---------------------------------------------------------------------------
 * Loaded last on every front-end page (functions.php, priority 130).
 *
 * Contract: plans/visitor-product-redesign-2026-08-28/DESIGN.md
 *           plans/visitor-product-redesign-2026-08-28/ui-ux-contract.md
 *           plans/visitor-product-redesign-2026-08-28/ui-ux-refinement.md
 *
 * This file exists because three problems are structural, not per-page:
 *
 *   1. The parent theme (escortwp, commercial, read-only) paints 63 selectors
 *      in #E0006C magenta and its form fields in a #f15cd8 pink. It cannot be
 *      edited, so it is neutralised here.
 *   2. Every form on the site inherits one 40px, white, float-based field
 *      system that predates the dark product. One input system replaces it.
 *   3. Focus, motion, media reservation and the horizontal-overflow guard are
 *      all-page concerns and belong in one place.
 *
 * Rules this file enforces and must keep enforcing:
 *   - One red, --brand-primary, reserved for the single commit action.
 *   - Gold is the verified mark's ink only. It is never a border or an
 *     outline anywhere in the interface (Ian, 2026-08-29). The focus ring is
 *     neutral white.
 *   - Colour never carries information alone.
 *   - Icons inherit currentColor.
 *   - Animate transform and opacity only, and always honour reduced motion.
 *   - Spacing 4/8/12/16/24/32/48. Radii 6/10/14/4/18/20/26/28/999.
 */

/* ==========================================================================
   01. Token supplement
   The base contract lives in override.css :root. These are the additions the
   foundation needs and a few aliases so a component can read a role, not a hex.
   ========================================================================== */

:root {
	--ex-field-height: 44px;
	--ex-field-radius: var(--radius-md, 10px);
	--ex-control-gap: var(--spacing-2, 8px);
	--ex-tap-min: 44px;

	/* Tint steps. Depth comes from tint, not from borders. */
	--ex-tint-1: rgba(255, 255, 255, 0.03);
	--ex-tint-2: rgba(255, 255, 255, 0.06);
	--ex-tint-3: rgba(255, 255, 255, 0.09);

	/*
	 * The one focus treatment. Neutral white, not gold - yellow is not used as
	 * a border anywhere in this interface. The ring stays: a keyboard user must
	 * always be able to see where they are.
	 */
	--ex-focus-color: var(--focus-ring-color, #FFFFFF);
	--ex-focus-width: 2px;
	--ex-focus-offset: 3px;

	/*
	 * --error (#EF4444) is correct as a fill and a border, but as text on the
	 * near-black canvas it lands around 4.4:1 and fails body contrast. This is
	 * the same red, lightened only enough to clear 4.5:1, and it is used only
	 * for error and destructive *text*.
	 */
	--error-ink: #F87171;
}

/* ==========================================================================
   02. Parent palette neutralisation
   escortwp/style.css is a commercial file we may not edit. Every rule below
   answers a specific magenta/pink declaration in it, mapped to the role the
   element actually plays. Removing a line here reintroduces the magenta.
   ========================================================================== */

/* Section and settings titles — reference structure, not a commit action. */
.bodybox h3,
.bodybox .settingspagetitle,
.pagedesign a,
.post-edit-link,
.edit-registration-content .editregfields .tabletop {
	color: var(--text-primary);
}

/* Legacy gallery/profile surfaces (.girlsingle) carried the magenta hardest. */
.girlsingle a,
.girlsingle h4,
.girlsingle .aboutme h4,
.clientreviewtext h4,
.girlsingle .contact span a,
.agencydetails a,
.agency-reviews-bodybox h4,
.listagencies .agencytr td a {
	color: var(--text-primary);
}

.girlsingle h4,
.girlsingle .aboutme h4,
.clientreviewtext h4,
.agency-reviews-bodybox h4 {
	border-left-color: var(--border-strong);
}

.girlsingle .visitor-counter .count {
	text-shadow: none;
}

.girlsingle .profile-page-no-media:hover {
	border-color: var(--border-strong);
}

.girlsingle .profile-page-no-media p u,
.girlsingle .profile-page-no-media .icon {
	color: var(--text-secondary);
}

/* Media counters and locked sections are facts, so they read as neutral chips. */
.girlsingle .max-photos b,
.girlsingle .max-videos b {
	background: var(--bg-tertiary);
	color: var(--text-primary);
	border-radius: var(--radius-badge, 4px);
}

.girlsingle .lockedsection {
	background-color: var(--bg-tertiary);
	color: var(--text-primary);
	border: 1px solid var(--border-default);
	border-radius: var(--radius-md, 10px);
}

/* Review author names are people, not emphasis. They are never headings. */
.onereviewtext .author span {
	color: var(--text-secondary);
	font-weight: 600;
}

/* Hover borders on cards/thumbs. */
.girl .thumb:hover,
.manage_escorts_page .thumb i:hover {
	border-color: var(--border-strong);
}

/* Sidebar promo blocks were solid magenta panels. */
.sidebar-right .upgradeescortbox,
.sidebar-right .quicksearch,
.listagencies .trhead th {
	background-color: var(--bg-tertiary);
	color: var(--text-primary);
}

/* Pagination is navigation. It never gets the commit colour. */
.escort-pagination li a {
	background: var(--bg-tertiary);
	color: var(--text-primary);
	border: 1px solid var(--border-subtle);
	border-radius: var(--radius-md, 10px);
	filter: none;
}

.escort-pagination li a:hover {
	background: var(--bg-elevated);
	border-color: var(--border-strong);
}

.escort-pagination li span.current {
	color: var(--text-primary);
	border-color: var(--border-strong);
	font-weight: 700;
}

/* Tabs rank by weight and tint, not by a saturated fill. */
.edit-user-types .tabs .tab,
.edit-user-types .tabs .tab:hover {
	background: var(--bg-tertiary);
	border-top-color: var(--border-strong);
	color: var(--text-primary);
}

.edit-user-types .tabs .tab-active,
.edit-user-types .tabs .tab-active:hover {
	background: var(--bg-elevated);
	border-top-color: var(--text-primary);
	color: var(--text-primary);
}

/* select2 shipped a magenta border and a magenta highlight row. */
.select2-dropdown,
.select2 .select2-selection.select2-selection--single {
	border-color: var(--border-default) !important;
	background: var(--bg-tertiary) !important;
	color: var(--text-primary) !important;
}

.select2-container--default .select2-results__option--highlighted[data-selected] {
	background: var(--bg-elevated) !important;
	color: var(--text-primary) !important;
}

/*
 * Prose links must be distinguishable without colour.
 *
 * Phase 8 made link colour equal to body colour so that no link is a stray red.
 * That removed the only thing separating a link from the sentence around it,
 * which Lighthouse flags as link-in-text-block. An underline at rest restores
 * the distinction and does not depend on colour vision at all. Scoped to prose
 * so cards, chrome and buttons that happen to be anchors are untouched.
 */
.entry-content :is(p, li, td) a,
.listing-editorial__content :is(p, li, td) a,
.discovery-editorial__content :is(p, li, td) a,
.taxonomy-description-box a,
.bodybox > p a,
.site-footer__widgets p a,
.onereviewtext p a {
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
	text-decoration-color: var(--border-strong);
}

.entry-content :is(p, li, td) a:hover,
.listing-editorial__content :is(p, li, td) a:hover,
.discovery-editorial__content :is(p, li, td) a:hover,
.taxonomy-description-box a:hover,
.bodybox > p a:hover,
.site-footer__widgets p a:hover,
.onereviewtext p a:hover {
	text-decoration-color: currentColor;
}

/* ==========================================================================
   03. Type hierarchy
   The audit's finding was "five type levels all at weight 800". Weight carries
   rank; size carries scale. These match the DESIGN.md type tokens.
   ========================================================================== */

.section-heading,
.bodybox h3 {
	font-weight: 600 !important;
	letter-spacing: 0 !important;
}

h1 {
	font-weight: 700;
	letter-spacing: 0;
}

h2 {
	font-weight: 700;
	letter-spacing: 0;
}

h3 {
	font-weight: 600;
	letter-spacing: 0;
}

/* Uppercase eyebrows are the one place letter-spacing is allowed. */
.eyebrow,
[class$="__eyebrow"] {
	font-size: 11px;
	font-weight: 600;
	line-height: 1.4;
	letter-spacing: 1.2px;
	text-transform: uppercase;
	color: var(--text-tertiary);
}

/* ==========================================================================
   04. Focus
   One visible focus treatment, on every interactive element, at every width.
   Neutral white: it reads as "the keyboard is here" without introducing a
   colour, and it is the highest-contrast ring available on this canvas.
   ========================================================================== */

:is(a, button, input, select, textarea, summary, [tabindex], [role="button"], [role="tab"], [role="link"]):focus-visible {
	outline: var(--ex-focus-width) solid var(--ex-focus-color);
	outline-offset: var(--ex-focus-offset);
	border-radius: var(--radius-sm, 6px);
}

/* The parent theme sets a magenta focus border on login fields. */
.login form.form-styling .form-input .input:focus {
	border-color: var(--border-strong) !important;
	box-shadow: none !important;
}

/* Never remove focus without replacing it. */
:is(a, button, input, select, textarea):focus:not(:focus-visible) {
	outline: none;
}

/* ==========================================================================
   05. Motion
   Motion tokens only, transform and opacity only, and a reduced-motion answer
   that dampens rather than deletes the feedback.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {

	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}

	/*
	 * Feedback still has to exist. Anything that signalled state by moving now
	 * signals it by tint and border, which reduced motion does not suppress.
	 */
	:is(a, button, [role="button"]):hover,
	:is(a, button, [role="button"]):active {
		transform: none !important;
	}
}

/* ==========================================================================
   06. Layout guards
   The page body never scrolls horizontally. Wide things scroll inside their
   own container.
   ========================================================================== */

/*
 * The header chain carries `overflow: visible !important` so sub-menu dropdowns
 * can escape it. That !important also defeated the tablet scroll rail, so
 * between 641px and 1180px the ten-item main menu overflowed the document by
 * up to 253px on every page on the site. Wrapping the menu keeps the dropdowns
 * escaping and removes the overflow without hiding a single link.
 */
@media screen and (min-width: 641px) and (max-width: 1180px) {

	header.modern-header .modern-nav-wrapper ul.header-menu {
		flex-wrap: wrap;
		row-gap: var(--spacing-1, 4px);
		justify-content: flex-start;
	}
}

/* Media, tables and code never widen the page; they scroll inside themselves. */
.ex-scroll-x {
	overflow-x: auto;
	overflow-y: hidden;
	-webkit-overflow-scrolling: touch;
	overscroll-behavior-x: contain;
}

img,
video,
iframe,
svg {
	max-width: 100%;
}

/* ==========================================================================
   07. Media reservation
   A 40-card grid without a reserved ratio guarantees CLS. Every card media
   box declares its ratio before the image arrives.
   ========================================================================== */

.ex-media {
	position: relative;
	display: block;
	overflow: hidden;
	background: var(--bg-secondary);
	border-radius: inherit;
}

.ex-media--portrait {
	aspect-ratio: 3 / 4;
}

.ex-media--square {
	aspect-ratio: 1 / 1;
}

.ex-media--wide {
	aspect-ratio: 16 / 9;
}

.ex-media > img,
.ex-media > video {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* ==========================================================================
   08. The input system
   One field treatment, applied both to the new .ex-* markup and, defensively,
   to the legacy .form-styling / .input / .longinput / .textarea markup that
   still ships in templates this phase does not convert.
   44px minimum, 10px radius, 16px text on mobile (anything smaller makes iOS
   zoom the viewport on focus), visible label, error next to the field.
   ========================================================================== */

.ex-field {
	display: grid;
	gap: var(--spacing-2, 8px);
	margin-bottom: var(--spacing-4, 16px);
}

.ex-label {
	display: block;
	font-size: 13px;
	font-weight: 600;
	line-height: 1.35;
	color: var(--text-secondary);
}

.ex-label .ex-required {
	color: var(--text-primary);
	font-weight: 700;
}

.ex-hint {
	font-size: 12px;
	line-height: 1.4;
	color: var(--text-tertiary);
}

.ex-input,
.ex-select,
.ex-textarea,
.form-styling .form-input .input,
.form-styling .form-input .textarea,
.form-styling .form-input select,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="tel"],
input[type="url"],
input[type="number"],
input[type="date"],
textarea,
select {
	min-height: var(--ex-field-height);
	padding: 10px 12px;
	background: var(--bg-tertiary);
	border: 1px solid var(--border-default);
	border-radius: var(--ex-field-radius);
	color: var(--text-primary);
	font-family: inherit;
	font-size: 16px;
	line-height: 1.4;
	box-sizing: border-box;
	transition: border-color var(--transition-fast), background-color var(--transition-fast);
	appearance: none;
	-webkit-appearance: none;
}

/*
 * Width is deliberately NOT set on the shared block above: the parent theme
 * sizes fields with .longinput (85%), .smallinput (100px) and .col100 (100%),
 * and blanket-setting width here would collapse the narrow ones.
 */
.ex-input,
.ex-select,
.ex-textarea {
	width: 100%;
}

.ex-textarea,
textarea,
.form-styling .form-input .textarea {
	min-height: 120px;
	line-height: 1.55;
	resize: vertical;
}

/* A native select next to a custom input is the inconsistency this removes. */
.ex-select,
select,
.form-styling .form-input select {
	padding-right: 40px;
	background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
		linear-gradient(135deg, currentColor 50%, transparent 50%);
	background-position: calc(100% - 19px) calc(50% + 2px), calc(100% - 14px) calc(50% + 2px);
	background-size: 5px 5px, 5px 5px;
	background-repeat: no-repeat;
	cursor: pointer;
}

.ex-select option,
select option {
	background: var(--bg-secondary);
	color: var(--text-primary);
}

.ex-input::placeholder,
.ex-textarea::placeholder,
input::placeholder,
textarea::placeholder {
	color: var(--text-tertiary);
	opacity: 1;
}

.ex-input:hover,
.ex-select:hover,
.ex-textarea:hover,
.form-styling .form-input .input:hover,
.form-styling .form-input select:hover {
	border-color: var(--border-strong);
}

.ex-input:disabled,
.ex-select:disabled,
.ex-textarea:disabled,
input:disabled,
select:disabled,
textarea:disabled {
	opacity: 0.55;
	cursor: not-allowed;
	background: var(--bg-secondary);
}

/* Error state carries a border, a word, and an announcement — never colour alone. */
.ex-field.is-invalid .ex-input,
.ex-field.is-invalid .ex-select,
.ex-field.is-invalid .ex-textarea,
.ex-input[aria-invalid="true"],
.ex-select[aria-invalid="true"],
.ex-textarea[aria-invalid="true"] {
	border-color: var(--error);
	background: var(--bg-tertiary);
}

.ex-error {
	display: flex;
	align-items: flex-start;
	gap: 6px;
	font-size: 13px;
	line-height: 1.4;
	font-weight: 600;
	color: var(--error-ink);
}

.ex-error::before {
	content: "!";
	flex: 0 0 auto;
	width: 16px;
	height: 16px;
	margin-top: 1px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: 1px solid currentColor;
	border-radius: 50%;
	font-size: 11px;
	font-weight: 700;
}

.ex-field.is-valid .ex-input,
.ex-field.is-valid .ex-select {
	border-color: var(--success);
}

/* Checkboxes and radios get the same treatment, and a real 44px target. */
.ex-check {
	display: flex;
	align-items: center;
	gap: var(--spacing-3, 12px);
	min-width: 0;
	min-height: var(--ex-tap-min);
	padding: var(--spacing-2, 8px) 0;
	cursor: pointer;
	font-size: 15px;
	line-height: 1.4;
	color: var(--text-primary);
	/* A consent sentence is prose: it must wrap, not push the page sideways. */
	flex-wrap: wrap;
	overflow-wrap: anywhere;
}

/*
 * The terms row is a full-width consent statement, never a 64% floated field.
 * It used to carry the legacy .col100 to say so; the layout says it here now.
 */
.form-styling .form-input.form-input-accept-tos {
	width: 100%;
	float: none;
	box-sizing: border-box;
}

.ex-check input[type="checkbox"],
.ex-check input[type="radio"] {
	flex: 0 0 auto;
	width: 20px;
	height: 20px;
	margin: 0;
	accent-color: var(--brand-primary);
	cursor: pointer;
}

/*
 * The parent lays fields out as a 34% / 64% float pair, which collapses badly
 * below 768px. A grid keeps the same visual relationship on desktop and stacks
 * honestly on mobile, without touching a single form processor.
 */
.form-styling .form-label,
.form-styling .form-input {
	box-sizing: border-box;
}

@media screen and (max-width: 767px) {

	.form-styling .form-label,
	.form-styling .form-input,
	.form-styling .form-label.col100,
	.form-styling .form-input.col100 {
		width: 100% !important;
		float: none !important;
	}

	.form-styling .form-label label {
		line-height: 1.35;
		padding-bottom: 6px;
	}

	.form-styling .form-input .longinput,
	.form-styling .form-input .smallinput {
		width: 100% !important;
	}
}

/* ==========================================================================
   09. The button system
   44px minimum height, 10px radius, and exactly one red per viewport. The
   legacy .pinkbutton / .redbutton / .greenbutton / .graybutton classes are
   retrofitted here so templates this phase does not convert still land on
   the contract.
   ========================================================================== */

.ex-btn,
.pinkbutton,
.redbutton,
.greenbutton,
.graybutton,
.whitebutton,
.blueishbutton,
.orangebutton {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--spacing-2, 8px);
	min-height: var(--ex-tap-min);
	padding: 0 var(--spacing-4, 16px);
	border: 1px solid transparent;
	border-radius: var(--radius-md, 10px) !important;
	font-family: inherit;
	font-size: 14px;
	font-weight: 600;
	line-height: 1;
	text-align: center;
	text-decoration: none !important;
	cursor: pointer;
	box-sizing: border-box;
	transition: transform var(--transition-fast), background-color var(--transition-fast), border-color var(--transition-fast);
}

/* The single commit action. */
.ex-btn--primary,
.pinkbutton,
.greenbutton {
	background: var(--brand-primary) !important;
	color: var(--on-primary, #ECECEC) !important;
	border-color: transparent !important;
	filter: none !important;
}

.ex-btn--primary:hover,
.pinkbutton:hover,
.greenbutton:hover {
	background: var(--brand-primary-active) !important;
	filter: none !important;
}

.ex-btn--primary:active,
.pinkbutton:active,
.greenbutton:active {
	background: var(--brand-primary-active) !important;
	transform: translateY(1px);
}

/* Everything that is not the commit action is neutral. */
.ex-btn--secondary,
.graybutton,
.whitebutton,
.blueishbutton,
.orangebutton {
	background: var(--bg-tertiary) !important;
	color: var(--text-primary) !important;
	border-color: var(--border-default) !important;
	filter: none !important;
}

.ex-btn--secondary:hover,
.graybutton:hover,
.whitebutton:hover,
.blueishbutton:hover,
.orangebutton:hover {
	background: var(--bg-elevated) !important;
	border-color: var(--border-strong) !important;
	filter: none !important;
}

/* Destructive is not the commit colour; it is the error colour, plus a word. */
.ex-btn--danger,
.redbutton {
	background: transparent !important;
	color: var(--error-ink) !important;
	border-color: var(--error) !important;
	filter: none !important;
}

.ex-btn--danger:hover,
.redbutton:hover {
	background: rgba(239, 68, 68, 0.12) !important;
	filter: none !important;
}

.ex-btn--ghost {
	background: transparent;
	color: var(--text-secondary);
	border-color: transparent;
}

.ex-btn--ghost:hover {
	background: var(--ex-tint-1);
	color: var(--text-primary);
}

.ex-btn[disabled],
.ex-btn.is-disabled,
.pinkbutton[disabled],
.greenbutton[disabled],
button[disabled] {
	opacity: 0.5;
	cursor: not-allowed;
	transform: none !important;
}

/*
 * Pending must appear within 300ms and must not change the control's size.
 * The label is replaced in place, so the box never reflows.
 */
.ex-btn.is-pending {
	position: relative;
	color: transparent !important;
	pointer-events: none;
}

.ex-btn.is-pending::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	width: 16px;
	height: 16px;
	margin: -8px 0 0 -8px;
	border: 2px solid currentColor;
	border-top-color: transparent;
	border-radius: 50%;
	color: var(--on-primary, #ECECEC);
	animation: ex-spin 700ms linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {

	.ex-btn.is-pending::after {
		animation: none;
		border-top-color: currentColor;
		opacity: 0.6;
	}
}

/* Icon-only controls keep a 44px target around a smaller visual box. */
.ex-icon-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--ex-tap-min);
	height: var(--ex-tap-min);
	padding: 0;
	background: transparent;
	border: 0;
	color: var(--text-primary);
	cursor: pointer;
}

.ex-icon-btn > svg {
	width: 20px;
	height: 20px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.5;
	stroke-linecap: round;
	stroke-linejoin: round;
	pointer-events: none;
}

/* Adjacent icon buttons need 8px of separation unless deliberately segmented. */
.ex-icon-btn + .ex-icon-btn {
	margin-left: var(--ex-control-gap);
}

/* ==========================================================================
   09b. Touch targets
   Every discrete control is at least 44x44. Inline links inside a sentence are
   deliberately excluded — WCAG exempts them, and padding prose links to 44px
   destroys the line rhythm of the marketing copy marketing owns. These are the
   controls measured under 44px at 390px on home, listing and profile.
   ========================================================================== */

.mobile-menu-icon,
.mobile-login-icon,
.close-menu,
.profile-hero__nav-btn,
.profile-hero__next-profile,
.profile-review-entry__star {
	min-width: var(--ex-tap-min);
	min-height: var(--ex-tap-min);
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

.footer-meta-links__pill,
.section-see-all,
.see-all-top,
.globale-cta,
.filter-results-summary__clear,
.listing-summary__clear,
.online-empty-state__action,
.aboutme__readmore,
.post-edit-link {
	min-height: var(--ex-tap-min);
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

/*
 * The star row is a rating control, so the visual star stays small while the
 * hit area grows around it. Segmented by intent, so the 8px separation rule
 * does not apply between them.
 */
.profile-review-entry__star {
	padding: 0 2px;
}

/* ==========================================================================
   09c. The route back into My Exotic
   A member who browses out of the workspace needs a way back. On desktop that
   is a header action; on mobile it is the account panel and the bottom nav.
   These are the three controls Phase 8 added for that, and all three use the
   ten-icon set rather than the parent icon font.
   ========================================================================== */

/*
 * Desktop header. Post Ad and My Exotic never render together — Post Ad is the
 * signed-out primary, My Exotic the signed-in one — so this takes the same
 * burgundy the register button wears and the header keeps exactly one branded
 * action in either state.
 *
 * The background is flagged !important because the parent chain still ships
 * `.header-top-bar .subnav-menu li.subnav-menu-btn a { background: #45B1D8 }`
 * at a higher specificity than this selector. Its Login/Log Out siblings in
 * override.css answer it the same way; without the flag this button renders in
 * that off-brand blue.
 */
.modern-header-actions .my-exotic-btn a.modern-my-exotic-cta {
	display: inline-flex;
	align-items: center;
	gap: var(--spacing-2, 8px);
	min-height: 40px;
	background: linear-gradient(135deg, var(--brand-primary), var(--brand-primary-active)) !important;
	border: 1px solid rgba(139, 10, 26, 0.38);
	/* On burgundy the contract text colour is --on-primary, never pure white. */
	color: var(--on-primary) !important;
	font-weight: 600;
	box-shadow: 0 10px 24px rgba(139, 10, 26, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.16);
}

.modern-header-actions .my-exotic-btn a.modern-my-exotic-cta:hover {
	background: linear-gradient(135deg, var(--brand-primary), var(--brand-primary)) !important;
	border-color: rgba(139, 10, 26, 0.55);
	box-shadow: 0 14px 30px rgba(139, 10, 26, 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.18);
	transform: translateY(-1px);
}

.modern-header-actions .my-exotic-btn svg,
.mobile-account-action--my-exotic > svg,
.modern-mobile-signin > svg {
	flex: 0 0 auto;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.5;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.modern-header-actions .my-exotic-btn svg,
.modern-mobile-signin > svg {
	width: 20px;
	height: 20px;
}

/*
 * The panel rows are a 18px / 1fr / auto grid built for the parent icon font.
 * The glyph matches that column rather than the grid being widened for it, so
 * this row sits in exactly the same rhythm as the ones beside it.
 */
.mobile-account-action--my-exotic > svg {
	width: 18px;
	height: 18px;
}

/*
 * The account button is a mark now, not a word. Its accessible name still comes
 * from the aria-label the button already carried, so screen readers are
 * unchanged; only the visible glyph replaced the text.
 */
.modern-mobile-signin > svg {
	margin: 0;
}

/* ==========================================================================
   10. Badges
   One specification, one slot per card, resolved by priority.
   ========================================================================== */

.ex-badge {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	height: 22px;
	padding: 0 7px;
	border: 1px solid var(--border-default);
	border-radius: var(--radius-badge, 4px);
	background: var(--ex-tint-2);
	color: var(--text-secondary);
	font-size: 11px;
	font-weight: 700;
	line-height: 1;
	letter-spacing: 0.02em;
	white-space: nowrap;
}

.ex-badge--verified {
	border-color: rgba(255, 255, 255, 0.22);
	background: rgba(230, 196, 106, 0.12);
	color: var(--gold-accent, #E6C46A);
}

.ex-badge--online {
	border-color: rgba(16, 185, 129, 0.5);
	background: rgba(16, 185, 129, 0.12);
	color: var(--success);
}

.ex-badge--online::before {
	content: "";
	width: 5px;
	height: 5px;
	border-radius: 50%;
	background: currentColor;
}

.ex-badge--new {
	border-color: var(--border-strong);
	background: var(--ex-tint-2);
	color: var(--text-primary);
}

/* ==========================================================================
   11. The ten icons
   20x20 viewBox, 1.5px stroke, round caps and joins, fill none, currentColor.
   State is never a different icon: saved is the same bookmark, filled.
   ========================================================================== */

.ex-icon {
	width: 20px;
	height: 20px;
	flex: 0 0 auto;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.5;
	stroke-linecap: round;
	stroke-linejoin: round;
	vertical-align: middle;
	pointer-events: none;
}

.ex-icon--filled {
	fill: currentColor;
}

/* ==========================================================================
   12. Empty and offline states
   Every list gets a designed empty state that says what to do next.
   ========================================================================== */

.ex-empty {
	display: grid;
	gap: var(--spacing-3, 12px);
	justify-items: start;
	padding: var(--spacing-6, 32px) var(--spacing-4, 16px);
	border: 1px dashed var(--border-subtle);
	border-radius: var(--radius-lg, 14px);
	background: var(--ex-tint-1);
}

.ex-empty__title {
	margin: 0;
	font-size: 16px;
	font-weight: 600;
	color: var(--text-primary);
}

.ex-empty__body {
	margin: 0;
	font-size: 14px;
	line-height: 1.55;
	color: var(--text-secondary);
	max-width: 46ch;
}

/* Skeletons for content, spinners only inside controls. */
.ex-skeleton {
	background: linear-gradient(90deg, var(--ex-tint-1), var(--ex-tint-3), var(--ex-tint-1));
	background-size: 200% 100%;
	border-radius: var(--radius-md, 10px);
	animation: ex-shimmer 1.4s ease-in-out infinite;
}

@keyframes ex-shimmer {
	to {
		background-position: -200% 0;
	}
}

@media (prefers-reduced-motion: reduce) {

	.ex-skeleton {
		animation: none;
		background: var(--ex-tint-2);
	}
}

/*
 * [hidden] is a UA rule and loses to any class selector that sets display.
 * Every foundation component that toggles it needs this said explicitly.
 */
.ex-btn[hidden],
.ex-icon-btn[hidden],
.ex-badge[hidden],
.ex-empty[hidden],
.ex-field[hidden] {
	display: none !important;
}

/* ==========================================================================
   13. The member gate
   Follow and Compare render for a signed-out visitor too. Pressing one opens
   this dialog rather than failing silently: the visitor has already shown
   interest by tapping, which is the best moment to explain what an account is
   for. It never blocks the page — dismissing it returns them to browsing.

   Lives here because product-foundation.css loads on every page, while
   customer-product.css is enqueued only for signed-in members.
   ========================================================================== */

.mx-gate {
	position: fixed;
	inset: 0;
	z-index: 11000;
	display: flex;
	align-items: flex-end;
	justify-content: center;
	padding: 0;
}

/* [hidden] is a UA rule and loses to the class selector above. */
.mx-gate[hidden] {
	display: none !important;
}

.mx-gate__backdrop {
	position: absolute;
	inset: 0;
	background: rgba(0, 0, 0, 0.72);
	backdrop-filter: blur(2px);
	opacity: 0;
	transition: opacity var(--transition-base, 250ms);
}

.mx-gate.is-open .mx-gate__backdrop {
	opacity: 1;
}

.mx-gate__panel {
	position: relative;
	width: 100%;
	max-width: 460px;
	max-height: 92vh;
	overflow-y: auto;
	padding: var(--spacing-6, 32px) var(--spacing-5, 24px) calc(var(--spacing-5, 24px) + env(safe-area-inset-bottom, 0px));
	border: 1px solid var(--border-default);
	border-bottom: 0;
	border-radius: var(--radius-hero, 26px) var(--radius-hero, 26px) 0 0;
	background: var(--bg-secondary);
	box-shadow: var(--shadow-lg);
	/* Rises from the bottom, which is where the thumb that opened it is. */
	transform: translateY(16px);
	opacity: 0;
	transition: transform var(--transition-sheet, 260ms cubic-bezier(0.22, 1, 0.36, 1)), opacity var(--transition-fast, 150ms);
}

.mx-gate.is-open .mx-gate__panel {
	transform: none;
	opacity: 1;
}

@media (min-width: 600px) {

	.mx-gate {
		align-items: center;
		padding: var(--spacing-5, 24px);
	}

	.mx-gate__panel {
		border-bottom: 1px solid var(--border-default);
		border-radius: var(--radius-hero, 26px);
	}
}

.mx-gate__close {
	position: absolute;
	top: 12px;
	right: 12px;
	width: 44px;
	height: 44px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: 0;
	background: transparent;
	color: var(--text-secondary);
	font-size: 22px;
	line-height: 1;
	cursor: pointer;
}

.mx-gate__close:hover {
	color: var(--text-primary);
}

.mx-gate__eyebrow {
	margin: 0 0 6px;
	font-size: 11px;
	font-weight: 600;
	line-height: 1.4;
	letter-spacing: 1.2px;
	text-transform: uppercase;
	color: var(--text-tertiary);
}

.mx-gate__title {
	margin: 0 0 8px;
	font-size: 24px;
	font-weight: 700;
	line-height: 1.16;
	color: var(--text-primary);
}

.mx-gate__copy {
	margin: 0 0 var(--spacing-5, 24px);
	font-size: 15px;
	line-height: 1.55;
	color: var(--text-secondary);
	max-width: 42ch;
}

/* ---------- The face rail ----------
   The photographs are the argument. A slow, continuous drift of real profiles
   says "there is something here to keep up with" faster than a sentence can,
   and it is the one asset this product actually owns. It never becomes a
   control: no hover, no pointer, no tab stop. */

.mx-gate__faces {
	position: relative;
	/*
	 * Pulled out of the panel's padding on three sides so it reads as a header
	 * band rather than a strip floating inside the copy. Its own top corners
	 * are rounded to match the panel, because the panel scrolls and therefore
	 * cannot clip them itself.
	 */
	margin: calc(var(--spacing-6, 32px) * -1) calc(var(--spacing-5, 24px) * -1) var(--spacing-5, 24px);
	padding: var(--spacing-5, 24px) 0 var(--spacing-4, 16px);
	border-radius: var(--radius-hero, 26px) var(--radius-hero, 26px) 0 0;
	background: linear-gradient(180deg, var(--ex-tint-2), transparent);
	overflow: hidden;
	/* Fades at both ends so the loop has no visible entry or exit. */
	-webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
	mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
	pointer-events: none;
}

.mx-gate__faces-track {
	display: flex;
	align-items: center;
	gap: var(--spacing-2, 8px);
	width: max-content;
	/* The track holds two identical passes, so -50% lands on a seamless wrap. */
	animation: mx-gate-drift 38s linear infinite;
}

.mx-gate__face {
	flex: 0 0 auto;
	display: block;
	width: 48px;
	height: 48px;
	border-radius: 999px;
	overflow: hidden;
	border: 1px solid var(--border-subtle);
	background: var(--bg-tertiary);
}

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

@keyframes mx-gate-drift {
	from {
		transform: translate3d(0, 0, 0);
	}

	to {
		transform: translate3d(-50%, 0, 0);
	}
}

/* Reduced motion keeps the faces and drops the drift. */
@media (prefers-reduced-motion: reduce) {

	.mx-gate__faces-track {
		animation: none;
	}
}

.mx-gate__actions {
	display: grid;
	gap: var(--spacing-2, 8px);
}

.mx-gate__btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 48px;
	padding: 0 var(--spacing-4, 16px);
	border-radius: var(--radius-md, 10px);
	border: 1px solid transparent;
	font-size: 15px;
	font-weight: 600;
	text-decoration: none;
	transition: background-color var(--transition-fast, 150ms), border-color var(--transition-fast, 150ms);
}

/* The one commit action in this dialog. */
.mx-gate__btn--primary {
	background: var(--brand-primary);
	color: var(--on-primary, #ECECEC) !important;
}

.mx-gate__btn--primary:hover {
	background: var(--brand-primary-active);
}

.mx-gate__btn--secondary {
	background: var(--bg-tertiary);
	border-color: var(--border-default);
	color: var(--text-primary) !important;
}

.mx-gate__btn--secondary:hover {
	background: var(--bg-elevated);
	border-color: var(--border-strong);
}

.mx-gate__foot {
	margin: var(--spacing-4, 16px) 0 0;
	font-size: 12px;
	line-height: 1.45;
	color: var(--text-tertiary);
	text-align: center;
}

/* The page behind the dialog does not scroll while it is open. */
body.mx-gate-open {
	overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {

	.mx-gate__backdrop,
	.mx-gate__panel {
		transition-duration: 0.01ms !important;
		transform: none !important;
	}
}

/*
 * A gated control is the real control, at the real size, so the interface does
 * not change shape when the visitor signs in. Only the outcome differs.
 */
.mx-gate-trigger {
	cursor: pointer;
}

/* ==========================================================================
   09d. Desktop header navigation overflow
   Above 1180px the header is a three-column flex row and `ul.header-menu` is
   the flexible middle one, `flex-wrap: nowrap` inside a chain forced to
   `overflow: visible` so its dropdowns can escape. With a short menu that row
   fits and reads well. With a long one it does not: ten items want ~990px,
   are given ~615px, and the surplus paints straight across the header's
   action buttons.

   header-nav-overflow.js measures that row and parks the items that do not
   fit in a "More" dropdown at the end of it. These are the styles for that
   control, plus the guard that keeps the row from spilling in the frame
   before the script has measured.
   ========================================================================== */

@media (min-width: 1181px) {

	/*
	 * Until the script reports it has measured, the row is clipped rather
	 * than allowed to paint over the buttons beside it. Clipping rather than
	 * wrapping because a wrap would change the header's height on every load
	 * and shift the page under the visitor. The links are all still in the
	 * markup, and the mobile drawer and footer both carry the full menu, so
	 * the scripting-off case loses a hover affordance, not a route.
	 *
	 * The parent chain sets `overflow: visible !important` on this element,
	 * so this has to out-specify it rather than merely follow it.
	 */
	header.modern-header .modern-nav-wrapper:not(.is-nav-overflow-ready) ul.header-menu {
		overflow: hidden !important;
	}

	/*
	 * The edge fades were the affordance for a horizontally scrolling row,
	 * which this breakpoint no longer has — the parent's `overflow: visible`
	 * had already taken the scroll away and left them dimming the first and
	 * last link for no reason. The overflow menu replaces them.
	 */
	.modern-nav-wrapper::before,
	.modern-nav-wrapper::after {
		display: none;
	}
}

/*
 * `ul.header-menu>li` is `display: flex`, which beats the [hidden] attribute's
 * UA `display: none`. Without this the empty control holds its width in the
 * row it is supposed to be absent from.
 */
.modern-nav-wrapper ul.header-menu .header-nav-more[hidden] {
	display: none !important;
}

.modern-nav-wrapper ul.header-menu .header-nav-more {
	position: relative;
	display: flex;
	align-items: center;
	flex: 0 0 auto;
}

/* Sized and coloured as a peer of the links it stands in for. */
.modern-nav-wrapper ul.header-menu .header-nav-more__toggle {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	margin: 0;
	padding: 3px 6px;
	border: 0;
	border-radius: var(--radius-sm);
	background: transparent;
	color: var(--text-secondary);
	font-family: inherit;
	font-size: 12.5px;
	font-weight: 500;
	line-height: 1.4;
	white-space: nowrap;
	cursor: pointer;
	transition: all var(--transition-fast);
}

.modern-nav-wrapper ul.header-menu .header-nav-more__toggle:hover,
.modern-nav-wrapper ul.header-menu .header-nav-more:focus-within .header-nav-more__toggle,
.modern-nav-wrapper ul.header-menu .header-nav-more[data-open="true"] .header-nav-more__toggle {
	color: var(--text-primary);
	background: var(--bg-tertiary);
}

.modern-nav-wrapper ul.header-menu .header-nav-more__caret {
	width: 6px;
	height: 6px;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: translateY(-2px) rotate(45deg);
	transition: transform var(--transition-fast);
}

.modern-nav-wrapper ul.header-menu .header-nav-more[data-open="true"] .header-nav-more__caret {
	transform: translateY(1px) rotate(-135deg);
}

/*
 * The control sits at the right edge of the row, hard against the action
 * buttons, so its panel opens leftward from that edge instead of running off
 * under them. Everything else about it is the menu's existing `.sub-menu`.
 */
.modern-nav-wrapper ul.header-menu .header-nav-more__list {
	left: auto;
	right: 0;
	min-width: 220px;
}

.modern-nav-wrapper ul.header-menu .header-nav-more[data-open="true"] > .sub-menu {
	display: block;
}

/*
 * A parked item is a plain row in the panel, not a flex cell in the header,
 * and its own dropdown flies out to the left for the same reason the panel
 * itself opens leftward.
 */
.modern-nav-wrapper ul.header-menu .header-nav-more__list > li {
	display: block;
	width: 100%;
}

.modern-nav-wrapper ul.header-menu .header-nav-more__list > li > a {
	display: block;
	width: 100%;
	padding: 10px 16px;
	border-radius: 8px;
	font-size: 13px;
	font-weight: 500;
	white-space: nowrap;
}

.modern-nav-wrapper ul.header-menu .header-nav-more__list .sub-menu {
	top: 0;
	left: auto;
	right: 100%;
	margin: 0 8px 0 0;
}

/* ---------- Header My Exotic CTA — icon balance ----------
   The mark ships at the workspace default of 20px with a 1.5 stroke, which is
   correct in a 44px nav row but oversized inside a 40px header button next to
   15px/600 text: it out-weighted the label it was meant to accompany, and the
   symmetric 8px gap left the button reading icon-heavy on the left.

   Optical, not geometric: the glyph sits on the box centre while the label
   sits on its cap height, so the mark needs a pixel up to share a centre line
   with the text beside it.
   ------------------------------------------------------------------------ */

.modern-header-actions .my-exotic-btn a.modern-my-exotic-cta {
	gap: 7px;
	padding-right: 17px;
	padding-left: 14px;
}

.modern-header-actions .my-exotic-btn a.modern-my-exotic-cta .mx-icon {
	width: 17px;
	height: 17px;
	stroke-width: 1.7;
	margin-top: -1px;
}
