/*!
 * Vibe Pass - Public customer pages (registration, login, dashboard)
 * Version: 1.0.0
 *
 * Layout only. Colours, spacing and every other value come from tokens.css,
 * so these pages follow Light and Dark Mode automatically. No raw colours
 * are allowed in this file - see the rule at the top of tokens.css.
 */

/* --- Auth pages (register / login / forgot password) ------------------- */

.vp-auth-wrap {
	display: flex;
	justify-content: center;
	padding: var(--vp-space-6) var(--vp-space-4);
	min-height: 60vh;
}

.vp-auth-card {
	width: 100%;
	max-width: 480px;
}

.vp-auth-title {
	margin: 0 0 var(--vp-space-2);
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-2xl);
	font-weight: var(--vp-weight-semibold);
	line-height: var(--vp-line-tight);
	color: var(--vp-text);
}

.vp-auth-subtitle {
	margin: 0 0 var(--vp-space-5);
	font-size: var(--vp-text-sm);
	color: var(--vp-text-subtle);
}

.vp-auth-switch {
	margin: var(--vp-space-5) 0 0;
	font-size: var(--vp-text-sm);
	color: var(--vp-text-subtle);
	text-align: center;
}

.vp-auth-switch a {
	color: var(--vp-brand);
	font-weight: var(--vp-weight-semibold);
	text-decoration: none;
	margin-left: var(--vp-space-1);
}

.vp-auth-switch a:hover {
	text-decoration: underline;
}

.vp-auth-form .vp-btn {
	width: 100%;
}

@media screen and (max-width: 480px) {
	.vp-auth-wrap {
		padding: var(--vp-space-4) var(--vp-space-3);
	}

	.vp-auth-card {
		padding: var(--vp-space-4);
	}
}

/* --- Logged-in dashboard shell ------------------------------------------
 * Phase 24: the dashboard now matches the App Shell header/footer's own
 * width (--vp-shell-width) instead of capping itself at 860px, and its
 * section navigation is a sidebar (desktop) that collapses into a single
 * tappable "current section" button (mobile/tablet) instead of a row of
 * pills - see .vp-dashboard__body below. */

.vp-dashboard {
	max-width: var(--vp-shell-width);
	margin: 0 auto;
	padding: var(--vp-space-5) var(--vp-space-5);
}

.vp-dashboard__header {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--vp-space-3);
	margin-bottom: var(--vp-space-4);
}

.vp-dashboard__header .vp-auth-title {
	margin-bottom: var(--vp-space-1);
}

.vp-dashboard__header .vp-auth-subtitle {
	margin: 0;
}

.vp-dashboard__body {
	display: grid;
	grid-template-columns: 248px minmax(0, 1fr);
	align-items: start;
	gap: var(--vp-space-5);
}

.vp-dashboard__main {
	min-width: 0;
}

/* Phase 46: the Business Dashboard's new "Overview" landing tab - a row of
 * "go do something" links under the stat cards. Plain flex-wrap of the
 * existing .vp-btn component (no new button style) so it already matches
 * every other button on the site and never needs its own responsive rules -
 * it just wraps to more lines on a narrow screen like any other button row. */
.vp-dashboard-overview__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--vp-space-2);
	margin-top: var(--vp-space-2);
}

.vp-dashboard__sidebar {
	position: sticky;
	top: calc(var(--vp-shell-header-h) + var(--vp-space-3));
}

/* A fixed --vp-ink navy panel, like the mockup's dashboard sidebar - unlike
 * .vp-card it does NOT switch with Light/Dark Mode (see the --vp-ink token
 * comment in tokens.css), the same as the wallet card. */
.vp-dashboard__nav {
	display: flex;
	flex-direction: column;
	gap: 2px;
	background: var(--vp-ink);
	border: 1px solid var(--vp-ink-border);
	border-radius: var(--vp-radius-lg);
	padding: var(--vp-space-2);
}

.vp-dashboard__nav-link {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--vp-space-2);
	padding: 10px 14px;
	border-radius: var(--vp-radius);
	color: var(--vp-ink-text-muted);
	font-size: var(--vp-text-sm);
	font-weight: var(--vp-weight-medium);
	text-decoration: none;
	transition: background var(--vp-transition), color var(--vp-transition);
}

.vp-dashboard__nav-link-label {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* Phase 28: a small heading above each cluster of related sections, so a
 * long sidebar (the Business Dashboard has a dozen tabs) reads as a few
 * clearly-labelled groups instead of one flat wall of links. Purely a label
 * - it never collapses or hides its group, so nothing breaks if JavaScript
 * is off. The first one sits flush with the nav's own top padding; every
 * one after that gets a little extra breathing room from the group above. */
.vp-dashboard__nav-group-label {
	padding: 10px 14px 4px;
	font-size: 11px;
	font-weight: var(--vp-weight-semibold);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--vp-ink-accent);
	opacity: 0.85;
}

.vp-dashboard__nav-group-label:not(:first-child) {
	margin-top: var(--vp-space-2);
}

/* Unread-style count next to a section such as Notifications, so anything
 * needing attention is visible right in the section list instead of being
 * something the visitor only discovers after clicking in. */
.vp-dashboard__nav-badge {
	flex-shrink: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 20px;
	height: 20px;
	padding: 0 6px;
	border-radius: var(--vp-radius-pill);
	background: var(--vp-danger);
	color: var(--vp-text-inverse);
	font-size: var(--vp-text-xs);
	font-weight: var(--vp-weight-semibold);
	line-height: 1;
}

.vp-dashboard__nav-link:hover,
.vp-dashboard__nav-link:focus-visible {
	background: var(--vp-ink-2);
	color: var(--vp-ink-text);
}

.vp-dashboard__nav-link--active {
	background: var(--vp-ink-2);
	color: var(--vp-ink-text);
	font-weight: var(--vp-weight-semibold);
}

.vp-empty-state {
	padding: var(--vp-space-3) 0;
}

.vp-auth-logout {
	display: inline-flex;
	align-items: center;
	padding: 8px 16px;
	border-radius: var(--vp-radius);
	border: 1px solid var(--vp-border-strong);
	color: var(--vp-text);
	font-size: var(--vp-text-sm);
	font-weight: var(--vp-weight-semibold);
	text-decoration: none;
	white-space: nowrap;
}

.vp-auth-logout:hover {
	background: var(--vp-surface-2);
}

/* Tablet/mobile: this in-page sidebar is hidden outright (Phase 40.1) -
 * `Frontend_Shell::dashboard_nav_groups()` already folds this exact same
 * section list into the App Shell header's global hamburger menu (Phase 37),
 * so below 900px that hamburger is the one and only way to reach these
 * sections; this sidebar used to also collapse into its own "current
 * section" accordion button here, which was simply a second, redundant copy
 * of the same links once the hamburger covered them too. */
@media screen and (max-width: 900px) {
	.vp-dashboard__body {
		grid-template-columns: 1fr;
	}

	.vp-dashboard__sidebar {
		display: none;
	}
}

@media screen and (max-width: 480px) {
	.vp-dashboard {
		padding: var(--vp-space-4) var(--vp-space-3);
	}
}

/* --- Wallet tab (My Vibe Pass & QR) -------------------------------------
 * .vp-wallet itself (the coloured membership card) lives in tokens.css, so
 * it matches the admin Design Preview exactly. Everything below is layout
 * only for the rest of the tab. */

