/*!
 * Vibe Pass - Design Tokens
 * Version: 1.0.0
 *
 * This is the single source of truth for every colour, spacing step, radius,
 * shadow and font in the Vibe Pass interface.
 *
 * Rule for all Vibe Pass code: never write a raw colour value in a component
 * stylesheet. Always reference a token below. That is what makes Dark Mode
 * work everywhere automatically instead of being patched screen by screen.
 *
 * Theme resolution
 * ----------------
 *   no data-vp-theme attribute  -> follow the device setting (system)
 *   data-vp-theme="light"       -> always light
 *   data-vp-theme="dark"        -> always dark
 */

/* -------------------------------------------------------------------------
 * 1. Light theme (the base)
 * ---------------------------------------------------------------------- */

:root {
	/* Brand - overridden at runtime from the admin settings (Vibe Pass >
	   Settings > Primary Color / Accent Color). These are just the
	   out-of-the-box defaults - since the "Deep Navy & Gold" redesign
	   (Editorial/Luxury direction, redone across Phases 23-25) the default
	   is a warm bronze/gold rather than the old Phase 23 blue.
	   Assets::brand_overrides() derives every shade below from whatever
	   single colour the admin actually picks, in both Light and Dark Mode,
	   so this file's own values only matter before that runs. */
	--vp-brand: #b4863a;
	--vp-brand-strong: #8c6526;
	--vp-brand-soft: #f5ead3;
	--vp-brand-contrast: #1c1912;
	--vp-accent: #c9a24b;
	--vp-accent-soft: #fbf1dd;

	/* Fixed "ink" surfaces - the deep navy used for the dashboard sidebar,
	   the membership wallet card and primary buttons. Unlike --vp-bg/
	   --vp-surface these do NOT change between Light and Dark Mode: navy
	   already reads as a dark, premium surface in both contexts, exactly
	   like a physical card's material does not change colour depending on
	   the light in the room. --vp-ink-accent is a slightly brighter gold
	   than --vp-brand, tuned for contrast against --vp-ink rather than
	   against a light surface (small-caps labels, nav icons, hairline
	   dividers on dark surfaces). */
	--vp-ink: #0f1a2c;
	--vp-ink-2: #182741;
	--vp-ink-text: #f1e4c4;
	--vp-ink-text-muted: #c7cdda;
	--vp-ink-border: rgba(239, 231, 214, 0.14);
	--vp-ink-accent: #c9a24b;

	/* Surfaces */
	--vp-bg: #f6f1e7;
	--vp-surface: #fbf8f0;
	--vp-surface-2: #f0e8d6;
	--vp-surface-3: #e6dbc2;
	--vp-overlay: rgba(24, 20, 12, 0.5);

	/* Text */
	--vp-text: #1c1912;
	--vp-text-muted: #57503f;
	--vp-text-subtle: #837a66;
	--vp-text-inverse: #f6f1e7;

	/* Lines */
	--vp-border: #e3dbc7;
	--vp-border-strong: #cbbe9e;
	--vp-focus: #9c7530;

	/* Status - each has a text colour, a soft background and a border. */
	--vp-success: #047857;
	--vp-success-bg: #d1fae5;
	--vp-success-border: #6ee7b7;

	--vp-warning: #b45309;
	--vp-warning-bg: #fef3c7;
	--vp-warning-border: #fcd34d;

	--vp-danger: #b91c1c;
	--vp-danger-bg: #fee2e2;
	--vp-danger-border: #fca5a5;

	--vp-info: #1d4ed8;
	--vp-info-bg: #dbeafe;
	--vp-info-border: #93c5fd;

	--vp-neutral: #374151;
	--vp-neutral-bg: #f3f4f6;
	--vp-neutral-border: #d1d5db;

	/* Tier accents - the "physical card" redesign (matches the project
	   owner's own hand-drawn sketch and the 4 printed card mockups he
	   supplied: Silver/Gold/Platinum/Diamond). Each tier now has its own
	   rich gradient card face + darker frame colour, exactly like a real
	   printed card uses a different card stock/foil per tier, replacing
	   the older single dark --vp-ink body with just a metallic trim. Kept
	   fixed (not theme-variant): a physical card's own artwork does not
	   change with the site's Light/Dark Mode, the same way a real card in
	   your wallet does not change colour at night - see .vp-wallet's own
	   comment in section 4. -bg names are kept (as flat colours, close to
	   the mid gradient stop) only so nothing that still references them
	   for a non-card use breaks.
	   -face-1/2/3: the 3-stop diagonal gradient of the card body itself.
	   -frame: the darker border "frame" printed around the card edge.
	   -ink:   the text colour used on the card face (dark on every tier -
	           all 4 mockups use near-black text on a light/metallic face). */
	--vp-tier-silver: #a7b0bd;
	--vp-tier-silver-bg: #a7b0bd;
	--vp-tier-silver-face-1: #f3f4f6;
	--vp-tier-silver-face-2: #b7bec8;
	--vp-tier-silver-face-3: #838a95;
	--vp-tier-silver-frame: #232427;
	--vp-tier-silver-ink: #1a1a1a;
	--vp-tier-gold: #c9a24b;
	--vp-tier-gold-bg: #c9a24b;
	--vp-tier-gold-face-1: #f6dfa0;
	--vp-tier-gold-face-2: #cf9a35;
	--vp-tier-gold-face-3: #8f5f16;
	--vp-tier-gold-frame: #2b1608;
	--vp-tier-gold-ink: #1a1206;
	--vp-tier-platinum: #e7e2d6;
	--vp-tier-platinum-bg: #e7e2d6;
	--vp-tier-platinum-face-1: #f8d5e7;
	--vp-tier-platinum-face-2: #ec9fc6;
	--vp-tier-platinum-face-3: #d873a9;
	--vp-tier-platinum-frame: #4a0f2e;
	--vp-tier-platinum-ink: #241019;
	--vp-tier-diamond: #a9dcf2;
	--vp-tier-diamond-bg: #a9dcf2;
	--vp-tier-diamond-face-1: #eaf6fc;
	--vp-tier-diamond-face-2: #8fc9e8;
	--vp-tier-diamond-face-3: #4d9bcb;
	--vp-tier-diamond-frame: #0c2638;
	--vp-tier-diamond-ink: #0c1c26;

	/* Charts - colour-blind safe and legible on both themes. */
	--vp-chart-1: #b4863a;
	--vp-chart-2: #0891b2;
	--vp-chart-3: #ea580c;
	--vp-chart-4: #059669;
	--vp-chart-5: #db2777;
	--vp-chart-6: #ca8a04;
	--vp-chart-grid: #e3dbc7;

	/* Elevation - the editorial look leans on hairline borders rather than
	   drop shadows, so these are now used sparingly (dropdown/menu-style
	   overlays and the "pressed" segmented-control state), not on cards. */
	--vp-shadow-sm: 0 1px 2px rgba(28, 25, 18, 0.06);
	--vp-shadow: 0 2px 8px rgba(28, 25, 18, 0.08);
	--vp-shadow-lg: 0 8px 24px rgba(28, 25, 18, 0.1);

	/* Shape - smaller, sharper corners than the old Phase 23 look. */
	--vp-radius-sm: 3px;
	--vp-radius: 4px;
	--vp-radius-lg: 6px;
	--vp-radius-pill: 999px;

	/* Spacing scale */
	--vp-space-1: 4px;
	--vp-space-2: 8px;
	--vp-space-3: 12px;
	--vp-space-4: 16px;
	--vp-space-5: 24px;
	--vp-space-6: 32px;
	--vp-space-7: 48px;

	/* Layout (Phase 24) - shared by the App Shell header/footer (shell.css)
	   and every page's own content container (front.css), so the header bar
	   and the content beneath it always line up at the same width instead
	   of one being wider than the other. --vp-shell-header-h is an estimate
	   of the sticky header's rendered height (logo/nav row plus its own
	   padding and border), used only to keep a sticky in-page element (like
	   the dashboard sidebar) from being covered by the header once both are
	   stuck to the top of the viewport - it does not need to be exact, only
	   at least as tall as the header really is. */
	--vp-shell-width: 1180px;
	--vp-shell-header-h: 64px;

	/* Typography - Work Sans/Noto Sans Bengali for UI and body text (the
	   Bengali stack is listed first so Bengali text renders with a proper
	   Bengali face instead of a fallback that breaks conjuncts); a serif
	   pairing for headlines and large numbers (--vp-font-serif) is what
	   gives the editorial/luxury look its "premium print" feel - see
	   .vp-section__title, .vp-stat__value, .vp-wallet__tier and similar. Both
	   families are enqueued by Assets::register_frontend()/register_admin()
	   (vibe-pass-fonts, from Google Fonts). */
	--vp-font: "Work Sans", "Noto Sans Bengali", -apple-system,
		BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
	--vp-font-serif: "Source Serif 4", "Noto Serif Bengali", Georgia, serif;
	--vp-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

	--vp-text-xs: 12px;
	--vp-text-sm: 13px;
	--vp-text-base: 15px;
	--vp-text-lg: 18px;
	--vp-text-xl: 22px;
	--vp-text-2xl: 28px;
	--vp-text-3xl: 36px;

	--vp-weight-normal: 400;
	--vp-weight-medium: 500;
	--vp-weight-semibold: 600;
	--vp-weight-bold: 700;

	--vp-line-tight: 1.25;
	--vp-line-normal: 1.6;

	/* Motion */
	--vp-transition: 160ms cubic-bezier(0.4, 0, 0.2, 1);

	color-scheme: light;
}

