/**
 * Search by photo (M-VS3).
 *
 * Lives in the header's search panel, inside a .site-header that is sticky on
 * interior pages and fixed over the hero on the homepage — so everything here
 * is bounded and scrolls internally rather than growing the header past the
 * viewport. Once a photo is chosen the panel also takes an opaque surface of
 * its own; see "Expanded workspace" below.
 *
 * Product cards inside the results grid are styled by main.css (global), so
 * this file defines only the grid they sit in.
 */

/* JS drives visibility with [hidden]; nothing below may out-specify it. */
.visual-search [hidden] {
	display: none !important;
}

.visual-search {
	margin-top: var(--space-3);
}

/* ── Entry: divider, then "Search by photo" ─────────────────────────────── */

/*
 * The resting state stays dominated by the text field above it: one hairline
 * with a small "or", then a single quiet control. Nothing about uploading a
 * photo is on screen until the customer asks for it.
 */
.visual-search__entry {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-2);
}

.visual-search__divider {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	width: 100%;
	margin: var(--space-1) 0 0;
	font-family: var(--font-sans);
	font-size: 10px;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--color-muted);
}

.visual-search__divider::before,
.visual-search__divider::after {
	content: "";
	flex: 1;
	height: 1px;
	background: var(--color-line);
}

.visual-search__open {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	min-height: 44px;
	padding: 0 var(--space-3);
	border: 0;
	background: none;
	color: var(--color-ink);
	font-family: var(--font-sans);
	font-size: 13px;
	letter-spacing: 0.04em;
	cursor: pointer;
}

.visual-search__open span {
	border-bottom: 1px solid var(--color-ink);
	padding-bottom: 2px;
}

.visual-search__open:hover {
	color: var(--color-muted);
}

.visual-search__open:hover span {
	border-bottom-color: var(--color-muted);
}

/*
 * Visually hidden but focusable and operable: the label + button pair above
 * is the real control. display:none would take it out of the accessibility
 * tree and stop .click() working in some browsers.
 */
.visual-search__file {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
	border: 0;
}

/* ── Panel ──────────────────────────────────────────────────────────────── */

.visual-search__panel {
	/*
	 * The header is fixed on the homepage (home-hero.css) and sticky on
	 * interior pages (main.css), so in both cases this panel can only overlay
	 * the page: it cannot push content down, and anything past the bottom of
	 * the screen would be unreachable. The workspace therefore runs from the
	 * bottom of the search pill to the bottom of the viewport and scrolls
	 * inside itself.
	 *
	 * Every part of that offset is a layout token except the pill's own line
	 * box, the one literal here:
	 *   --announcement-height  the fixed announcement bar
	 *   --header-height        the header row's min-height
	 *   --space-3              .visual-search margin-top at this width
	 *   --space-3 * 2          the pill's vertical padding (main.css)
	 *   19px                   the pill's 17px line box (14px Inter at normal
	 *                          line-height) + its 1px borders — no token
	 *                          describes this, and the pill is approved as-is
	 *
	 * 100vh first as a legacy fallback, then 100svh — the SMALL viewport, with
	 * all mobile browser chrome shown. Same ladder and the same reason as
	 * .hero in home-hero.css: under plain 100vh iOS reports the
	 * chrome-collapsed height, so the last row of results ends up behind
	 * Safari's toolbar where it cannot be reached.
	 */
	--visual-search-offset: calc(
		var(--announcement-height) + var(--header-height)
		+ var(--space-3) * 3 + 19px
	);
	max-height: calc(100vh - var(--visual-search-offset));
	max-height: calc(100svh - var(--visual-search-offset));
	overflow-y: auto;
	overscroll-behavior: contain;
	-webkit-overflow-scrolling: touch;
	padding-top: var(--space-4);
}

/* ── Expanded workspace ─────────────────────────────────────────────────── */

/*
 * From the moment a photo is chosen, Visual Search stops borrowing the page's
 * background and gets its own. On the homepage the header is transparent over
 * the hero, so without this the eyebrow, preview, controls, privacy line and
 * results composite straight onto the hero photograph and its typography.
 *
 * Gated on data-state, which the state machine already maintains: every
 * expanded state (selected, searching, results, empty, error) is covered, and
 * idle — the approved pill / OR / white "Search by photo" composition — is
 * not, because the selector never matches it. No JavaScript, template or
 * z-index change is needed: the panel already sits inside a header that stacks
 * above the hero, so an opaque fill is the whole of the separation.
 *
 * Deliberately not a card — no radius, border or shadow — so it reads as a
 * section of the page rather than a modal or a file-upload widget.
 */