.vp-wallet-card {
	display: grid;
	grid-template-columns: 1.4fr 1fr;
	gap: var(--vp-space-5);
	align-items: start;
}

.vp-wallet-card__info {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-4);
}

.vp-wallet-card__details {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-2);
}

.vp-wallet-card__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--vp-space-3);
	padding: var(--vp-space-2) 0;
	border-bottom: 1px solid var(--vp-border);
	font-size: var(--vp-text-sm);
}

.vp-wallet-card__row:last-child {
	border-bottom: none;
}

.vp-wallet-card__label {
	color: var(--vp-text-subtle);
}

.vp-wallet-card__value {
	color: var(--vp-text);
	font-weight: var(--vp-weight-medium);
	text-align: right;
}

.vp-wallet-card__qr {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--vp-space-3);
	padding: var(--vp-space-4);
	background: var(--vp-surface-2);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-lg);
	text-align: center;
}

.vp-wallet-card__qr-image {
	width: 100%;
	max-width: 220px;
	line-height: 0;
}

.vp-wallet-card__qr-image svg {
	width: 100%;
	height: auto;
	border-radius: var(--vp-radius-sm);
}

@media screen and (max-width: 640px) {
	.vp-wallet-card {
		grid-template-columns: 1fr;
	}
}

/* --- Business directory & profile (Phase 5) ----------------------------- */

.vp-directory {
	max-width: 1080px;
	margin: 0 auto;
	padding: var(--vp-space-5) var(--vp-space-4);
}

.vp-directory__intro {
	margin-bottom: var(--vp-space-5);
}

.vp-directory__filters {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: var(--vp-space-3);
	padding: var(--vp-space-4);
	background: var(--vp-surface);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-lg);
	margin-bottom: var(--vp-space-5);
}

.vp-directory__filters .vp-field {
	margin-bottom: 0;
	flex: 1 1 180px;
}

.vp-directory__filters select,
.vp-directory__filters input {
	max-width: none;
}

.vp-directory__grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
	gap: var(--vp-space-4);
}

.vp-biz-card {
	display: flex;
	flex-direction: column;
	background: var(--vp-surface);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-lg);
	overflow: hidden;
	text-decoration: none;
	color: var(--vp-text);
	transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.vp-biz-card:hover {
	border-color: var(--vp-brand);
	box-shadow: 0 10px 24px rgba(15, 26, 44, 0.12);
	transform: translateY(-2px);
}

/* Phase: the cover/banner image was dropped from the business registration
 * form at the project owner's request (a business only ever supplies a
 * logo now), so the card no longer reserves a photo band above the logo -
 * the logo sits directly in the card body, in normal flow, with a simple
 * initial-letter placeholder for the rare business with no logo on file. */
.vp-biz-card__top {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--vp-space-2);
	margin-bottom: var(--vp-space-2);
}

.vp-biz-card__logo {
	width: 56px;
	height: 56px;
	flex-shrink: 0;
	object-fit: cover;
	border-radius: var(--vp-radius);
	border: 1px solid var(--vp-border);
	background: var(--vp-surface-2);
}

.vp-biz-card__logo--placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-lg);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text-subtle);
}

/* A small pill flagging a business with a running offer right now - see the
 * Offers::get_active_for_business() call in render_card(). Reuses the
 * existing .vp-badge component rather than a bespoke style, matching
 * Section 65's "cards, badges" vocabulary used everywhere else on the site. */
.vp-biz-card__offer-flag {
	flex-shrink: 0;
	white-space: nowrap;
}

.vp-biz-card__body {
	padding: var(--vp-space-4) var(--vp-space-3) var(--vp-space-3);
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.vp-biz-card__name {
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-base);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text);
}

.vp-biz-card__badge {
	margin-inline-start: 6px;
	vertical-align: middle;
}

.vp-biz-card__category {
	font-size: var(--vp-text-sm);
	color: var(--vp-text-subtle);
}

.vp-biz-card__location {
	font-size: var(--vp-text-xs);
	color: var(--vp-text-subtle);
	margin-bottom: 4px;
}

.vp-directory__pagination {
	display: flex;
	flex-wrap: wrap;
	gap: var(--vp-space-2);
	margin-top: var(--vp-space-5);
}

.vp-biz-profile__back {
	font-size: var(--vp-text-sm);
	color: var(--vp-text-subtle);
	text-decoration: none;
}

.vp-biz-profile__back:hover {
	color: var(--vp-text);
}

/* Phase 46: the project owner deliberately dropped the *uploaded* cover
 * photo feature (see the matching .vp-biz-card__logo comment and
 * Businesses::handle_uploads()) because it left an empty band for every
 * business that never uploaded one - a data-dependent feature with no
 * fallback. This hero panel is different: a fixed decorative gradient (no
 * upload, no per-business data, never empty) that turns the header into a
 * premium "page banner" look, matching the Deep Navy & Gold theme already
 * used for the dashboard sidebar (--vp-ink / --vp-ink-2, see
 * .vp-dashboard__nav above) and the wallet card. It looks identical and
 * intentional whether or not a business has a logo on file. */
.vp-biz-profile__hero {
	position: relative;
	overflow: hidden;
	background: linear-gradient(135deg, var(--vp-ink) 0%, var(--vp-ink-2) 100%);
	border: 1px solid var(--vp-ink-border);
	border-radius: var(--vp-radius-lg);
	padding: var(--vp-space-5);
	margin-bottom: var(--vp-space-5);
}

/* A soft gold glow in one corner - purely decorative, never carries
 * information, so it needs no alt text or fallback for anyone who can't
 * see it. */
.vp-biz-profile__hero::before {
	content: "";
	position: absolute;
	inset: 0;
	background: radial-gradient(ellipse 70% 100% at 100% 0%, rgba(201, 162, 75, 0.22), transparent 60%);
	pointer-events: none;
}

.vp-biz-profile__header-row {
	position: relative;
	z-index: 1;
	display: flex;
	align-items: center;
	gap: var(--vp-space-4);
}

.vp-biz-profile__logo {
	width: 96px;
	height: 96px;
	flex-shrink: 0;
	object-fit: cover;
	border-radius: var(--vp-radius-lg);
	border: 3px solid var(--vp-ink-text, var(--vp-surface));
	box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
	background: var(--vp-surface-2);
}

.vp-biz-profile__logo--placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-2xl);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text-subtle);
}

.vp-biz-profile__header {
	flex: 1;
	min-width: 0;
}

.vp-biz-profile__name {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--vp-space-2);
	margin: 0 0 var(--vp-space-2);
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-2xl);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-ink-text);
}

.vp-biz-profile__meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--vp-space-3);
	font-size: var(--vp-text-sm);
	color: var(--vp-ink-text-muted);
}

.vp-biz-profile__grid {
	display: grid;
	grid-template-columns: 1.6fr 1fr;
	gap: var(--vp-space-5);
	align-items: start;
}

.vp-biz-profile__section {
	margin-bottom: var(--vp-space-4);
}

.vp-biz-profile__description {
	white-space: pre-line;
	color: var(--vp-text-muted);
	line-height: var(--vp-line-normal, 1.6);
}