/* -------------------------------------------------------------------------
 * 2. Dark theme - device setting (only when the user has not forced light)
 * ---------------------------------------------------------------------- */

@media (prefers-color-scheme: dark) {
	:root:not([data-vp-theme="light"]) {
		--vp-brand-strong: #d4ae6e;
		--vp-brand-soft: #2e2410;
		--vp-brand-contrast: #1c1912;
		--vp-accent-soft: #2e2410;

		--vp-bg: #1a212e;
		--vp-surface: #212938;
		--vp-surface-2: #293243;
		--vp-surface-3: #323c52;
		--vp-overlay: rgba(0, 0, 0, 0.6);

		--vp-text: #ede6d3;
		--vp-text-muted: #b9c0cc;
		--vp-text-subtle: #8a93a3;
		--vp-text-inverse: #1a212e;

		--vp-border: #38425a;
		--vp-border-strong: #48536e;
		--vp-focus: #d4ae6e;

		--vp-success: #6ee7b7;
		--vp-success-bg: #052e21;
		--vp-success-border: #0f5132;

		--vp-warning: #fcd34d;
		--vp-warning-bg: #3a2503;
		--vp-warning-border: #78450a;

		--vp-danger: #fca5a5;
		--vp-danger-bg: #3b0d0d;
		--vp-danger-border: #7f1d1d;

		--vp-info: #93c5fd;
		--vp-info-bg: #0c1f3d;
		--vp-info-border: #1e40af;

		--vp-neutral: #cbd5e1;
		--vp-neutral-bg: #1e2637;
		--vp-neutral-border: #3d4a63;

		--vp-chart-1: #d4ae6e;
		--vp-chart-2: #22d3ee;
		--vp-chart-3: #fb923c;
		--vp-chart-4: #34d399;
		--vp-chart-5: #f472b6;
		--vp-chart-6: #facc15;
		--vp-chart-grid: #38425a;

		--vp-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
		--vp-shadow: 0 4px 12px rgba(0, 0, 0, 0.45);
		--vp-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);

		color-scheme: dark;
	}
}

/* -------------------------------------------------------------------------
 * 3. Dark theme - explicitly chosen by the user (wins over the device)
 * ---------------------------------------------------------------------- */

:root[data-vp-theme="dark"] {
	--vp-brand-strong: #d4ae6e;
	--vp-brand-soft: #2e2410;
	--vp-brand-contrast: #1c1912;
	--vp-accent-soft: #2e2410;

	--vp-bg: #1a212e;
	--vp-surface: #212938;
	--vp-surface-2: #293243;
	--vp-surface-3: #323c52;
	--vp-overlay: rgba(0, 0, 0, 0.6);

	--vp-text: #ede6d3;
	--vp-text-muted: #b9c0cc;
	--vp-text-subtle: #8a93a3;
	--vp-text-inverse: #1a212e;

	--vp-border: #38425a;
	--vp-border-strong: #48536e;
	--vp-focus: #d4ae6e;

	--vp-success: #6ee7b7;
	--vp-success-bg: #052e21;
	--vp-success-border: #0f5132;

	--vp-warning: #fcd34d;
	--vp-warning-bg: #3a2503;
	--vp-warning-border: #78450a;

	--vp-danger: #fca5a5;
	--vp-danger-bg: #3b0d0d;
	--vp-danger-border: #7f1d1d;

	--vp-info: #93c5fd;
	--vp-info-bg: #0c1f3d;
	--vp-info-border: #1e40af;

	--vp-neutral: #cbd5e1;
	--vp-neutral-bg: #1e2637;
	--vp-neutral-border: #3d4a63;

	--vp-chart-1: #d4ae6e;
	--vp-chart-2: #22d3ee;
	--vp-chart-3: #fb923c;
	--vp-chart-4: #34d399;
	--vp-chart-5: #f472b6;
	--vp-chart-6: #facc15;
	--vp-chart-grid: #38425a;

	--vp-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
	--vp-shadow: 0 4px 12px rgba(0, 0, 0, 0.45);
	--vp-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);

	color-scheme: dark;
}

/* -------------------------------------------------------------------------
 * 4. Shared primitives
 * ---------------------------------------------------------------------- */