.visual-search:not([data-state="idle"]) .visual-search__panel {
	background: var(--color-cream);
	padding-top: var(--space-6);
	padding-bottom: var(--space-6);
	/*
	 * .site-search keeps a --space-4 bottom padding at every width; left alone
	 * it shows as a strip of hero under the surface.
	 */
	margin-bottom: calc(var(--space-4) * -1);
}

.visual-search__stage {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-3);
	text-align: center;
}

.visual-search__eyebrow {
	margin: 0;
	font-family: var(--font-sans);
	font-size: 11px;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--color-muted);
}

/*
 * Portrait container: fashion photography is overwhelmingly vertical, and
 * `contain` keeps a full look visible (a crop would cut the garment the
 * customer is searching for).
 */
.visual-search__preview {
	width: 132px;
	aspect-ratio: 2 / 3;
	background: var(--color-cream);
	border: 1px solid var(--color-line);
	overflow: hidden;
}

.visual-search__preview img {
	width: 100%;
	height: 100%;
	object-fit: contain;
	display: block;
}

.visual-search[data-state="searching"] .visual-search__preview {
	opacity: 0.55;
	transition: opacity 200ms ease;
}

.visual-search__tools {
	display: flex;
	align-items: center;
	gap: var(--space-2);
}

.visual-search__tools-sep {
	color: var(--color-line);
}

.visual-search__link {
	min-height: 44px;
	padding: 0 var(--space-1);
	border: 0;
	background: none;
	color: var(--color-muted);
	font-family: var(--font-sans);
	font-size: 12px;
	letter-spacing: 0.04em;
	text-decoration: underline;
	text-underline-offset: 3px;
	cursor: pointer;
}

.visual-search__link:hover {
	color: var(--color-ink);
}

.visual-search__submit {
	width: 100%;
	max-width: 320px;
	min-height: 48px;
	padding: 0 var(--space-6);
	border: 1px solid var(--color-ink);
	background: var(--color-ink);
	color: var(--color-paper);
	font-family: var(--font-sans);
	font-size: 12px;
	font-weight: 500;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	cursor: pointer;
	transition: background-color 180ms ease, color 180ms ease;
}

.visual-search__submit:hover:not(:disabled) {
	background: var(--color-paper);
	color: var(--color-ink);
}

.visual-search__submit:disabled {
	cursor: default;
	opacity: 0.5;
}

.visual-search__note {
	margin: 0;
	max-width: 34ch;
	font-family: var(--font-sans);
	font-size: 10px;
	line-height: 1.5;
	color: var(--color-muted);
}

/* ── Searching / error ──────────────────────────────────────────────────── */

.visual-search__status,
.visual-search__error {
	margin: var(--space-4) 0 0;
	font-family: var(--font-sans);
	font-size: 12px;
	letter-spacing: 0.04em;
	text-align: center;
}

.visual-search__status {
	color: var(--color-muted);
}

/*
 * Errors are marked by an inset rule and their own wording, not by color
 * alone — the palette has no red and this text stays legible either way.
 * Paper, not cream: the workspace behind it is cream, and a cream block on a
 * cream surface is no block at all.
 */
.visual-search__error {
	padding: var(--space-3) var(--space-4);
	border-left: 2px solid var(--color-ink);
	background: var(--color-paper);
	color: var(--color-ink);
	text-align: left;
	line-height: 1.5;
}

.visual-search__status::after {
	content: "";
	display: inline-block;
	width: 3px;
	height: 3px;
	margin-left: var(--space-2);
	border-radius: 50%;
	background: currentColor;
	vertical-align: middle;
	animation: visual-search-pulse 1.1s ease-in-out infinite;
}

@keyframes visual-search-pulse {
	0%, 100% { opacity: 0.25; }
	50%      { opacity: 1; }
}

/* ── Results ────────────────────────────────────────────────────────────── */

/*
 * Once results are on screen the photo has done its job, so the stage steps
 * back: the preview drops to a thumbnail, and the two things that only matter
 * before a search runs — the submit button and the privacy line — leave the
 * screen. Replace / Remove stay, and are the change-photo action.
 *
 * The stage also turns from a stack into one compact row: thumbnail on the
 * left, its label and controls beside it, so the products rather than the photo
 * the customer already chose hold the top of the workspace. Two rows in the
 * second column instead of a wrapper element, because the markup has none —
 * eyebrow above, Replace / Remove below, the thumbnail spanning both. The
 * spanning thumbnail sets the row's height and the pair centres against it.
 *
 * Applied at every width: the reclaimed height is what matters on a phone, and
 * the same row reads as a results header on desktop.
 *
 * Presentation only. The state machine still shows the stage here (it keys off
 * "an image is selected", not the state name), the submit button keeps its
 * wiring, and display:none takes both of the hidden controls out of the grid
 * rather than leaving empty tracks behind.
 */
.visual-search[data-state="results"] .visual-search__stage {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	column-gap: var(--space-4);
	row-gap: var(--space-1);
	align-items: center;
	justify-items: start;
	text-align: left;
}