.vp-biz-profile__discount-badges {
	display: flex;
	flex-wrap: wrap;
	gap: var(--vp-space-2);
}

.vp-biz-profile__contact p {
	margin: 0 0 var(--vp-space-2);
	font-size: var(--vp-text-sm);
	color: var(--vp-text-muted);
}

.vp-biz-profile__contact a {
	color: var(--vp-brand);
	text-decoration: none;
}

.vp-biz-profile__contact a:hover {
	text-decoration: underline;
}

/* Phase 40.1: a small matching icon in front of every contact/social row -
 * the address, phone, email, WhatsApp, Facebook, Instagram, TikTok and
 * Website lines each get the same icon `Offer\Home_Cards::icon_svg()` shows
 * for that kind of link elsewhere (the homepage Quick Links cards), so the
 * two places read as one consistent icon language. */
.vp-biz-profile__contact-row {
	display: flex;
	align-items: center;
	gap: var(--vp-space-2);
}

.vp-biz-profile__contact-icon {
	flex-shrink: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 20px;
	height: 20px;
	color: var(--vp-text-subtle);
}

.vp-biz-profile__contact-icon svg {
	width: 18px;
	height: 18px;
}

/* The "Get Directions" button already lays its own children out with
 * `.vp-btn`'s flex/gap rules - its icon just needs to match the button's own
 * (light) text colour instead of the muted default above. */
.vp-biz-profile__contact-row .vp-btn .vp-biz-profile__contact-icon {
	color: inherit;
}

@media screen and (max-width: 720px) {
	.vp-biz-profile__grid {
		grid-template-columns: 1fr;
	}
}

/* --- QR Scanner & Transactions (Phase 7, redesigned Phase 25) ----------- */

.vp-scanner {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-4);
}

.vp-scanner__panel {
	max-width: 420px;
	margin: 0 auto;
	width: 100%;
	/* A light tinted surface, not .vp-card - the "amount"/"scan" step
	 * already renders inside the dashboard tab's own .vp-card panel, and
	 * .vp-card .vp-card strips a full nested card (the "boxes inside boxes"
	 * fix) - this still visually separates the camera/manual-entry panels
	 * from each other without re-introducing that problem. */
	background: var(--vp-surface-2);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-lg);
	padding: var(--vp-space-4);
}

.vp-scanner__panel--manual {
	max-width: 480px;
}

/* Phase 38 (the project owner's own request): the manual card-number field
 * used to be one plain text input where the whole "VP-S-000042" string had
 * to be typed by hand. It is now a card-type dropdown (the "VP-S-" part)
 * next to a digits-only field (see Scanner::manual_entry_prefixes() /
 * ::manual_card_number_from_request()), so only the printed number itself
 * needs typing. */
.vp-scanner-manual-number {
	display: flex;
	gap: var(--vp-space-2);
}

.vp-scanner-manual-number select {
	flex: 0 0 auto;
	max-width: 52%;
}

.vp-scanner-manual-number input {
	flex: 1 1 auto;
	min-width: 0;
}

@media screen and (max-width: 480px) {
	.vp-scanner-manual-number {
		flex-direction: column;
	}

	.vp-scanner-manual-number select {
		max-width: none;
	}
}

.vp-scanner__camera {
	position: relative;
	overflow: hidden;
	border-radius: var(--vp-radius-lg);
	background: #000;
	margin-bottom: var(--vp-space-3);
}

.vp-scanner__video {
	display: block;
	width: 100%;
	height: auto;
}

.vp-scanner__frame {
	position: absolute;
	inset: 12%;
	border: 3px solid var(--vp-brand);
	border-radius: var(--vp-radius-md);
	pointer-events: none;
	box-shadow: 0 0 0 999px rgba(0, 0, 0, 0.35);
}

.vp-scanner__camera-actions {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--vp-space-2);
	text-align: center;
}

/* A plain "OR" divider between the camera panel and the manual-entry panel -
 * the same idea as a login page's "or continue with" rule, so it is obvious
 * these are two ways to do the same thing, not two separate steps. */
.vp-scanner__divider {
	display: flex;
	align-items: center;
	gap: var(--vp-space-3);
	max-width: 420px;
	margin: 0 auto;
	width: 100%;
	color: var(--vp-text-subtle);
	font-size: var(--vp-text-xs);
	font-weight: var(--vp-weight-semibold);
	letter-spacing: 0.08em;
}

.vp-scanner__divider::before,
.vp-scanner__divider::after {
	content: "";
	flex: 1;
	height: 1px;
	background: var(--vp-border);
}

/* The card-holder summary (amount-entry and review steps) - the same
 * .vp-wallet tier-gradient component the customer sees on their own wallet
 * tab, so staff verify the same "this looks like a real Vibe Pass card"
 * visual the customer does, not a plain text list. */
.vp-scanner-card {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-3);
	margin-bottom: var(--vp-space-4);
	max-width: 480px;
}

.vp-scanner-card__status {
	display: flex;
	align-items: center;
	gap: var(--vp-space-2);
}

.vp-scanner-receipt {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-1);
	margin-bottom: var(--vp-space-4);
	max-width: 480px;
}

.vp-scanner-receipt__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--vp-space-3);
	padding: var(--vp-space-2) 0;
	border-bottom: 1px solid var(--vp-border);
	font-size: var(--vp-text-sm);
}

.vp-scanner-receipt__row:last-child {
	border-bottom: none;
}

/* The discount/savings lines - the whole reason Vibe Pass exists - get the
 * brand colour so they stand out from the plain purchase/date/customer
 * rows around them, on both the pre-sale review screen and the final
 * receipt. */
.vp-scanner-receipt__row--highlight span:last-child,
.vp-scanner-receipt__row--highlight strong {
	color: var(--vp-brand);
	font-weight: var(--vp-weight-semibold);
}

.vp-scanner-receipt__row--total {
	font-size: var(--vp-text-base);
	padding-top: var(--vp-space-3);
	border-top: 1px solid var(--vp-border-strong);
}

.vp-scanner-receipt__row--total strong {
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-xl);
}

.vp-scanner-success {
	display: flex;
	align-items: center;
	gap: var(--vp-space-3);
	margin-bottom: var(--vp-space-3);
}

.vp-scanner-success .vp-section__title {
	margin: 0;
}

.vp-scanner-success__icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	flex: 0 0 36px;
	border-radius: 50%;
	background: var(--vp-success-bg);
	color: var(--vp-success);
	font-size: var(--vp-text-lg);
	font-weight: var(--vp-weight-bold);
}

.vp-scanner-lock {
	position: fixed;
	inset: 0;
	z-index: 9999;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: var(--vp-space-4);
	background: rgba(0, 0, 0, 0.6);
}

.vp-scanner-lock__box {
	width: 100%;
	max-width: 360px;
}

@media print {
	.vp-dashboard__header,
	.vp-dashboard__sidebar,
	.vp-scanner-lock,
	.vp-form-actions {
		display: none !important;
	}

	.vp-dashboard__body {
		display: block;
	}
}

/* Phase 8: the "Request cancellation" mini-form on the business Transaction
 * History tab. A plain <details>/<summary> disclosure needs no JavaScript to
 * open and close, matching this plugin's front-end script generally staying
 * out of the way unless a feature (like the scanner) genuinely needs it. */

.vp-inline-form summary {
	cursor: pointer;
	list-style: none;
}