.vp-root {
	font-family: var(--vp-font);
	font-size: var(--vp-text-base);
	line-height: var(--vp-line-normal);
	color: var(--vp-text);
	background: var(--vp-bg);
}

/*
 * The single biggest cause of "fields running off the edge of the screen"
 * on a phone: without this, every element's `width` (or `max-width`) sets
 * only its *content* box - its own padding and border are then added on
 * top of that width instead of eaten out of it. An input styled
 * `width: 100%; padding: 8px 12px; border: 1px solid` (see
 * `.vp-root input[type="text"]` etc. below) was therefore always rendering
 * about 26px WIDER than its container - invisible on a wide desktop
 * container with room to spare, but enough to push the field's right edge
 * past a narrow phone screen every time. `border-box` makes padding and
 * border count *inside* the width that was actually set, everywhere in the
 * plugin, in one place, instead of patching every individual field.
 */
.vp-root,
.vp-root *,
.vp-root *::before,
.vp-root *::after {
	box-sizing: border-box;
}

/*
 * The browser's own `[hidden] { display: none; }` rule has the same
 * specificity as any single class selector (0,1,0), so a component class
 * that also sets its own `display` (e.g. `.vp-notice { display: flex }`)
 * silently wins the tie and keeps the element visible even while its
 * `hidden` attribute is set/toggled from JavaScript. Re-assert `[hidden]`
 * here, after every component's own rules have loaded, so the attribute
 * always wins for every current and future toggled element (notices,
 * modals, overlays, camera panes, etc.) across the whole plugin.
 */
[hidden] {
	display: none !important;
}

.vp-card {
	background: var(--vp-surface);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-lg);
	padding: var(--vp-space-5);
}

/*
 * Every dashboard tab's own content already renders inside the dashboard
 * shell's own `.vp-dashboard__panel.vp-card` (see front.css) - so a
 * sub-section inside a tab that also reaches for `.vp-card` (Help &
 * Support's "Open a new support request" box, the Scanner's cart panel, an
 * Offer's edit box, and others) was always drawing a second border/
 * background/shadow directly on top of the first one it already sits
 * inside, instead of reading as one panel with sections in it - "boxes
 * inside boxes" on every screen size, not only on mobile. A nested
 * `.vp-card` now keeps its own spacing (margin, max-width, etc. - each of
 * those was set with its own inline style/class, untouched here) but loses
 * the box chrome that was only ever meant to mark the outermost panel.
 */
.vp-card .vp-card {
	background: none;
	border: none;
	border-radius: 0;
	box-shadow: none;
	padding: 0;
}

.vp-badge {
	display: inline-flex;
	align-items: center;
	gap: var(--vp-space-1);
	padding: 3px 10px;
	border-radius: var(--vp-radius-pill);
	font-size: var(--vp-text-xs);
	font-weight: var(--vp-weight-semibold);
	border: 1px solid transparent;
	white-space: nowrap;
}

.vp-badge--success { color: var(--vp-success); background: var(--vp-success-bg); border-color: var(--vp-success-border); }
.vp-badge--warning { color: var(--vp-warning); background: var(--vp-warning-bg); border-color: var(--vp-warning-border); }
.vp-badge--danger  { color: var(--vp-danger);  background: var(--vp-danger-bg);  border-color: var(--vp-danger-border); }
.vp-badge--info    { color: var(--vp-info);    background: var(--vp-info-bg);    border-color: var(--vp-info-border); }
.vp-badge--neutral { color: var(--vp-neutral); background: var(--vp-neutral-bg); border-color: var(--vp-neutral-border); }

/* Public "Verified" mark (Business\Businesses::verified_badge_html()) -
 * correction round after Phase 45: the project owner asked for a blue
 * checkmark icon, like Facebook's own badge, with no "Verified" text next
 * to it ("নীল টিক ( ফেইজবুকের মত )"). A plain solid-blue circle with a
 * white check is exactly Facebook's own badge shape (Twitter/X uses a
 * scalloped seal instead - not what was asked for). `color: var(--vp-info)`
 * is the same blue this badge already used as `.vp-badge--info`, so the
 * mark stays visually consistent with the rest of the design system. The
 * word itself is not gone, only invisible - see that method's aria-label/
 * title for the screen-reader/tooltip text. */
.vp-verified-badge {
	display: inline-flex;
	flex: 0 0 auto;
	align-items: center;
	justify-content: center;
	width: 15px;
	height: 15px;
	margin-inline-start: 4px;
	vertical-align: middle;
	color: var(--vp-info);
}

.vp-verified-badge svg {
	display: block;
	width: 100%;
	height: 100%;
}

/* Larger size for a business's own profile page, next to its name in a
 * page heading rather than inside a small card row. */
.vp-verified-badge--lg {
	width: 20px;
	height: 20px;
	margin-inline-start: 6px;
}

/* Notification Center (Phase 12) - shared by the customer dashboard's
 * Notifications tab, the business dashboard's Notifications tab, and
 * nowhere else, so these rules live here in the shared token sheet rather
 * than in front.css alone. */
.vp-notif-list {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-3);
}

.vp-notif {
	display: flex;
	justify-content: space-between;
	align-items: flex-start;
	gap: var(--vp-space-4);
	padding: var(--vp-space-4);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius);
	background: var(--vp-surface-2);
}

.vp-notif--unread {
	border-color: var(--vp-brand);
	background: var(--vp-brand-soft);
}

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

.vp-notif__title {
	margin: 0 0 4px;
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text);
}

.vp-notif__message {
	margin: 0 0 6px;
	color: var(--vp-text-muted);
}

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

.vp-notif__actions {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-2);
	flex-shrink: 0;
}

/* Support ticket thread (Phase 12). */
.vp-ticket-thread {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-3);
	margin: var(--vp-space-4) 0;
}

.vp-ticket-msg {
	padding: var(--vp-space-4);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius);
	background: var(--vp-surface-2);
}

.vp-ticket-msg--admin {
	border-color: var(--vp-brand);
	background: var(--vp-brand-soft);
}

.vp-ticket-msg p:last-child {
	margin-bottom: 0;
}

/* Data table - moved here from admin.css in Phase 6, because the Business
 * Dashboard's front-end Discounts and Staff tabs need the exact same table
 * styling the admin screens already use (staff roster, discount rows), not
 * a second copy of it. */
.vp-table-wrap {
	overflow-x: auto;
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-lg);
	background: var(--vp-surface);
}

.vp-table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--vp-text-sm);
	min-width: 560px;
}

.vp-table th,
.vp-table td {
	padding: 11px 14px;
	text-align: left;
	border-bottom: 1px solid var(--vp-border);
	vertical-align: middle;
}

.vp-table th {
	background: var(--vp-surface-2);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text-muted);
	font-size: var(--vp-text-xs);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

.vp-table tbody tr:last-child td {
	border-bottom: 0;
}

.vp-table tbody tr:hover td {
	background: var(--vp-surface-2);
}