.visual-search[data-state="results"] .visual-search__preview {
	width: 64px;
	grid-column: 1;
	grid-row: 1 / span 2;
}

.visual-search[data-state="results"] .visual-search__eyebrow {
	grid-column: 2;
	grid-row: 1;
	align-self: end;
}

.visual-search[data-state="results"] .visual-search__tools {
	grid-column: 2;
	grid-row: 2;
	align-self: start;
}

.visual-search[data-state="results"] .visual-search__submit,
.visual-search[data-state="results"] .visual-search__note {
	display: none;
}

.visual-search__results,
.visual-search__empty {
	margin-top: var(--space-6);
	padding-top: var(--space-5);
	border-top: 1px solid var(--color-line);
}

/*
 * The hairline stays — it is what keeps the compact header from reading as
 * crammed against the products — but the air either side of it is tightened
 * once results exist, since nothing above it is waiting for input any more.
 * Scoped to the results state so the empty state keeps its roomier rhythm.
 */
.visual-search[data-state="results"] .visual-search__results {
	margin-top: var(--space-4);
	padding-top: var(--space-4);
}

.visual-search__heading {
	margin: 0 0 var(--space-4);
	font-family: var(--font-sans);
	font-size: 11px;
	font-weight: 500;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--color-ink);
}

.visual-search__heading:focus-visible,
.visual-search__empty-title:focus-visible {
	outline: 2px solid var(--color-ink);
	outline-offset: 4px;
}

/*
 * The endpoint returns its cards already wrapped in .product-grid, so that
 * inner element — not this container — is what has to become the grid.
 * Declared here rather than borrowed from shop.css, which only loads on shop
 * pages while these results can open in the header on any page.
 *
 * Three columns, not the archive's four: the workspace is the search
 * composition's own 760px measure, where four 2:3 cards would be cramped.
 */
.visual-search__grid .product-grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--space-6) var(--space-4);
}

.visual-search__empty-title {
	margin: 0 0 var(--space-2);
	font-family: var(--font-display);
	font-size: 20px;
	color: var(--color-ink);
}

.visual-search__empty-text {
	margin: 0 0 var(--space-3);
	font-family: var(--font-sans);
	font-size: 13px;
	color: var(--color-muted);
}

.visual-search__empty-actions {
	display: flex;
	align-items: center;
	gap: var(--space-5);
	flex-wrap: wrap;
}

/* ── Responsive ─────────────────────────────────────────────────────────── */

/*
 * Desktop reads top to bottom as one centred composition: field, hairline
 * divider, photo control. main.css constrains the whole search panel, so the
 * only thing to add here is a little more air between the three — enough to
 * separate them without making the header panel tall.
 */
@media (min-width: 768px) {
	.visual-search {
		margin-top: var(--space-4);
	}

	.visual-search__entry {
		gap: var(--space-3);
	}

	.visual-search__panel {
		/* Same composition as the base rule, with the --space-4 gap above. */
		--visual-search-offset: calc(
			var(--announcement-height) + var(--header-height)
			+ var(--space-4) + var(--space-3) * 2 + 19px
		);
	}

	/*
	 * main.css caps .site-search at 760px from here up and drops its side
	 * padding, so the surface is exactly that approved column — not widened —
	 * and carries its own side padding so cards never touch the cream edge.
	 */
	.visual-search:not([data-state="idle"]) .visual-search__panel {
		padding-inline: var(--space-6);
	}
}

@media (max-width: 1024px) {
	.visual-search__grid .product-grid {
		gap: var(--space-4) var(--space-3);
	}
}

@media (max-width: 767px) {
	.visual-search__grid .product-grid {
		/* Two columns on phones, matching the storefront's own mobile grid. */
		grid-template-columns: repeat(2, 1fr);
		gap: var(--space-4) var(--space-3);
	}

	/*
	 * Full bleed on phones: the negative margin absorbs .site-search's side
	 * padding so the cream reaches both screen edges, and the same token is
	 * restated as padding so the content keeps exactly the measure it had. The
	 * border box ends up the width of the viewport, so nothing overflows
	 * sideways. The safe-area inset is folded into this declaration because it
	 * would otherwise be out-specified by the padding-bottom above.
	 */
	.visual-search:not([data-state="idle"]) .visual-search__panel {
		margin-inline: calc(var(--page-margin) * -1);
		padding-inline: var(--page-margin);
		padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
	}

	.visual-search__preview {
		width: 116px;
	}

	.visual-search__submit {
		max-width: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.visual-search__status::after {
		animation: none;
		opacity: 0.6;
	}

	.visual-search[data-state="searching"] .visual-search__preview,
	.visual-search__submit {
		transition: none;
	}
}