.vp-inline-form summary::-webkit-details-marker {
	display: none;
}

.vp-inline-form[open] summary {
	margin-bottom: var(--vp-space-2);
}

/* --- Renewal tab plan cards (Phase 10, Vibe Pass Commerce) --------------
 * Rendered by the companion Commerce plugin on the customer dashboard's
 * Renewal tab (first purchase, renewal, upgrade, free renewal). Styled
 * here in Core so it shares the same design tokens as the rest of the
 * dashboard rather than shipping its own separate stylesheet. */

.vp-plan-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
	gap: var(--vp-space-4);
	margin: var(--vp-space-3) 0 var(--vp-space-5);
}

.vp-plan-card {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-2);
}

.vp-plan-card__title {
	font-family: var(--vp-font-serif);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text);
}

.vp-plan-card__price {
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-xl);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-brand);
}

.vp-plan-card .vp-btn {
	width: 100%;
	text-align: center;
}

/* Spend Target progress bar (Phase 9), shown inside the Wallet tab and the
   admin Loyalty Targets screen's per-row progress cell. */
.vp-target-progress {
	margin-top: var(--vp-space-4);
	padding-top: var(--vp-space-4);
	border-top: 1px solid var(--vp-border);
}

/* --- Offers grid (Phase 11: customer dashboard Offers tab & business
   profile "Active Vibe Pass Offers" section) -------------------------- */

.vp-offers-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
	gap: var(--vp-space-4);
	margin-top: var(--vp-space-3);
}

.vp-offer-card {
	display: flex;
	flex-direction: column;
}

.vp-offer-card__banner {
	width: 100%;
	height: auto;
	border-radius: var(--vp-radius);
	margin-bottom: var(--vp-space-2);
}

/* --- Homepage promo: banner slider + Featured Businesses (Phase 11) -----
 * Rendered by the `[vibe_pass_home_promo]` shortcode. The banner slider and
 * the Featured Businesses grid are two independent sections - see the note
 * in Offer\Homepage's class docblock. */

.vp-home-promo {
	max-width: 1080px;
	margin: 0 auto;
	padding: var(--vp-space-5) var(--vp-space-4);
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-6);
}

/* Phase 37: the project owner's own explicit layout request - 3 tiles per
 * row (so a 24-logo slider page - see `.vp-biz-slider` below - naturally
 * works out to 8 rows). Was `repeat(auto-fill, minmax(220px, 1fr))` (as
 * many columns as would fit).
 *
 * Phase 37 second correction: the FIRST correction pass (1.38.1) still
 * stepped this down to 2 columns under 720px and 1 column under 480px -
 * reasonable for the old, fuller `.vp-biz-card` (name/category/badge, needed
 * real width), but the project owner tested it on an actual phone and
 * reported the grid "showing in one column" - which was that 480px
 * breakpoint doing exactly what it was told to, just not what they wanted.
 * Their own words were explicit ("আমার তো ৩ কলামে লাগবে" - "I told you I
 * need 3 columns"), and now that each item is only a small logo + one line
 * of text (`.vp-biz-tile` below, since the same correction pass), 3 narrow
 * columns comfortably fit a phone screen - so this is now a fixed 3-column
 * grid at every screen width, no breakpoints left to reduce it. The gap and
 * tile size still shrink slightly on a narrow phone (below), just never the
 * column count. */
.vp-biz-grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--vp-space-4) var(--vp-space-3);
}

@media screen and (max-width: 480px) {
	.vp-biz-grid {
		gap: var(--vp-space-3) var(--vp-space-2);
	}
}

/* Phase 37 correction: ONE single outer card per slider page, wrapping the
 * whole 24-logo grid - the project owner tested the first Phase 37 build on
 * mobile and clarified they wanted exactly this ("within just one card,
 * there should be 24 logos, and below them just the business name"), not 24
 * separately-bordered cards inside a plain grid. `.vp-card` already supplies
 * the background/border/radius/shadow every other card on the site shares;
 * this only adds the padding the 24-item grid needs inside it. */
.vp-biz-page-card {
	padding: var(--vp-space-5) var(--vp-space-4);
}

@media screen and (max-width: 480px) {
	.vp-biz-page-card {
		padding: var(--vp-space-3) var(--vp-space-2);
	}
}

/* Phase 37 correction: the minimal logo + name tile that replaced the fuller
 * `.vp-biz-card` inside the homepage business slider specifically - no
 * category, no verified badge, no border/shadow of its own (the single
 * `.vp-biz-page-card` above already carries all of that for the whole
 * page). The Business Directory's own full listing keeps `.vp-biz-card`
 * completely unchanged - this is a separate, homepage-only style. */
.vp-biz-tile {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--vp-space-2);
	padding: var(--vp-space-2);
	border-radius: var(--vp-radius);
	text-decoration: none;
	color: var(--vp-text);
	text-align: center;
	transition: background 0.15s ease;
}

.vp-biz-tile:hover,
.vp-biz-tile:focus-visible {
	background: var(--vp-surface-2);
}

.vp-biz-tile__logo {
	width: 56px;
	height: 56px;
	object-fit: cover;
	border-radius: var(--vp-radius);
	border: 1px solid var(--vp-border);
	background: var(--vp-surface-2);
}

@media screen and (max-width: 480px) {
	.vp-biz-tile__logo {
		width: 44px;
		height: 44px;
	}

	.vp-biz-tile {
		padding: var(--vp-space-1);
		gap: var(--vp-space-1);
	}

	.vp-biz-tile__name {
		font-size: var(--vp-text-xs);
	}
}

.vp-biz-tile__logo--placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-lg);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text-subtle);
}

.vp-biz-tile__name {
	font-size: var(--vp-text-sm);
	font-weight: var(--vp-weight-medium);
	color: var(--vp-text);
	overflow: hidden;
	text-overflow: ellipsis;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	line-height: 1.3;
}

/* --- Phase 37 business slider (Offer\Homepage::render_business_slider())
 * Featured Businesses and All Businesses both render as a series of "pages"
 * of up to 24 logos (`.vp-biz-grid` above, inside one `.vp-biz-page-card`
 * per page), laid out side by side in `.vp-biz-slider__track` and shown one
 * at a time via `translateX()` - the exact same technique
 * `.vp-banner-slider__track` above already uses, just sliding a whole card
 * of logos per "slide" instead of a single image. See `home-promo.js`'s
 * `bindBizSlider()`. */
.vp-biz-slider {
	position: relative;
}

.vp-biz-slider__viewport {
	overflow: hidden;
}

.vp-biz-slider__track {
	display: flex;
	align-items: flex-start;
	transition: transform 0.4s ease;
}

.vp-biz-slider__page {
	flex: 0 0 100%;
	min-width: 0;
}

.vp-biz-slider__arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	border: 1px solid var(--vp-border);
	border-radius: 50%;
	background: var(--vp-surface);
	color: var(--vp-text);
	font-size: var(--vp-text-sm);
	line-height: 1;
	cursor: pointer;
	box-shadow: var(--vp-shadow, 0 6px 16px rgba(0, 0, 0, 0.12));
	transition: background 0.15s ease;
}

.vp-biz-slider__arrow:hover {
	background: var(--vp-surface-2);
}

.vp-biz-slider__arrow--prev {
	left: -14px;
}