.vp-table code {
	font-family: var(--vp-font-mono);
	font-size: var(--vp-text-xs);
	background: var(--vp-surface-3);
	color: var(--vp-text-muted);
	padding: 2px 6px;
	border-radius: var(--vp-radius-sm);
}

/* Phase 22 - cart builder (Scan & Transactions "amount" step, shown once a
 * business has named at least one product). Phase 25 redesigned each line
 * from a spreadsheet-style table row into its own small card - always the
 * same layout on phone and desktop, instead of a table that only became
 * usable on a small screen through the generic .vp-table mobile-stacking
 * rules below. Every data-vp-cart-* hook scanner.js reads stayed exactly
 * where it was; only the surrounding markup and these styles changed. */
.vp-cart-list {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-3);
}

.vp-cart-row {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-3);
	/* Deliberately its own lighter treatment, not .vp-card - this list
	 * already sits inside the dashboard tab's own .vp-card panel, and
	 * .vp-card .vp-card strips a full nested card's chrome anyway (the
	 * "boxes inside boxes" fix). A tinted surface + border still separates
	 * one cart line from the next without re-introducing that problem. */
	background: var(--vp-surface-2);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius);
	padding: var(--vp-space-3);
}

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

.vp-cart-row__product,
.vp-cart-row__name {
	width: 100%;
}

.vp-cart-row__controls {
	display: flex;
	flex-wrap: wrap;
	gap: var(--vp-space-3);
}

.vp-cart-row__control {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-1);
	flex: 1 1 90px;
}

.vp-cart-row__control-label {
	font-size: var(--vp-text-xs);
	color: var(--vp-text-subtle);
	font-weight: var(--vp-weight-medium);
}

.vp-cart-row__qty,
.vp-cart-row__price {
	width: 100%;
}

.vp-cart-row__subtotal-field {
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-1);
	flex: 1 1 90px;
}

.vp-cart-row__subtotal {
	font-size: var(--vp-text-base);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-brand);
}

.vp-cart-row__remove {
	align-self: flex-end;
	background: none;
	border: none;
	color: var(--vp-danger);
	font-size: var(--vp-text-sm);
	font-weight: var(--vp-weight-medium);
	cursor: pointer;
	padding: var(--vp-space-1) var(--vp-space-2);
}

.vp-cart-row__remove:hover {
	text-decoration: underline;
}

.vp-cart-total-bar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--vp-space-3);
	margin-top: var(--vp-space-2);
	padding: var(--vp-space-3) var(--vp-space-4);
	background: var(--vp-surface-2);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius);
	font-weight: var(--vp-weight-semibold);
}

.vp-cart-total-bar strong {
	font-size: var(--vp-text-lg);
	color: var(--vp-brand);
}

@media screen and (min-width: 560px) {
	.vp-cart-row {
		flex-direction: row;
		align-items: flex-start;
	}

	.vp-cart-row__product-field {
		flex: 1 1 220px;
	}

	.vp-cart-row__controls {
		flex: 2 1 320px;
	}

	.vp-cart-row__remove {
		align-self: center;
	}
}

/* Phase 44 note: this is the one place on the site the new bundled logo
 * IMAGE (Settings' brand_logo_url, shown everywhere else through
 * Frontend_Shell::brand_html()) deliberately stays as plain text
 * (.vp-wallet__brand, still "Vibe Pass") instead of becoming an <img>. The
 * logo artwork is one fixed white colour; this card face is not - each
 * tier below has its own gradient "card stock" (light Silver, dark
 * Diamond, ...), so a single flat-colour logo image would either
 * disappear against a light tier or clash against a dark one. Plain text
 * inherits `color: var(--vp-wallet-ink)`, which already correctly swaps
 * per tier, so it reads correctly on every card. The slogan text
 * (.vp-wallet__subtitle) is NOT part of this exception - it now reads from
 * the same brand_tagline_en/brand_tagline_bn settings as the header and
 * footer (Frontend_Shell::tagline_html()), so changing the slogan in
 * Settings updates it everywhere, cards included; only the logo IMAGE is
 * card-face-exempt. */

/* Wallet card - the membership card shown on the admin Design Preview
 * screen and, since Phase 3, at the top of the real Customer dashboard
 * wallet tab (and, since Phase 20/39, the Business Owner "My Card" tab and
 * the homepage demo deck). Defined once here so every place always matches.
 *
 * Phase 43 redesign: replaces the flat "Deep Navy & Gold" single-body card
 * (one fixed dark --vp-ink body, tier shown only as a metallic trim) with
 * the project owner's own printed card design - a distinct gradient "card
 * stock" per tier (Silver/Gold/Platinum/Diamond), a dark diamond-quilted
 * panel along the bottom carrying the QR code and the tier name, and a
 * small "VIBEPASS / A Loyalty Card" wordmark - matching the 4 physical card
 * mockups and the hand-drawn demo-card sketch he supplied. Deliberately
 * fixed, not theme-variant, the same as the old design: a physical card's
 * own printed artwork does not change with the site's Light/Dark Mode, the
 * same way a real card in your wallet does not change colour at night.
 *
 * Structure every consumer already renders (unchanged, so this is a
 * restyle, not a rewrite of every template):
 *   .vp-wallet.vp-wallet--<tier>
 *     > div                      (top group)
 *       .vp-wallet__brand        "Vibe Pass" (or a composed caption)
 *       .vp-wallet__subtitle     new, optional - "A Loyalty Card"
 *       .vp-wallet__tier         tier or holder name
 *     > div                      (id group)
 *       .vp-wallet__number       card number (the tap-icon is pure CSS,
 *                                 ::before/::after on this element - no
 *                                 markup needed)
 *       .vp-wallet__meta         validity / savings line
 *     .vp-wallet__foot           new, optional - the dark quilted strip
 *       .vp-wallet__qr             QR code, embedded on the card face
 *       .vp-wallet__foot-tier      tier name, printed the way the real
 *                                  card prints it
 * `.vp-wallet__foot` is only rendered by the templates that show a real or
 * demo card (Customer\Wallet, Business\Owner_Card_Tab, Offer\Homepage's
 * demo deck, the admin Design Preview) - the smaller "card chip" summaries
 * (Scanner, Instant_Registration) keep the plain top/id groups only. */
.vp-wallet {
	--vp-wallet-face-1: var(--vp-brand-soft);
	--vp-wallet-face-2: var(--vp-accent);
	--vp-wallet-face-3: var(--vp-brand-strong);
	--vp-wallet-frame: var(--vp-ink);
	--vp-wallet-ink: var(--vp-ink);
	--vp-wallet-accent: var(--vp-ink-accent);
	position: relative;
	overflow: hidden;
	background: linear-gradient(135deg, var(--vp-wallet-face-1) 0%, var(--vp-wallet-face-2) 55%, var(--vp-wallet-face-3) 100%);
	color: var(--vp-wallet-ink);
	/* Correction round after Phase 43: the project owner said this frame
	 * had come out too thick/heavy ("চারপাশে ব্লাক বর্ডার টা বেশি মোটা বা
	 * গাড় হয়ে গিয়েছে") - 2px reads as a slim metallic edge, matching the
	 * reference card mockups, instead of a heavy outline. */
	border: 2px solid var(--vp-wallet-frame);
	border-radius: 14px;
	padding: var(--vp-space-4) var(--vp-space-5);
	min-height: 190px;
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	box-shadow: var(--vp-shadow-lg);
}

/* Decorative "signal lines" printed top-right on every real card mockup -
 * purely ornamental (no information), so it is drawn with ::before instead
 * of adding a non-translatable element to every template that renders a
 * card. */
.vp-wallet::before {
	content: "";
	position: absolute;
	top: var(--vp-space-4);
	right: var(--vp-space-4);
	left: 46%;
	height: 30px;
	background-image: repeating-linear-gradient(to bottom, currentColor 0, currentColor 2px, transparent 2px, transparent 6px);
	color: var(--vp-wallet-ink);
	opacity: 0.5;
	pointer-events: none;
}

.vp-wallet--silver {
	--vp-wallet-face-1: var(--vp-tier-silver-face-1);
	--vp-wallet-face-2: var(--vp-tier-silver-face-2);
	--vp-wallet-face-3: var(--vp-tier-silver-face-3);
	--vp-wallet-frame: var(--vp-tier-silver-frame);
	--vp-wallet-ink: var(--vp-tier-silver-ink);
	--vp-wallet-accent: #6b7280;
}

.vp-wallet--gold {
	--vp-wallet-face-1: var(--vp-tier-gold-face-1);
	--vp-wallet-face-2: var(--vp-tier-gold-face-2);
	--vp-wallet-face-3: var(--vp-tier-gold-face-3);
	--vp-wallet-frame: var(--vp-tier-gold-frame);
	--vp-wallet-ink: var(--vp-tier-gold-ink);
	--vp-wallet-accent: #7a4e14;
}

.vp-wallet--platinum {
	--vp-wallet-face-1: var(--vp-tier-platinum-face-1);
	--vp-wallet-face-2: var(--vp-tier-platinum-face-2);
	--vp-wallet-face-3: var(--vp-tier-platinum-face-3);
	--vp-wallet-frame: var(--vp-tier-platinum-frame);
	--vp-wallet-ink: var(--vp-tier-platinum-ink);
	--vp-wallet-accent: #9c2f63;
}

.vp-wallet--business-owner,
.vp-wallet--diamond {
	--vp-wallet-face-1: var(--vp-tier-diamond-face-1);
	--vp-wallet-face-2: var(--vp-tier-diamond-face-2);
	--vp-wallet-face-3: var(--vp-tier-diamond-face-3);
	--vp-wallet-frame: var(--vp-tier-diamond-frame);
	--vp-wallet-ink: var(--vp-tier-diamond-ink);
	--vp-wallet-accent: #1c6a91;
}

.vp-wallet--default {
	--vp-wallet-face-1: var(--vp-brand-soft);
	--vp-wallet-face-2: var(--vp-accent);
	--vp-wallet-face-3: var(--vp-brand-strong);
	--vp-wallet-frame: var(--vp-ink);
	--vp-wallet-ink: var(--vp-brand-contrast);
	--vp-wallet-accent: var(--vp-ink-accent);
}

.vp-wallet__brand {
	position: relative;
	font-size: var(--vp-text-lg);
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--vp-wallet-ink);
	font-weight: var(--vp-weight-bold, 700);
}

.vp-wallet__subtitle {
	font-size: 10px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--vp-wallet-ink);
	opacity: 0.72;
	font-weight: var(--vp-weight-semibold);
	margin-top: 2px;
}

.vp-wallet__subtitle::after {
	content: "";
	display: inline-block;
	width: 30px;
	height: 3px;
	margin-left: var(--vp-space-2);
	background: currentColor;
	vertical-align: middle;
	opacity: 0.85;
}

.vp-wallet__tier {
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-xl);
	font-weight: var(--vp-weight-semibold);
	margin-top: var(--vp-space-1);
	color: var(--vp-wallet-ink);
}

.vp-wallet__number {
	position: relative;
	padding-left: 34px;
	min-height: 20px;
	display: flex;
	align-items: center;
	font-family: var(--vp-font-mono);
	font-size: var(--vp-text-sm);
	letter-spacing: 0.14em;
	color: var(--vp-wallet-ink);
}

/* The small "tap to pay"-style wave icon printed next to the card number on
 * every mockup - two arcs (::before) and a dot (::after), pure CSS so no
 * markup change is needed on any of the many templates that already print
 * a `.vp-wallet__number`. */
.vp-wallet__number::before,
.vp-wallet__number::after {
	content: "";
	position: absolute;
	top: 50%;
	border-radius: 50%;
}

.vp-wallet__number::before {
	left: 0;
	width: 20px;
	height: 20px;
	margin-top: -10px;
	border: 1.5px solid currentColor;
	border-right-color: transparent;
	border-bottom-color: transparent;
	transform: rotate(-45deg);
	opacity: 0.45;
}

.vp-wallet__number::after {
	left: 14px;
	width: 5px;
	height: 5px;
	margin-top: -2.5px;
	background: currentColor;
	opacity: 0.75;
}

.vp-wallet__meta {
	font-size: var(--vp-text-xs);
	color: var(--vp-wallet-ink);
	opacity: 0.72;
	margin-top: var(--vp-space-1);
}

/* The dark, diamond-quilted strip along the bottom of the card, carrying
 * the QR code (embedded on the card face itself, matching the printed
 * physical card - see the class docblock above) and the tier name, printed
 * the way the real card prints it: small-caps, wide letter-spacing, white,
 * bottom-right of the panel. */
.vp-wallet__foot {
	position: relative;
	margin-top: var(--vp-space-3);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--vp-space-3);
	min-height: 64px;
	padding: var(--vp-space-2) var(--vp-space-3);
	border-radius: var(--vp-radius);
	background-color: #17181b;
	background-image:
		repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.08) 0, rgba(255, 255, 255, 0.08) 1px, transparent 1px, transparent 11px),
		repeating-linear-gradient(-45deg, rgba(255, 255, 255, 0.08) 0, rgba(255, 255, 255, 0.08) 1px, transparent 1px, transparent 11px);
}

.vp-wallet__qr {
	flex-shrink: 0;
	width: 56px;
	height: 56px;
	padding: 4px;
	background: #fff;
	border-radius: var(--vp-radius-sm);
	line-height: 0;
}

.vp-wallet__qr svg {
	width: 100%;
	height: 100%;
	display: block;
}

.vp-wallet__foot-tier {
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-sm);
	font-weight: var(--vp-weight-semibold);
	letter-spacing: 0.3em;
	text-transform: uppercase;
	color: #f3f0e8;
}