.vp-biz-slider__arrow--next {
	right: -14px;
}

.vp-biz-slider__dots {
	display: flex;
	justify-content: center;
	gap: 8px;
	margin-top: var(--vp-space-4);
}

.vp-biz-slider__dot {
	width: 8px;
	height: 8px;
	padding: 0;
	border: none;
	border-radius: 50%;
	background: var(--vp-border);
	cursor: pointer;
	transition: width 0.15s ease, border-radius 0.15s ease, background 0.15s ease;
}

.vp-biz-slider__dot.is-active {
	width: 20px;
	border-radius: var(--vp-radius-pill);
	background: var(--vp-brand);
}

@media screen and (max-width: 600px) {
	.vp-biz-slider__arrow--prev {
		left: 0;
	}

	.vp-biz-slider__arrow--next {
		right: 0;
	}
}

.vp-home-promo__all-businesses-more {
	display: flex;
	justify-content: center;
	margin-top: var(--vp-space-5);
}

.vp-banner-slider {
	/* Phase 32: banners are now square (Section 3 of the project owner's
	 * request), which would be uncomfortably tall at the full 1080px
	 * content width used above (Featured/All Businesses etc.) - so the
	 * slider itself is capped to a moderate card width and centered,
	 * rather than stretching the square image edge-to-edge. */
	position: relative;
	width: 100%;
	max-width: 420px;
	margin: 0 auto;
	border-radius: var(--vp-radius-lg);
	overflow: hidden;
	background: var(--vp-surface-3);
}

.vp-banner-slider__track {
	display: flex;
	transition: transform 0.5s ease;
}

.vp-banner-slider__slide {
	flex: 0 0 100%;
	min-width: 0;
}

.vp-banner-slider__link {
	display: block;
	line-height: 0;
}

.vp-banner-slider__link img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 1 / 1;
	object-fit: cover;
}

.vp-banner-slider__arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	border: none;
	border-radius: 50%;
	background: rgba(15, 26, 44, 0.5);
	color: #fff;
	font-size: var(--vp-text-sm);
	line-height: 1;
	cursor: pointer;
	transition: background 0.15s ease;
}

.vp-banner-slider__arrow:hover {
	background: rgba(15, 26, 44, 0.75);
}

.vp-banner-slider__arrow--prev {
	left: var(--vp-space-3);
}

.vp-banner-slider__arrow--next {
	right: var(--vp-space-3);
}

.vp-banner-slider__dots {
	position: absolute;
	left: 0;
	right: 0;
	bottom: var(--vp-space-3);
	display: flex;
	justify-content: center;
	gap: 8px;
}

.vp-banner-slider__dot {
	width: 8px;
	height: 8px;
	padding: 0;
	border: none;
	border-radius: 50%;
	background: rgba(255, 255, 255, 0.55);
	cursor: pointer;
	transition: width 0.15s ease, border-radius 0.15s ease, background 0.15s ease;
}

.vp-banner-slider__dot.is-active {
	width: 20px;
	border-radius: var(--vp-radius-pill);
	background: #fff;
}

@media screen and (max-width: 480px) {
	.vp-banner-slider__arrow {
		width: 28px;
		height: 28px;
	}
}

/* --- Phase 40 "Business Owner Spotlight" carousel (Offer\Homepage::
 * render_spotlight()) - auto-advances exactly like .vp-banner-slider above
 * (same interval/arrow/dot mechanics in home-promo.js's bindSpotlight()),
 * but each slide is a richer photo+text card rather than a single image,
 * so it gets its own, wider set of classes instead of reusing the banner
 * slider's. ------------------------------------------------------------- */

.vp-home-promo__spotlight {
	margin-top: var(--vp-space-6);
}

.vp-spotlight-slider {
	position: relative;
	width: 100%;
	max-width: 640px;
	margin: 0 auto;
	overflow: hidden;
}

.vp-spotlight-slider__track {
	display: flex;
	transition: transform 0.5s ease;
}

.vp-spotlight-slider__slide {
	flex: 0 0 100%;
	min-width: 0;
	padding: 0 var(--vp-space-2);
}

.vp-spotlight-card {
	display: flex;
	align-items: center;
	gap: var(--vp-space-5);
	padding: var(--vp-space-5);
}

.vp-spotlight-card__photo {
	flex: 0 0 auto;
	width: 100px;
	height: 100px;
	border-radius: 50%;
	object-fit: cover;
	box-shadow: var(--vp-shadow-sm);
}

.vp-spotlight-card__body {
	min-width: 0;
}

.vp-spotlight-card__name {
	font-size: var(--vp-text-lg);
	font-weight: 700;
	color: var(--vp-text);
}

.vp-spotlight-card__position {
	font-size: var(--vp-text-sm);
	color: var(--vp-text-muted);
	margin-top: 2px;
}

.vp-spotlight-card__details {
	margin: var(--vp-space-2) 0 0;
	font-size: var(--vp-text-sm);
	color: var(--vp-text-muted);
	line-height: 1.5;
}

.vp-spotlight-card__link {
	display: inline-block;
	margin-top: var(--vp-space-3);
}

.vp-spotlight-slider__arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	border: none;
	border-radius: 50%;
	background: rgba(15, 26, 44, 0.5);
	color: #fff;
	font-size: var(--vp-text-sm);
	line-height: 1;
	cursor: pointer;
	transition: background 0.15s ease;
}

.vp-spotlight-slider__arrow:hover {
	background: rgba(15, 26, 44, 0.75);
}

.vp-spotlight-slider__arrow--prev {
	left: 0;
}

.vp-spotlight-slider__arrow--next {
	right: 0;
}

.vp-spotlight-slider__dots {
	display: flex;
	justify-content: center;
	gap: 8px;
	margin-top: var(--vp-space-4);
}

.vp-spotlight-slider__dot {
	width: 8px;
	height: 8px;
	padding: 0;
	border: none;
	border-radius: 50%;
	background: var(--vp-border);
	cursor: pointer;
	transition: width 0.15s ease, border-radius 0.15s ease, background 0.15s ease;
}

.vp-spotlight-slider__dot.is-active {
	width: 20px;
	border-radius: var(--vp-radius-pill);
	background: var(--vp-brand);
}

@media screen and (max-width: 600px) {
	.vp-spotlight-card {
		flex-direction: column;
		text-align: center;
	}

	.vp-spotlight-slider__arrow {
		width: 28px;
		height: 28px;
	}

	.vp-spotlight-slider__arrow--prev {
		left: -4px;
	}

	.vp-spotlight-slider__arrow--next {
		right: -4px;
	}
}

/* --- Phase 34 homepage news ticker (Offer\Homepage::render_ticker()) ----
 * Phase 31 shipped a discrete "vertical slide" ticker (one headline
 * replaces the next by sliding up/down, like the earlier version of this
 * feature); Phase 32 and Phase 33 each fixed a bug in that vertical
 * version. Phase 34 replaces the whole approach at the project owner's own
 * request: a classic horizontal "news ticker tape" - every headline in one
 * continuous single-line strip, endlessly scrolling right-to-left, rather
 * than swapping whole headlines in place. `render_ticker()` now prints
 * every headline TWICE back to back (the second copy `aria-hidden`, so
 * screen readers only hear it once) - a standard marquee technique: a pure
 * CSS `@keyframes` animation moves the doubled strip from `translateX(0)`
 * to exactly `translateX(-50%)` (precisely one copy's width) on an
 * infinite loop, so the moment it "resets" is invisible - the second copy
 * is already sitting exactly where the first one started, so the strip
 * looks like it never stops moving. `home-promo.js`'s `bindTicker()` only
 * measures one copy's real pixel width and sets `animation-duration` from
 * it (a fixed pixels-per-second speed, not a fixed duration), so the
 * scroll speed stays visually consistent whether there is one short
 * headline or several long ones - a fixed duration would make a short
 * strip zoom by and a long one crawl. */