.vp-progress {
	height: 10px;
	border-radius: var(--vp-radius-pill);
	background: var(--vp-surface-3);
	overflow: hidden;
}

.vp-progress__bar {
	height: 100%;
	border-radius: var(--vp-radius-pill);
	background: linear-gradient(90deg, var(--vp-brand) 0%, var(--vp-accent) 100%);
	transition: width var(--vp-transition);
}

.vp-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--vp-space-2);
	min-height: 40px;
	padding: 9px 18px;
	border-radius: var(--vp-radius);
	border: 1px solid transparent;
	font-family: inherit;
	font-size: var(--vp-text-sm);
	font-weight: var(--vp-weight-semibold);
	cursor: pointer;
	text-decoration: none;
	transition: background var(--vp-transition), border-color var(--vp-transition), color var(--vp-transition), box-shadow var(--vp-transition), transform 80ms ease;
}

/* Primary CTA: a fixed --vp-ink body with a --vp-brand hairline and
 * uppercase, letter-spaced label - the same "Complete Transaction" style
 * card-review buttons use everywhere else in the app (Save, Register, Pay,
 * ...), not a solid brand-colour fill. This still follows whatever colour
 * the admin picks as the Primary Color (Assets::brand_overrides()) - it is
 * just applied as a border/text accent on ink instead of as a background
 * fill, so a custom brand colour still shows up correctly here. */
.vp-btn--primary {
	background: var(--vp-ink);
	color: var(--vp-brand);
	border-color: var(--vp-brand);
	text-transform: uppercase;
	letter-spacing: 0.04em;
}
.vp-btn--primary:hover { background: var(--vp-ink-2); color: var(--vp-brand-strong); border-color: var(--vp-brand-strong); }
.vp-btn--primary:active { transform: translateY(1px); }
.vp-btn--secondary { background: var(--vp-surface); color: var(--vp-text); border-color: var(--vp-border-strong); }
.vp-btn--secondary:hover { background: var(--vp-surface-2); border-color: var(--vp-text-subtle); }
.vp-btn--secondary:active { transform: translateY(1px); }
.vp-btn--sm { min-height: 32px; padding: 5px 12px; font-size: var(--vp-text-xs); }
.vp-btn--block { display: flex; width: 100%; }

.vp-btn[disabled],
.vp-btn:disabled,
.vp-btn[aria-disabled="true"] {
	opacity: 0.55;
	cursor: not-allowed;
	pointer-events: none;
	box-shadow: none;
	transform: none;
}

/* Small inline loading spinner - drop `<span class="vp-spinner"></span>`
   inside a button or notice while an action is in flight (Section 65 -
   "Loading states"). Colour follows currentColor so it reads correctly on a
   primary button, a secondary button, or plain body text alike. */
.vp-spinner {
	display: inline-block;
	width: 1em;
	height: 1em;
	border: 2px solid currentColor;
	border-right-color: transparent;
	border-radius: 50%;
	opacity: 0.75;
	animation: vp-spin 0.65s linear infinite;
}

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

/* Skeleton loading placeholder - a shimmering block the width/height of the
   real content it stands in for, until that content is ready. */
.vp-skeleton {
	display: block;
	border-radius: var(--vp-radius-sm);
	background: linear-gradient(90deg, var(--vp-surface-3) 25%, var(--vp-surface-2) 37%, var(--vp-surface-3) 63%);
	background-size: 400% 100%;
	animation: vp-skeleton-shimmer 1.4s ease infinite;
}

@keyframes vp-skeleton-shimmer {
	0% { background-position: 100% 50%; }
	100% { background-position: 0 50%; }
}

@media (prefers-reduced-motion: reduce) {
	.vp-spinner,
	.vp-skeleton {
		animation: none;
	}
}

.vp-btn:focus-visible,
.vp-toggle button:focus-visible {
	outline: 2px solid var(--vp-focus);
	outline-offset: 2px;
}

/* Segmented control used by the theme and language switchers. */
.vp-toggle {
	display: inline-flex;
	padding: 3px;
	gap: 2px;
	background: var(--vp-surface-3);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-pill);
}

.vp-toggle button {
	appearance: none;
	border: 0;
	background: transparent;
	color: var(--vp-text-muted);
	font-family: inherit;
	font-size: var(--vp-text-xs);
	font-weight: var(--vp-weight-semibold);
	padding: 5px 14px;
	border-radius: var(--vp-radius-pill);
	cursor: pointer;
	transition: background var(--vp-transition), color var(--vp-transition);
}

.vp-toggle button[aria-pressed="true"] {
	background: var(--vp-surface);
	color: var(--vp-text);
	box-shadow: var(--vp-shadow-sm);
}

/* Respect a reduced-motion preference. */
@media (prefers-reduced-motion: reduce) {
	.vp-progress__bar,
	.vp-btn,
	.vp-toggle button {
		transition: none;
	}
}

/* -------------------------------------------------------------------------
 * 5. Shared form & notice primitives
 *
 * These live here (not in admin.css) because both the WordPress Admin
 * screens and the public-facing customer pages (registration, login,
 * dashboard) need identical, Dark-Mode-correct fields and notices. Scoped to
 * .vp-root, the one wrapper class every Vibe Pass screen uses, so this never
 * leaks onto unrelated admin or theme markup.
 * ---------------------------------------------------------------------- */

.vp-grid {
	display: grid;
	gap: var(--vp-space-4);
}

/* min(Npx, 100%) instead of a bare Npx: on a viewport narrower than the
 * column minimum (e.g. a 375px phone with page padding eating into it),
 * the track shrinks to fit the container instead of forcing horizontal
 * overflow. Behaves identically to the old bare-px value everywhere wider
 * than the minimum, so this changes nothing above phone widths. */
.vp-grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
.vp-grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.vp-grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr)); }

/* Stat tiles - originally admin-only, moved here in Phase 8 so the
 * customer's Savings Summary and the business's/admin's transaction totals
 * can reuse the exact same tile instead of a second near-duplicate style. */

.vp-stat {
	background: var(--vp-surface);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-lg);
	padding: var(--vp-space-4);
	box-shadow: var(--vp-shadow-sm);
}

.vp-stat__label {
	font-size: var(--vp-text-xs);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text-subtle);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	margin: 0 0 var(--vp-space-2);
}

.vp-stat__value {
	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);
	margin: 0;
}

.vp-stat__meta {
	margin: var(--vp-space-2) 0 0;
	font-size: var(--vp-text-xs);
	color: var(--vp-text-subtle);
}

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

.vp-section__title {
	margin: 0 0 var(--vp-space-3);
	font-family: var(--vp-font-serif);
	font-size: var(--vp-text-lg);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text);
}

.vp-section__hint {
	margin: 0 0 var(--vp-space-4);
	font-size: var(--vp-text-sm);
	color: var(--vp-text-subtle);
	max-width: 70ch;
}

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

/*
 * Phase 28: a long profile-style form (Customer Profile, Business Profile &
 * Hours) used to stack every single field in one narrow column - each input
 * already caps at 420px (see below) - leaving a large empty gutter beside it
 * on any normal desktop width, and no visual grouping between fields that
 * are actually related. `.vp-field-row` is an opt-in wrapper (nothing uses
 * it unless a template explicitly reaches for it, so no existing form's
 * layout changes) that lets two clearly-related fields (name + email,
 * district + upazila, ...) sit side by side on a wide screen and simply
 * stack again on a narrow one - no new box chrome, matching the project's
 * own "no boxes inside boxes" rule for `.vp-card` above.
 */
.vp-field-row {
	display: flex;
	flex-wrap: wrap;
	gap: 0 var(--vp-space-5);
}

.vp-field-row > .vp-field {
	flex: 1 1 240px;
	min-width: 0;
}

.vp-field-row > .vp-field input,
.vp-field-row > .vp-field select {
	max-width: none;
	width: 100%;
}

.vp-field__label {
	display: block;
	font-size: var(--vp-text-sm);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text);
	margin-bottom: var(--vp-space-2);
}

.vp-field__hint {
	display: block;
	font-size: var(--vp-text-xs);
	color: var(--vp-text-subtle);
	margin-top: var(--vp-space-2);
	max-width: 60ch;
}

/* Phase 39: the icon picker on Vibe Pass > Home Quick Links - a grid of
 * radio-button "chips", each showing one of Home_Cards::icon_svg()'s
 * preset icons plus its label, so the administrator picks a known-safe icon
 * instead of typing or pasting anything (Section 61). Also reused, read-only
 * (no <input> inside), as the small icon swatch in that screen's own list
 * table. */
.vp-icon-picker {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
	gap: var(--vp-space-2);
	margin-top: var(--vp-space-1);
}

.vp-icon-picker__option {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--vp-space-1);
	padding: var(--vp-space-3) var(--vp-space-2);
	border: 1px solid var(--vp-border-strong);
	border-radius: var(--vp-radius);
	background: var(--vp-surface);
	cursor: pointer;
	text-align: center;
	transition: border-color var(--vp-transition), background var(--vp-transition);
}

.vp-icon-picker__option:hover {
	border-color: var(--vp-brand);
}

.vp-icon-picker__option.is-selected {
	border-color: var(--vp-brand);
	background: var(--vp-surface-2);
}

.vp-icon-picker__option input[type="radio"] {
	position: absolute;
	opacity: 0;
	width: 1px;
	height: 1px;
}

.vp-icon-picker__glyph {
	display: inline-flex;
	width: 24px;
	height: 24px;
	color: var(--vp-text);
}

.vp-icon-picker__glyph svg {
	width: 100%;
	height: 100%;
}

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

.vp-root input[type="text"],
.vp-root input[type="number"],
.vp-root input[type="email"],
.vp-root input[type="password"],
.vp-root input[type="tel"],
.vp-root input[type="url"],
.vp-root input[type="time"],
.vp-root select,
.vp-root textarea {
	width: 100%;
	max-width: 420px;
	padding: 8px 12px;
	font-family: inherit;
	font-size: var(--vp-text-sm);
	color: var(--vp-text);
	background: var(--vp-surface);
	border: 1px solid var(--vp-border-strong);
	border-radius: var(--vp-radius);
	box-shadow: none;
}

.vp-root input[type="text"]:focus,
.vp-root input[type="number"]:focus,
.vp-root input[type="email"]:focus,
.vp-root input[type="password"]:focus,
.vp-root input[type="tel"]:focus,
.vp-root input[type="url"]:focus,
.vp-root input[type="time"]:focus,
.vp-root select:focus,
.vp-root textarea:focus {
	border-color: var(--vp-focus);
	outline: 2px solid var(--vp-focus);
	outline-offset: 1px;
}

.vp-root input[readonly] {
	background: var(--vp-surface-2);
	color: var(--vp-text-subtle);
	cursor: not-allowed;
}

.vp-root input[type="checkbox"] {
	accent-color: var(--vp-brand);
	width: 16px;
	height: 16px;
}

.vp-root input[type="file"] {
	width: 100%;
	max-width: 420px;
	padding: 8px 0;
	font-family: inherit;
	font-size: var(--vp-text-sm);
	color: var(--vp-text);
}

.vp-root input[type="file"]:focus-visible {
	outline: 2px solid var(--vp-focus);
	outline-offset: 1px;
}

.vp-root input[type="color"] {
	width: 56px;
	height: 34px;
	padding: 2px;
	border: 1px solid var(--vp-border-strong);
	border-radius: var(--vp-radius);
	background: var(--vp-surface);
	cursor: pointer;
}

.vp-checkbox {
	display: flex;
	align-items: flex-start;
	gap: var(--vp-space-2);
	font-size: var(--vp-text-sm);
	color: var(--vp-text);
}

/* A checkbox that hangs under a text field (e.g. "Same as mobile number"
 * under a WhatsApp number field, Phase 16) rather than standing alone. */
.vp-field__checkbox {
	display: flex;
	align-items: center;
	gap: var(--vp-space-2);
	font-size: var(--vp-text-sm);
	color: var(--vp-text-muted);
	margin-top: var(--vp-space-2);
	cursor: pointer;
}

.vp-form-actions {
	margin-top: var(--vp-space-5);
	padding-top: var(--vp-space-4);
	border-top: 1px solid var(--vp-border);
	display: flex;
	gap: var(--vp-space-3);
	align-items: center;
	flex-wrap: wrap;
}

.vp-notice {
	display: flex;
	gap: var(--vp-space-3);
	padding: var(--vp-space-4);
	border-radius: var(--vp-radius);
	border: 1px solid;
	font-size: var(--vp-text-sm);
	margin-bottom: var(--vp-space-4);
	align-items: flex-start;
}

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

.vp-notice strong { font-weight: var(--vp-weight-bold); }

/* -------------------------------------------------------------------------
 * 6. Charts (Phase 14) - hand-rolled inline SVG, rendered server-side by
 *    Analytics\Charts, no external JS charting library. Every colour comes
 *    from the --vp-chart-* tokens defined above, so a chart is correct in
 *    Light and Dark Mode automatically with no extra markup or JS.
 * ---------------------------------------------------------------------- */

.vp-chart-card {
	background: var(--vp-surface);
	border: 1px solid var(--vp-border);
	border-radius: var(--vp-radius-lg);
	padding: var(--vp-space-5);
	margin-bottom: var(--vp-space-4);
}

.vp-chart-card__title {
	font-size: var(--vp-text-lg);
	font-weight: var(--vp-weight-semibold);
	color: var(--vp-text);
	margin: 0 0 var(--vp-space-4) 0;
}

.vp-chart {
	width: 100%;
	height: auto;
	overflow: visible;
}