.vp-news-ticker {
	display: flex;
	align-items: stretch;
	gap: 0;
	background: linear-gradient(135deg, var(--vp-brand) 0%, var(--vp-brand-strong) 100%);
	border-radius: var(--vp-radius-lg);
	box-shadow: var(--vp-shadow-sm);
	overflow: hidden;
}

.vp-news-ticker__viewport {
	flex: 1 1 auto;
	position: relative;
	overflow: hidden;
	min-width: 0;
	height: 2.6em;
}

.vp-news-ticker__track {
	position: absolute;
	top: 0;
	left: 0;
	height: 100%;
	display: flex;
	align-items: center;
	width: max-content;
	white-space: nowrap;
	will-change: transform;
	animation-name: vp-news-ticker-scroll;
	animation-timing-function: linear;
	animation-iteration-count: infinite;
	/* home-promo.js always sets a real animation-duration once it measures
	 * the strip's width - this is only a safe fallback before that first
	 * measurement runs (or if JS fails to load at all). */
	animation-duration: 30s;
}

@keyframes vp-news-ticker-scroll {
	from {
		transform: translateX(0);
	}

	to {
		/* Exactly one copy's width, since the strip is printed twice - see
		 * the class-level comment above for why this makes the loop
		 * seamless. */
		transform: translateX(-50%);
	}
}

.vp-news-ticker:hover .vp-news-ticker__track,
.vp-news-ticker:focus-within .vp-news-ticker__track {
	animation-play-state: paused;
}

.vp-news-ticker__item {
	display: inline-flex;
	align-items: center;
	padding: 0 var(--vp-space-3);
}

.vp-news-ticker__item a,
.vp-news-ticker__item {
	color: var(--vp-brand-contrast);
	font-size: var(--vp-text-sm);
	font-weight: var(--vp-weight-medium);
	white-space: nowrap;
	text-decoration: none;
}

.vp-news-ticker__item a:hover {
	text-decoration: underline;
}

.vp-news-ticker__sep {
	color: var(--vp-brand-contrast);
	opacity: 0.55;
	font-size: 0.4em;
}

@media screen and (max-width: 480px) {
	.vp-news-ticker__item {
		padding: 0 var(--vp-space-2);
	}

	.vp-news-ticker__item a,
	.vp-news-ticker__item {
		font-size: var(--vp-text-xs);
	}
}

/* --- Phase 39 homepage demo card deck (Offer\Homepage::render_demo_card_deck()),
 * correction round after Phase 43 - a finger-swipeable set of sample Vibe
 * Pass cards, one per active tier, right under the news ticker. Each
 * `.vp-demo-card` reuses `.vp-wallet`'s own look (tokens.css) - same card
 * body, same metallic tier trim - so a visitor sees exactly what a real
 * card looks like.
 *
 * Phase 43 originally shipped this as a 3D "coverflow" stack (every other
 * card visible, tilted, in the background behind the active one). The
 * project owner said plainly that was wrong ("বর্তমানে তো ব্যাকগ্রাউন্ডে সব
 * কার্ড দেখা যাচ্ছে এরকম না") and asked for the exact same behaviour as the
 * "Business Owner Spotlight" carousel above (`render_spotlight()`): a
 * classic one-at-a-time track slide, translateX'd by 100% per step, with
 * visible left/right arrow icons - never more than one card visible at a
 * time. So this now reuses that identical `__track`/flex/`translateX()`
 * pattern (see `.vp-spotlight-slider__track` above and `bindDemoDeck()` in
 * home-promo.js, which was rewritten to match `bindSpotlight()`) instead of
 * its own bespoke 3D positioning. */
/*
 * `.vp-demo-deck` is a direct child of `.vp-home-promo`, which is itself
 * `display: flex; flex-direction: column` - so this deck is a flex ITEM,
 * not a plain block. A flex item's `min-width` defaults to `auto`, which
 * resolves to a content-based automatic minimum rather than 0 - and because
 * this deck's own descendants (`.vp-demo-card` below) declare a real fixed
 * pixel width, that automatic minimum can end up LARGER than the actual
 * space `.vp-home-promo` has available on a narrow phone. `min-width` wins
 * over `max-width` when they conflict, so without this override the deck
 * was silently ignoring its own `max-width: 420px` ceiling and rendering at
 * a fixed width wider than the screen - overflowing off the right edge of
 * the page (reported by the project owner: "কার্ড গুলো বাহিরে চলে যাচ্ছে").
 * `min-width: 0` is the standard fix for this well-known flexbox pitfall.
 *
 * Fourth correction round: `min-width: 0` alone turned out not to be
 * enough - the project owner tested live (vibepass.riyadulpias.com) and
 * the card was STILL shifted right off the edge of the screen even on the
 * 1.44.3 build ("এখনো তো ডেমো কার্ড টা ডানে সরে আছে এটাকে মাঝে আনো"). Found
 * by inspecting that live page directly (a headless-browser render of an
 * isolated copy of this markup had NOT been catching this - it never
 * reproduced the actual bug): `margin: 0 auto` puts an `auto` value on
 * both of this item's CROSS-AXIS margins (`.vp-home-promo` is a COLUMN
 * flex container, so left/right is the cross axis) - and per the flexbox
 * spec, an auto cross-axis margin on a flex item turns OFF that item's
 * default "stretch to fill the container" sizing. Without stretch and
 * without an explicit `width`, the item's width was being resolved by
 * fit-content/shrink-to-fit sizing instead - which, because its content
 * (the card) wants to be wide, landed right on the `max-width: 420px`
 * ceiling regardless of how much space `.vp-home-promo` actually had -
 * confirmed on the live site by measuring `.vp-demo-deck` at a literal
 * 420px wide inside a 359px-wide parent, and by then injecting
 * `width: 100%` on that same live page and watching it correctly shrink
 * to 327px and stop overflowing. The missing piece was simply an explicit
 * `width: 100%` - the standard "fluid, centered, capped" combination
 * (`width: 100%` fills the available cross-axis space; `max-width` then
 * caps it on wider screens; `margin: 0 auto` centers it once narrower
 * than that cap) - which this rule never actually had.
 */
.vp-demo-deck {
	width: 100%;
	max-width: 420px;
	min-width: 0;
	margin: 0 auto;
}

.vp-demo-deck__stage {
	position: relative;
	overflow: hidden;
	min-width: 0;
}

.vp-demo-deck__track {
	display: flex;
	transition: transform 0.45s ease;
	min-width: 0;
}

.vp-demo-deck__slide {
	flex: 0 0 100%;
	min-width: 0;
	display: flex;
	justify-content: center;
}