.vp-chart__grid {
	stroke: var(--vp-chart-grid);
	stroke-width: 1;
}

.vp-chart__bar {
	transition: opacity 0.15s ease;
}

.vp-chart__bar:hover {
	opacity: 0.8;
}

.vp-chart__label,
.vp-chart__value {
	font-size: 10px;
	fill: var(--vp-text-muted);
	font-family: var(--vp-font, inherit);
}

.vp-chart--donut {
	display: flex;
	gap: var(--vp-space-5);
	align-items: center;
	flex-wrap: wrap;
}

.vp-chart--donut svg {
	width: 160px;
	height: 160px;
	flex-shrink: 0;
}

.vp-chart__legend {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--vp-space-2);
	font-size: var(--vp-text-sm);
	color: var(--vp-text);
}

.vp-chart__swatch {
	display: inline-block;
	width: 10px;
	height: 10px;
	border-radius: 2px;
	margin-inline-end: 6px;
}

.vp-chart__empty {
	color: var(--vp-text-muted);
	font-size: var(--vp-text-sm);
}

@media screen and (max-width: 782px) {
	.vp-root input[type="text"],
	.vp-root input[type="number"],
	.vp-root input[type="email"],
	.vp-root input[type="password"],
	.vp-root input[type="tel"],
	.vp-root select,
	.vp-root textarea {
		max-width: 100%;
	}

	/* Responsive tables: below this width a real <table> (staff rosters,
	 * discount/product lists, transaction history, the scan cart, admin
	 * list screens - .vp-table is shared by all of them) no longer forces
	 * horizontal scrolling to read. Each row becomes its own bordered
	 * block and each cell stacks with a small label above it, instead of
	 * columns squeezed sideways off the edge of the phone.
	 *
	 * The label comes from a `data-label` attribute copied onto every
	 * <td>/<th> from that column's own <thead> heading - assets/js/
	 * front.js does this once on page load (initResponsiveTables()), so
	 * none of this plugin's ~25 table views need to duplicate their own
	 * column headings by hand. A table with no <thead> at all (a plain
	 * label/value table, one <th> naming each row instead of each column)
	 * already reads correctly with no label needed, since the <th> itself
	 * stacks directly above its <td> in normal document order.
	 */
	.vp-table-wrap {
		overflow-x: visible;
		border: none;
		background: none;
		box-shadow: none;
	}

	.vp-table {
		display: block;
		min-width: 0;
	}

	.vp-table thead {
		display: none;
	}

	.vp-table tbody,
	.vp-table tfoot {
		display: block;
	}

	.vp-table tr {
		display: block;
		margin-bottom: var(--vp-space-3);
		padding: var(--vp-space-3);
		border: 1px solid var(--vp-border);
		border-radius: var(--vp-radius-lg);
		background: var(--vp-surface);
	}

	.vp-table tbody tr:last-child,
	.vp-table tfoot tr {
		margin-bottom: 0;
	}

	.vp-table th,
	.vp-table td {
		display: block;
		width: auto;
		padding: 7px 0;
		border-bottom: 1px dashed var(--vp-border);
		text-align: left;
	}

	.vp-table tr > th:last-child,
	.vp-table tr > td:last-child {
		border-bottom: none;
		padding-bottom: 0;
	}

	.vp-table th:empty,
	.vp-table td:empty {
		display: none;
	}

	.vp-table td[data-label]::before,
	.vp-table th[data-label]::before {
		content: attr(data-label);
		display: block;
		margin-bottom: 2px;
		color: var(--vp-text-subtle);
		font-size: var(--vp-text-xs);
		font-weight: var(--vp-weight-semibold);
		text-transform: uppercase;
		letter-spacing: 0.04em;
	}

	.vp-table tbody tr:hover td {
		background: none;
	}

	/* Cards, everywhere: on a phone-width screen the desktop 24px padding /
	 * 16px corner radius (see .vp-card above) reads as a lot of empty
	 * border around very little content, and when one .vp-card sits inside
	 * another (the dashboard header card above the dashboard panel card,
	 * both inside .vp-dashboard's own padding) the padding compounds three
	 * times over before any real content starts - this is what makes the
	 * whole page look cramped and "boxed in" on mobile. Trimming the
	 * padding/radius here is a single, sitewide change (every .vp-card on
	 * every dashboard, form, admin screen and print view shares this one
	 * rule) so no individual template needs its own mobile tweak.
	 */
	.vp-card {
		padding: var(--vp-space-4);
		border-radius: var(--vp-radius);
	}

	/* The dashboard's own "Welcome, Name" header card is the clearest case
	 * of that compounding (it sits directly above the equally-boxed panel
	 * card), so it drops its border/background/shadow entirely on mobile
	 * and reads as a plain page heading instead of a second nested box -
	 * the panel card below it remains the one visible boundary on screen. */
	.vp-dashboard__header.vp-card {
		padding: 0 0 var(--vp-space-3);
		border: none;
		background: none;
		box-shadow: none;
		border-radius: 0;
	}
}

/* -------------------------------------------------------------------------
 * 7. Print - always light, so printed cards and receipts save ink and are
 *    readable in black on white.
 * ---------------------------------------------------------------------- */

@media print {
	:root,
	:root[data-vp-theme="dark"] {
		--vp-bg: #ffffff;
		--vp-surface: #ffffff;
		--vp-surface-2: #ffffff;
		--vp-surface-3: #ffffff;
		--vp-text: #000000;
		--vp-text-muted: #333333;
		--vp-text-subtle: #555555;
		--vp-border: #999999;
		--vp-border-strong: #666666;
		--vp-shadow-sm: none;
		--vp-shadow: none;
		--vp-shadow-lg: none;
		color-scheme: light;
	}

	.vp-no-print {
		display: none !important;
	}

	/*
	 * Customer\History's "Transactions" table (front-end dashboard) opens
	 * one transaction's receipt inline, inside that row's own <td>, via
	 * <details class="vp-print-scope">. Printing "Print receipt" from
	 * inside it should print just that receipt, not the whole table of
	 * every other transaction sitting around it - the `:has()` selectors
	 * scope this to only a table that actually contains a
	 * `.vp-print-scope` element, so no other `.vp-table` on the site (an
	 * admin list, the scan cart, and so on) is affected.
	 */
	.vp-table:has(.vp-print-scope) thead {
		display: none;
	}

	.vp-table:has(.vp-print-scope) tbody tr:not(:has(.vp-print-scope[open])) {
		display: none;
	}

	.vp-table:has(.vp-print-scope) tbody tr td:not(:has(.vp-print-scope)) {
		display: none;
	}

	/*
	 * The "View" toggle itself (a <summary>) is only useful on screen to
	 * open/close the receipt - once it is open and the page is being
	 * printed, showing the click target serves no purpose, so it is hidden
	 * the same way .vp-no-print hides any other screen-only control.
	 */
	.vp-print-scope > summary {
		display: none;
	}
}