/* Correction round: the card's on-screen design had drifted to roughly
 * 4:3 - the project owner asked for a true 16:9 instead ("ডেমো কার্ড এর
 * ডিজাইন টা 4:3 হয়ে গেছে কিন্তু 16:9 রেশিও করবে"). `aspect-ratio` fixes the
 * box exactly at 16:9 for any width (see the mobile rule below); the
 * padding/QR/foot sizes here are trimmed down (from `.vp-wallet`'s own
 * defaults) just enough that the card's real content still fits inside
 * that shorter box without being clipped by `.vp-wallet`'s own
 * `overflow: hidden` - verified by rendering the card at both this and the
 * mobile width and measuring the actual content height. This is scoped to
 * `.vp-demo-card` only, not `.vp-wallet` itself - the shared component
 * keeps its normal proportions everywhere else (customer wallet, business
 * owner card tab, admin design preview), since only "the demo card's
 * design" was reported as the wrong ratio. */
.vp-demo-card {
	/* `width: 100%` (of the already width:0-safe `.vp-demo-deck__slide`
	 * above), capped by `max-width`, rather than a bare fixed `width` - a
	 * definite fixed width here was part of what fed the flex min-width:auto
	 * overflow bug fixed above (a descendant with a real fixed width can
	 * inflate an ancestor flex item's automatic minimum size). Using a
	 * relative width with a cap avoids that regardless of what flex/grid
	 * context this card ever ends up nested inside. */
	width: 100%;
	max-width: 320px;
	aspect-ratio: 16 / 9;
	min-height: 0;
	min-width: 0;
	padding: 10px 16px;
}

.vp-demo-card .vp-wallet__foot {
	min-height: 0;
	margin-top: var(--vp-space-2);
	padding: 6px var(--vp-space-2);
}

.vp-demo-card .vp-wallet__qr {
	width: 40px;
	height: 40px;
	padding: 3px;
}

/* Inside the fixed 16:9 box, the top identity block and the bottom QR strip
 * must never shrink - only the middle price/status line has genuinely
 * variable-length text (the Diamond tier's own line runs noticeably longer
 * than the others'), so it is the one allowed to give ground, clamped to two
 * lines instead of letting flexbox squash the QR strip to fit. */
.vp-demo-card > div:first-child,
.vp-demo-card .vp-wallet__foot {
	flex-shrink: 0;
}

.vp-demo-card .vp-wallet__meta {
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
	text-overflow: ellipsis;
}

.vp-demo-card__ribbon {
	position: absolute;
	top: var(--vp-space-2);
	right: var(--vp-space-2);
	padding: 2px var(--vp-space-2);
	border-radius: var(--vp-radius-pill);
	background: var(--vp-wallet-accent, var(--vp-ink-accent));
	color: var(--vp-ink);
	font-size: 10px;
	font-weight: var(--vp-weight-semibold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/* Third correction round: the project owner said plainly that the arrows
 * were still not right ("ডান বাম এরো গুলো ডেমো কার্ড এর ডানে এবং বামে দিয়ে
 * দাও" - "put the left-right arrows to the right and left of the demo
 * card") - the first correction round had copied the Spotlight slider's
 * track/translateX sliding mechanism but left these arrow buttons in their
 * original below-the-card control row, styled as plain outlined circles,
 * instead of overlaid on the card's own edges the way
 * `.vp-spotlight-slider__arrow` sits inside `.vp-spotlight-slider`. This
 * now copies that pattern exactly: `.vp-demo-deck__arrow` is `position:
 * absolute` inside `.vp-demo-deck__stage` (which is already `position:
 * relative; overflow: hidden` above), pinned to the vertical center and the
 * left/right edge, styled as the same semi-transparent dark circle
 * Spotlight uses - see class-homepage.php's render_demo_card_deck(), which
 * now renders these two buttons as direct children of `.vp-demo-deck__stage`
 * (siblings of `.vp-demo-deck__track`), not inside `.vp-demo-deck__controls`
 * any more. */
.vp-demo-deck__arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	border: none;
	border-radius: 50%;
	background: rgba(15, 26, 44, 0.5);
	color: #fff;
	font-size: var(--vp-text-sm);
	line-height: 1;
	cursor: pointer;
	transition: background 0.15s ease;
}

.vp-demo-deck__arrow:hover {
	background: rgba(15, 26, 44, 0.75);
}

.vp-demo-deck__arrow--prev {
	left: 0;
}

.vp-demo-deck__arrow--next {
	right: 0;
}

/* Now holds only the dots (the arrows moved into `.vp-demo-deck__stage`
 * above), so this just centers that single row below the stage. */
.vp-demo-deck__controls {
	display: flex;
	align-items: center;
	justify-content: center;
	margin-top: var(--vp-space-3);
}

.vp-demo-deck__dots {
	display: flex;
	gap: var(--vp-space-2);
}

.vp-demo-deck__dot {
	width: 8px;
	height: 8px;
	padding: 0;
	border-radius: 50%;
	border: none;
	background: var(--vp-border-strong);
	cursor: pointer;
}

.vp-demo-deck__dot.is-active {
	background: var(--vp-brand);
}

@media screen and (max-width: 480px) {
	.vp-demo-card {
		max-width: 280px;
		padding: 8px 12px;
	}

	.vp-demo-card .vp-wallet__qr {
		width: 32px;
		height: 32px;
	}

	.vp-demo-card .vp-wallet__meta {
		font-size: 11px;
	}

	/* Same small-screen shrink `.vp-spotlight-slider__arrow` uses (front.css,
	 * Phase 40) - a smaller circle pulled slightly outward so it still sits
	 * clear of the narrower card's own edge on a phone. */
	.vp-demo-deck__arrow {
		width: 28px;
		height: 28px;
	}

	.vp-demo-deck__arrow--prev {
		left: -4px;
	}

	.vp-demo-deck__arrow--next {
		right: -4px;
	}
}

/* --- Phase 39 homepage Quick Links (Offer\Homepage::render_quick_links()) -
 * the fixed "Offers" card plus however many admin-added custom cards
 * (Offer\Home_Cards). Deliberately reuses `.vp-icon-picker__glyph`'s icon
 * sizing token-for-token (tokens.css) so the same icon looks identical here
 * and on the admin picker.
 *
 * Phase 40.1: the project owner asked for exactly 3 tiles per row instead of
 * however many happened to fit a given screen width - a fixed 3-column grid
 * (rather than the previous flex-wrap row, where a wide screen could show 5
 * or 6 tiles across and a narrow one 2) gives every screen size the same
 * predictable 3-per-row layout; a tile count that is not a multiple of 3
 * simply leaves the last row short, which is normal, expected grid
 * behaviour. `minmax(0, 160px)` keeps each column from stretching into an
 * oversized tile on a wide desktop screen while still dividing the row into
 * exactly three tracks. */
.vp-quick-links {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 160px));
	justify-content: center;
	gap: var(--vp-space-3);
}

.vp-quick-link {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--vp-space-2);
	width: 100%;
	padding: var(--vp-space-4) var(--vp-space-2);
	border-radius: var(--vp-radius-lg);
	background: var(--vp-surface);
	border: 1px solid var(--vp-border);
	box-shadow: var(--vp-shadow-sm);
	text-align: center;
	transition: transform var(--vp-transition), border-color var(--vp-transition);
	box-sizing: border-box;
}

.vp-quick-link:hover,
.vp-quick-link:focus-visible {
	transform: translateY(-2px);
	border-color: var(--vp-brand);
}

.vp-quick-link__icon {
	display: inline-flex;
	width: 28px;
	height: 28px;
	color: var(--vp-brand);
}

.vp-quick-link__icon svg {
	width: 100%;
	height: 100%;
}

.vp-quick-link__label {
	font-size: var(--vp-text-xs);
	font-weight: var(--vp-weight-medium);
	color: var(--vp-text);
	word-break: break-word;
}

/* --- Phase 33 homepage quick search (Offer\Homepage::render_search_bar()) -
 * this widget no longer has any styling of its own. At the project owner's
 * explicit request it now renders with the exact same markup and CSS
 * classes as the Businesses directory page's own search form
 * (`.vp-directory__intro`/`.vp-auth-title`/`.vp-auth-subtitle` for the
 * heading, `.vp-directory__filters`/`.vp-field`/`.vp-field__label` for the
 * fields, `.vp-form-actions`/`.vp-btn` for the Search button - all defined
 * above under "Business directory & profile" and in tokens.css), so the two
 * pages stay pixel-identical automatically with nothing to keep in sync by
 * hand. `.vp-home-search` itself is just a spacing wrapper. */

.vp-home-search {
	margin-bottom: var(--vp-space-5);
	position: relative;
}

/* --- Phase 37 second correction - live AJAX results dropdown ------------
 * The Category/District/Upazila fields are gone (project owner: "আমি চাই
 * শুধু খালি ফিল্ড থাকুক") and the one remaining text field now searches as
 * the visitor types (`shell.js`'s `bindLiveSearch()`,
 * `Offer\Homepage::ajax_search()`). This block is new; everything above it
 * in this comment ("Phase 33 homepage quick search") still describes the
 * field/label styling, which is unchanged. */

.vp-home-search__results {
	position: absolute;
	left: 0;
	right: 0;
	top: 100%;
	margin-top: var(--vp-space-2);
	background: var(--vp-surface);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-lg);
	box-shadow: var(--vp-shadow-lg, 0 12px 32px rgba(0, 0, 0, 0.18));
	max-height: 360px;
	overflow-y: auto;
	z-index: 20;
}

.vp-home-search__message {
	margin: 0;
	padding: var(--vp-space-3) var(--vp-space-4);
	color: var(--vp-text-muted);
	font-size: var(--vp-text-sm);
}

.vp-home-search__list {
	list-style: none;
	margin: 0;
	padding: var(--vp-space-2) 0;
}

.vp-home-search__item {
	display: flex;
	align-items: center;
	gap: var(--vp-space-3);
	padding: var(--vp-space-2) var(--vp-space-4);
	text-decoration: none;
	color: inherit;
}

.vp-home-search__item:hover,
.vp-home-search__item:focus {
	background: var(--vp-surface-alt, var(--vp-bg));
}

.vp-home-search__logo {
	flex: 0 0 auto;
	width: 36px;
	height: 36px;
	border-radius: var(--vp-radius-md);
	background: var(--vp-brand);
	color: var(--vp-brand-contrast);
	display: flex;
	align-items: center;
	justify-content: center;
	font-weight: var(--vp-weight-medium);
	font-size: var(--vp-text-sm);
	overflow: hidden;
}

.vp-home-search__logo img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.vp-home-search__text {
	display: flex;
	flex-direction: column;
	min-width: 0;
}

.vp-home-search__name {
	font-weight: var(--vp-weight-medium);
	font-size: var(--vp-text-sm);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.vp-home-search__meta {
	color: var(--vp-text-muted);
	font-size: var(--vp-text-xs);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.vp-home-search__more {
	display: block;
	text-align: center;
	padding: var(--vp-space-3) var(--vp-space-4);
	border-top: 1px solid var(--vp-border);
	font-size: var(--vp-text-sm);
	font-weight: var(--vp-weight-medium);
	color: var(--vp-brand);
	text-decoration: none;
}

.vp-home-search__more:hover {
	text-decoration: underline;
}

/* --- Public Leaderboard (Phase 20) --------------------------------------
 * Rendered by the `[vibe_pass_leaderboard]` shortcode
 * (Offer\Leaderboard::render_shortcode()). Two independent panels, side by
 * side on wide screens and stacked on mobile - a customer's row only ever
 * appears when that customer opted in themselves (see Customer\Profile's
 * Leaderboard preference), so there is nothing here that reveals a name
 * without consent. */

.vp-leaderboard {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: var(--vp-space-5);
}

@media screen and (max-width: 720px) {
	.vp-leaderboard {
		grid-template-columns: 1fr;
	}
}

.vp-leaderboard__panel {
	/* Bug fix (correction round after Phase 45): the project owner's own
	 * screenshot showed the "Top Savers"/"Top Businesses" cards pushed off
	 * the right edge on a narrow phone ("টপ সেভারস এবং টপ বিজনেসেস এর কার্ড
	 * গুলো ডান দিকে বেশি চলে যাচ্ছে"). Root cause, confirmed by measuring this
	 * panel at a real 320px-wide viewport: `.vp-leaderboard` is a CSS Grid
	 * (`1fr` column at this width) and, exactly like a flex item, a grid
	 * item's `min-width` defaults to `auto` - not 0 - which resolves to the
	 * largest UN-SHRINKABLE content inside it. `.vp-leaderboard__value`
	 * (the ৳ amount) and the "Verified" badge next to a business name are
	 * both `white-space: nowrap` with nothing to make them shrink, so their
	 * combined minimum content width was wider than the actual `1fr` track
	 * on a narrow phone, and the grid track grew to fit them instead of
	 * shrinking - carrying this panel past the screen edge. This is the
	 * exact same pitfall (and fix) as `.vp-demo-deck`'s own `min-width: 0`
	 * above, just on a grid item instead of a flex item.
	 */
	min-width: 0;
	background: var(--vp-surface);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-lg);
	padding: var(--vp-space-4);
}

.vp-leaderboard__row {
	display: flex;
	align-items: center;
	gap: var(--vp-space-3);
	padding: var(--vp-space-2) 0;
	border-bottom: 1px solid var(--vp-border);
}

.vp-leaderboard__row:last-child {
	border-bottom: none;
}

.vp-leaderboard__rank {
	flex: 0 0 28px;
	height: 28px;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: var(--vp-radius-pill);
	background: var(--vp-surface-2);
	color: var(--vp-text-subtle);
	font-size: var(--vp-text-xs);
	font-weight: var(--vp-weight-semibold);
}

.vp-leaderboard__row:nth-child(1) .vp-leaderboard__rank {
	background: var(--vp-brand);
	color: var(--vp-brand-contrast);
}

.vp-leaderboard__body {
	flex: 1 1 auto;
	min-width: 0;
}

.vp-leaderboard__name {
	font-weight: var(--vp-weight-medium);
	color: var(--vp-text);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.vp-leaderboard__meta {
	font-size: var(--vp-text-xs);
	color: var(--vp-text-subtle);
}

.vp-leaderboard__value {
	flex: 0 0 auto;
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text);
	white-space: nowrap;
}

.vp-leaderboard__empty {
	color: var(--vp-text-subtle);
	font-size: var(--vp-text-sm);
}
