/*
 * BizzFlow components.
 *
 * Built only from tokens.css â no raw colour values below this line.
 *
 * The visual brief, in one sentence: this is a tool someone stands in front of
 * for ten hours, so it should be dense, quiet and confident rather than airy
 * and decorative. Colour is reserved for money and for status; everything else
 * earns attention through hierarchy and spacing.
 */

/* ==================================================================
   Buttons
   ================================================================== */

/*
 * The primary button is a solid brand fill, so its text is --bzf-on-brand and
 * never --bzf-text-invert. --bzf-brand-solid is chosen to carry white at 4.6:1;
 * the brighter --bzf-brand-500 next to it manages only 3.3:1, which is why the
 * fill is the deeper of the two. Do not swap them.
 */
.bzf-btn--primary {
	background: var(--bzf-brand-solid);
	color: var(--bzf-on-brand);
	box-shadow: var(--bzf-shadow-xs);
}

.bzf-btn--primary:hover { background: var(--bzf-brand-solid-hover); box-shadow: var(--bzf-shadow-brand); }

/*
 * A disabled primary used to be the brand colour at 45% opacity, which reads as
 * a rendering fault rather than an unavailable button. "Not yet" is a neutral
 * state, so it gets a neutral colour. Only [disabled] is dimmed â an enabled
 * primary is always the full solid fill.
 */
.bzf-btn--primary[disabled] {
	background: var(--bzf-surface-3);
	color: var(--bzf-text-subtle);
	box-shadow: none;
	opacity: 1;
}

.bzf-btn--ghost {
	background: var(--bzf-surface);
	border-color: var(--bzf-border);
	color: var(--bzf-text);
}

.bzf-btn--ghost:hover { background: var(--bzf-surface-2); border-color: var(--bzf-border-strong); }

.bzf-btn--subtle {
	background: transparent;
	color: var(--bzf-text-muted);
}

.bzf-btn--subtle:hover { background: var(--bzf-surface-3); color: var(--bzf-text); }

/* Destructive actions are never the visually easiest thing to click. */
.bzf-btn--danger { background: var(--bzf-danger); color: #fff; }
.bzf-btn--danger:hover { background: var(--bzf-danger-fg); }

.bzf-btn--lg { padding: 11px 20px; font-size: var(--bzf-text-md); }
.bzf-btn--sm { padding: 5px 10px; font-size: var(--bzf-text-sm); }

.bzf-iconbtn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 34px;
	height: 34px;
	border: 1px solid transparent;
	background: transparent;
	color: var(--bzf-text-muted);
	border-radius: var(--bzf-radius);
	cursor: pointer;
	transition: background var(--bzf-fast), color var(--bzf-fast);
}

.bzf-iconbtn:hover { background: var(--bzf-surface-3); color: var(--bzf-text); text-decoration: none; }

/* ==================================================================
   Shell
   ================================================================== */

.bzf-shell {
	display: grid;
	grid-template-columns: var(--bzf-sidebar-w) minmax(0, 1fr);
	min-height: 100vh;
	min-height: 100dvh;
}

.bzf-shell__main { display: flex; flex-direction: column; min-width: 0; }

/*
 * The paid plan's layout: no sidebar column at all.
 *
 * Not a hidden sidebar â the element is never rendered, so the grid is one
 * column and the till gets the whole window. That is what the paid tier is
 * selling, and `display: none` on a 244px column would leave the gap behind on
 * any browser that mis-computes it.
 */
.bzf-shell--topbar {
	grid-template-columns: minmax(0, 1fr);
}

/*
 * Six dropdowns, not a scrolling row of twenty.
 *
 * Measured at 1366x768 before this change: the flat list needed 1738px and had
 * 1351px, so five items sat off the right edge behind a horizontal scroll that
 * almost nobody finds. The list no longer scrolls at all — it does not need to.
 */
.bzf-topnav {
	position: sticky;
	/* Under the top bar, which is itself sticky at 0. */
	top: var(--bzf-topbar-h);
	z-index: 18;
	background: var(--bzf-surface);
	border-bottom: 1px solid var(--bzf-border);
}

/*
 * CENTRED, and a segmented bar rather than a row of underlined tabs.
 *
 * Flush left with underlines is a website's masthead nav: it reads as "these
 * are the sections of this site". Six groups sitting centred in a tinted track,
 * with the current one as a filled pill, reads as an app's segmented control —
 * "this is where you are, and these are the other places you can be".
 *
 * `justify-content: center` and not `margin-inline: auto` on the list: the list
 * is the full width of the bar and centres its CONTENT, so the tinted track and
 * the bar's own border still run edge to edge. Centring the list itself would
 * shrink-wrap it and leave the border floating — the same cross-axis trap that
 * cost the operator portal a release (see the .bzo-main note).
 */
.bzf-topnav__list {
	display: flex;
	justify-content: center;
	align-items: center;
	gap: var(--bzf-1);
	margin: 0;
	padding: var(--bzf-2) var(--bzf-4);
	list-style: none;
	flex-wrap: wrap;
}

/* The dropdown is positioned against its own group, not the bar. */
.bzf-topnav__group { position: relative; }

.bzf-topnav__trigger {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	padding: 9px var(--bzf-4);
	font: inherit;
	font-size: var(--bzf-text-sm);
	font-weight: 550;
	white-space: nowrap;
	color: var(--bzf-text-muted);
	background: none;
	border: 0;
	border-radius: var(--bzf-radius-full);
	cursor: pointer;
	transition: color 0.15s ease, background 0.15s ease;
}

.bzf-topnav__trigger:hover {
	color: var(--bzf-text);
	background: var(--bzf-surface-2);
}

/*
 * The group holding the current screen stays lit, so the bar still answers
 * "where am I" — the job the underline used to do.
 *
 * A filled pill, not an underline: an underline under a pill is two marks for
 * one state. Brand ink on the brand wash, which is the pairing the table
 * headings already use, so it is a known-readable combination rather than a new
 * one to re-measure.
 */
.bzf-topnav__trigger.is-active,
.bzf-topnav__trigger.is-open {
	color: var(--bzf-brand-600);
	background: var(--bzf-brand-50);
}

.bzf-topnav__trigger.is-active:hover,
.bzf-topnav__trigger.is-open:hover {
	background: var(--bzf-brand-50);
}

.bzf-topnav__trigger .bzf-nav__icon { display: grid; place-items: center; }

.bzf-topnav__caret { display: grid; place-items: center; opacity: .7; }
.bzf-topnav__trigger.is-open .bzf-topnav__caret { transform: rotate(180deg); }

.bzf-topnav__menu {
	position: absolute;
	top: 100%;
	inset-inline-start: 0;
	z-index: 30;
	min-width: 210px;
	padding: var(--bzf-2);
	background: var(--bzf-surface);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius);
	box-shadow: var(--bzf-shadow-lg);
}

/*
 * Inside a dropdown an item is a full-width row, not a tab. The underline that
 * marked the active tab would be a stray line across a menu, so the active row
 * is marked by its background instead.
 */
.bzf-topnav__item {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	width: 100%;
	padding: 9px var(--bzf-3);
	font: inherit;
	font-size: var(--bzf-text-sm);
	font-weight: 550;
	white-space: nowrap;
	text-align: start;
	color: var(--bzf-text-muted);
	background: none;
	border: 0;
	border-radius: var(--bzf-radius-sm);
	cursor: pointer;
	transition: color 0.15s ease, background 0.15s ease;
}

.bzf-topnav__item:hover { color: var(--bzf-text); background: var(--bzf-surface-2); }

.bzf-topnav__item.is-active {
	color: var(--bzf-brand-600);
	background: var(--bzf-brand-50);
}

.bzf-topnav__item.is-primary { color: var(--bzf-brand-600); }

.bzf-topnav__item .bzf-nav__icon { display: grid; place-items: center; }

/* On a phone the six triggers wrap to two rows rather than scrolling, and the
   labels stay: six words is not a smear the way twenty was. */
@media (max-width: 640px) {
	.bzf-topnav__list { padding-inline: var(--bzf-2); }
	.bzf-topnav__trigger { padding: 10px var(--bzf-2); }

	/* Anchored to the bar rather than the group, or a dropdown opened by the
	   rightmost trigger would hang off the screen edge. */
	.bzf-topnav__menu {
		position: fixed;
		inset-inline: var(--bzf-2);
		min-width: 0;
	}
}

/*
 * A locked item, in either layout.
 *
 * Dimmed and carrying a shield rather than its own icon, but still a real
 * button: it has something to say ("this is on the paid plan") and must be
 * reachable by keyboard to say it. `[disabled]` would take it out of the tab
 * order and silence it.
 */
/*
 * Locked items are dimmed with a COLOUR, not with opacity.
 *
 * This was `opacity: 0.55`, which measured 2.31:1 on the label — well under
 * WCAG AA — and no sweep ever caught it. The desktop sweeps ran on the paid
 * plan, which renders no sidebar at all, and the phone sweeps hide
 * `.bzf-nav__label` entirely. So the free plan, where THIRTEEN of the twenty
 * menu items are locked, had most of its menu below the readability floor on
 * every release that claimed 0 contrast failures.
 *
 * Opacity is the wrong tool here for the same reason glass is measured
 * carefully everywhere else in this file: it multiplies whatever is behind it,
 * so the contrast of the text depends on the surface rather than on a token.
 * A muted colour is a fixed value that can be measured once and trusted.
 */
/*
 * 1.48.0: moved from --bzf-text-muted to its own token, one step dimmer at the
 * owner's request. Still measured, still above AA on all three surfaces — see
 * the note on --bzf-nav-locked in tokens.css for the numbers.
 */
.bzf-nav__item.is-locked,
.bzf-topnav__item.is-locked {
	color: var(--bzf-nav-locked);
}

.bzf-nav__item.is-locked:hover,
.bzf-topnav__item.is-locked:hover {
	color: var(--bzf-text);
}

/* The padlock itself may stay quiet — it is decoration, not a label, and
   WCAG 1.4.3 is about text. */
.bzf-nav__item.is-locked .bzf-nav__lock,
.bzf-topnav__item.is-locked .bzf-nav__lock {
	opacity: 0.7;
}

@media (max-width: 900px) {
	.bzf-topnav__item .bzf-nav__label { display: none; }
	.bzf-topnav__item { padding: 11px var(--bzf-4); }
}

/* ---------------------------------------------------------------
 * Look & feel â accent swatches and logo
 * --------------------------------------------------------------- */

.bzf-swatches {
	display: flex;
	flex-wrap: wrap;
	gap: var(--bzf-3);
	margin: var(--bzf-3) 0 var(--bzf-4);
}

.bzf-swatch {
	width: 30px;
	height: 30px;
	padding: 0;
	border-radius: 50%;
	background: var(--swatch);
	border: 2px solid transparent;
	/* The ring sits OUTSIDE the colour via box-shadow rather than a border, so
	 * selecting a swatch does not shrink the colour the owner is judging. */
	box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12);
	cursor: pointer;
	transition: box-shadow var(--bzf-fast), transform var(--bzf-fast);
}

.bzf-swatch:hover { transform: scale(1.08); }

.bzf-swatch.is-chosen {
	box-shadow: 0 0 0 2px var(--bzf-surface), 0 0 0 4px var(--swatch);
}

.bzf-logorow {
	display: flex;
	align-items: center;
	gap: var(--bzf-4);
	margin-top: var(--bzf-3);
}

.bzf-logorow__preview {
	display: grid;
	place-items: center;
	flex: none;
	width: 52px;
	height: 52px;
	border-radius: var(--bzf-radius);
	font-size: var(--bzf-text-xl);
	font-weight: 680;
	color: var(--bzf-on-brand);
	background: var(--bzf-brand-solid);
	/* A logo is content, not an icon â painted, never an <img>. See the rule in
	 * HANDOFF about why icons and photographs are different things. */
	background-size: cover;
	background-position: center;
}

.bzf-logorow__preview.has-logo {
	color: transparent;
	background-color: var(--bzf-surface);
	border: 1px solid var(--bzf-border);
}

.bzf-logorow__actions {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	flex-wrap: wrap;
}

/* The sidebar mark, once a shop has uploaded a logo. */
.bzf-sidebar__mark.has-logo,
.bzf-topbar__mark.has-logo {
	color: transparent;
	background-size: cover;
	background-position: center;
}

.bzf-sidebar__mark.has-logo svg,
.bzf-topbar__mark.has-logo svg { display: none; }

/* ---------------------------------------------------------------
 * Data & exports
 * --------------------------------------------------------------- */

.bzf-exportlist { display: flex; flex-direction: column; }

.bzf-exportrow {
	display: flex;
	align-items: center;
	gap: var(--bzf-4);
	padding: var(--bzf-4) 0;
	border-bottom: 1px solid var(--bzf-border);
}

.bzf-exportrow:last-child { border-bottom: 0; }

.bzf-exportrow__icon {
	display: grid;
	place-items: center;
	flex: none;
	width: 34px;
	height: 34px;
	border-radius: var(--bzf-radius);
	color: var(--bzf-brand-600);
	/* --bzf-brand-wash was never defined, so this always fell through to a
	   hardcoded orange and stayed orange whatever the shop picked. */
	background: var(--bzf-brand-50);
}

.bzf-exportrow__main { flex: 1; min-width: 0; }
.bzf-exportrow__label { font-size: var(--bzf-text-sm); font-weight: 600; }
.bzf-exportrow__hint { font-size: var(--bzf-text-xs); color: var(--bzf-text-muted); }

/* ---------------------------------------------------------------
 * Notifications screen
 * --------------------------------------------------------------- */

.bzf-noticebar {
	display: flex;
	align-items: center;
	gap: var(--bzf-3);
	margin-bottom: var(--bzf-4);
	padding: var(--bzf-3) var(--bzf-4);
	border-radius: var(--bzf-radius);
	background: var(--bzf-warning-bg);
	color: var(--bzf-warning-fg);
}

.bzf-noticebar__text { flex: 1; font-size: var(--bzf-text-sm); font-weight: 600; }

.bzf-notelist { margin: 0; padding: 0; list-style: none; }

.bzf-notelist__row {
	display: flex;
	align-items: flex-start;
	gap: var(--bzf-3);
	padding: var(--bzf-4) 0;
	border-bottom: 1px solid var(--bzf-border);
}

.bzf-notelist__row:last-child { border-bottom: 0; }

.bzf-notelist__dot {
	flex: none;
	width: 8px;
	height: 8px;
	margin-top: 6px;
	border-radius: 50%;
	background: var(--bzf-border);
}

/* Only unread rows get a coloured dot. Colouring every row would make the
 * severity meaningless and the unread state invisible. */
.bzf-notelist__row.is-unread .bzf-notelist__dot { background: var(--bzf-brand-500); }
.bzf-notelist__row--warning.is-unread .bzf-notelist__dot { background: var(--bzf-warning-fg); }
.bzf-notelist__row--critical.is-unread .bzf-notelist__dot { background: var(--bzf-danger-fg); }

.bzf-notelist__main { flex: 1; min-width: 0; }

.bzf-notelist__title { font-size: var(--bzf-text-sm); font-weight: 600; }
.bzf-notelist__row.is-unread .bzf-notelist__title { font-weight: 700; }

.bzf-notelist__body {
	margin-top: 2px;
	font-size: var(--bzf-text-xs);
	line-height: 1.5;
	color: var(--bzf-text-muted);
}

.bzf-notelist__when {
	flex: none;
	font-size: var(--bzf-text-xs);
	color: var(--bzf-text-muted);
	white-space: nowrap;
}

.bzf-shell__content {
	flex: 1;
	min-width: 0;
	width: 100%;
	/*
	 * Past roughly 1500px a dashboard stops reading as a layout and starts
	 * reading as widgets shoved against the edges of a wall: a stat card grows
	 * to 400px to hold six characters, and the eye has to cross the whole
	 * monitor to get from a label to its number. The topbar stays full width on
	 * purpose â it is chrome, and chrome belongs to the window.
	 */
	max-width: 1520px;
	margin-inline: auto;
	padding: var(--bzf-6) var(--bzf-7);
}

/*
 * A screen that wants the whole content area: no page padding, no reading-width
 * cap. The POS asks for this â it is a workspace, not a document, and a 1520px
 * cap on a wide monitor would strand the cart in the middle of the screen.
 * Views set it themselves in render() and drop it again on cleanup.
 */
.bzf-shell__content.is-flush {
	max-width: none;
	margin-inline: 0;
	padding: 0;
}

/* --- Sidebar --- */

/*
 * White, with an orange wash on the active row.
 *
 * It uses its own tokens rather than surface/text so it can be tuned
 * independently of cards and panels â a sidebar that is literally the same
 * colour as every card next to it loses its edge, and this one carries a
 * hairline and its own hover instead.
 *
 * Nothing in here may hardcode a light-on-dark rgba(255,255,255,â¦): that was
 * true while the sidebar was dark green and became invisible the moment it
 * turned white.
 */
.bzf-sidebar {
	display: flex;
	flex-direction: column;
	gap: var(--bzf-2);
	padding: var(--bzf-4) var(--bzf-3);
	background: var(--bzf-sidebar-bg);
	color: var(--bzf-sidebar-text);
	border-inline-end: 1px solid var(--bzf-sidebar-border);
	position: sticky;
	top: 0;
	height: 100vh;
	height: 100dvh;
	overflow-y: auto;
}

.bzf-sidebar__brand {
	display: flex;
	align-items: center;
	gap: var(--bzf-3);
	padding: var(--bzf-2) var(--bzf-2) var(--bzf-4);
	min-width: 0;
}

.bzf-sidebar__mark {
	width: 34px;
	height: 34px;
	flex-shrink: 0;
	border-radius: var(--bzf-radius);
	background: linear-gradient(140deg, var(--bzf-brand-300), var(--bzf-brand-solid));
	color: var(--bzf-on-brand);
	display: grid;
	place-items: center;
	font-size: var(--bzf-text-sm);
	font-weight: 700;
	letter-spacing: 0;
	box-shadow: var(--bzf-shadow-brand);
}

.bzf-sidebar__names { min-width: 0; display: flex; flex-direction: column; }

.bzf-sidebar__shop {
	font-size: var(--bzf-text-base);
	font-weight: 620;
	letter-spacing: var(--bzf-track-snug);
	color: var(--bzf-sidebar-active-fg);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.bzf-sidebar__type {
	font-size: var(--bzf-text-xs);
	color: var(--bzf-sidebar-text-soft);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.bzf-sidebar__tagline {
	margin-top: 2px;
	font-size: var(--bzf-text-xs);
	font-style: italic;
	color: var(--bzf-sidebar-text-soft);
	font-family: var(--bzf-font-heading);
}

.bzf-nav { list-style: none; display: flex; flex-direction: column; gap: 1px; }

.bzf-nav__item {
	position: relative;
	width: 100%;
	display: flex;
	align-items: center;
	gap: var(--bzf-3);
	padding: 8px var(--bzf-3);
	border: 0;
	background: transparent;
	border-radius: var(--bzf-radius);
	color: var(--bzf-sidebar-text);
	font-size: var(--bzf-text-base);
	font-weight: 500;
	cursor: pointer;
	text-align: start;
	transition: background var(--bzf-fast), color var(--bzf-fast);
}

.bzf-nav__item:hover { background: var(--bzf-sidebar-hover-bg); color: var(--bzf-sidebar-active-fg); }

.bzf-nav__item.is-active {
	background: var(--bzf-sidebar-active-bg);
	color: var(--bzf-sidebar-active-fg);
	font-weight: 600;
}

/* A left rule marks the active item â clearer than colour alone, and it
   survives greyscale and colour-vision deficiency. */
.bzf-nav__item.is-active::before {
	content: '';
	position: absolute;
	inset-inline-start: -12px;
	top: 50%;
	transform: translateY(-50%);
	width: 3px;
	height: 18px;
	border-radius: 0 var(--bzf-radius-full) var(--bzf-radius-full) 0;
	background: var(--bzf-brand-solid);
}

.bzf-nav__icon {
	display: inline-flex;
	flex-shrink: 0;
	opacity: .9;
	color: var(--bzf-text-subtle);
	transition: color var(--bzf-fast);
}

/* The active icon takes the brand colour. The left rule already marks the row
   without relying on colour, so this is reinforcement, not the signal. */
.bzf-nav__item.is-active .bzf-nav__icon { color: var(--bzf-brand-600); opacity: 1; }
.bzf-nav__item:hover .bzf-nav__icon { color: var(--bzf-text-muted); }

.bzf-nav__label {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.bzf-sidebar__footer { margin-top: auto; padding-top: var(--bzf-4); }

.bzf-planbadge {
	width: 100%;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-2);
	padding: var(--bzf-3);
	border: 1px solid var(--bzf-sidebar-border);
	border-radius: var(--bzf-radius);
	background: var(--bzf-sidebar-hover-bg);
	color: var(--bzf-sidebar-text);
	cursor: pointer;
	text-align: start;
	transition: border-color var(--bzf-fast), background var(--bzf-fast);
}

.bzf-planbadge:hover { border-color: var(--bzf-brand-200); background: var(--bzf-sidebar-active-bg); }
.bzf-planbadge__name { font-size: var(--bzf-text-sm); font-weight: 600; color: var(--bzf-sidebar-active-fg); }
.bzf-planbadge__hint { font-size: var(--bzf-text-xs); color: var(--bzf-brand-600); font-weight: 550; }

/* --- Topbar --- */

.bzf-topbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-4);
	height: var(--bzf-topbar-h);
	padding: 0 var(--bzf-8);
	background: var(--bzf-surface);
	border-bottom: 1px solid var(--bzf-border);
	position: sticky;
	top: 0;
	z-index: 20;
}

.bzf-topbar__left, .bzf-topbar__right { display: flex; align-items: center; gap: var(--bzf-2); }
.bzf-topbar__left { min-width: 0; }

/* --- Breadcrumb --- */

.bzf-crumbs { display: flex; align-items: center; gap: var(--bzf-2); min-width: 0; }

/*
 * `--bzf-text-muted`, not `--bzf-text-subtle`.
 *
 * This is a button, so `.bizzflow-body button { color: inherit }` used to
 * outrank it and it rendered in near-black. Once the reset lost its specificity
 * the written colour finally applied â and subtle is 3.4:1, which fails AA for
 * text this size. A breadcrumb is navigation, not decoration; subtle is for
 * separators and placeholders.
 */
.bzf-crumbs__link {
	border: 0;
	background: transparent;
	padding: 0;
	font: inherit;
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
	cursor: pointer;
	white-space: nowrap;
}

.bzf-crumbs__link:hover { color: var(--bzf-brand-600); text-decoration: underline; }
.bzf-crumbs__sep { color: var(--bzf-text-subtle); font-size: var(--bzf-text-sm); }

.bzf-crumbs__here {
	font-size: var(--bzf-text-base);
	font-weight: 620;
	letter-spacing: var(--bzf-track-snug);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* The crumb trail is context, not the heading â on a phone the screen name in
   the sidebar and the H1 both already say where you are. */
@media (max-width: 640px) {
	.bzf-crumbs__link, .bzf-crumbs__sep { display: none; }
}

.bzf-switcher { position: relative; display: inline-flex; align-items: center; }

.bzf-switcher__select {
	appearance: none;
	padding: 6px 30px 6px var(--bzf-3);
	border-radius: var(--bzf-radius);
	border: 1px solid var(--bzf-border);
	background: var(--bzf-surface);
	color: var(--bzf-text);
	font-size: var(--bzf-text-sm);
	font-weight: 550;
	cursor: pointer;
}

.bzf-switcher svg {
	position: absolute;
	inset-inline-end: var(--bzf-2);
	pointer-events: none;
	color: var(--bzf-text-subtle);
}

.bzf-account {
	position: relative;
	padding-inline-start: var(--bzf-3);
	margin-inline-start: var(--bzf-1);
	border-inline-start: 1px solid var(--bzf-border);
}

.bzf-account__trigger {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	padding: 4px 6px;
	border: 1px solid transparent;
	border-radius: var(--bzf-radius);
	background: transparent;
	color: var(--bzf-text);
	font: inherit;
	text-align: start;
	cursor: pointer;
	transition: background var(--bzf-fast), border-color var(--bzf-fast);
}

.bzf-account__trigger:hover,
.bzf-account.is-open .bzf-account__trigger {
	background: var(--bzf-surface-2);
	border-color: var(--bzf-border);
}

.bzf-account__trigger svg { color: var(--bzf-text-subtle); flex-shrink: 0; }

/* ==================================================================
   Dropdown menu
   ================================================================== */

.bzf-menu {
	position: absolute;
	top: calc(100% + 6px);
	inset-inline-end: 0;
	z-index: 60;
	min-width: 250px;
	max-width: 320px;
	display: flex;
	flex-direction: column;
	padding: var(--bzf-2);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius-lg);
	background: var(--bzf-surface);
	box-shadow: var(--bzf-shadow-lg);
}

.bzf-menu__heading {
	padding: var(--bzf-2) var(--bzf-3) var(--bzf-1);
	font-size: var(--bzf-text-sm);
	font-weight: 600;
	/* An email address has no spaces to wrap at. */
	overflow-wrap: anywhere;
}

.bzf-menu__section {
	padding: var(--bzf-3) var(--bzf-3) var(--bzf-1);
	font-size: var(--bzf-text-xs);
	font-weight: 620;
	letter-spacing: var(--bzf-track-wide);
	text-transform: uppercase;
	color: var(--bzf-text-subtle);
}

.bzf-menu__separator {
	height: 1px;
	margin: var(--bzf-2) var(--bzf-1);
	background: var(--bzf-border);
}

/*
 * Scoped to .bzf-menu on purpose. app.css styles `.bizzflow-body a`, which at
 * (0,1,1) would out-specify a bare .bzf-menu__item and paint every link in the
 * menu brand orange. Two classes beats one class plus an element.
 */
.bzf-menu .bzf-menu__item {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	width: 100%;
	padding: 7px var(--bzf-3);
	border: 0;
	border-radius: var(--bzf-radius-sm);
	background: transparent;
	color: var(--bzf-text);
	font: inherit;
	font-size: var(--bzf-text-base);
	text-align: start;
	text-decoration: none;
	cursor: pointer;
	transition: background var(--bzf-fast);
}

.bzf-menu .bzf-menu__item:hover { background: var(--bzf-surface-2); }
.bzf-menu .bzf-menu__item.is-active { font-weight: 620; }

.bzf-menu__icon {
	display: grid;
	place-items: center;
	width: 18px;
	flex-shrink: 0;
	color: var(--bzf-text-subtle);
}

.bzf-menu .bzf-menu__item.is-active .bzf-menu__icon { color: var(--bzf-brand-600); }

.bzf-menu__label {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* --- Notification bell --- */

.bzf-bell { border-inline-start: 0; padding-inline-start: 0; margin-inline-start: 0; }
.bzf-bell .bzf-iconbtn { position: relative; }

.bzf-bell__badge {
	position: absolute;
	top: 1px;
	inset-inline-end: 1px;
	min-width: 15px;
	height: 15px;
	padding: 0 3px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-danger);
	color: #fff;
	font-size: 10px;
	font-weight: 700;
	line-height: 15px;
	text-align: center;
}

.bzf-bell__menu { min-width: 300px; max-width: 360px; max-height: 420px; overflow-y: auto; }

.bzf-menu__body { display: flex; flex-direction: column; min-width: 0; gap: 1px; }

/*
 * A notification is two lines, so it opts out of the single-line ellipsis the
 * rest of the menu uses.
 */
.bzf-menu .bzf-note { align-items: flex-start; padding-block: var(--bzf-3); }
.bzf-note__title { font-weight: 550; white-space: normal; }
.bzf-note__body { font-size: var(--bzf-text-sm); color: var(--bzf-text-muted); white-space: normal; }

/* Severity reads on the left edge, so the eye can sort the list without
   reading it. */
.bzf-menu .bzf-note--warning { border-inline-start: 3px solid var(--bzf-warning); }
.bzf-menu .bzf-note--critical { border-inline-start: 3px solid var(--bzf-danger); }

.bzf-account__avatar {
	width: 30px;
	height: 30px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	color: var(--bzf-text-muted);
	display: grid;
	place-items: center;
	font-size: var(--bzf-text-xs);
	font-weight: 650;
	flex-shrink: 0;
}

.bzf-account__details { display: flex; flex-direction: column; line-height: 1.25; }
.bzf-account__name { font-size: var(--bzf-text-sm); font-weight: 550; }
.bzf-account__role { font-size: var(--bzf-text-xs); color: var(--bzf-text-subtle); }

/* ==================================================================
   Page furniture
   ================================================================== */

.bzf-page__head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--bzf-4);
	margin-bottom: var(--bzf-6);
	flex-wrap: wrap;
}

.bzf-page__subtitle { color: var(--bzf-text-muted); margin-top: 2px; font-size: var(--bzf-text-base); }
.bzf-page__actions { display: flex; gap: var(--bzf-2); }

/*
 * The default resting surface.
 *
 * Shadow, no border. A card that has both leans on the border â the outline
 * does all the separating and the shadow is decoration nobody sees, which is
 * how the whole app ended up looking flat. One or the other; here it is the
 * shadow, because a shadow says "in front of" and a border only says "edge".
 */
.bzf-card {
	background: var(--bzf-surface);
	border-radius: var(--bzf-radius-lg);
	padding: var(--bzf-5);
	box-shadow: var(--bzf-shadow-sm);
	margin-bottom: var(--bzf-6);
}

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

.bzf-subhead {
	margin: var(--bzf-6) 0 var(--bzf-2);
	font-size: var(--bzf-text-xs);
	font-weight: 620;
	text-transform: uppercase;
	letter-spacing: var(--bzf-track-wide);
	color: var(--bzf-text-subtle);
}

.bzf-muted { color: var(--bzf-text-muted); }
.bzf-text-success { color: var(--bzf-success-fg); }
.bzf-text-warning { color: var(--bzf-warning-fg); }
.bzf-text-danger  { color: var(--bzf-danger-fg); }

.bzf-link { color: var(--bzf-brand-600); font-weight: 550; }

.bzf-toolbar {
	display: flex;
	gap: var(--bzf-3);
	align-items: center;
	flex-wrap: wrap;
	margin-bottom: var(--bzf-4);
}

/* ==================================================================
   Stats
   ================================================================== */

.bzf-statgrid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
	gap: var(--bzf-5);
	margin-bottom: var(--bzf-6);
}

.bzf-stat {
	position: relative;
	background: var(--bzf-surface);
	border-radius: var(--bzf-radius-lg);
	padding: var(--bzf-4) var(--bzf-5) var(--bzf-5);
	box-shadow: var(--bzf-shadow-sm);
	overflow: hidden;
}

/* The headline number gets a hairline of brand colour; the rest stay neutral
   so the eye lands on the one figure that matters most. */
.bzf-stat--accent::before {
	content: '';
	position: absolute;
	inset-inline-start: 0;
	top: 0;
	bottom: 0;
	width: 3px;
	background: linear-gradient(180deg, var(--bzf-brand-300), var(--bzf-brand-solid));
}

.bzf-stat__head {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: var(--bzf-2);
	margin-bottom: var(--bzf-2);
}

.bzf-stat__label {
	font-size: var(--bzf-text-xs);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: var(--bzf-track-wide);
	color: var(--bzf-text-subtle);
}

/* The icon sits in a brand wash rather than floating grey next to the label â
   it gives the card a fixed anchor point that the eye finds before the number. */
.bzf-stat__icon {
	display: inline-grid;
	place-items: center;
	width: 30px;
	height: 30px;
	flex-shrink: 0;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-brand-50);
	color: var(--bzf-brand-600);
}

.bzf-stat__value {
	font-size: var(--bzf-text-2xl);
	font-weight: 660;
	letter-spacing: var(--bzf-track-tight);
	line-height: 1.15;
}

.bzf-stat__delta {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	margin-top: var(--bzf-2);
	font-size: var(--bzf-text-xs);
	font-weight: 600;
}

.bzf-stat__delta--up { color: var(--bzf-success-fg); }
.bzf-stat__delta--down { color: var(--bzf-danger-fg); }
.bzf-stat__delta--neutral { color: var(--bzf-text-subtle); }

.bzf-stat__hint { margin-top: var(--bzf-1); font-size: var(--bzf-text-xs); color: var(--bzf-text-subtle); }

.bzf-ministat { display: flex; flex-direction: column; gap: 2px; }
.bzf-ministat > span:first-child { font-size: var(--bzf-text-xs); text-transform: uppercase; letter-spacing: var(--bzf-track-wide); }
.bzf-ministat strong { font-size: var(--bzf-text-xl); font-weight: 650; letter-spacing: var(--bzf-track-tight); }

.bzf-dash__columns {
	display: grid;
	/*
	 * Equal halves, which is what the artboard draws.
	 *
	 * This was 1.9fr/1fr until the 2026-08-22 audit measured the Figma frame:
	 * `Latest sales` and `Running out` are both 560 wide inside a 1136 column
	 * with a 16 gap. Both lists are read DOWN, so neither earns the extra
	 * width, and the wide/narrow pairing made Running out too cramped to scan.
	 */
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: var(--bzf-4);
	align-items: start;
}

/*
 * One column when there is no side content.
 *
 * A two-track grid holding a single child still reserves the second track, so
 * a new shop with no low stock and nobody on credit gets a chart taking two
 * thirds of the screen and a third of the screen showing nothing. That reads as
 * a broken page, not a spacious one.
 */
.bzf-dash__columns--single { grid-template-columns: minmax(0, 1fr); }

.bzf-dash__side {
	display: flex;
	flex-direction: column;
	gap: var(--bzf-5);
}

/* --- Getting started checklist --- */

.bzf-checklist { list-style: none; display: flex; flex-direction: column; }

.bzf-checklist__item {
	display: flex;
	align-items: center;
	gap: var(--bzf-3);
	padding: var(--bzf-3) 0;
	border-bottom: 1px solid var(--bzf-border-soft);
}

.bzf-checklist__item:last-child { border-bottom: 0; }

.bzf-checklist__mark {
	display: grid;
	place-items: center;
	width: 26px;
	height: 26px;
	flex-shrink: 0;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	color: var(--bzf-text-subtle);
}

.bzf-checklist__item.is-done .bzf-checklist__mark {
	background: var(--bzf-success-bg);
	color: var(--bzf-success-fg);
}

.bzf-checklist__body { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.bzf-checklist__title { font-weight: 550; }
.bzf-checklist__hint { font-size: var(--bzf-text-sm); color: var(--bzf-text-muted); }

/* Done is context, not the point. It steps back so the eye lands on what is
   still outstanding. */
.bzf-checklist__item.is-done .bzf-checklist__title {
	color: var(--bzf-text-muted);
	text-decoration: line-through;
	font-weight: 500;
}

/*
 * .bzf-card carries its own margin-bottom for the common case of cards stacked
 * down a page. Wherever a container already provides a gap, that margin doubles
 * the spacing and leaves a stray gap under the last card.
 */
.bzf-dash__columns > .bzf-card,
.bzf-dash__side > .bzf-card { margin-bottom: 0; }

/*
 * One column below 1180px — and minmax(0, 1fr), NOT 1fr.
 *
 * `1fr` is shorthand for `minmax(auto, 1fr)`, and an `auto` minimum means
 * min-content: the track refuses to shrink below the widest thing inside it.
 * On a 375px phone that made the Live Ops hour chart size a 540px column and
 * the whole page scroll sideways by 182px. The base rule above already knew
 * this; the override did not.
 */
@media (max-width: 1180px) { .bzf-dash__columns { grid-template-columns: minmax(0, 1fr); } }

.bzf-chart { color: var(--bzf-brand-600); }
.bzf-chart__svg { width: 100%; height: auto; display: block; }

.bzf-chart__legend {
	margin-top: var(--bzf-4);
	padding-top: var(--bzf-3);
	border-top: 1px solid var(--bzf-border-soft);
	font-size: var(--bzf-text-sm);
}

/* ==================================================================
   Tables
   ================================================================== */

.bzf-table-wrap { overflow-x: auto; margin-inline: calc(var(--bzf-5) * -1); padding-inline: var(--bzf-5); }

.bzf-table { width: 100%; border-collapse: collapse; font-size: var(--bzf-text-base); }

/*
 * Compact columns on a wide screen.
 *
 * A table set to 100% width shares the slack equally, so on a 1500px monitor
 * "SKU" and "Stock" end up 300px apart with nothing between them and the eye
 * has to travel to connect a row. Giving the first column all the slack lets
 * every other column shrink to its content â the name stretches, the numbers
 * stay together.
 */
.bzf-table td:first-child,
.bzf-table th:first-child { width: 100%; }

.bzf-table td:not(:first-child),
.bzf-table th:not(:first-child) { white-space: nowrap; width: 1%; }

/*
 * Header row on a mint tint. A business table is read by scanning down a
 * column, and a tinted header is what tells the eye where each column starts
 * without adding a rule between every one of them.
 */
.bzf-table th {
	text-align: start;
	font-weight: 600;
	font-size: var(--bzf-text-xs);
	text-transform: uppercase;
	letter-spacing: var(--bzf-track-wide);
	color: var(--bzf-brand-600);
	background: var(--bzf-brand-50);
	padding: 9px var(--bzf-3);
	border-bottom: 1px solid var(--bzf-border);
	white-space: nowrap;
	position: sticky;
	top: 0;
	z-index: 1;
}

.bzf-table th:first-child { border-start-start-radius: var(--bzf-radius-sm); }
.bzf-table th:last-child { border-start-end-radius: var(--bzf-radius-sm); }

.bzf-table td {
	padding: 11px var(--bzf-3);
	border-bottom: 1px solid var(--bzf-border-soft);
	vertical-align: middle;
}

.bzf-table tbody tr:last-child td { border-bottom: 0; }
.bzf-td--right { text-align: end; }
.bzf-tr--clickable { cursor: pointer; }
.bzf-tr--clickable:hover td { background: var(--bzf-surface-2); }

.bzf-pager {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-4);
	padding-top: var(--bzf-4);
	margin-top: var(--bzf-2);
	border-top: 1px solid var(--bzf-border-soft);
	font-size: var(--bzf-text-sm);
}

/* ==================================================================
   Forms
   ================================================================== */

.bzf-form { display: flex; flex-direction: column; gap: var(--bzf-4); }
.bzf-form__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--bzf-4); }
.bzf-form__row--wrap { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }

.bzf-form__section {
	border-top: 1px solid var(--bzf-border-soft);
	padding-top: var(--bzf-4);
	display: flex;
	flex-direction: column;
	gap: var(--bzf-3);
}

.bzf-form__section h4 {
	font-size: var(--bzf-text-xs);
	font-weight: 620;
	text-transform: uppercase;
	letter-spacing: var(--bzf-track-wide);
	color: var(--bzf-text-subtle);
}

.bzf-form__actions { display: flex; gap: var(--bzf-2); justify-content: flex-end; }

@media (max-width: 640px) { .bzf-form__row { grid-template-columns: 1fr; } }

.bzf-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.bzf-field--inline { flex-direction: row; align-items: center; gap: var(--bzf-2); flex-wrap: wrap; }

.bzf-field__label {
	font-size: var(--bzf-text-sm);
	font-weight: 550;
	color: var(--bzf-text);
}

.bzf-field__hint { font-size: var(--bzf-text-xs); color: var(--bzf-text-subtle); line-height: 1.45; }
.bzf-field__error { font-size: var(--bzf-text-xs); color: var(--bzf-danger-fg); font-weight: 550; }

.bzf-input {
	width: 100%;
	padding: 9px var(--bzf-3);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius);
	background: var(--bzf-surface);
	color: var(--bzf-text);
	font-size: var(--bzf-text-base);
	transition: border-color var(--bzf-fast), box-shadow var(--bzf-fast);
}

.bzf-input::placeholder { color: var(--bzf-text-subtle); }

.bzf-input:focus {
	outline: none;
	border-color: var(--bzf-brand-500);
	box-shadow: var(--bzf-ring);
}

.bzf-input--error { border-color: var(--bzf-danger); }
.bzf-input--error:focus { box-shadow: 0 0 0 3px var(--bzf-danger-bg); }

.bzf-checkbox { width: 16px; height: 16px; accent-color: var(--bzf-brand-600); flex-shrink: 0; }
.bzf-search { max-width: 360px; }

.bzf-chips { display: flex; gap: 6px; flex-wrap: wrap; }

.bzf-chip {
	font-size: var(--bzf-text-sm);
	font-weight: 550;
	padding: 5px var(--bzf-3);
	border-radius: var(--bzf-radius-full);
	border: 1px solid var(--bzf-border);
	background: var(--bzf-surface);
	color: var(--bzf-text-muted);
	cursor: pointer;
	transition: background var(--bzf-fast), border-color var(--bzf-fast), color var(--bzf-fast);
}

.bzf-chip:hover { background: var(--bzf-surface-3); color: var(--bzf-text); }

.bzf-chip.is-active {
	background: var(--bzf-brand-solid);
	border-color: var(--bzf-brand-solid);
	color: var(--bzf-on-brand);
}

/*
 * The count on a filter chip.
 *
 * NO OPACITY. The first version faded it with `opacity: .62`, which measured
 * 2.73:1 against the chip â an AA failure I wrote myself, on the one element
 * whose entire job is to be read at a glance. Opacity is a tempting way to say
 * "secondary" and it is a contrast reduction wearing a costume.
 *
 * It inherits the chip's own colour instead, so it is exactly as readable as
 * the label beside it in both states â muted on white, on-brand on brand â and
 * reads as secondary through size and the gap alone.
 */
.bzf-chip { display: inline-flex; align-items: center; gap: 6px; }

.bzf-chip__count {
	font-size: var(--bzf-text-xs);
	font-weight: 640;
	font-variant-numeric: tabular-nums;
}

/* The brand row sits under the categories and reads as secondary to them â
   a shopkeeper narrows by aisle first and by maker second. */
.bzf-chips--brands { margin-top: 6px; }
.bzf-chips--brands .bzf-chip { font-size: var(--bzf-text-xs); padding: 4px 10px; }

.bzf-badge {
	display: inline-flex;
	align-items: center;
	font-size: var(--bzf-text-xs);
	font-weight: 600;
	padding: 2px 8px;
	border-radius: var(--bzf-radius-sm);
	background: var(--bzf-surface-3);
	color: var(--bzf-text-muted);
}

.bzf-badge--warning { background: var(--bzf-warning-bg); color: var(--bzf-warning-fg); }
.bzf-badge--success { background: var(--bzf-success-bg); color: var(--bzf-success-fg); }
.bzf-badge--danger  { background: var(--bzf-danger-bg);  color: var(--bzf-danger-fg); }

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

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

.bzf-tab {
	font-size: var(--bzf-text-base);
	font-weight: 550;
	padding: var(--bzf-3) var(--bzf-4);
	border: 0;
	background: transparent;
	color: var(--bzf-text-muted);
	cursor: pointer;
	border-bottom: 2px solid transparent;
	margin-bottom: -1px;
	white-space: nowrap;
	transition: color var(--bzf-fast), border-color var(--bzf-fast);
}

.bzf-tab:hover { color: var(--bzf-text); }
.bzf-tab.is-active { color: var(--bzf-brand-600); border-bottom-color: var(--bzf-brand-600); }

/* ==================================================================
   Feedback
   ================================================================== */

.bzf-alert {
	padding: var(--bzf-3) var(--bzf-4);
	border-radius: var(--bzf-radius);
	margin-bottom: var(--bzf-4);
	font-size: var(--bzf-text-base);
	display: flex;
	flex-direction: column;
	gap: var(--bzf-2);
	align-items: flex-start;
}

.bzf-alert--info    { background: var(--bzf-info-bg);    color: var(--bzf-info-fg); }
.bzf-alert--warning { background: var(--bzf-warning-bg); color: var(--bzf-warning-fg); }
.bzf-alert--danger  { background: var(--bzf-danger-bg);  color: var(--bzf-danger-fg); }

.bzf-empty { text-align: center; padding: var(--bzf-12) var(--bzf-4); color: var(--bzf-text-muted); }

.bzf-empty__icon {
	display: inline-grid;
	place-items: center;
	width: 52px;
	height: 52px;
	margin-bottom: var(--bzf-4);
	border-radius: var(--bzf-radius-lg);
	background: var(--bzf-surface-3);
	color: var(--bzf-text-subtle);
}

.bzf-empty h3 { margin-bottom: var(--bzf-2); }
.bzf-empty p { margin-bottom: var(--bzf-4); max-width: 44ch; margin-inline: auto; font-size: var(--bzf-text-base); }

.bzf-skeleton { display: flex; flex-direction: column; gap: var(--bzf-3); padding: var(--bzf-3) 0; }

.bzf-skeleton__line {
	height: 13px;
	border-radius: var(--bzf-radius-sm);
	background: linear-gradient(90deg, var(--bzf-surface-3) 25%, var(--bzf-border-soft) 37%, var(--bzf-surface-3) 63%);
	background-size: 400% 100%;
	animation: bzf-shimmer 1.4s var(--bzf-ease) infinite;
}

.bzf-skeleton__line:nth-child(2) { width: 84%; }
.bzf-skeleton__line:nth-child(3) { width: 62%; }

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

.bzf-toasts {
	position: fixed;
	bottom: var(--bzf-5);
	inset-inline-end: var(--bzf-5);
	display: flex;
	flex-direction: column;
	gap: var(--bzf-2);
	z-index: 100000;
}

.bzf-toast {
	background: var(--bzf-surface);
	border: 1px solid var(--bzf-border);
	border-inline-start: 3px solid var(--bzf-info);
	border-radius: var(--bzf-radius);
	padding: var(--bzf-3) var(--bzf-4);
	box-shadow: var(--bzf-shadow-lg);
	font-size: var(--bzf-text-base);
	max-width: 400px;
	animation: bzf-toast-in 200ms var(--bzf-ease);
}

.bzf-toast--success { border-inline-start-color: var(--bzf-success); }
.bzf-toast--warning { border-inline-start-color: var(--bzf-warning); }
.bzf-toast--danger  { border-inline-start-color: var(--bzf-danger); }
.bzf-toast--out { opacity: 0; transform: translateY(6px); transition: 180ms var(--bzf-ease); }

@keyframes bzf-toast-in {
	from { opacity: 0; transform: translateY(10px) scale(.98); }
	to   { opacity: 1; transform: none; }
}

/* ==================================================================
   Modal
   ================================================================== */

.bzf-modal-overlay {
	position: fixed;
	inset: 0;
	background: var(--bzf-overlay);
	backdrop-filter: blur(2px);
	display: grid;
	place-items: center;
	padding: var(--bzf-4);
	z-index: 99999;
	animation: bzf-fade 140ms var(--bzf-ease);
}

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

.bzf-modal {
	background: var(--bzf-surface);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius-xl);
	/* Top rung: a modal sits over a scrim, above everything else on the page. */
	box-shadow: var(--bzf-shadow-xl);
	width: min(520px, 100%);
	max-height: 88vh;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	animation: bzf-modal-in 200ms var(--bzf-ease);
}

@keyframes bzf-modal-in {
	from { opacity: 0; transform: translateY(12px) scale(.985); }
	to   { opacity: 1; transform: none; }
}

.bzf-modal--wide { width: min(780px, 100%); }

.bzf-modal__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-3);
	padding: var(--bzf-4) var(--bzf-5);
	border-bottom: 1px solid var(--bzf-border-soft);
}

.bzf-modal__body { padding: var(--bzf-5); overflow-y: auto; }

.bzf-modal__foot {
	display: flex;
	gap: var(--bzf-2);
	justify-content: flex-end;
	padding: var(--bzf-4) var(--bzf-5);
	border-top: 1px solid var(--bzf-border-soft);
	background: var(--bzf-surface-2);
}

.bzf-confirm__impact {
	margin-top: var(--bzf-3);
	padding: var(--bzf-3);
	background: var(--bzf-warning-bg);
	color: var(--bzf-warning-fg);
	border-radius: var(--bzf-radius);
	font-size: var(--bzf-text-sm);
	font-weight: 550;
}

.bzf-picker { display: flex; flex-direction: column; gap: var(--bzf-3); }
.bzf-picker__list { display: flex; flex-direction: column; gap: 1px; max-height: 340px; overflow-y: auto; }

.bzf-picker__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-3);
	padding: var(--bzf-3);
	border: 0;
	background: transparent;
	border-radius: var(--bzf-radius);
	text-align: start;
	cursor: pointer;
	font-size: var(--bzf-text-base);
}

.bzf-picker__row:hover { background: var(--bzf-surface-3); }

.bzf-detail__stats { display: flex; gap: var(--bzf-10); margin-bottom: var(--bzf-6); flex-wrap: wrap; }
.bzf-detail__meta { display: flex; flex-direction: column; gap: var(--bzf-2); margin-bottom: var(--bzf-6); }
.bzf-detail__row { display: flex; gap: var(--bzf-4); font-size: var(--bzf-text-base); }
.bzf-detail__row > span:first-child { min-width: 92px; color: var(--bzf-text-subtle); }

/* ==================================================================
   Point of sale
   ================================================================== */

/*
 * The POS sits inside the shell like every other screen â sidebar on the left,
 * top bar above. It used to render full-bleed, which made the one screen a shop
 * lives in the only screen that did not look like the product, and left the
 * cashier with no way out of the till.
 *
 * What it does keep is the whole content area: .is-flush strips the page
 * padding, and the height is the viewport minus the top bar, so the catalogue
 * and the cart scroll independently instead of the document scrolling as one.
 */
.bzf-pos {
	display: grid;
	grid-template-rows: auto minmax(0, 1fr);
	height: calc(100vh - var(--bzf-topbar-h));
	height: calc(100dvh - var(--bzf-topbar-h));
	background: var(--bzf-canvas);
}

/* The POS's own page header: title on the left, till controls on the right. */
.bzf-posbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-4);
	min-height: 60px;
	padding: var(--bzf-3) var(--bzf-5);
	background: var(--bzf-surface);
	border-bottom: 1px solid var(--bzf-border);
}

.bzf-posbar__left { display: flex; align-items: center; gap: var(--bzf-3); min-width: 0; }
.bzf-posbar__names { min-width: 0; display: flex; flex-direction: column; }

/* Two classes, because .bizzflow-body h1 would otherwise win on specificity and
   set this to the full 26px page-heading size â too tall for a bar. */
.bzf-posbar .bzf-posbar__title {
	margin: 0;
	font-family: var(--bzf-font-heading);
	font-size: var(--bzf-text-xl);
	font-weight: 600;
	letter-spacing: var(--bzf-track-tight);
	line-height: 1.2;
}

.bzf-posbar__shop {
	font-size: var(--bzf-text-xs);
	color: var(--bzf-text-subtle);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.bzf-posbar__actions { display: flex; align-items: center; gap: var(--bzf-2); flex-shrink: 0; }

/* The function-key legend. Hidden on narrow screens, where there is no
   keyboard to press them on anyway. */
.bzf-posbar__keys {
	display: flex;
	gap: var(--bzf-3);
	font-size: var(--bzf-text-xs);
	color: var(--bzf-text-subtle);
}

.bzf-posbar__keys kbd {
	font-family: var(--bzf-font-mono);
	font-size: 10.5px;
	padding: 1px 5px;
	border: 1px solid var(--bzf-border);
	border-bottom-width: 2px;
	border-radius: var(--bzf-radius-xs);
	background: var(--bzf-surface-2);
	color: var(--bzf-text-muted);
	margin-inline-end: 4px;
}

@media (max-width: 1100px) { .bzf-posbar__keys { display: none; } }

.bzf-pos__body {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 440px;
	min-height: 0;
}

/*
 * Narrow screens: the two panes stack and the page itself scrolls.
 *
 * The desktop layout pins the viewport and scrolls the grid and the cart
 * independently. Stacked, that gives two short scrollers inside a page that
 * cannot scroll â the cart footer ends up unreachable. So the inner overflow is
 * released and the document takes over.
 */
@media (max-width: 960px) {
	.bzf-pos { height: auto; }
	.bzf-pos__body { grid-template-columns: 1fr; }
	.bzf-pos__catalogue { overflow: visible; }
	.bzf-pos__results { overflow: visible; }
	.bzf-pos__cart { overflow: visible; border-inline-start: 0; border-top: 1px solid var(--bzf-border); }
	.bzf-cart__lines { overflow: visible; }
	.bzf-posbar { position: sticky; top: 0; z-index: 20; }
}

.bzf-pos__catalogue { display: flex; flex-direction: column; min-width: 0; overflow: hidden; }

.bzf-pos__searchbar {
	padding: var(--bzf-4) var(--bzf-5);
	background: var(--bzf-surface);
	border-bottom: 1px solid var(--bzf-border);
}

/* The magnifier sits inside the field rather than beside it, so the input keeps
   the full width of the bar. */
.bzf-pos__searchwrap { position: relative; display: flex; align-items: center; }

.bzf-pos__searchwrap svg {
	position: absolute;
	inset-inline-start: var(--bzf-3);
	color: var(--bzf-text-subtle);
	pointer-events: none;
}

.bzf-pos__search {
	width: 100%;
	padding: 11px var(--bzf-4) 11px 40px;
	font-size: var(--bzf-text-md);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-canvas);
	color: var(--bzf-text);
	transition: border-color var(--bzf-fast), box-shadow var(--bzf-fast), background var(--bzf-fast);
}

.bizzflow-body[dir="rtl"] .bzf-pos__search { padding: 11px 40px 11px var(--bzf-4); }
.bzf-pos__search:focus { background: var(--bzf-surface); }

.bzf-pos__search:focus { outline: none; border-color: var(--bzf-brand-500); box-shadow: var(--bzf-ring); }

/*
 * Category chips. Horizontally scrollable rather than wrapping: a shop with
 * fifteen categories would otherwise push the product grid halfway down the
 * screen, and the grid is what the cashier is actually looking at.
 */
/*
 * Two stacked rows since 1.27.0 â categories, then brands.
 *
 * A COLUMN, and the scrolling moved down onto each row. This used to be one
 * scrolling flex row of chips; leaving it that way put the brand row *beside*
 * the category row instead of under it, and both then scrolled as one strip.
 */
.bzf-pos__filters {
	display: flex;
	flex-direction: column;
	gap: 6px;
	margin-top: var(--bzf-3);
}

.bzf-pos__filters:empty { display: none; }

/* Each row scrolls on its own rather than wrapping: a second line of chips
   would push the product grid down, and the grid is the screen. */
.bzf-pos__filters .bzf-chips {
	flex-wrap: nowrap;
	overflow-x: auto;
	padding-bottom: 2px;
	scrollbar-width: thin;
	gap: var(--bzf-2);
}

/* Slightly larger than the chips elsewhere in the app: these are the one
   control on this screen that gets tapped with a wet thumb. */
.bzf-pos__filters .bzf-chip {
	flex-shrink: 0;
	padding: 7px var(--bzf-4);
	font-size: var(--bzf-text-base);
}

/* The brand row stays a size down even here â see .bzf-chips--brands. */
.bzf-pos__filters .bzf-chips--brands .bzf-chip {
	padding: 5px var(--bzf-3);
	font-size: var(--bzf-text-sm);
}

.bzf-pos__results { flex: 1; overflow-y: auto; padding: var(--bzf-5); }

/* ------------------------------------------------------------------
   Product thumbnail

   One component, two sizes, used by both the grid and the cart.

   The photo is painted as a background-image rather than an <img>: it lets the
   crop be handled by object-fit-equivalent CSS on a fixed circle, and it keeps
   the "no <img> anywhere" rule intact â that rule exists so icons never become
   image files, and a shopkeeper's product photo is not an icon.

   Today every product falls back to the initial, because uploading a photo is
   the next piece of work. Written photo-first so that lands as data rather than
   as another redesign.
   ------------------------------------------------------------------ */
.bzf-thumb {
	width: 40px;
	height: 40px;
	flex-shrink: 0;
	border-radius: var(--bzf-radius-full);
	border: 1px solid var(--bzf-border-soft);
	background: var(--bzf-brand-50);
	background-size: cover;
	background-position: center;
	background-repeat: no-repeat;
	color: var(--bzf-brand-600);
	display: grid;
	place-items: center;
	font-size: var(--bzf-text-sm);
	font-weight: 640;
	text-transform: uppercase;
	overflow: hidden;
	user-select: none;
}

/* When a photo is set the wash behind it would rim the circle orange. */
.bzf-thumb.has-photo { background-color: var(--bzf-surface-3); color: transparent; }

.bzf-thumb--lg { width: 68px; height: 68px; font-size: var(--bzf-text-xl); }

/* Bigger than a mouse target needs, because half of these are tapped on a
   touchscreen by someone who is also holding a bag. */
.bzf-pos__grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
	gap: var(--bzf-4);
}

.bzf-prodcard {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--bzf-2);
	padding: var(--bzf-4) var(--bzf-3) var(--bzf-3);
	background: var(--bzf-surface);
	border: 0;
	border-radius: var(--bzf-radius-lg);
	cursor: pointer;
	text-align: center;
	box-shadow: var(--bzf-shadow-sm);
	transition: transform var(--bzf-fast), box-shadow var(--bzf-fast);
}

/* Rises and grows very slightly under the cursor. 1.02 is deliberate: enough
   that the card under the hand is unmistakable, small enough that a grid of
   them does not appear to breathe as the mouse crosses it. */
.bzf-prodcard:hover {
	transform: translateY(-3px) scale(1.02);
	box-shadow: var(--bzf-shadow-md);
}

.bzf-prodcard:hover .bzf-thumb { border-color: var(--bzf-brand-200); }

/* Presses back down. Without this the card stays lifted while being tapped,
   which on a touchscreen reads as "nothing happened". */
.bzf-prodcard:active { transform: translateY(0) scale(.99); box-shadow: var(--bzf-shadow-xs); }
.bzf-prodcard.is-out { opacity: .55; }

.bzf-prodcard__name {
	font-size: var(--bzf-text-base);
	font-weight: 560;
	line-height: 1.35;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.bzf-prodcard__price {
	font-size: var(--bzf-text-md);
	font-weight: 680;
	margin-top: auto;
	color: var(--bzf-brand-600);
	letter-spacing: var(--bzf-track-snug);
}

.bzf-prodcard__stock {
	font-size: var(--bzf-text-xs);
	color: var(--bzf-text-subtle);
	padding: 2px var(--bzf-2);
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-2);
}

.bzf-prodcard__stock.is-out {
	color: var(--bzf-danger-fg);
	background: var(--bzf-danger-bg);
	font-weight: 550;
}

/* --- Cart --- */

.bzf-pos__cart {
	background: var(--bzf-surface);
	border-inline-start: 1px solid var(--bzf-border);
	display: flex;
	flex-direction: column;
	overflow: hidden;
}

.bzf-cart__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-3);
	padding: var(--bzf-4) var(--bzf-5) var(--bzf-3);
}

.bzf-cart__heading { font-size: var(--bzf-text-md); font-weight: 620; letter-spacing: var(--bzf-track-snug); }

/* Reads "3 items", not an invoice number. The real number is assigned by the
   server when the sale is accepted, and inventing one here would put a figure
   on screen that no receipt will ever match. */
.bzf-cart__count {
	font-size: var(--bzf-text-xs);
	font-weight: 620;
	letter-spacing: var(--bzf-track-wide);
	text-transform: uppercase;
	color: var(--bzf-brand-600);
	background: var(--bzf-brand-50);
	padding: 4px var(--bzf-3);
	border-radius: var(--bzf-radius-full);
	white-space: nowrap;
}

.bzf-cart__customer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-3);
	margin: 0 var(--bzf-4) var(--bzf-2);
	padding: var(--bzf-3) var(--bzf-3) var(--bzf-3) var(--bzf-4);
	border: 1px solid var(--bzf-border-soft);
	border-radius: var(--bzf-radius-lg);
	background: var(--bzf-surface-2);
}

.bzf-cart__customer .bzf-muted {
	display: block;
	font-size: var(--bzf-text-xs);
	text-transform: uppercase;
	letter-spacing: var(--bzf-track-wide);
}

.bzf-cart__customer strong { font-size: var(--bzf-text-base); font-weight: 600; }

.bzf-cart__lines { flex: 1; min-height: 0; overflow-y: auto; padding: var(--bzf-1) var(--bzf-3) var(--bzf-3); }

/* Centres the "Cart is empty" state in the space it is already filling, rather
   than pinning it to the top with the totals stranded below. */
.bzf-cart__lines.is-empty { display: grid; place-items: center; }

.bzf-cartline {
	display: flex;
	align-items: flex-start;
	gap: var(--bzf-3);
	padding: var(--bzf-3);
	border-radius: var(--bzf-radius-lg);
	border: 1px solid transparent;
	transition: background var(--bzf-fast), border-color var(--bzf-fast);
}

.bzf-cartline:hover { background: var(--bzf-surface-2); }

.bzf-cartline.is-warning {
	background: var(--bzf-warning-bg);
	border-color: var(--bzf-warning);
}

.bzf-cartline__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }

.bzf-cartline__top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--bzf-2); }

.bzf-cartline__name {
	font-size: var(--bzf-text-base);
	font-weight: 560;
	line-height: 1.35;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	min-width: 0;
}

.bzf-cartline__meta { font-size: var(--bzf-text-xs); color: var(--bzf-text-subtle); }
.bzf-cartline__warn { font-size: var(--bzf-text-xs); color: var(--bzf-warning-fg); font-weight: 550; }

.bzf-cartline__bottom {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-2);
	margin-top: 2px;
}

.bzf-cartline__total {
	font-weight: 660;
	font-size: var(--bzf-text-md);
	text-align: end;
}

/*
 * Per-line note.
 *
 * "No onions", "gift wrap", "size exchanged". The column has existed on
 * sale_items since the first schema and the sale engine already stores it â
 * there was simply nowhere to type one, so a documented field was dead weight.
 */
.bzf-cartline__note {
	align-self: flex-start;
	border: 0;
	background: transparent;
	padding: 0;
	font-size: var(--bzf-text-xs);
	font-weight: 550;
	color: var(--bzf-brand-600);
	cursor: pointer;
	text-align: start;
}

.bzf-cartline__note:hover { text-decoration: underline; }

.bzf-cartline__noted {
	font-size: var(--bzf-text-xs);
	color: var(--bzf-text-muted);
	font-style: italic;
	overflow-wrap: anywhere;
}

.bzf-cartline__remove {
	flex-shrink: 0;
	border: 0;
	background: transparent;
	color: var(--bzf-text-subtle);
	cursor: pointer;
	padding: 2px 4px;
	border-radius: var(--bzf-radius-sm);
	font-size: var(--bzf-text-xs);
	line-height: 1;
}

.bzf-cartline__remove:hover { color: var(--bzf-danger-fg); background: var(--bzf-danger-bg); }

/* One pill holding â qty +, rather than three separate controls. It reads as a
   single quantity widget and gives the two buttons a 28px tap target without
   three borders fighting each other. */
.bzf-stepper {
	display: inline-flex;
	align-items: center;
	gap: 2px;
	padding: 2px;
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface);
}

.bzf-step {
	width: 28px;
	height: 28px;
	border: 0;
	background: transparent;
	border-radius: var(--bzf-radius-full);
	cursor: pointer;
	font-size: var(--bzf-text-md);
	line-height: 1;
	color: var(--bzf-text-muted);
	transition: background var(--bzf-fast), color var(--bzf-fast);
}

.bzf-step:hover { background: var(--bzf-brand-50); color: var(--bzf-brand-600); }

.bzf-qty {
	width: 40px;
	text-align: center;
	font-variant-numeric: tabular-nums;
	font-weight: 620;
	padding: 4px 0;
	border: 0;
	background: transparent;
	color: var(--bzf-text);
	-moz-appearance: textfield;
}

.bzf-qty:focus { outline: none; }

.bzf-qty::-webkit-outer-spin-button,
.bzf-qty::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.bzf-cart__foot {
	padding: var(--bzf-4) var(--bzf-5) var(--bzf-5);
	border-top: 1px solid var(--bzf-border);
	background: var(--bzf-surface-2);
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.bzf-cart__row { display: flex; justify-content: space-between; font-size: var(--bzf-text-base); }
.bzf-cart__row--discount .bzf-num { color: var(--bzf-success-fg); font-weight: 550; }

.bzf-cart__total {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	padding-top: var(--bzf-3);
	margin-top: var(--bzf-2);
	border-top: 1px dashed var(--bzf-border-strong);
}

.bzf-cart__total > span { font-size: var(--bzf-text-base); font-weight: 550; }

.bzf-cart__total strong {
	font-size: var(--bzf-text-3xl);
	font-weight: 700;
	letter-spacing: var(--bzf-track-tight);
	color: var(--bzf-brand-600);
}

.bzf-cart__actions {
	display: grid;
	grid-template-columns: 1fr 1.4fr;
	gap: var(--bzf-2);
	margin-top: var(--bzf-3);
}

.bzf-cart__actions .bzf-btn { padding: 13px; justify-content: center; }
.bzf-cart__actions .bzf-btn--primary { font-size: var(--bzf-text-md); font-weight: 620; }

/* Why the Charge button is off. Sits under the actions so the eye lands on it
   immediately after finding the button disabled. */
.bzf-cart__blocked {
	margin-top: var(--bzf-3);
	padding: var(--bzf-3);
	border-radius: var(--bzf-radius-sm);
	background: var(--bzf-danger-bg);
	color: var(--bzf-danger-fg);
	font-size: var(--bzf-text-sm);
	line-height: 1.5;
}

.bzf-cart__clear {
	border: 0;
	background: transparent;
	color: var(--bzf-text-subtle);
	font-size: var(--bzf-text-xs);
	cursor: pointer;
	padding: var(--bzf-1);
	align-self: center;
}

.bzf-cart__clear:hover { color: var(--bzf-danger-fg); }

/* --- Checkout --- */

.bzf-checkout { display: flex; flex-direction: column; gap: var(--bzf-5); }
.bzf-checkout__total { display: flex; justify-content: space-between; align-items: baseline; }
.bzf-checkout__total strong { font-size: var(--bzf-text-3xl); font-weight: 700; letter-spacing: var(--bzf-track-tight); }
.bzf-checkout__change { min-height: 22px; font-size: var(--bzf-text-base); }

.bzf-methods { display: grid; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); gap: var(--bzf-2); }

.bzf-method {
	padding: var(--bzf-3);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius);
	background: var(--bzf-surface);
	color: var(--bzf-text-muted);
	font-size: var(--bzf-text-sm);
	font-weight: 550;
	cursor: pointer;
	transition: border-color var(--bzf-fast), background var(--bzf-fast), color var(--bzf-fast);
}

.bzf-method:hover { border-color: var(--bzf-border-strong); color: var(--bzf-text); }

.bzf-method.is-selected {
	border-color: var(--bzf-brand-600);
	background: var(--bzf-brand-50);
	color: var(--bzf-brand-700);
	font-weight: 620;
}

/* ==================================================================
   Business health score
   ==================================================================

   The number and the word, together. A bare 62 means nothing without a
   scale, and the colour alone is not enough â it has to survive being
   read by someone who cannot tell the colours apart.
   ================================================================== */

.bzf-health {
	display: flex;
	align-items: baseline;
	gap: var(--bzf-3);
	margin-bottom: var(--bzf-4);
}

.bzf-health__score { display: flex; align-items: baseline; gap: 2px; }
.bzf-health__score strong { font-size: var(--bzf-text-3xl); font-weight: 700; }

.bzf-health__band {
	padding: 3px 10px;
	border-radius: var(--bzf-radius-full);
	font-size: var(--bzf-text-sm);
	font-weight: 600;
}

.bzf-health--good .bzf-health__band { background: var(--bzf-success-bg); color: var(--bzf-success-fg); }
.bzf-health--fair .bzf-health__band { background: var(--bzf-warning-bg); color: var(--bzf-warning-fg); }
.bzf-health--poor .bzf-health__band { background: var(--bzf-danger-bg);  color: var(--bzf-danger-fg); }

/* ==================================================================
   Idle auto-logout warning
   ==================================================================

   Deliberately a full-screen block, not a toast. This is the one message
   in the product where ignoring it loses work, and a toast in the corner
   of a till nobody is standing at would be seen by no one.
   ================================================================== */

.bzf-idle {
	position: fixed;
	inset: 0;
	z-index: 100;
	display: grid;
	place-items: center;
	padding: var(--bzf-4);
	background: rgba(20, 16, 12, .58);
	backdrop-filter: blur(2px);
}

.bzf-idle__box {
	width: min(420px, 100%);
	padding: var(--bzf-6);
	border-radius: var(--bzf-radius-lg);
	background: var(--bzf-surface);
	box-shadow: var(--bzf-shadow-xl);
	text-align: center;
}

.bzf-idle__count {
	margin: var(--bzf-3) 0;
	font-size: var(--bzf-text-lg);
	font-variant-numeric: tabular-nums;
}

.bzf-idle__actions {
	display: flex;
	gap: var(--bzf-2);
	justify-content: center;
	margin-top: var(--bzf-4);
}

/* ==================================================================
   Support chat â the shop's panel
   ==================================================================

   An overlay anchored under the top bar rather than a route, so it can be
   opened from the till without navigating away mid-sale. Fixed height with
   its own scroll: the log grows, and the composer has to stay reachable
   without scrolling the page behind it.
   ================================================================== */

.bzf-chatpanel {
	position: fixed;
	/*
	 * NO physical `right` here — see the 1.49.1 block at the end of this file,
	 * which sets `inset-inline-end`. Leaving `right: 16px` in place did not just
	 * lose harmlessly: in RTL the logical property resolves to `left`, so both
	 * edges ended up set with a width, and an over-constrained box in a
	 * right-to-left container drops `left`. The Urdu build kept the panel on the
	 * right, away from the button that opened it.
	 */
	top: 64px;
	z-index: 60;
	width: min(380px, calc(100vw - var(--bzf-8)));
	max-height: min(560px, calc(100vh - 96px));
	display: flex;
	flex-direction: column;
	background: var(--bzf-surface);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius-lg);
	box-shadow: var(--bzf-shadow-xl);
	overflow: hidden;
}

.bzf-chatpanel__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-3);
	padding: var(--bzf-3) var(--bzf-4);
	border-bottom: 1px solid var(--bzf-border-soft);
	background: var(--bzf-surface-2);
}

.bzf-chatpanel__body {
	display: flex;
	flex-direction: column;
	gap: var(--bzf-3);
	padding: var(--bzf-4);
	overflow-y: auto;
	min-height: 0;
}

.bzf-chatpanel__threadhead { display: flex; align-items: center; gap: var(--bzf-2); }
.bzf-chatpanel__actions { display: flex; gap: var(--bzf-2); justify-content: flex-end; }

.bzf-threadlist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }

.bzf-threadlist__row {
	display: flex;
	align-items: center;
	gap: var(--bzf-3);
	padding: var(--bzf-3);
	border-radius: var(--bzf-radius);
	cursor: pointer;
}

.bzf-threadlist__row:hover { background: var(--bzf-surface-2); }
.bzf-threadlist__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.bzf-threadlist__subject { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The log scrolls; the composer under it does not move. */
.bzf-chatlog {
	display: flex;
	flex-direction: column;
	gap: var(--bzf-3);
	overflow-y: auto;
	min-height: 160px;
	max-height: 320px;
	padding-right: 2px;
}

.bzf-bubble { max-width: 85%; display: flex; flex-direction: column; gap: 3px; }

.bzf-bubble__body {
	padding: var(--bzf-2) var(--bzf-3);
	border-radius: var(--bzf-radius-lg);
	font-size: var(--bzf-text-base);
	line-height: 1.5;
	/* A shopkeeper describing a problem pastes an error message. Without
	   these two a single long token stretches the panel off the screen, and
	   the line breaks they typed disappear. */
	overflow-wrap: anywhere;
	white-space: pre-wrap;
}

.bzf-bubble__meta {
	display: flex;
	gap: var(--bzf-2);
	font-size: var(--bzf-text-xs);
	color: var(--bzf-text-muted);
}

/* The shop's own messages are brand-filled and right-aligned; the operator's
   are a neutral surface on the left. Left and right is the reading cue people
   already know from every phone they own. */
.bzf-bubble--mine { align-self: flex-end; align-items: flex-end; }
.bzf-bubble--mine .bzf-bubble__body { background: var(--bzf-brand-solid); color: var(--bzf-on-brand); }

.bzf-bubble--theirs { align-self: flex-start; }
.bzf-bubble--theirs .bzf-bubble__body { background: var(--bzf-surface-2); color: var(--bzf-text); }

.bzf-chatcompose {
	display: flex;
	gap: var(--bzf-2);
	align-items: flex-end;
	padding-top: var(--bzf-3);
	border-top: 1px solid var(--bzf-border-soft);
}

.bzf-chatcompose .bzf-input { resize: none; }

@media (max-width: 640px) {
	/* Full width on a phone. A 380px panel on a 360px screen is a panel with
	   its own horizontal scrollbar. */
	.bzf-chatpanel {
		right: var(--bzf-2);
		left: var(--bzf-2);
		width: auto;
		max-height: calc(100vh - 120px);
	}
}

/* ==================================================================
   Ranked bars â Analytics, Live Ops
   ==================================================================

   A comparison drawn as rows rather than as a chart. The question these
   answer is "which is biggest and by how much", and a horizontal bar with
   the label on it answers that without an axis to decode.

   Widths are set inline as a percentage of the largest value, so the
   shape is computed where the data is and CSS only has to draw it.
   ================================================================== */

.bzf-bars {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--bzf-3);
}

.bzf-bars__row { display: flex; flex-direction: column; gap: 5px; }

.bzf-bars__label {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: var(--bzf-3);
	font-size: var(--bzf-text-base);
}

/* The name gives way, the number never does â a truncated figure is a
   wrong figure, while a truncated product name is still recognisable. */
.bzf-bars__label > span:first-child {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.bzf-bars__value { flex-shrink: 0; font-weight: 600; }

.bzf-bars__track {
	height: 7px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	overflow: hidden;
}

.bzf-bars__fill {
	height: 100%;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-brand-solid);
	transition: width var(--bzf-base);
}

/* Expenses are not a league table to win, so they are drawn in neutral
   rather than in the brand colour the sales rankings use. */
.bzf-bars__fill--muted { background: var(--bzf-border-strong); }

.bzf-bars__meta {
	display: flex;
	justify-content: space-between;
	gap: var(--bzf-3);
	font-size: var(--bzf-text-xs);
}

/* ==================================================================
   Live Ops
   ================================================================== */

/*
 * The connection pill.
 *
 * It exists to be believed, so it has exactly two states and says which one
 * it is in words as well as colour: "Live" with a time, or "Reconnecting".
 * There is no third state where numbers are shown without a claim about
 * whether they are current.
 */
.bzf-livepill {
	display: inline-flex;
	align-items: center;
	gap: var(--bzf-2);
	padding: 5px 11px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-2);
	border: 1px solid var(--bzf-border);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

.bzf-livepill__dot {
	width: 7px;
	height: 7px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-text-subtle);
	flex-shrink: 0;
}

.bzf-livepill--live .bzf-livepill__dot { background: var(--bzf-success); }
.bzf-livepill--stale .bzf-livepill__dot { background: var(--bzf-warning); }

.bzf-livepill--stale {
	background: var(--bzf-warning-bg);
	border-color: var(--bzf-warning);
	color: var(--bzf-warning-fg);
}

/* The pulse stops when the connection does â an animated dot on a stale
   pill is the interface insisting everything is fine. */
.bzf-livepill--live .bzf-livepill__dot { animation: bzf-livepulse 2.4s ease-in-out infinite; }

@keyframes bzf-livepulse {
	0%, 100% { opacity: 1; }
	50%      { opacity: .35; }
}

@media (prefers-reduced-motion: reduce) {
	.bzf-livepill--live .bzf-livepill__dot { animation: none; }
}

/*
 * Today by the hour.
 *
 * Bars, because hours are buckets: a line drawn between 11am and 12pm
 * implies a reading at 11:30 that was never taken.
 */
.bzf-hours {
	display: flex;
	align-items: flex-end;
	gap: 3px;
	height: 132px;
	padding-top: var(--bzf-2);
}

.bzf-hours__col {
	flex: 1 1 0;
	min-width: 0;
	height: 100%;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	align-items: center;
	gap: 4px;
}

.bzf-hours__bar {
	width: 100%;
	min-height: 2px;
	border-radius: var(--bzf-radius-xs) var(--bzf-radius-xs) 0 0;
	background: var(--bzf-surface-3);
}

/* Only the busiest hour is coloured. Colouring them all would say nothing;
   colouring one says which hour paid for the day. */
.bzf-hours__col.is-peak .bzf-hours__bar { background: var(--bzf-brand-solid); }

.bzf-hours__tick {
	font-size: var(--bzf-text-xs);
	color: var(--bzf-text-muted);
	font-variant-numeric: tabular-nums;
}

/* Below this width the hour labels collide into an unreadable smear, and a
   bar chart with no axis still shows the shape of the day. */
@media (max-width: 720px) {
	.bzf-hours__tick { display: none; }
}

/* ==================================================================
   Small utilities
   ================================================================== */

.bzf-small { font-size: var(--bzf-text-xs); }

/* Timestamps and quantities wrap into nonsense â "17 Aug" over "2026" reads
   as two different dates at a glance. */
.bzf-nowrap { white-space: nowrap; }

/* ==================================================================
   Receipt
   ================================================================== */

/*
 * The printed slip.
 *
 * DESIGNED FOR A THERMAL HEAD, not for a screen that happens to print. The
 * printer has one colour and one temperature, so: no greys, no hairlines, no
 * background fills, and separators as dashes rather than 1px rules — a 1px
 * light-grey border is what disappears entirely on cheap paper.
 *
 * Two widths, because both are on counters in Pakistan: 58mm gives about 32
 * characters, 80mm about 48. The 58mm rules drop the price column, since four
 * columns in 32 characters wraps every line and the slip doubles in length.
 */

.bzf-receipt {
	font-family: var(--bzf-font-mono);
	font-size: 12px;
	line-height: 1.45;
	color: #000;
	max-width: 302px;   /* 80mm printable area */
	margin-inline: auto;
}

.bzf-receipt--58 { max-width: 210px; font-size: 11px; }

/* --- head --- */

.bzf-receipt__head { text-align: center; margin-bottom: var(--bzf-3); }

/*
 * Painted, never an <img> — the same rule product photos follow. Height is
 * capped rather than set so a wide logo and a tall one both fit.
 */
.bzf-receipt__logo {
	width: 100%;
	height: 46px;
	margin-bottom: 6px;
	background-repeat: no-repeat;
	background-position: center;
	background-size: contain;
}

.bzf-receipt__shop {
	display: block;
	font-family: var(--bzf-font);
	font-size: 15px;
	font-weight: 700;
	letter-spacing: .01em;
}

.bzf-receipt__meta { font-size: 11px; }

/* --- invoice strip --- */

.bzf-receipt__strip {
	margin-block: var(--bzf-2);
	padding-block: 6px;
	border-block: 1px dashed #000;
}

.bzf-receipt__row {
	display: flex;
	justify-content: space-between;
	gap: var(--bzf-3);
}

.bzf-receipt__row.is-strong { font-weight: 700; }

/* --- items --- */

/*
 * A real table, not "3 × Sugar ....... 630".
 *
 * A shopkeeper checking the slip against the shelf reads DOWN the quantity
 * column and DOWN the amount column. Run together on one line that scan is
 * impossible, and every dispute becomes a re-read of the whole slip.
 */
.bzf-receipt__table {
	width: 100%;
	border-collapse: collapse;
	margin-block: var(--bzf-2);
}

.bzf-receipt__table th {
	font-size: 10px;
	font-weight: 700;
	text-align: start;
	text-transform: uppercase;
	letter-spacing: .04em;
	padding-bottom: 3px;
	border-bottom: 1px dashed #000;
}

.bzf-receipt__table td {
	padding-block: 3px;
	vertical-align: top;
}

.bzf-receipt__td--right { text-align: end; }

/* The name wraps; the numbers never do. A wrapped price is a misread price. */
.bzf-receipt__td--name { word-break: break-word; }
.bzf-receipt__table td.bzf-receipt__td--right { white-space: nowrap; }

/* 58mm cannot carry four columns. The unit price is the one to lose — the
   quantity and the line amount are what a customer actually queries. */
.bzf-receipt--58 .bzf-receipt__table th:nth-child(3),
.bzf-receipt--58 .bzf-receipt__table td:nth-child(3) { display: none; }

/* --- totals --- */

.bzf-receipt__totals {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding-top: 5px;
}

.bzf-receipt__grand {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	margin-block: 5px;
	padding-block: 5px;
	border-block: 1px dashed #000;
	font-size: 15px;
	font-weight: 700;
}

.bzf-receipt__count {
	margin-top: 6px;
	text-align: center;
	font-size: 10px;
}

/* --- tax authority block --- */

/*
 * Only rendered when the sale actually carries an FBR number — see the note in
 * pos.js. The demo banner is loud on purpose: a test slip that looks like a
 * reported one is the whole risk this block carries.
 */
.bzf-receipt__fbr {
	margin-top: var(--bzf-3);
	padding-top: 6px;
	border-top: 1px dashed #000;
	text-align: center;
}

.bzf-receipt__demo {
	margin-bottom: 5px;
	padding: 3px;
	border: 1px solid #000;
	font-size: 10px;
	font-weight: 700;
	letter-spacing: .04em;
}

.bzf-receipt__qr {
	width: 96px;
	height: 96px;
	margin: 6px auto;
	background-repeat: no-repeat;
	background-position: center;
	background-size: contain;
}

/* --- foot --- */

.bzf-receipt__foot {
	margin-top: var(--bzf-3);
	padding-top: 6px;
	border-top: 1px dashed #000;
	text-align: center;
	font-size: 11px;
}

.bzf-receipt__powered { margin-top: 4px; font-size: 9px; letter-spacing: .06em; }

/*
 * URDU RECEIPTS STAY LTR IN STRUCTURE.
 *
 * The words flip, the layout does not: thermal printers feed one way, the
 * quantity column belongs where the eye already looks for it, and `.bzf-num`
 * already isolates every figure. Flipping the table as well produces a slip
 * where the amounts have moved and nobody trusts the total.
 */
.bizzflow-body[dir="rtl"] .bzf-receipt { direction: rtl; }
.bizzflow-body[dir="rtl"] .bzf-receipt__table { direction: rtl; }
.bizzflow-body[dir="rtl"] .bzf-receipt__td--right { text-align: start; }

/*
 * Printing.
 *
 * This is also how BizzFlow produces a PDF. Generating one in PHP would need a
 * library, and there is no Composer and no build step here â whereas every
 * browser already has "Save as PDF" behind the print dialog, with fonts, Urdu
 * shaping and page breaks solved. Marking the region to print is the whole job.
 *
 * `visibility` rather than `display`, deliberately: hiding an ancestor with
 * display:none would take the printable region down with it, so instead
 * everything is made invisible and the region is turned back on.
 */
@media print {
	body * { visibility: hidden; }

	#bzf-receipt, #bzf-receipt *,
	.bzf-print-area, .bzf-print-area * { visibility: visible; }

	#bzf-receipt,
	.bzf-print-area {
		position: absolute;
		inset-inline-start: 0;
		top: 0;
		width: 100%;
		max-width: none;
	}

	/* Buttons and filters mean nothing on paper. */
	.bzf-print-hide { display: none !important; }

	/* Cards should not be split across a page break mid-figure. */
	.bzf-print-area .bzf-card { break-inside: avoid; box-shadow: none; border-color: #ccc; }

	/*
	 * A receipt is not an A4 page.
	 *
	 * Without a page size the browser lays the slip out for Letter or A4 and the
	 * thermal driver then scales or crops it — which is how a receipt comes out
	 * with the totals column sliced off. The roll has no fixed length, so height
	 * is auto: the page ends where the slip ends.
	 *
	 * Margins are 0 because the printer owns its own margin; adding one here
	 * pushes the slip off the paper on the side the head cannot reach.
	 */
	#bzf-receipt { color: #000; }
	#bzf-receipt .bzf-num { color: #000; }
}

/*
 * 80mm is the default roll. A shop on 58mm sets it in Settings, and the class
 * on the receipt drives both the on-screen preview and this page size.
 */
@page { size: 80mm auto; margin: 0; }


/* ==================================================================
   Setup wizard
   ================================================================== */

.bzf-setup { min-height: 100vh; min-height: 100dvh; padding: var(--bzf-10) var(--bzf-5); background: var(--bzf-canvas); }
.bzf-setup__inner { max-width: 960px; margin-inline: auto; }
.bzf-setup__brand { display: flex; align-items: center; gap: var(--bzf-4); margin-bottom: var(--bzf-8); }

.bzf-setup__mark {
	width: 48px;
	height: 48px;
	border-radius: var(--bzf-radius-lg);
	background: linear-gradient(140deg, var(--bzf-brand-300), var(--bzf-brand-solid));
	color: var(--bzf-on-brand);
	display: grid;
	place-items: center;
	font-size: var(--bzf-text-xl);
	font-weight: 700;
	box-shadow: var(--bzf-shadow-brand);
}

.bzf-setup__tagline { color: var(--bzf-text-subtle); font-size: var(--bzf-text-sm); }
.bzf-setup__split { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: var(--bzf-5); align-items: start; }

@media (max-width: 900px) { .bzf-setup__split { grid-template-columns: 1fr; } }

.bzf-steps { display: flex; gap: var(--bzf-6); list-style: none; margin-bottom: var(--bzf-5); }
.bzf-steps__item { display: flex; align-items: center; gap: var(--bzf-2); font-size: var(--bzf-text-sm); color: var(--bzf-text-subtle); }
.bzf-steps__item.is-current { color: var(--bzf-brand-600); font-weight: 600; }

.bzf-steps__num {
	width: 22px;
	height: 22px;
	border-radius: var(--bzf-radius-full);
	border: 1.5px solid currentColor;
	display: grid;
	place-items: center;
	font-size: var(--bzf-text-xs);
	font-weight: 650;
}

.bzf-typegroup { margin-top: var(--bzf-6); }

.bzf-typegroup__title {
	font-size: var(--bzf-text-xs);
	text-transform: uppercase;
	letter-spacing: var(--bzf-track-wide);
	color: var(--bzf-text-subtle);
	font-weight: 620;
	margin-bottom: var(--bzf-3);
}

.bzf-typegrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(146px, 1fr)); gap: var(--bzf-3); }

.bzf-typecard {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--bzf-2);
	padding: var(--bzf-4) var(--bzf-3);
	background: var(--bzf-surface);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius);
	cursor: pointer;
	text-align: center;
	transition: border-color var(--bzf-fast), transform var(--bzf-fast), box-shadow var(--bzf-fast);
}

.bzf-typecard:hover { border-color: var(--bzf-brand-400); transform: translateY(-2px); box-shadow: var(--bzf-shadow-md); }
.bzf-typecard.is-selected { border-color: var(--bzf-brand-600); background: var(--bzf-brand-50); }

/* Business-type emoji stay: twenty categories scanned at a glance is exactly
   what pictograms are good at, and these are illustrations, not icons. */
/* Line icons now, not emoji â so they take a colour instead of a font size. */
.bzf-typecard__icon { display: inline-flex; color: var(--bzf-text-muted); }
.bzf-typecard.is-selected .bzf-typecard__icon { color: var(--bzf-brand-600); }
.bzf-typecard__label { font-size: var(--bzf-text-sm); font-weight: 550; line-height: 1.3; }

.bzf-preview__head { display: flex; gap: var(--bzf-3); align-items: center; margin-bottom: var(--bzf-4); }
.bzf-preview__icon {
	display: grid;
	place-items: center;
	width: 40px;
	height: 40px;
	flex-shrink: 0;
	border-radius: var(--bzf-radius);
	background: var(--bzf-brand-50);
	color: var(--bzf-brand-600);
}
.bzf-preview__note { font-size: var(--bzf-text-xs); margin-top: var(--bzf-4); }

/* --- Building your workspace --- */

.bzf-building {
	position: fixed;
	inset: 0;
	z-index: 200;
	display: grid;
	place-items: center;
	padding: var(--bzf-5);
	background: var(--bzf-canvas);
}

.bzf-building__inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--bzf-3);
	text-align: center;
	max-width: 420px;
}

.bzf-building__mark-lg {
	display: grid;
	place-items: center;
	width: 56px;
	height: 56px;
	margin-bottom: var(--bzf-2);
	border-radius: var(--bzf-radius-lg);
	background: linear-gradient(140deg, var(--bzf-brand-300), var(--bzf-brand-solid));
	color: var(--bzf-on-brand);
	box-shadow: var(--bzf-shadow-brand);
	animation: bzf-pulse 1.8s var(--bzf-ease) infinite;
}

.bzf-building__steps {
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: var(--bzf-2);
	margin: var(--bzf-4) 0;
	text-align: start;
	width: 100%;
}

.bzf-building__step {
	display: flex;
	align-items: center;
	gap: var(--bzf-3);
	font-size: var(--bzf-text-base);
	/* Steps not reached yet are present but recessive, so the list does not
	   jump around as each one arrives. */
	color: var(--bzf-text-subtle);
	transition: color var(--bzf-base);
}

.bzf-building__mark {
	display: grid;
	place-items: center;
	width: 22px;
	height: 22px;
	flex-shrink: 0;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	color: transparent;
	transition: background var(--bzf-base), color var(--bzf-base);
}

.bzf-building__step.is-current { color: var(--bzf-text); }

.bzf-building__step.is-current .bzf-building__mark {
	background: var(--bzf-brand-50);
	color: var(--bzf-brand-600);
	animation: bzf-pulse 1.2s var(--bzf-ease) infinite;
}

.bzf-building__step.is-done { color: var(--bzf-text-muted); }

.bzf-building__step.is-done .bzf-building__mark {
	background: var(--bzf-success-bg);
	color: var(--bzf-success-fg);
	animation: none;
}

/* ==================================================================
   AI assistant
   ================================================================== */

.bzf-ai__setup { text-align: center; padding: var(--bzf-10) var(--bzf-4); }

.bzf-ai__icon {
	display: inline-grid;
	place-items: center;
	width: 60px;
	height: 60px;
	margin-bottom: var(--bzf-4);
	border-radius: var(--bzf-radius-lg);
	background: var(--bzf-brand-50);
	color: var(--bzf-brand-600);
}

.bzf-ai__setup h2 { margin-bottom: var(--bzf-2); }

.bzf-ai__note {
	max-width: 48ch;
	margin: var(--bzf-6) auto;
	text-align: start;
	background: var(--bzf-surface-2);
	border: 1px solid var(--bzf-border-soft);
	border-radius: var(--bzf-radius);
	padding: var(--bzf-4);
	font-size: var(--bzf-text-base);
	display: flex;
	flex-direction: column;
	gap: var(--bzf-2);
}

.bzf-ai__header { margin-bottom: var(--bzf-5); }
.bzf-ai__header p { margin-top: var(--bzf-1); font-size: var(--bzf-text-base); }
.bzf-ai__privacy { margin-top: var(--bzf-6); border-top: 1px solid var(--bzf-border-soft); padding-top: var(--bzf-4); }
.bzf-ai__privacy h4 { margin-bottom: var(--bzf-2); }

.bzf-chat__thread {
	display: flex;
	flex-direction: column;
	gap: var(--bzf-3);
	min-height: 280px;
	max-height: 48vh;
	overflow-y: auto;
	padding-bottom: var(--bzf-3);
}

.bzf-bubble { display: flex; }
.bzf-bubble--user { justify-content: flex-end; }

.bzf-bubble__body {
	padding: 10px var(--bzf-4);
	border-radius: var(--bzf-radius-lg);
	max-width: 80%;
	font-size: var(--bzf-text-base);
	line-height: 1.6;
	white-space: pre-wrap;
}

.bzf-bubble--user .bzf-bubble__body {
	background: var(--bzf-brand-solid);
	color: var(--bzf-on-brand);
	border-end-end-radius: var(--bzf-radius-xs);
}

.bzf-bubble--assistant .bzf-bubble__body {
	background: var(--bzf-surface-2);
	border: 1px solid var(--bzf-border-soft);
	border-end-start-radius: var(--bzf-radius-xs);
}

.bzf-bubble.is-pending .bzf-bubble__body { opacity: .6; font-style: italic; }

.bzf-chat__note { font-size: var(--bzf-text-xs); color: var(--bzf-text-subtle); text-align: center; padding: var(--bzf-2); }
.bzf-chat__note--danger { color: var(--bzf-danger-fg); }
.bzf-chat__suggestions { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: var(--bzf-3); }
.bzf-chat__composer { display: flex; gap: var(--bzf-2); align-items: flex-end; }

.bzf-chat__input {
	flex: 1;
	padding: var(--bzf-3);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius);
	background: var(--bzf-surface);
	color: var(--bzf-text);
	resize: vertical;
	font-size: var(--bzf-text-base);
}

.bzf-chat__input:focus { outline: none; border-color: var(--bzf-brand-500); box-shadow: var(--bzf-ring); }
.bzf-chat__usage { font-size: var(--bzf-text-xs); margin-top: var(--bzf-2); text-align: end; }

/* ==================================================================
   Plan & billing
   ================================================================== */

.bzf-usagelist { display: flex; flex-direction: column; gap: var(--bzf-4); }
.bzf-usage { display: flex; flex-direction: column; gap: 6px; }

.bzf-usage__head {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: var(--bzf-3);
	font-size: var(--bzf-text-base);
}

.bzf-usage__count { color: var(--bzf-text-muted); font-size: var(--bzf-text-sm); font-weight: 550; }
.bzf-usage__count.is-high { color: var(--bzf-warning-fg); }
.bzf-usage__count.is-full { color: var(--bzf-danger-fg); }

.bzf-usage__track {
	height: 6px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	overflow: hidden;
}

.bzf-usage__fill {
	height: 100%;
	border-radius: inherit;
	background: var(--bzf-brand-solid);
	transition: width var(--bzf-slow);
}

.bzf-usage__fill.is-high { background: var(--bzf-warning); }
.bzf-usage__fill.is-full { background: var(--bzf-danger); }

.bzf-plangrid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
	gap: var(--bzf-4);
	margin-bottom: var(--bzf-5);
}

.bzf-plan {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: var(--bzf-3);
	padding: var(--bzf-5);
	background: var(--bzf-surface);
	border: 1px solid transparent;
	border-radius: var(--bzf-radius-lg);
	box-shadow: var(--bzf-shadow-sm);
}

/* Keeps a border slot open so the current plan can claim it without the card
   changing size. */
.bzf-plan.is-current { border-color: var(--bzf-brand-500); box-shadow: var(--bzf-shadow-brand); }

.bzf-plan__badge {
	position: absolute;
	top: -9px;
	inset-inline-start: var(--bzf-5);
	padding: 2px 8px;
	border-radius: var(--bzf-radius-sm);
	background: var(--bzf-brand-solid);
	color: var(--bzf-on-brand);
	font-size: var(--bzf-text-xs);
	font-weight: 620;
}

.bzf-plan__name { font-size: var(--bzf-text-lg); }
.bzf-plan__desc { font-size: var(--bzf-text-sm); line-height: 1.5; }

.bzf-plan__price {
	display: flex;
	align-items: baseline;
	gap: 4px;
	padding-block: var(--bzf-2);
	border-block: 1px solid var(--bzf-border-soft);
}

.bzf-plan__price strong { font-size: var(--bzf-text-2xl); font-weight: 700; letter-spacing: var(--bzf-track-tight); }

.bzf-plan__features {
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: 6px;
	font-size: var(--bzf-text-sm);
	flex: 1;
}

.bzf-plan__features li { display: flex; align-items: center; gap: var(--bzf-2); color: var(--bzf-text-muted); }
.bzf-plan__features svg { color: var(--bzf-success); flex-shrink: 0; }
.bzf-plan .bzf-btn { justify-content: center; }

/* ==================================================================
   Profit & loss
   ================================================================== */

.bzf-pl { display: flex; flex-direction: column; }

.bzf-pl__row {
	display: flex;
	justify-content: space-between;
	gap: var(--bzf-4);
	padding: 7px 0;
	font-size: var(--bzf-text-base);
}

.bzf-pl__row--deduct { color: var(--bzf-text-muted); padding-inline-start: var(--bzf-4); }

.bzf-pl__row--subtotal {
	border-top: 1px solid var(--bzf-border-soft);
	font-weight: 620;
	padding-top: var(--bzf-3);
	margin-top: var(--bzf-1);
}

.bzf-pl__row--total {
	border-top: 2px solid var(--bzf-text);
	font-weight: 700;
	font-size: var(--bzf-text-lg);
	padding-top: var(--bzf-3);
	margin-top: var(--bzf-2);
}

.bzf-pl__footnotes {
	display: flex;
	gap: var(--bzf-6);
	flex-wrap: wrap;
	margin-top: var(--bzf-4);
	padding-top: var(--bzf-3);
	border-top: 1px solid var(--bzf-border-soft);
	font-size: var(--bzf-text-xs);
}

/* ==================================================================
   Responsive
   ================================================================== */

@media (max-width: 900px) {
	.bzf-shell { grid-template-columns: 1fr; }

	/* Sidebar becomes a bottom bar: thumbs reach the bottom of a phone, not
	   the top. */
	.bzf-sidebar {
		position: fixed;
		bottom: 0;
		inset-inline: 0;
		top: auto;
		height: auto;
		flex-direction: row;
		align-items: center;
		padding: var(--bzf-2);
		border-inline-end: 0;
		border-top: 1px solid var(--bzf-border);
		z-index: 50;
		box-shadow: 0 -2px 12px rgba(0, 0, 0, .06);
	}

	.bzf-sidebar__brand, .bzf-sidebar__footer { display: none; }
	.bzf-nav { flex-direction: row; width: 100%; justify-content: space-around; }
	.bzf-nav__label { display: none; }
	.bzf-nav__item { flex-direction: column; padding: 6px; gap: 0; }
	.bzf-nav__item.is-active::before { display: none; }

	.bzf-shell__content { padding: var(--bzf-4); padding-bottom: 76px; }

	/* A flush screen still has to clear the bottom nav, or the Charge button
	   sits underneath it. */
	.bzf-shell__content.is-flush { padding: 0 0 76px; }

	.bzf-topbar { padding: 0 var(--bzf-4); }
	.bzf-account__details { display: none; }
	.bzf-table-wrap { margin-inline: calc(var(--bzf-4) * -1); padding-inline: var(--bzf-4); }
}

/* ==================================================================
   Touch targets
   ==================================================================

   `pointer: coarse`, NOT a width breakpoint. A 1024px tablet propped on a
   counter is touched; a 700px browser window on a desktop is not. Width
   guesses at the input device — this asks it.

   Measured on a 375px phone before this existed: the POS brand chips came
   out 31px tall and .bzf-btn--sm 27px. The POS stylesheet already says its
   filter chips are "the one control on this screen that gets tapped with a
   wet thumb"; the brand row added in 1.27.0 quietly undercut that.

   Desktop density is untouched. Nothing here applies to a mouse.
   ================================================================== */

@media (pointer: coarse) {
	/*
	 * min-height, not padding.
	 *
	 * 1.30.0 sized these with padding and reached 32px. Padding is a function
	 * of the font, so raising the type scale moved every one of them again and
	 * the numbers had to be re-derived. A minimum says what is actually meant
	 * — this control is at least this big — and holds whatever the text does.
	 *
	 * 44px is the size a finger is expected to hit. Measured at 375px before
	 * this block: icon buttons 34, row buttons 27, POS chips 31, the language
	 * switch 19 wide, the breadcrumb 19 tall.
	 *
	 * Every one of these is already inline-flex and centred, so a minimum
	 * height grows the box around the label rather than shifting it.
	 */
	.bzf-btn,
	.bzf-btn--sm,
	.bzf-chip,
	.bzf-input,
	.bzf-select,
	select,
	textarea {
		min-height: 44px;
	}

	/*
	 * "Keep me signed in", on the sign-in screen.
	 *
	 * Measured at 22px in 1.48.0 — half a target, on the one screen every
	 * shopkeeper touches every morning. The checkbox itself stays 16px because
	 * a 44px checkbox looks broken; it is the LABEL that has to be 44 tall,
	 * since the label wraps the input and so carries the whole click area.
	 *
	 * This screen is not reached by the app's responsive sweep — that runs app
	 * routes, and this renders for signed-out visitors — which is exactly why
	 * it went unmeasured. Sweep the auth screens by hand.
	 */
	.bzf-auth__remember { min-height: 44px; }

	.bzf-btn--sm { padding: 9px 13px; }

	/*
	 * Width too, not just height. A short label makes a narrow target: the Urdu
	 * build's "نیا" and "آج" chips came out 42-43px wide while being a correct
	 * 44 tall, and a target you miss sideways is missed just the same.
	 */
	.bzf-chip { padding: 9px 14px; min-width: 44px; }

	/* Square controls need the width too, or a 44px-tall icon stays 34 wide. */
	.bzf-iconbtn {
		min-width: 44px;
		min-height: 44px;
		width: auto;
		height: auto;
	}

	/*
	 * The language switch hides its word under 480px and had padding-inline: 0,
	 * which left a 19px-wide target — the narrowest control in the app, and one
	 * whose whole purpose is to be found by somebody who cannot read the rest
	 * of the screen.
	 */
	.bzf-langtoggle { min-width: 44px; padding-inline: 10px; }

	/*
	 * The breadcrumb is a real control — it is how you get back — and it was
	 * 19px tall because it is styled as bare text. Padding rather than
	 * min-height so the row it sits in does not grow around it.
	 */
	.bzf-crumbs__link { padding-block: 12px; }

	/*
	 * The POS filter rows set their own padding at 0,2,0 and 0,3,0, so a bare
	 * .bzf-chip here would lose to them. Matched selector for selector rather
	 * than reached for !important.
	 */
	.bzf-pos__filters .bzf-chip { padding: 11px var(--bzf-4); }
	.bzf-pos__filters .bzf-chips--brands .bzf-chip { padding: 9px var(--bzf-3); }

	/* A table row that opens a detail page is a tap target too. */
	.bzf-tr--clickable td { padding-block: var(--bzf-3); }

	/*
	 * The menu itself. These are the most-tapped controls in the product and
	 * they came out 39px and 40px — close enough to look fine and short enough
	 * to miss, which is the worst of both.
	 */
	.bzf-topnav__trigger,
	.bzf-account__trigger,
	.bzf-nav__item {
		min-height: 44px;
	}
}

/*
 * Language switch.
 *
 * Wider than a plain icon button because it carries a word — "اردو" or
 * "English" — and the word is the point. An unlabelled globe asks the user to
 * guess, and the person who most needs this button is the one least able to
 * guess in English.
 */
.bzf-langtoggle {
	width: auto;
	gap: 6px;
	padding-inline: 10px;
}

.bzf-langtoggle__label {
	font-size: var(--bzf-text-sm);
	font-weight: 600;
	white-space: nowrap;
}

/* On a phone the top bar is tight; the globe alone still reads as "language"
   once someone has used it, and the tooltip keeps the word. */
@media (max-width: 480px) {
	.bzf-langtoggle__label { display: none; }
	.bzf-langtoggle { padding-inline: 0; }
}

/* ==================================================================
   Tables become cards on a phone
   ==================================================================

   A width breakpoint, NOT `pointer: coarse` — and the difference is the whole
   point. A table needs horizontal ROOM; a tablet in landscape has it and is
   still touched, a narrow desktop window has no room and is not. Touch targets
   ask the device, table layout asks the width.

   Measured at 375px before this existed: the Purchases table laid out at
   741px inside a 375px box and the Customers table at 643px, so the Total
   column was 366px off to the right, reachable only by scrolling inside the
   table. The page never scrolled sideways, which is why it passed every
   earlier check — the content was hidden inside a scroller instead.

   Each row becomes a card, <thead> is dropped, and every cell prints its own
   column heading from `data-label` (set by table() in ui.js). The first cell
   is the row's subject, so it becomes the card's heading rather than a
   labelled line.

   Specificity is matched selector-for-selector against the desktop rules
   above rather than reached for !important — that is the 1.21.0 lesson.
   ================================================================== */

@media (max-width: 640px) {
	/* The wrap exists to let a wide table scroll. There is nothing to scroll. */
	.bzf-table-wrap {
		overflow-x: visible;
		margin-inline: 0;
		padding-inline: 0;
	}

	.bzf-table { display: block; }

	.bzf-table thead { display: none; }

	.bzf-table tbody {
		display: flex;
		flex-direction: column;
		gap: var(--bzf-3);
	}

	.bzf-table tr {
		display: block;
		background: var(--bzf-surface);
		border: 1px solid var(--bzf-border);
		border-radius: var(--bzf-radius-lg);
		padding: var(--bzf-3) var(--bzf-4);
		box-shadow: var(--bzf-shadow-xs);
	}

	.bzf-table tbody tr + tr { border-top: 1px solid var(--bzf-border); }

	.bzf-table td {
		display: flex;
		align-items: baseline;
		justify-content: space-between;
		gap: var(--bzf-4);
		padding: 5px 0;
		border: 0;
		text-align: start;
	}

	/* Matched against `.bzf-table td:first-child` / `:not(:first-child)` above,
	   which set width and nowrap for the desktop layout. */
	.bzf-table td:first-child,
	.bzf-table td:not(:first-child) {
		width: auto;
		white-space: normal;
	}

	.bzf-table td::before {
		content: attr(data-label);
		flex: 0 0 auto;
		color: var(--bzf-text-muted);
		font-size: var(--bzf-text-sm);
		font-weight: 550;
	}

	/* An action cell has no heading, and a label reading "" is a stray gap. */
	.bzf-table td[data-label='']::before { display: none; }

	/* The row's subject: a product name, a customer. It is the card's title. */
	.bzf-table td.bzf-td--primary {
		display: block;
		font-size: var(--bzf-text-lg);
		font-weight: 600;
		line-height: 1.3;
		padding-bottom: var(--bzf-2);
	}

	.bzf-table td.bzf-td--primary::before { display: none; }

	/* Right alignment was how a money column was read DOWN the page. In a card
	   the label already sits opposite the value, so the flex row does that job
	   and a text-align would only fight it. */
	.bzf-table td.bzf-td--right { text-align: start; }

	/* The whole card is the tap target when the row opens something. */
	.bzf-table tr.bzf-tr--clickable { cursor: pointer; }
	.bzf-table tr.bzf-tr--clickable:active { background: var(--bzf-surface-2); }
}

/* ==================================================================
   The phone bottom bar
   ==================================================================

   The sidebar becomes a fixed bar along the bottom on a phone — thumbs reach
   the bottom, not the top — and that part was already right.

   What was wrong: the bar was written for a handful of destinations and the
   menu grew to twenty. `justify-content: space-around` split 375px between all
   of them and every item measured **17px wide**. Twenty slivers, and on the
   free plan this is the only navigation there is.

   Four slots plus More. Which four is chosen in shell.js, because a locked item
   must never take a slot.

   The `[data-bottom]` attribute is set by shell.js; everything else in the list
   is hidden until More is pressed, when the list becomes a sheet sitting on top
   of the bar.
   ================================================================== */

.bzf-nav__more { display: none; }

@media (max-width: 640px) {
	.bzf-sidebar { flex-wrap: wrap; }

	/* Only the slotted four ride in the bar. */
	.bzf-nav > li { display: none; }

	/*
	 * `flex: 1`, not `display: block`. Sized to their content these came out
	 * 29px wide — an icon and six pixels of padding — with 40px of dead space
	 * either side that looks tappable and is not. Sharing the bar between them
	 * makes each one about 70px, and the whole width of a tab belongs to it.
	 */
	.bzf-nav > li[data-bottom] {
		display: block;
		flex: 1;
		min-width: 44px;
	}

	.bzf-nav > li[data-bottom] .bzf-nav__item { width: 100%; }

	.bzf-nav {
		flex: 1;
		justify-content: space-around;
	}

	.bzf-nav__more {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		gap: 0;
		min-width: 44px;
		min-height: 44px;
		padding: 6px;
		border: 0;
		background: none;
		color: inherit;
		font: inherit;
		cursor: pointer;
	}

	.bzf-nav__more .bzf-nav__label { display: none; }

	/*
	 * Opened: the whole menu, above the bar it came from.
	 *
	 * Capped and scrollable rather than full height — twenty rows plus the bar
	 * would run past the top of a small phone, and a menu you have to scroll the
	 * PAGE to finish reading is a menu that looks broken.
	 */
	.bzf-sidebar.is-expanded {
		max-height: 78vh;
		overflow-y: auto;
	}

	.bzf-sidebar.is-expanded .bzf-nav {
		order: 2;
		flex-direction: column;
		width: 100%;
		gap: 2px;
		padding: var(--bzf-2) 0;
		border-top: 1px solid var(--bzf-sidebar-border);
	}

	.bzf-sidebar.is-expanded .bzf-nav > li { display: block; }

	.bzf-sidebar.is-expanded .bzf-nav__item {
		flex-direction: row;
		justify-content: flex-start;
		gap: var(--bzf-3);
		width: 100%;
		min-height: 44px;
		padding: 8px var(--bzf-4);
	}

	/* The labels are the point of opening it. */
	.bzf-sidebar.is-expanded .bzf-nav__label { display: block; }

	.bzf-sidebar.is-expanded .bzf-nav__more { order: 1; }
}

/* ==================================================================
   The 1.37.0 redesign — ground and glass chrome
   ==================================================================

   Appended last so it wins on source order at equal specificity, rather than
   editing rules scattered through 3,400 lines and hoping nothing downstream
   overrides them. That is the 1.21.0 lesson applied at the file level.
   ================================================================== */

/*
 * The ground everything else floats on. Fixed, so it does not scroll away
 * under a long product list and leave a plain white page behind it.
 *
 * `body.bizzflow-body`, not `.bizzflow-body`: app.css sets the canvas with
 * that exact selector — element plus class, specificity 0,1,1 — and a bare
 * class is 0,1,0, so it lost even from a later file. Matched selector for
 * selector rather than reached for !important. Same trap as 1.21.0.
 */
body.bizzflow-body {
	background: var(--bzf-ground);
	background-attachment: fixed;
}

/*
 * The material.
 *
 * One class's worth of rules, applied to every piece of chrome, so the top bar
 * and the sidebar and a filter chip are visibly the same substance.
 */
.bzf-topbar,
.bzf-sidebar,
.bzf-topnav {
	background: var(--bzf-glass-bg);
	-webkit-backdrop-filter: var(--bzf-glass-blur);
	backdrop-filter: var(--bzf-glass-blur);
	border-color: var(--bzf-glass-edge);
}

.bzf-topbar {
	box-shadow: var(--bzf-glass-shadow);
	border-bottom: 1px solid var(--bzf-glass-edge);
}

.bzf-sidebar {
	border-inline-end: 1px solid var(--bzf-glass-edge);
	box-shadow: 6px 0 36px -10px rgba(28, 28, 30, .07);
}

.bzf-topnav {
	border-bottom: 1px solid var(--bzf-glass-edge);
	box-shadow: var(--bzf-glass-shadow);
}

/* Search, chips and icon buttons are chrome too — they sit on the ground
   rather than inside a card. */
.bzf-search,
.bzf-chip,
.bzf-iconbtn,
.bzf-langtoggle {
	background: var(--bzf-glass-bg-2);
	-webkit-backdrop-filter: blur(14px);
	backdrop-filter: blur(14px);
	border-color: var(--bzf-glass-edge);
}

/*
 * No backdrop-filter, no pretending.
 *
 * A translucent pane without a blur behind it is not frosted, it is see-through
 * — the product grid reads straight through the sidebar labels. Where the
 * browser cannot blur, the chrome becomes almost solid instead.
 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
	.bzf-topbar,
	.bzf-sidebar,
	.bzf-topnav,
	.bzf-search,
	.bzf-chip,
	.bzf-iconbtn,
	.bzf-langtoggle {
		background: var(--bzf-glass-solid);
	}
}

/*
 * Content stays SOLID.
 *
 * Cards, tables and modal bodies are where the numbers live. They get the
 * softer, rounder, more layered treatment from the redesign — but they are
 * opaque, and that is the line the whole design is drawn along.
 */
.bzf-card,
.bzf-table-wrap,
.bzf-modal {
	border-radius: var(--bzf-radius-lg);
}

.bzf-card {
	background: rgba(255, 255, 255, .94);
	border: 1px solid rgba(255, 255, 255, .9);
	box-shadow:
		0 2px 8px -2px rgba(28, 28, 30, .06),
		0 12px 28px -8px rgba(28, 28, 30, .05);
}

/* Buttons: raised, lit along the top edge, and casting their own colour —
   the iOS "this is pressable" read. */
.bzf-btn--primary {
	border-radius: var(--bzf-radius-full);
	box-shadow:
		inset 0 1px 0 rgba(255, 255, 255, .28),
		0 5px 14px -4px rgba(var(--bzf-brand-rgb), .38);
}

.bzf-btn--ghost,
.bzf-btn--secondary {
	border-radius: var(--bzf-radius-full);
}

/* Table headings pick up the brand wash the design uses, in place of the old
   flat grey band. */
.bzf-table th {
	background: var(--bzf-brand-50);
}

/*
 * Motion.
 *
 * Short, and eased the way a physical thing settles. Honoured only when the
 * viewer has not asked for less — the reduced-motion rule already in this file
 * still applies over the top.
 */
.bzf-btn,
.bzf-chip,
.bzf-card,
.bzf-nav__item {
	transition:
		transform .16s cubic-bezier(.2, .8, .2, 1),
		box-shadow .16s cubic-bezier(.2, .8, .2, 1),
		background .16s ease;
}

.bzf-btn--primary:active,
.bzf-chip:active {
	transform: scale(.97);
}

/*
 * Settings tabs: a segmented control, not underlined links.
 *
 * The active tab was brand ink sitting straight on the page. Over the new
 * ground that composited to 4.37:1 — the single failure left in an 817-node
 * sweep. A raised white pill is what the redesign draws anyway, and it puts
 * the same ink back on solid white, where it measures 5.2:1.
 *
 * The underline goes with it: an underline under a pill is two marks for one
 * state, which is the same call the top bar made in 1.35.0.
 */
.bzf-tabs {
	gap: var(--bzf-1);
	padding: var(--bzf-1);
	border-bottom: 0;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-glass-bg);
	-webkit-backdrop-filter: blur(14px);
	backdrop-filter: blur(14px);
	border: 1px solid var(--bzf-glass-edge);
}

.bzf-tab {
	border-radius: var(--bzf-radius-full);
	border-bottom: 0;
	margin-bottom: 0;
}

.bzf-tab.is-active {
	color: var(--bzf-brand-600);
	background: #FFFFFF;
	border-bottom-color: transparent;
	box-shadow: 0 2px 6px -1px rgba(28, 28, 30, .10);
}

/* ==================================================================
   List screens, as the Figma draws them
   ==================================================================

   The materials pass changed what surfaces are made of. This changes their
   arrangement: the table becomes a card in its own right, rows separate by a
   hairline, and the search field becomes a pill.
   ================================================================== */

/*
 * The table is a card.
 *
 * On Products, Customers, Credit, Inventory, Suppliers and Purchases the table
 * IS the content of the screen, and it was sitting naked on the ground with no
 * surface under it. In the design it is a solid rounded panel — which is also
 * the rule the redesign runs on: the money lives on something opaque.
 */
.bzf-table-wrap {
	background: rgba(255, 255, 255, .94);
	border: 1px solid rgba(255, 255, 255, .9);
	border-radius: var(--bzf-radius-lg);
	box-shadow:
		0 2px 8px -2px rgba(28, 28, 30, .06),
		0 12px 28px -8px rgba(28, 28, 30, .05);
	/* The negative margin existed to let a table scroll to the edges of the
	   card around it. This one IS the card, so it has its own padding. */
	margin-inline: 0;
	padding-inline: 0;
	overflow: hidden;
	overflow-x: auto;
}

/*
 * …except where it is already inside one.
 *
 * Reports and the dashboard put tables inside `.bzf-card`. Left alone they
 * would become a card inside a card — two borders, two shadows, a visible
 * double edge. Higher specificity, so it wins wherever it applies.
 */
.bzf-card .bzf-table-wrap {
	background: none;
	border: 0;
	border-radius: 0;
	box-shadow: none;
	margin-inline: calc(var(--bzf-5) * -1);
	padding-inline: var(--bzf-5);
}

/* Rows separate by a hairline, the way a ledger does. The last one does not —
   a rule against the bottom edge of the card is a seam, not a separator. */
.bzf-table tbody tr {
	border-bottom: 1px solid rgba(220, 218, 226, .7);
}

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

/* Breathing room now that the table has walls of its own. */
.bzf-table th,
.bzf-table td {
	padding-inline: var(--bzf-5);
}

/* Search is a pill, and tall enough to be a real target on any device. */
.bzf-search {
	border-radius: var(--bzf-radius-full);
	min-height: 44px;
	padding-inline: var(--bzf-5);
}

/* Badges are soft-tinted pills, not grey chips. */
.bzf-badge {
	border-radius: var(--bzf-radius-full);
	padding: 4px 10px;
}

/*
 * The row's subject reads as the subject.
 *
 * In the design the first cell is the heaviest thing in the row — the product,
 * the customer, the reference — and everything else hangs off it. It was the
 * same weight as the SKU beside it.
 *
 * The design also puts a second line under it (category, address, supplier).
 * That is NOT done here: the list endpoints return database rows rather than a
 * declared shape, and the views only read the fields they already show, so
 * there is no field the subtitle could be built from that is reliably present.
 * An empty second line on half the rows is worse than none. It needs the API
 * to state what it returns first.
 */
.bzf-table td:first-child {
	font-weight: 600;
	color: var(--bzf-text);
}

/* ==================================================================
   The Till, in the redesign's materials
   ==================================================================

   The POS already had the shape the Figma draws — a till bar across the top,
   the catalogue on the left, the cart down the right. What it did not have was
   the material: both the bar and the cart were flat white panels.

   The cart is the clearest case for glass in the whole product. It floats over
   a grid of colourful product cards, so the blur has something busy to pick up
   and it reads as a real pane rather than a grey box. The lines INSIDE it stay
   opaque — that is where the prices are.
   ================================================================== */

.bzf-posbar {
	background: var(--bzf-glass-bg);
	-webkit-backdrop-filter: var(--bzf-glass-blur);
	backdrop-filter: var(--bzf-glass-blur);
	border-bottom: 1px solid var(--bzf-glass-edge);
	box-shadow: var(--bzf-glass-shadow);
}

.bzf-pos__cart {
	background: var(--bzf-glass-bg-2);
	-webkit-backdrop-filter: var(--bzf-glass-blur);
	backdrop-filter: var(--bzf-glass-blur);
	border-inline-start: 1px solid var(--bzf-glass-edge);
	box-shadow: -8px 0 40px -8px rgba(28, 28, 30, .10);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
	.bzf-posbar,
	.bzf-pos__cart { background: var(--bzf-glass-solid); }
}

/* Product cards: the layered, softer lift from the design, in place of the
   old flat 1px shadow. Still opaque — they carry the price. */
.bzf-prodcard {
	background: rgba(255, 255, 255, .94);
	border: 1px solid rgba(255, 255, 255, .9);
	box-shadow:
		0 2px 8px -2px rgba(28, 28, 30, .06),
		0 10px 24px -6px rgba(28, 28, 30, .05);
}

.bzf-prodcard:hover {
	box-shadow:
		0 4px 12px -2px rgba(28, 28, 30, .09),
		0 16px 32px -8px rgba(28, 28, 30, .08);
}

/* Cart lines are their own small solid cards, so the money sits on something
   opaque even though the panel behind them is glass. */
.bzf-cartline {
	background: rgba(255, 255, 255, .92);
	border: 1px solid rgba(255, 255, 255, .9);
	border-radius: var(--bzf-radius);
	box-shadow: 0 2px 8px -3px rgba(28, 28, 30, .05);
}

/*
 * Charge is the button the screen exists for. It gets the full treatment: a
 * lit top edge, its own colour cast beneath it, and it grows with the till
 * rather than sitting at button height.
 */
.bzf-cart__charge,
.bzf-pos__cart .bzf-btn--primary {
	min-height: 56px;
	font-size: var(--bzf-text-lg);
	box-shadow:
		inset 0 1px 0 rgba(255, 255, 255, .3),
		0 8px 20px -6px rgba(var(--bzf-brand-rgb), .45);
}

/*
 * On a phone the ROWS are the cards, so the wrap must not be one too.
 *
 * The list-screen pass above turned `.bzf-table-wrap` into a solid panel,
 * which is right on a desktop where the table sits on the bare ground. Below
 * 640px each row is already its own card — leaving the wrap styled as well
 * gives a white card inside a white card, two borders and two shadows deep.
 *
 * Appended after that pass on purpose: these rules have to win over it, and
 * they carry the same specificity.
 */
@media (max-width: 640px) {
	.bzf-table-wrap {
		background: none;
		border: 0;
		border-radius: 0;
		box-shadow: none;
		overflow: visible;
	}

	/* The row's own card edge is the separator. A border as well draws a line
	   across the inside of every card. */
	.bzf-table tbody tr {
		border-bottom: 0;
	}

	/* The wrap's desktop padding would double the card's own. */
	.bzf-table th,
	.bzf-table td {
		padding-inline: 0;
	}
}

/* ==================================================================
   The Till, laid out as the Figma draws it
   ==================================================================

   1.38.0 gave the till the right materials. This gives it the arrangement:
   a full-bleed tinted banner on each product card, the price and the stock on
   one line beneath it, a glass customer row, and the totals as their own solid
   panel with Charge under it.
   ================================================================== */

/*
 * The product card becomes a grid.
 *
 * Its four children — thumb, name, price, stock — are siblings with no wrapper,
 * and the design needs the price and the stock to share a line. A two-column
 * grid does that without touching pos.js, which is the one file in this product
 * where a structural change is worth avoiding: the cart guard, the barcode
 * focus and the F-key handlers all live in it.
 */
.bzf-prodcard {
	display: grid;
	grid-template-columns: 1fr auto;
	align-items: center;
	gap: var(--bzf-1) var(--bzf-2);
	padding: 0 0 var(--bzf-3);
	overflow: hidden;
	text-align: start;
}

.bzf-prodcard .bzf-thumb--lg {
	grid-column: 1 / -1;
	width: 100%;
	height: 96px;
	border-radius: 0;
	/*
	 * background-COLOR only.
	 *
	 * A product photo is painted onto this element as an inline
	 * `background-image`. Setting the `background` shorthand here would still
	 * lose to the inline style, but a gradient in `background-image` is one
	 * refactor away from hiding every shop's photos. A tint cannot.
	 */
	background-color: var(--bzf-brand-50);
	font-size: var(--bzf-text-2xl);
	color: var(--bzf-brand-600);
	margin-bottom: var(--bzf-2);
}

.bzf-prodcard__name {
	grid-column: 1 / -1;
	padding-inline: var(--bzf-3);
	font-weight: 600;
}

/* Price left, stock right, on one line — the row a shopkeeper's eye lands on. */
.bzf-prodcard__price {
	grid-column: 1;
	margin-top: 0;
	padding-inline-start: var(--bzf-3);
	/* Near-black, as the design has it: on a card this is the loudest thing,
	   and it does not need a colour to be found. */
	color: var(--bzf-text);
	font-size: var(--bzf-text-lg);
	font-weight: 700;
}

.bzf-prodcard__stock {
	grid-column: 2;
	padding: 0 var(--bzf-3) 0 0;
	background: none;
	font-weight: 550;
}

.bzf-prodcard__stock.is-out,
.bzf-prodcard__stock.is-low {
	background: none;
	color: var(--bzf-danger);
}

/* The customer sits in a glass inset, not a grey box. */
.bzf-cart__customer {
	background: var(--bzf-glass-bg-2);
	-webkit-backdrop-filter: blur(14px);
	backdrop-filter: blur(14px);
	border-color: var(--bzf-glass-edge);
	border-radius: var(--bzf-radius);
}

/*
 * The totals are a panel of their own, floating inside the glass cart rather
 * than being a grey strip welded to the bottom of it.
 */
.bzf-cart__foot {
	margin: var(--bzf-3) var(--bzf-4) var(--bzf-4);
	padding: var(--bzf-4);
	border: 1px solid rgba(255, 255, 255, .9);
	border-top: 1px solid rgba(255, 255, 255, .9);
	border-radius: var(--bzf-radius-lg);
	background: rgba(255, 255, 255, .92);
	box-shadow: 0 4px 16px -4px rgba(28, 28, 30, .07);
	gap: var(--bzf-2);
}

/* The total is the number the customer is about to hand money for. */
.bzf-cart__total {
	border-top: 1px solid var(--bzf-border);
	padding-top: var(--bzf-3);
}

.bzf-cart__total > strong {
	font-size: var(--bzf-text-2xl);
	font-weight: 700;
	letter-spacing: var(--bzf-track-tight);
}

/* The stepper reads as one solid control. */
.bzf-stepper {
	background: var(--bzf-surface-2);
	border-color: transparent;
}

/*
 * Two columns on a phone, as the design has it.
 *
 * The track is `minmax(178px, 1fr)`, and 375px minus the page padding leaves
 * 335px — two 178s do not fit, so it collapsed to a single column and a
 * cashier saw four products per screen. Two columns of ~160px is still a very
 * large tap target, and it doubles what is in view on the screen where that
 * matters most.
 */
@media (max-width: 640px) {
	.bzf-pos__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--bzf-3);
	}

	.bzf-prodcard .bzf-thumb--lg { height: 76px; }
}

/* ==================================================================
   Stat tiles, as the design weights them
   ==================================================================

   The tile was already the right shape. What was wrong was the emphasis: the
   label sat at 13.5px in near-black, the same ink as the number under it, so
   "Takings today" competed with the figure it names.

   In the design the label is quiet and small and the number is the loudest
   thing on the card. That is the whole point of a stat tile — you find the
   number first and read the label only if you need to.

   One rule, four screens: Dashboard, Live Ops, Analytics and Reports.
   ================================================================== */

.bzf-stat__head {
	font-size: var(--bzf-text-sm);
	font-weight: 550;
	color: var(--bzf-text-muted);
	letter-spacing: 0;
}

.bzf-stat__value {
	letter-spacing: var(--bzf-track-tight);
	line-height: 1.15;
}

/* ==================================================================
   Billing plan cards, and the last of the surfaces
   ==================================================================

   These carried the old flat shadow, which reads as a different product
   sitting next to the redesigned ones. Same layered lift as every other card.
   ================================================================== */

.bzf-plan {
	background: rgba(255, 255, 255, .94);
	border-color: rgba(255, 255, 255, .9);
	box-shadow:
		0 2px 8px -2px rgba(28, 28, 30, .06),
		0 12px 28px -8px rgba(28, 28, 30, .05);
}

/*
 * The plan you are on.
 *
 * A brand wash and a brand edge rather than a coloured shadow alone — on the
 * new ground a soft orange glow under a white card is barely visible, and
 * "which plan am I on" is the only question this screen has to answer.
 */
.bzf-plan.is-current {
	background: var(--bzf-brand-50);
	border-color: var(--bzf-brand-200);
	box-shadow:
		0 2px 8px -2px rgba(var(--bzf-brand-rgb), .16),
		0 12px 28px -8px rgba(var(--bzf-brand-rgb), .18);
}

.bzf-plan__badge {
	border-radius: var(--bzf-radius-full);
	padding: 3px 10px;
}

/* Alerts and empty states are surfaces too, and they were still square. */
.bzf-alert,
.bzf-empty {
	border-radius: var(--bzf-radius-lg);
}

/* Modals get the same lift and roundness as everything else that floats. */
.bzf-modal {
	box-shadow:
		0 4px 16px -4px rgba(28, 28, 30, .12),
		0 24px 56px -12px rgba(28, 28, 30, .18);
}

/*
 * The dashboard's main column.
 *
 * Mirrors `.bzf-dash__side`: a stacked column with the same rhythm, so the
 * chart and the latest-sales card sit apart by the same distance as the cards
 * opposite them. Without it the two would collapse against each other while
 * the side column kept its spacing, and the two halves would not line up.
 */
.bzf-dash__main {
	display: flex;
	flex-direction: column;
	gap: var(--bzf-5);
	min-width: 0;
}

.bzf-dash__main > .bzf-card { margin-bottom: 0; }

/* An inline segmented control, sized to sit in a card heading rather than
   across the top of a page like the Settings tabs do. */
.bzf-tabs--inline {
	flex: 0 0 auto;
	margin-bottom: 0;
}

.bzf-tabs--inline .bzf-tab {
	padding: 7px var(--bzf-3);
	font-size: var(--bzf-text-sm);
}

/* A heading-level link that is a button, not a nav item. */
.bzf-linkbtn {
	border: 0;
	background: none;
	padding: 0;
	font: inherit;
	font-size: var(--bzf-text-sm);
	font-weight: 600;
	color: var(--bzf-brand-600);
	cursor: pointer;
}

.bzf-linkbtn:hover { text-decoration: underline; }

/*
 * The two controls this pass added are still controls.
 *
 * The inline segment was given tighter padding so it fits inside a card
 * heading, and "See all" is styled as text — both came out under 44px on a
 * touch device, which is the rule the whole product is held to.
 *
 * Padding on the link rather than a minimum: a 44px-tall block inside a card
 * heading would push the heading around. The target grows, the line does not.
 */
@media (pointer: coarse) {
	.bzf-tabs--inline .bzf-tab {
		min-height: 44px;
		min-width: 44px;
		padding: 9px var(--bzf-4);
	}

	.bzf-linkbtn {
		/* 11px left it at 43.69px — short by a third of a pixel, and a miss is a
		   miss. Derived from the measurement, not from the round number. */
		padding: 12px var(--bzf-2);
		margin-inline-end: calc(var(--bzf-2) * -1);
	}
}

/*
 * A table cell with a second line — the design's list-row treatment.
 *
 * The subject on top at the row's own weight, the qualifier beneath it quiet
 * and small. Built by `cellStack()` in core/ui.js, which returns a bare string
 * when there is no subtitle, so this only ever wraps a real two-line cell.
 */
.bzf-cellstack {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}

.bzf-cellstack__top {
	font-weight: 600;
	line-height: 1.3;
}

.bzf-cellstack__sub {
	font-size: var(--bzf-text-sm);
	font-weight: 400;
	color: var(--bzf-text-subtle);
	line-height: 1.3;
	/* One line. A long address must not turn a table row into a paragraph. */
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* On a phone the first cell is the card's title, so the subtitle can breathe. */
@media (max-width: 640px) {
	.bzf-cellstack__sub { white-space: normal; }
}

/*
 * The till bar's centred search.
 *
 * Three parts: the shop name, the search, the actions. The search takes the
 * middle and is capped, so it stays centred on a wide monitor instead of
 * stretching into a letterbox — a barcode is short and a search field the width
 * of a desk is harder to aim at, not easier.
 */
.bzf-posbar .bzf-pos__searchwrap {
	flex: 1 1 auto;
	max-width: 520px;
	margin-inline: auto;
	min-height: 44px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-glass-bg-2);
	-webkit-backdrop-filter: blur(14px);
	backdrop-filter: blur(14px);
	border: 1px solid var(--bzf-glass-edge);
}

/* The filter row is the only thing left in the bar the search used to share. */
.bzf-pos__searchbar:empty { display: none; }

/*
 * Below 900px the three-part bar cannot hold all three: the shop name and the
 * key hints are already tight, and a centred field between them ends up too
 * narrow to type a product name into. It goes full width on its own line.
 */
@media (max-width: 900px) {
	.bzf-posbar { flex-wrap: wrap; }

	.bzf-posbar .bzf-pos__searchwrap {
		order: 3;
		flex: 1 0 100%;
		max-width: none;
		margin-inline: 0;
	}
}

/*
 * NOT centred — the artboard puts the search next to the shop name.
 *
 * 1.41.0 spent a release centring this field, on the belief that the Figma till
 * had "a centred search pill". The 2026-08-22 audit measured the artboard:
 * Identity sits at x 24, Search at x 236, Controls at x 852 on a 1440 frame.
 * The field is decidedly LEFT of the 720 centre line — it reads as belonging to
 * the shop identity beside it, not as a separate centred object.
 *
 * So the side blocks size themselves and the ACTIONS block takes the slack,
 * which pushes the till controls to the right edge and leaves the search where
 * the design puts it.
 */
.bzf-posbar__left {
	flex: 0 0 auto;
	min-width: 0;
}

.bzf-posbar__actions {
	flex: 1 1 auto;
	min-width: 0;
	justify-content: flex-end;
}

.bzf-posbar .bzf-pos__searchwrap {
	flex: 0 1 520px;
	margin-inline: 0;
}

@media (max-width: 900px) {
	/* Stacked: the side blocks go back to sizing themselves. */
	.bzf-posbar__left,
	.bzf-posbar__actions { flex: 0 1 auto; }
}

/*
 * A generic two-column split.
 *
 * `.bzf-dash__columns` already does this, but it is named for the dashboard and
 * weighted 1.9fr/1fr for a wide chart beside a narrow list. Reports needs two
 * cards of comparable importance — the ledger and the best sellers — so this is
 * a split of its own rather than a dashboard class borrowed onto another
 * screen.
 *
 * The RIGHT card is the wider one, which is easy to get backwards: the artboard
 * fixes `Profit & loss` at 520 and lets `Best sellers` fill the remaining 600
 * of a 1136 column. This file claimed "a slightly wider-left split" and shipped
 * 1.15fr/1fr — the mirror image — until the 2026-08-22 audit measured it.
 *
 * `align-items: start`, like the dashboard's: a tall ledger beside a short list
 * is the layout working, not two cards failing to line up. The audit skips any
 * container that says this.
 */
.bzf-split {
	display: grid;
	grid-template-columns: minmax(0, 0.87fr) minmax(0, 1fr);
	gap: var(--bzf-4);
	align-items: start;
	margin-bottom: var(--bzf-5);
}

/* Two cards of comparable weight need real width each. Below this they are
   better read one after the other than as two narrow strips. */
@media (max-width: 1000px) {
	.bzf-split { grid-template-columns: minmax(0, 1fr); }
}

.bzf-split > .bzf-card { margin-bottom: 0; }

/* ------------------------------------------------------------------
   The phone till: the total and the Charge button stay on screen
   ------------------------------------------------------------------
   The artboard (Till — Phone 390) draws a compact cart bar floating near the
   bottom of the frame — a count badge, "3 items / Rs. 2,000", and Charge —
   over a full-height product grid. The product stacked the WHOLE cart panel
   below the catalogue instead: measured at 390x844 on 2026-08-22, the cart
   started at y 785 on an 844-tall screen, so a cashier had to scroll past the
   entire catalogue to see what the customer owed.

   The foot already holds the total and the Charge button, so it is pinned
   rather than rebuilt. `pos.js` keeps the cart guard, the barcode focus and
   the F-key handlers, and none of them are worth disturbing for a bar.

   Deliberate deviation from the artboard: the cart LINES still stack under the
   catalogue rather than opening from the bar. Editing a line needs the list,
   and a bar that expands into a sheet is a new interaction, not a layout fix.
   ------------------------------------------------------------------ */
@media (max-width: 640px) {
	/*
	 * The blur has to go, and not for taste: `backdrop-filter` makes an element
	 * a CONTAINING BLOCK for fixed-position descendants, so the pinned foot
	 * below anchored itself to the cart panel instead of the viewport and
	 * measured at y 1012 on an 844-tall screen — still off screen, which is the
	 * exact bug this block exists to fix.
	 *
	 * Nothing is lost. On a phone the cart is a full-width panel stacked under
	 * the catalogue, not a pane floating over a grid of product cards, so there
	 * is nothing behind it for a blur to pick up.
	 */
	.bzf-pos__cart {
		-webkit-backdrop-filter: none;
		backdrop-filter: none;
		background: var(--bzf-surface);
		box-shadow: none;
	}

	.bzf-pos__cart .bzf-cart__foot {
		position: fixed;
		/* Clear of the bottom nav that the free plan's sidebar becomes. */
		inset-inline: var(--bzf-3);
		bottom: calc(76px + var(--bzf-2));
		z-index: 40;
		margin: 0;
		display: grid;
		grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
		align-items: center;
		gap: var(--bzf-3);
		padding: var(--bzf-3) var(--bzf-4);
	}

	/* The paid plan renders no sidebar, so there is no bottom nav to clear. */
	.bzf-shell--topbar .bzf-pos__cart .bzf-cart__foot {
		bottom: var(--bzf-2);
	}

	/*
	 * Subtotal, tax and discount are all shown again in the charge sheet. In
	 * the bar they would push it past the artboard's ~90px and bury the total.
	 */
	.bzf-pos__cart .bzf-cart__foot .bzf-cart__row { display: none; }

	/* Label over amount, the way the artboard stacks "3 items" over the money. */
	.bzf-pos__cart .bzf-cart__total {
		flex-direction: column;
		align-items: flex-start;
		gap: 0;
		margin-top: 0;
		padding-top: 0;
		border-top: 0;
	}

	.bzf-pos__cart .bzf-cart__total strong { font-size: var(--bzf-text-xl); }

	.bzf-pos__cart .bzf-cart__actions { margin-top: 0; }

	/* The catalogue has to end above the pinned bar, not behind it. */
	.bzf-pos { padding-bottom: 104px; }
}

/* ------------------------------------------------------------------
   Full-screen till
   ------------------------------------------------------------------
   The artboard (Till — Desktop 1440) has ONE 68px bar and no nav. The product
   rendered the till inside the shell like every other screen, which stacked
   three bars — shell header 56 + topnav 38 + posbar 74 = 168px — above a
   catalogue that wants every pixel.

   The owner chose full screen on 2026-08-22. `pos.js` puts this class on
   <body> in render() and takes it off in cleanup, so it is scoped to the till
   and nothing else has to know about it.

   The exit button is what makes this safe rather than a trap: with the sidebar
   and the top nav gone it is the only way back, and it is in guardNav's
   selector list so leaving mid-sale still asks about the cart.
   ------------------------------------------------------------------ */
body.bzf-till-fullscreen .bzf-topbar,
body.bzf-till-fullscreen .bzf-topnav,
body.bzf-till-fullscreen .bzf-sidebar,
/* The legal footer goes too. The till is the whole window by the owner's
   call, and a cashier mid-sale is not the person who needs the terms. */
body.bzf-till-fullscreen .bzf-legalbar {
	display: none;
}

/* One column: the sidebar track has to go, not just its contents. A 248px
   column holding a hidden element is still 248px of empty screen. */
body.bzf-till-fullscreen .bzf-shell {
	grid-template-columns: minmax(0, 1fr);
}

/* Nothing above it any more, so the till is the whole window. */
body.bzf-till-fullscreen .bzf-pos {
	height: 100vh;
	height: 100dvh;
}

.bzf-posbar__exit {
	display: inline-grid;
	place-items: center;
	inline-size: 44px;
	block-size: 44px;
	flex-shrink: 0;
	margin-inline-end: var(--bzf-2);
	border: 1px solid var(--bzf-glass-edge);
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-glass-bg-2);
	color: var(--bzf-text);
	cursor: pointer;
	transition: background var(--bzf-fast), border-color var(--bzf-fast);
}

.bzf-posbar__exit:hover { background: var(--bzf-surface-2); }
.bzf-posbar__exit:focus-visible { outline: 2px solid var(--bzf-brand-600); outline-offset: 2px; }

/* The chevron points the way out, which is the other way in an RTL build. */
.bizzflow-body[dir="rtl"] .bzf-posbar__exit svg { transform: scaleX(-1); }

@media (max-width: 640px) {
	/* The bottom nav is hidden here, so the space reserved for it is dead. */
	body.bzf-till-fullscreen .bzf-shell__content.is-flush { padding-bottom: 0; }

	/* And the pinned cart bar has nothing to clear but the screen edge. */
	body.bzf-till-fullscreen .bzf-pos__cart .bzf-cart__foot { bottom: var(--bzf-2); }
}

/* ------------------------------------------------------------------
   The free plan's locked rows, and the popover that explains them
   ------------------------------------------------------------------
   Three things the owner asked for on 2026-08-24, all from one screenshot of
   the free sidebar:

   1. every row keeps its OWN icon — the padlock is a separate badge, not a
      replacement, because thirteen identical shields is not a menu;
   2. a struck-through label, so a locked row reads as unavailable at a glance
      rather than merely dim;
   3. a small popover on click instead of a silent jump to Billing.
   ------------------------------------------------------------------ */

/* The padlock sits at the end of the row, after the label takes its space. */
.bzf-nav__lock {
	display: inline-grid;
	place-items: center;
	margin-inline-start: auto;
	flex-shrink: 0;
	color: var(--bzf-text-muted);
}

/*
 * The "-------" through the label.
 *
 * `line-through` alone reads as "deleted". Dashed makes it read as a dotted
 * rule drawn over something not yet available, which is the intent — and it
 * degrades to a plain line on browsers without decoration-style rather than
 * disappearing.
 */
.bzf-nav__item.is-locked .bzf-nav__label,
.bzf-topnav__item.is-locked .bzf-nav__label {
	text-decoration: line-through;
	text-decoration-style: dashed;
	text-decoration-thickness: 1px;
	text-underline-offset: 2px;
}

/* Quiet divider above the locked block. Not a button, not focusable. */
.bzf-nav__heading {
	margin: var(--bzf-4) var(--bzf-3) var(--bzf-1);
	padding-top: var(--bzf-3);
	border-top: 1px solid var(--bzf-sidebar-border);
	font-size: var(--bzf-text-xs);
	font-weight: 600;
	letter-spacing: var(--bzf-track-wide);
	text-transform: uppercase;
	color: var(--bzf-sidebar-text-soft);
	list-style: none;
}

/* --- the popover --- */
.bzf-lockpop {
	position: fixed;
	z-index: 60;
	inline-size: min(280px, calc(100vw - 24px));
	padding: var(--bzf-4);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius-lg);
	background: var(--bzf-surface);
	box-shadow: 0 12px 32px -8px rgba(28, 28, 30, .22);
	display: flex;
	flex-direction: column;
	gap: var(--bzf-3);
}

.bzf-lockpop__head {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	color: var(--bzf-text);
	font-size: var(--bzf-text-md);
}

.bzf-lockpop__head strong { font-weight: 620; }

.bzf-lockpop__body {
	margin: 0;
	font-size: var(--bzf-text-base);
	color: var(--bzf-text-muted);
}

.bzf-lockpop__actions {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: var(--bzf-2);
}

.bzf-lockpop__actions .bzf-btn { justify-content: center; min-height: 44px; }

@media (max-width: 640px) {
	/*
	 * RETIRED in 1.49.0.
	 *
	 * This pinned the popover above the phone's bottom bar — `bottom: calc(76px
	 * + …)`, where 76px was that bar's height. There is no bottom bar any more;
	 * the sidebar it belonged to was removed with the app bar rewrite, so the
	 * offset was measuring against something that no longer exists.
	 *
	 * The panel centres itself on every width now. Left as an empty block with
	 * this note rather than deleted, because `bottom: 76px` on a phone is the
	 * kind of magic number somebody re-adds when it "looks too high".
	 */

	/* The bottom bar has room for four icons, not for a section heading. */
	.bzf-sidebar:not(.is-expanded) .bzf-nav__heading { display: none; }
}

/* ==================================================================
   1.48.0 — Plan & billing, redrawn; and Checkout, new
   ==================================================================

   Appended last for the same reason the 1.37.0 block was: source order at
   equal specificity, rather than editing rules scattered through four thousand
   lines and hoping nothing downstream overrides them.
   ================================================================== */

/* ------------------------------------------------------------------
   The plan strip
   ------------------------------------------------------------------
   Replaces a tall card that held two lines of text, a status pill and no way
   to act on any of it — the upgrade button used to sit below the fold, past
   seven usage bars.
   ------------------------------------------------------------------ */

.bzf-planhero {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--bzf-5);
	margin-bottom: var(--bzf-4);
	padding: var(--bzf-5) var(--bzf-6);
	background: var(--bzf-surface);
	border-radius: var(--bzf-radius-lg);
	box-shadow: var(--bzf-shadow-sm);
	/* A brand rule on the leading edge, which flips for RTL on its own. */
	border-inline-start: 3px solid var(--bzf-brand-solid);
}

.bzf-planhero__plan { flex: 1 1 200px; min-width: 0; }

.bzf-planhero__name {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--bzf-3);
	font-size: var(--bzf-text-lg);
	font-weight: 660;
	letter-spacing: var(--bzf-track-tight);
}

.bzf-planhero__meta {
	margin-top: 3px;
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

.bzf-planhero__price { text-align: end; }

.bzf-planhero__amt {
	font-size: var(--bzf-text-2xl);
	font-weight: 680;
	letter-spacing: var(--bzf-track-tight);
}

.bzf-planhero__per { font-size: var(--bzf-text-sm); color: var(--bzf-text-muted); }

/* ------------------------------------------------------------------
   Usage meters
   ------------------------------------------------------------------
   Seven identical full-width bars in a column meant "0 / 100" and "1 / 1"
   looked the same, so the metrics actually at their limit did not stand out.
   The number leads; the bar is a hint under it.
   ------------------------------------------------------------------ */

.bzf-meters {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
	gap: 1px;
	background: var(--bzf-border-soft);
	border-radius: var(--bzf-radius);
	overflow: hidden;
}

.bzf-meter { background: var(--bzf-surface); padding: var(--bzf-3) var(--bzf-4); }

.bzf-meter__k {
	display: block;
	margin-bottom: 5px;
	font-size: var(--bzf-text-xs);
	color: var(--bzf-text-muted);
}

.bzf-meter__v {
	display: flex;
	align-items: baseline;
	gap: 3px;
	font-size: var(--bzf-text-lg);
	font-weight: 640;
}

.bzf-meter__v small { font-size: var(--bzf-text-sm); font-weight: 400; color: var(--bzf-text-muted); }

.bzf-meter__track {
	height: 3px;
	margin-top: var(--bzf-2);
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	overflow: hidden;
}

.bzf-meter__fill {
	height: 100%;
	border-radius: inherit;
	background: var(--bzf-brand-solid);
	transition: width var(--bzf-slow);
}

/* Colour only once it means something. */
.bzf-meter.is-high .bzf-meter__fill { background: var(--bzf-warning); }
.bzf-meter.is-high .bzf-meter__v    { color: var(--bzf-warning-fg); }
.bzf-meter.is-full .bzf-meter__fill { background: var(--bzf-danger); }
.bzf-meter.is-full .bzf-meter__v    { color: var(--bzf-danger-fg); }
.bzf-meter.is-idle .bzf-meter__fill { background: var(--bzf-border); }

/* ------------------------------------------------------------------
   Plan cards
   ------------------------------------------------------------------ */

.bzf-plans { margin-top: var(--bzf-5); }

/*
 * The ribbon, in flow.
 *
 * .bzf-plan__badge — which this replaces — was `position: absolute; top: -9px`,
 * so it hung above the card's own top edge and printed straight across
 * whatever sat above it. On a real install that was the "ALL PLANS" heading.
 * A first child in normal flow cannot collide with anything.
 */
.bzf-plan__ribbon {
	align-self: flex-start;
	padding: 3px 10px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	color: var(--bzf-text-muted);
	font-size: var(--bzf-text-xs);
	font-weight: 620;
	letter-spacing: var(--bzf-track-wide);
	text-transform: uppercase;
}

.bzf-plan__ribbon--featured { background: var(--bzf-brand-50); color: var(--bzf-brand-700); }

/* The plan being sold, when it is not the one you are on. */
.bzf-plan.is-featured {
	border-color: var(--bzf-brand-300);
	box-shadow:
		0 2px 10px -2px rgba(var(--bzf-brand-rgb), .12),
		0 16px 34px -10px rgba(var(--bzf-brand-rgb), .16);
}

.bzf-plan__more { font-size: var(--bzf-text-sm); color: var(--bzf-text-subtle); }

/* Pins the button to the bottom so cards of different heights line up. */
.bzf-plan__cta { margin-top: auto; padding-top: var(--bzf-2); }
.bzf-plan__cta .bzf-btn { width: 100%; }

.bzf-plan__features li { align-items: flex-start; }
.bzf-plan__features svg { margin-top: 3px; }

/* ==================================================================
   Checkout
   ================================================================== */

.bzf-buy {
	background: var(--bzf-surface-2);
	border-radius: var(--bzf-radius-lg);
	box-shadow: var(--bzf-shadow-sm);
	overflow: hidden;
}

.bzf-buy__bar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: var(--bzf-4);
	padding: var(--bzf-3) var(--bzf-6);
	background: var(--bzf-surface);
	border-bottom: 1px solid var(--bzf-border);
}

.bzf-buy__brand {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	font-size: var(--bzf-text-base);
	font-weight: 620;
}

.bzf-buy__mark {
	display: grid;
	place-items: center;
	width: 30px;
	height: 30px;
	border-radius: var(--bzf-radius-sm);
	background: linear-gradient(140deg, var(--bzf-brand-300), var(--bzf-brand-solid));
	color: var(--bzf-on-brand);
}

.bzf-buy__secure {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

/* --- the three steps --- */

/*
 * The steps NEVER shrink.
 *
 * Every child of the checkout bar had the flex default `shrink: 1`, so the
 * three-step trail was squeezed from the 200px it needs down to 168 and the
 * last label was cut mid-word — the shipped bar read "Paymer". Measured, not
 * guessed: scrollWidth 200 against clientWidth 168.
 *
 * The brand and the "Secure" note can give ground; the thing telling you where
 * you are in a purchase cannot.
 */
.bzf-buy__steps {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	flex-shrink: 0;
	/*
	 * The floor that actually fixed it.
	 *
	 * `flex-shrink: 0` alone was not enough: the trail still resolved to 168px
	 * against a 200px content width, and each step was squeezed to 28px — the
	 * width of its number disc — so every label painted OUTSIDE its own box and
	 * over the next disc. On the shipped bar that read as "Paymer".
	 *
	 * `max-content` is the honest minimum: this row is three words and two
	 * rules, and there is no width at which showing half of them is right. The
	 * phone rule below hides the labels instead, which is a different answer to
	 * the same problem and the correct one there.
	 */
	min-width: max-content;
}

/* A step is a disc plus a word. Neither is optional, so neither shrinks. */
.bzf-buy__step { flex-shrink: 0; }

/* These two may give ground instead — the brand and a "Secure" note can wrap
   or ellipsis without anybody losing their place in a purchase. */
.bzf-buy__brand { flex-shrink: 1; min-width: 0; }
.bzf-buy__secure { flex-shrink: 1; min-width: 0; }

.bzf-buy__step {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-subtle);
}

.bzf-buy__step__n {
	display: grid;
	place-items: center;
	width: 22px;
	height: 22px;
	flex-shrink: 0;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	color: var(--bzf-text-muted);
	font-size: var(--bzf-text-xs);
	font-weight: 620;
}

.bzf-buy__step.is-done .bzf-buy__step__n { background: var(--bzf-success-bg); color: var(--bzf-success-fg); }
.bzf-buy__step.is-now  .bzf-buy__step__n { background: var(--bzf-brand-solid); color: var(--bzf-on-brand); }
.bzf-buy__step.is-now  { color: var(--bzf-text); font-weight: 560; }

.bzf-buy__step__rule { width: 26px; height: 1px; background: var(--bzf-border); }

/* --- the two columns --- */

.bzf-buy__body {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 300px;
	align-items: start;
	gap: var(--bzf-5);
	padding: var(--bzf-6);
}

.bzf-buy__pane { min-width: 0; }

.bzf-buy__h { font-size: var(--bzf-text-xl); font-weight: 660; }

.bzf-buy__sub {
	margin: var(--bzf-1) 0 var(--bzf-4);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

.bzf-buy__slab {
	margin: var(--bzf-6) 0 var(--bzf-3);
	font-size: var(--bzf-text-xs);
	font-weight: 600;
	letter-spacing: var(--bzf-track-wide);
	text-transform: uppercase;
	color: var(--bzf-text-muted);
}

.bzf-buy__gets { display: grid; gap: var(--bzf-2); font-size: var(--bzf-text-sm); }

.bzf-buy__gets li { display: flex; align-items: flex-start; gap: var(--bzf-2); }
.bzf-buy__gets svg { flex-shrink: 0; margin-top: 3px; color: var(--bzf-success); }

.bzf-buy__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--bzf-3);
	margin-top: var(--bzf-6);
}

.bzf-buy__actions--center { justify-content: center; }

/* --- billing cycle --- */

.bzf-cycle { display: grid; grid-template-columns: 1fr 1fr; gap: var(--bzf-3); }

.bzf-cycle__opt {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-height: 44px;
	padding: var(--bzf-3) var(--bzf-4);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius);
	background: var(--bzf-surface);
	font: inherit;
	color: inherit;
	text-align: start;
	cursor: pointer;
	transition: border-color var(--bzf-fast), background var(--bzf-fast);
}

/*
 * The selected ring is a box-shadow, not a second border.
 *
 * A 2px border on selection and 1px otherwise makes the card change size and
 * everything beside it jump a pixel.
 */
.bzf-cycle__opt.is-on {
	border-color: var(--bzf-brand-solid);
	box-shadow: 0 0 0 1px var(--bzf-brand-solid);
	background: var(--bzf-brand-50);
}

.bzf-cycle__t { font-size: var(--bzf-text-base); font-weight: 580; }
.bzf-cycle__p { font-size: var(--bzf-text-sm); color: var(--bzf-text-muted); }

.bzf-cycle__save {
	position: absolute;
	top: var(--bzf-2);
	inset-inline-end: var(--bzf-3);
	padding: 2px 7px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-success-bg);
	color: var(--bzf-success-fg);
	font-size: var(--bzf-text-xs);
	font-weight: 620;
}

/* --- payment methods --- */

.bzf-pay { display: grid; gap: var(--bzf-3); }

.bzf-pay__opt {
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius);
	background: var(--bzf-surface);
	overflow: hidden;
}

.bzf-pay__opt.is-on {
	border-color: var(--bzf-brand-solid);
	box-shadow: 0 0 0 1px var(--bzf-brand-solid);
}

.bzf-pay__pick {
	display: grid;
	grid-template-columns: 18px minmax(0, 1fr) auto;
	align-items: center;
	gap: var(--bzf-3);
	width: 100%;
	min-height: 44px;
	padding: var(--bzf-4);
	border: 0;
	background: transparent;
	font: inherit;
	color: inherit;
	text-align: start;
	cursor: pointer;
}

/*
 * A method that cannot take money is dimmed with a COLOUR, never opacity —
 * the same rule the locked sidebar rows follow, and for the same reason:
 * opacity multiplies whatever is behind it, so the contrast would depend on
 * the surface instead of on a token.
 */
.bzf-pay__opt.is-off .bzf-pay__pick { cursor: not-allowed; }
.bzf-pay__opt.is-off .bzf-pay__t { color: var(--bzf-text-muted); }

.bzf-pay__radio {
	width: 18px;
	height: 18px;
	border-radius: var(--bzf-radius-full);
	border: 1.5px solid var(--bzf-border-strong);
	transition: border-color var(--bzf-fast), border-width var(--bzf-fast);
}

.bzf-pay__opt.is-on .bzf-pay__radio { border-color: var(--bzf-brand-solid); border-width: 5px; }

.bzf-pay__copy { min-width: 0; }

.bzf-pay__t { display: block; font-size: var(--bzf-text-base); font-weight: 560; }
.bzf-pay__d { display: block; margin-top: 1px; font-size: var(--bzf-text-sm); color: var(--bzf-text-muted); }

.bzf-pay__tag {
	flex-shrink: 0;
	padding: 2px 8px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	color: var(--bzf-text-muted);
	font-size: var(--bzf-text-xs);
	font-weight: 620;
	letter-spacing: var(--bzf-track-wide);
	text-transform: uppercase;
	white-space: nowrap;
}

.bzf-pay__tag--live { background: var(--bzf-success-bg); color: var(--bzf-success-fg); }
.bzf-pay__tag--soon { background: var(--bzf-warning-bg); color: var(--bzf-warning-fg); }

.bzf-pay__body {
	padding-top: var(--bzf-3);
	margin: 0 var(--bzf-4) var(--bzf-4);
	border-top: 1px dashed var(--bzf-border);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
	line-height: 1.6;
}

.bzf-pay__rows { display: grid; gap: 6px; margin-top: var(--bzf-2); }

.bzf-pay__row {
	display: flex;
	justify-content: space-between;
	gap: var(--bzf-4);
	padding: 7px var(--bzf-3);
	border-radius: var(--bzf-radius-xs);
	background: var(--bzf-surface-2);
	color: var(--bzf-text);
}

.bzf-pay__row b { font-weight: 600; }
.bzf-pay__note { margin-top: var(--bzf-2); }

/*
 * The converted card charge.
 *
 * Warning-toned rather than muted, and that is the point of it: the figure
 * differs from the rupee total two inches away, and a line that explains why
 * has to be read, not skimmed past in grey. Not an alert box either — nothing
 * has gone wrong, it is simply a fact about the charge.
 */
.bzf-pay__fx,
.bzf-buy__fx {
	margin-top: var(--bzf-2);
	padding: 8px var(--bzf-3);
	border-radius: var(--bzf-radius-xs);
	background: var(--bzf-warning-bg);
	color: var(--bzf-warning-fg);
	font-size: var(--bzf-text-sm);
	line-height: 1.55;
}

.bzf-buy__fx { margin-bottom: var(--bzf-3); }

/* --- order summary --- */

.bzf-sum {
	position: sticky;
	top: var(--bzf-4);
	padding: var(--bzf-5);
	background: var(--bzf-surface);
	border-radius: var(--bzf-radius-lg);
	box-shadow: var(--bzf-shadow-sm);
}

.bzf-sum h3 { font-size: var(--bzf-text-base); font-weight: 620; margin-bottom: var(--bzf-3); }

.bzf-sum__items {
	display: grid;
	gap: var(--bzf-2);
	padding-bottom: var(--bzf-3);
	margin-bottom: var(--bzf-2);
	border-bottom: 1px solid var(--bzf-border-soft);
}

.bzf-sum__item { display: flex; align-items: flex-start; gap: var(--bzf-3); }

.bzf-sum__thumb {
	display: grid;
	place-items: center;
	width: 36px;
	height: 36px;
	flex-shrink: 0;
	border-radius: var(--bzf-radius-sm);
	background: linear-gradient(140deg, var(--bzf-brand-300), var(--bzf-brand-solid));
	color: var(--bzf-on-brand);
}

.bzf-sum__iname { font-size: var(--bzf-text-sm); font-weight: 560; }
.bzf-sum__idesc { font-size: var(--bzf-text-xs); color: var(--bzf-text-muted); }

.bzf-sum__line {
	display: flex;
	justify-content: space-between;
	gap: var(--bzf-3);
	padding: 5px 0;
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

.bzf-sum__line b { color: var(--bzf-text); font-weight: 550; }

.bzf-sum__total {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--bzf-3);
	margin-top: var(--bzf-3);
	padding-top: var(--bzf-3);
	border-top: 1px solid var(--bzf-border);
}

.bzf-sum__total span { font-size: var(--bzf-text-base); font-weight: 560; }
.bzf-sum__total b { font-size: var(--bzf-text-xl); font-weight: 700; }

.bzf-sum__note {
	margin-top: var(--bzf-3);
	font-size: var(--bzf-text-xs);
	line-height: 1.5;
	color: var(--bzf-text-subtle);
}

/* --- the done step --- */

.bzf-buy__done {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--bzf-3);
	padding: var(--bzf-8) var(--bzf-5) var(--bzf-4);
	text-align: center;
}

.bzf-buy__tick {
	display: grid;
	place-items: center;
	width: 54px;
	height: 54px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-success-bg);
	color: var(--bzf-success-fg);
}

.bzf-buy__done h2 { font-size: var(--bzf-text-xl); font-weight: 670; }

.bzf-buy__donetext {
	max-width: 46ch;
	font-size: var(--bzf-text-base);
	color: var(--bzf-text-muted);
}

.bzf-buy__ref {
	padding: var(--bzf-2) var(--bzf-4);
	border-radius: var(--bzf-radius-sm);
	background: var(--bzf-surface-2);
	font-family: var(--bzf-font-mono);
	font-size: var(--bzf-text-base);
	letter-spacing: var(--bzf-track-wide);
	/* Long enough to matter and short enough to type — make it selectable. */
	user-select: all;
}

.bzf-buy__next {
	display: grid;
	gap: 1px;
	width: 100%;
	max-width: 460px;
	margin-top: var(--bzf-2);
	background: var(--bzf-border-soft);
	border-radius: var(--bzf-radius);
	overflow: hidden;
	text-align: start;
}

.bzf-buy__next li {
	display: grid;
	grid-template-columns: 20px minmax(0, 1fr);
	align-items: start;
	gap: var(--bzf-3);
	padding: var(--bzf-3) var(--bzf-4);
	background: var(--bzf-surface);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

.bzf-buy__next b { display: block; color: var(--bzf-text); font-weight: 560; }

.bzf-buy__nextn {
	display: grid;
	place-items: center;
	width: 20px;
	height: 20px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	color: var(--bzf-text-muted);
	font-size: var(--bzf-text-xs);
	font-weight: 620;
}

/* ------------------------------------------------------------------
   Narrow
   ------------------------------------------------------------------ */

@media (max-width: 900px) {
	/* The summary stops being a column and becomes a block above the total. */
	.bzf-buy__body { grid-template-columns: minmax(0, 1fr); }
	.bzf-sum { position: static; }
}

@media (max-width: 640px) {
	.bzf-buy__bar { padding: var(--bzf-3) var(--bzf-4); }
	.bzf-buy__body { padding: var(--bzf-4); }
	.bzf-cycle { grid-template-columns: 1fr; }
	.bzf-planhero { padding: var(--bzf-4); }
	.bzf-planhero__price { text-align: start; }

	/* The step labels go; the numbered discs still say where you are. */
	.bzf-buy__step__label { display: none; }
	.bzf-buy__step__rule { width: 14px; }

	/* A tag under the name reads better than a squeezed third column. */
	.bzf-pay__pick { grid-template-columns: 18px minmax(0, 1fr); }
	.bzf-pay__tag { grid-column: 2; justify-self: start; }
}

/* ==================================================================
   1.49.0 — ONE DARK APP BAR, BOTH PLANS
   ==================================================================

   Replaces two layouts at once: the free plan's 248px sidebar and the paid
   plan's 68px top bar stacked on a 46px menu row. Appended last so it wins on
   source order over both of those, which are still in this file above.

   EVERY COLOUR HERE IS MEASURED against the bar, not against the light tokens
   the rest of the product uses. The ones that decided a choice:

     nav label   #ADA298 on #1B1612   7.18:1
     locked      #948A80              5.30:1
     active pill #C94C0C   white on it 4.64:1 · pill against the bar 3.87:1
     NOT #EA6412 — white on brand-500 is 3.32:1, which fails AA. That is why
     the active pill is brand-solid and not the brighter orange.
   ================================================================== */

.bzf-shell--topbar { grid-template-columns: minmax(0, 1fr); }

.bzf-appbar {
	position: sticky;
	top: 0;
	z-index: 20;
	display: flex;
	align-items: center;
	gap: var(--bzf-4);
	height: var(--bzf-appbar-h);
	padding: 0 var(--bzf-7);
	/*
	 * BizzFlow's own warm near-black, not the reference product's navy. A blue
	 * bar would put a second brand colour back into a product that has just
	 * been moved onto burnt orange, and the two would fight.
	 */
	background: var(--bzf-appbar-bg);
	/* No glass here. A blurred bar over a scrolling product grid was legible
	   when it was white; over near-black it turns into a smear. */
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	border: 0;
}

.bzf-appbar__left { display: flex; align-items: center; gap: var(--bzf-3); flex-shrink: 0; }

.bzf-appbar__brand { display: flex; align-items: center; gap: var(--bzf-3); min-width: 0; }

.bzf-appbar__mark {
	display: grid;
	place-items: center;
	width: 36px;
	height: 36px;
	flex-shrink: 0;
	border-radius: var(--bzf-radius);
	background: linear-gradient(140deg, var(--bzf-brand-300), var(--bzf-brand-solid));
	color: var(--bzf-on-brand);
	/* Set by applyLogo() when the shop has uploaded one. */
	background-size: cover;
	background-position: center;
}

.bzf-appbar__shop {
	font-size: var(--bzf-text-md);
	font-weight: 620;
	letter-spacing: var(--bzf-track-tight);
	color: var(--bzf-appbar-fg);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 22ch;
}

/* The hamburger is phone-only; the media query at the bottom reveals it. */
.bzf-appbar__burger { display: none; }

/* ------------------------------------------------------------------
   The six groups
   ------------------------------------------------------------------ */

.bzf-appbar .bzf-topnav {
	/* It was its own sticky strip. Inside the bar it is just a row. */
	position: static;
	flex: 1;
	min-width: 0;
	background: none;
	border: 0;
	box-shadow: none;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	display: flex;
	justify-content: center;
}

.bzf-appbar .bzf-topnav__list {
	display: flex;
	align-items: center;
	gap: var(--bzf-1);
	margin: 0;
	padding: 0;
	list-style: none;
	overflow: visible;
}

.bzf-appbar .bzf-topnav__trigger {
	display: flex;
	align-items: center;
	gap: 7px;
	height: 40px;
	padding: 0 var(--bzf-3);
	border: 0;
	border-radius: var(--bzf-radius);
	background: transparent;
	font: inherit;
	font-size: var(--bzf-text-base);
	font-weight: 500;
	white-space: nowrap;
	color: var(--bzf-appbar-dim);
	cursor: pointer;
	transition: background var(--bzf-fast), color var(--bzf-fast);
}

.bzf-appbar .bzf-topnav__trigger:hover,
.bzf-appbar .bzf-topnav__trigger.is-open {
	background: rgba(255, 255, 255, .07);
	color: var(--bzf-appbar-fg);
}

.bzf-appbar .bzf-topnav__trigger.is-active {
	background: var(--bzf-brand-solid);
	color: var(--bzf-on-brand);
	font-weight: 600;
}

.bzf-appbar .bzf-topnav__trigger.is-active:hover { background: var(--bzf-brand-solid); }

/*
 * A group with no unlocked child at all — Stock and Buying on the free plan.
 *
 * Dimmed with a COLOUR, never opacity: opacity multiplies whatever is behind
 * it, so the contrast would depend on the surface instead of on a token. Same
 * rule the locked menu rows follow.
 */
.bzf-appbar .bzf-topnav__trigger.is-locked { color: var(--bzf-appbar-locked); }
.bzf-appbar .bzf-topnav__trigger.is-locked:hover {
	background: rgba(255, 255, 255, .05);
	color: var(--bzf-appbar-locked);
}

.bzf-appbar .bzf-nav__icon { color: inherit; opacity: .85; }
.bzf-appbar .bzf-topnav__trigger.is-active .bzf-nav__icon { opacity: 1; }
.bzf-appbar .bzf-nav__label { color: inherit; }
.bzf-appbar .bzf-topnav__caret { display: inline-flex; opacity: .7; }
.bzf-appbar .bzf-topnav__lock { display: inline-flex; opacity: .85; }

/* --- the dropdown --- */

.bzf-appbar .bzf-topnav__menu {
	position: absolute;
	top: calc(100% + 8px);
	inset-inline-start: 0;
	z-index: 40;
	min-width: 268px;
	padding: var(--bzf-2);
	border-radius: var(--bzf-radius-lg);
	/* One step up from the bar, so the panel separates from it. */
	background: var(--bzf-appbar-raised);
	border: 1px solid rgba(255, 255, 255, .09);
	box-shadow: 0 16px 34px -8px rgba(0, 0, 0, .45);
}

.bzf-appbar .bzf-topnav__group { position: relative; }

.bzf-appbar .bzf-topnav__item {
	display: flex;
	align-items: center;
	gap: var(--bzf-3);
	width: 100%;
	min-height: 44px;
	padding: 0 var(--bzf-3);
	border: 0;
	border-radius: 9px;
	background: none;
	font: inherit;
	font-size: var(--bzf-text-base);
	font-weight: 500;
	text-align: start;
	color: var(--bzf-appbar-fg);
	cursor: pointer;
	transition: background var(--bzf-fast), color var(--bzf-fast);
}

.bzf-appbar .bzf-topnav__item:hover { background: rgba(255, 255, 255, .07); }

.bzf-appbar .bzf-topnav__item.is-active {
	background: var(--bzf-brand-solid);
	color: var(--bzf-on-brand);
}

.bzf-appbar .bzf-topnav__item.is-locked { color: var(--bzf-appbar-locked); }
.bzf-appbar .bzf-topnav__item.is-locked:hover {
	background: rgba(255, 255, 255, .05);
	color: var(--bzf-appbar-fg);
}

.bzf-appbar .bzf-topnav__item .bzf-nav__lock { color: inherit; opacity: .85; }

/* ------------------------------------------------------------------
   The account cluster
   ------------------------------------------------------------------ */

.bzf-appbar__right {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	flex-shrink: 0;
	margin-inline-start: auto;
}

/*
 * Every icon control on the bar, restyled for the dark ground.
 *
 * Scoped rather than a new class: the bell, the support launcher and the
 * language toggle are built elsewhere and none of them knows it now sits on
 * near-black.
 */
/*
 * NO `display` IN THIS RULE.
 *
 * It used to set `display: grid`, and it sits ~150 lines BELOW
 * `.bzf-appbar__burger { display: none; }`. Same specificity, later in source,
 * so it won — and the hamburger appeared on desktop, where clicking it did
 * nothing because the drawer rules only exist under 900px. That is the shipped
 * "structure icon does not work" the owner reported.
 *
 * Sizing and skin are shared; who is visible is each control's own business.
 */
.bzf-appbar .bzf-iconbtn,
.bzf-appbar .bzf-langtoggle,
.bzf-appbar__burger {
	width: 40px;
	height: 40px;
	min-width: 40px;
	place-items: center;
	border: 0;
	border-radius: var(--bzf-radius);
	background: rgba(255, 255, 255, .07);
	color: var(--bzf-appbar-dim);
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	cursor: pointer;
	transition: background var(--bzf-fast), color var(--bzf-fast);
}

/*
 * FLEX, NOT GRID.
 *
 * `display: grid; place-items: center` looks equivalent for a button holding
 * one icon, and is not for one holding two children: the language toggle is an
 * icon PLUS its label, and grid stacked them into two rows. On the live build
 * that printed a globe with "اردو" squashed underneath it — the "icons shape
 * kharab" the owner reported.
 *
 * inline-flex centres a single child just as well and keeps a pair side by
 * side, which is what `.bzf-langtoggle`'s own `gap: 6px` was always assuming.
 */
.bzf-appbar .bzf-iconbtn,
.bzf-appbar .bzf-langtoggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

.bzf-appbar .bzf-langtoggle { width: auto; padding: 0 var(--bzf-3); font-weight: 560; }

.bzf-appbar .bzf-iconbtn:hover,
.bzf-appbar .bzf-langtoggle:hover,
.bzf-appbar__burger:hover {
	background: rgba(255, 255, 255, .14);
	color: var(--bzf-appbar-fg);
}

/* The badge needs the BAR behind its ring, not the light surface it assumed. */
.bzf-appbar .bzf-bell__badge { border-color: var(--bzf-appbar-bg); }

.bzf-appbar .bzf-account__trigger {
	background: rgba(255, 255, 255, .07);
	border-color: transparent;
	color: var(--bzf-appbar-fg);
}

.bzf-appbar .bzf-account__trigger:hover { background: rgba(255, 255, 255, .14); }
.bzf-appbar .bzf-account__name { color: var(--bzf-appbar-fg); }
.bzf-appbar .bzf-account__role { color: var(--bzf-appbar-dim); }

/* --- the plan chip, which the sidebar footer used to carry --- */

/*
 * 40px, matching every other control on the bar.
 *
 * It was 32, and sat visibly short beside four 40px neighbours — the first
 * thing the owner pointed at when they said the header had no alignment. A bar
 * of controls only reads as a row if they are the same height.
 */
.bzf-planchip {
	display: inline-flex;
	align-items: center;
	gap: var(--bzf-2);
	height: 40px;
	padding: 0 var(--bzf-4);
	border-radius: var(--bzf-radius-full);
	background: rgba(255, 255, 255, .08);
	border: 1px solid rgba(255, 255, 255, .14);
	font: inherit;
	font-size: var(--bzf-text-sm);
	font-weight: 600;
	color: var(--bzf-appbar-dim);
	white-space: nowrap;
	cursor: pointer;
	transition: background var(--bzf-fast), border-color var(--bzf-fast);
}

.bzf-planchip:hover { background: rgba(255, 255, 255, .14); border-color: rgba(255, 255, 255, .24); }
.bzf-planchip__name { color: inherit; }
/* brand-300, not brand-solid: the deep orange disappears on near-black. */
.bzf-planchip__hint { color: var(--bzf-brand-300); }

/* ------------------------------------------------------------------
   The lock popover, on a dark anchor
   ------------------------------------------------------------------ */

/*
 * CENTRED, AND BIGGER — the owner's call after using it.
 *
 * It used to hang off the row that opened it, which made sense when that row
 * was in a 248px sidebar and had a whole page of empty space beside it. Hanging
 * off a dropdown inside a bar, it landed in the top-left corner of a 1900px
 * screen at 280px wide, and read as a tooltip that had escaped rather than as
 * an answer. It is a small centred panel now.
 *
 * `position: fixed` with a translate, NOT top/left maths in JS: nothing here
 * has to know where the anchor was, so the whole positioning block in
 * openLockPopover() went with it.
 */
.bzf-lockpop {
	inset-inline-start: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	inline-size: min(420px, calc(100vw - 32px));
	padding: var(--bzf-6);
	gap: var(--bzf-4);
	background: var(--bzf-appbar-raised);
	border-color: rgba(255, 255, 255, .1);
	box-shadow:
		0 24px 60px -12px rgba(0, 0, 0, .6),
		0 0 0 100vmax rgba(27, 22, 18, .45);
}

/* RTL: translate is physical, so the sign has to flip with the direction. */
[dir="rtl"] .bzf-lockpop { transform: translate(50%, -50%); }

.bzf-lockpop__head {
	font-size: var(--bzf-text-lg);
	color: var(--bzf-appbar-fg);
}

.bzf-lockpop__body {
	font-size: var(--bzf-text-md);
	line-height: 1.55;
	color: var(--bzf-appbar-dim);
}

.bzf-lockpop__actions .bzf-btn { font-size: var(--bzf-text-base); }

/*
 * The scrim is a BOX-SHADOW, not a pseudo-element.
 *
 * It was `::before { position: fixed; inset: 0; z-index: -1 }` — a square that
 * covered the viewport and sat behind the panel. The panel's own background is
 * clipped by its border-radius; a pseudo-element is not. So at each of the four
 * rounded corners the square scrim showed through the curve, and the panel
 * appeared to have a dark notch bitten out of every corner. That is exactly
 * what the owner circled.
 *
 * A spread shadow follows the element's border SHAPE, radius included, so the
 * hole it leaves is the panel's own rounded outline and no corner can escape
 * it. `100vmax` covers the viewport from anywhere on it.
 */

/* ------------------------------------------------------------------
   Phone
   ------------------------------------------------------------------ */

@media (max-width: 900px) {
	.bzf-appbar {
		height: var(--bzf-appbar-h-phone);
		padding: 0 var(--bzf-4);
		gap: var(--bzf-2);
		flex-wrap: wrap;
	}

	.bzf-appbar__burger { display: grid; }

	/* The shop's name is the first thing to go — the mark still identifies it. */
	.bzf-appbar__shop { display: none; }

	/*
	 * The six groups become a drawer.
	 *
	 * Not `display:none` on the nav — it stays in the DOM and in the tab order
	 * only when open, so a keyboard user is never tabbing through a menu that
	 * is not on screen.
	 */
	.bzf-appbar .bzf-topnav {
		display: none;
		order: 3;
		flex: 1 0 100%;
		justify-content: flex-start;
		padding: var(--bzf-2) 0 var(--bzf-4);
	}

	.bzf-appbar.is-drawer-open {
		height: auto;
		padding-bottom: var(--bzf-2);
	}

	.bzf-appbar.is-drawer-open .bzf-topnav { display: block; }

	.bzf-appbar.is-drawer-open .bzf-topnav__list {
		flex-direction: column;
		align-items: stretch;
		gap: 2px;
	}

	.bzf-appbar .bzf-topnav__trigger {
		width: 100%;
		min-height: 44px;
		justify-content: flex-start;
	}

	/* In a drawer the panel is a nested block, not a floating layer. */
	.bzf-appbar .bzf-topnav__menu {
		position: static;
		min-width: 0;
		margin: 2px 0 var(--bzf-2) var(--bzf-6);
		box-shadow: none;
		background: rgba(255, 255, 255, .04);
	}

	/* The chip is the upgrade route and must survive the squeeze. */
	.bzf-appbar .bzf-langtoggle { display: none; }
}

@media (max-width: 420px) {
	.bzf-planchip__name { display: none; }
}

/*
 * Touch.
 *
 * The bar's controls rest at 40px and the plan chip at 32, which is right for a
 * mouse and wrong for a thumb. The file's existing `pointer: coarse` block
 * covers .bzf-btn and .bzf-input but knew nothing about these — a tablet at
 * 1024px would have got 40px targets, below the phone breakpoint that would
 * otherwise have caught it.
 */
@media (pointer: coarse) {
	.bzf-appbar .bzf-topnav__trigger,
	.bzf-appbar .bzf-iconbtn,
	.bzf-appbar .bzf-langtoggle,
	.bzf-appbar__burger,
	.bzf-planchip {
		min-height: 44px;
	}

	.bzf-appbar .bzf-iconbtn,
	.bzf-appbar .bzf-langtoggle,
	.bzf-appbar__burger {
		min-width: 44px;
	}
}

/* ------------------------------------------------------------------
   Settings → Sound
   ------------------------------------------------------------------ */

/*
 * A switch with its explanation under it.
 *
 * The whole row is the label, so the hint is part of the target — a 44px
 * checkbox is ugly, but a 44px row containing a 17px checkbox is both correct
 * and normal-looking.
 */
.bzf-switchrow {
	display: flex;
	align-items: flex-start;
	gap: var(--bzf-3);
	min-height: 44px;
	padding: var(--bzf-3) 0;
	border-bottom: 1px solid var(--bzf-border-soft);
	cursor: pointer;
}

.bzf-switchrow:last-of-type { border-bottom: 0; }

.bzf-switchrow input { margin-top: 2px; flex-shrink: 0; }

.bzf-switchrow__label {
	display: block;
	font-size: var(--bzf-text-base);
	font-weight: 550;
	color: var(--bzf-text);
}

.bzf-switchrow__hint {
	display: block;
	margin-top: 2px;
	font-size: var(--bzf-text-sm);
	line-height: 1.5;
	color: var(--bzf-text-muted);
}

.bzf-rangerow {
	display: flex;
	align-items: center;
	gap: var(--bzf-4);
}

.bzf-range {
	flex: 1;
	min-width: 0;
	/* 44px of grabbable height around a 4px track — the thumb is what the
	   finger aims at, and a bare input[type=range] is 16px tall. */
	height: 44px;
	accent-color: var(--bzf-brand-solid);
	cursor: pointer;
}

.bzf-rangerow .bzf-num {
	min-width: 4ch;
	text-align: end;
	font-size: var(--bzf-text-sm);
	font-weight: 560;
	color: var(--bzf-text-muted);
}

/*
 * The tune picker.
 *
 * A grid of real buttons rather than a <select>: choosing a sound means hearing
 * it, and a native dropdown will not play anything until it has closed. Twenty
 * four of them wrap into however many columns fit.
 */
.bzf-tunes {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
	gap: var(--bzf-2);
	margin-top: var(--bzf-3);
}

.bzf-tune {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-2);
	min-height: 44px;
	padding: 0 var(--bzf-3);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius);
	background: var(--bzf-surface);
	font: inherit;
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text);
	text-align: start;
	cursor: pointer;
	transition: border-color var(--bzf-fast), background var(--bzf-fast);
}

.bzf-tune:hover { border-color: var(--bzf-border-strong); background: var(--bzf-surface-2); }

/*
 * The chosen one. A ring rather than a thicker border, so the tile does not
 * change size when it is picked and shuffle the twenty three beside it.
 */
.bzf-tune.is-on {
	border-color: var(--bzf-brand-solid);
	box-shadow: 0 0 0 1px var(--bzf-brand-solid);
	background: var(--bzf-brand-50);
	font-weight: 560;
}

.bzf-tune.is-on svg { color: var(--bzf-brand-700); flex-shrink: 0; }

.bzf-tune__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ------------------------------------------------------------------
   Support panel — redrawn 1.49.1
   ------------------------------------------------------------------
   Two real bugs went with the redesign:

     · `right: 16px` was PHYSICAL, so on the Urdu build the panel opened on the
       wrong side of the screen — away from the button that opened it.
     · `top: 64px` was measured against the old top bar. The app bar is 72px
       since 1.49.0, so the panel was tucked 8px underneath it.

   Both are `inset-inline-end` and a token now, so neither can drift again.
   ------------------------------------------------------------------ */

.bzf-chatpanel {
	position: fixed;
	/*
	 * Logical, so it follows the language.
	 *
	 * There is no `right: auto` here on purpose. Adding one to "cancel" the old
	 * physical rule cancels THIS rule instead: in LTR `right` is the physical
	 * longhand `inset-inline-end` resolves to, and a later `right: auto` in the
	 * same block wins. That put the panel against the left edge of a 1900px
	 * screen. The old rule is already overridden by source order — nothing
	 * needs cancelling.
	 */
	inset-inline-end: var(--bzf-4);
	/* Hangs off the bar rather than guessing at its height. */
	top: calc(var(--bzf-appbar-h) + var(--bzf-2));
	z-index: 60;
	width: min(400px, calc(100vw - var(--bzf-8)));
	max-height: min(600px, calc(100vh - var(--bzf-appbar-h) - var(--bzf-6)));
	border-radius: var(--bzf-radius-xl);
	box-shadow: 0 24px 60px -14px rgba(28, 28, 30, .30), 0 2px 8px -2px rgba(28, 28, 30, .10);
	overflow: hidden;
}

/*
 * NO POINTER ARROW.
 *
 * There was one, aimed 26px in from the panel's trailing edge on the theory
 * that it sat under the launcher. Measured: the panel's edge lands at 1884px on
 * a 1900px screen while the launcher's is at 1606 — the support button is not
 * the last thing in the bar, the avatar is. The arrow pointed at empty bar.
 *
 * A panel hanging from the trailing edge under the bar is a pattern people
 * already read. An arrow that points at nothing is worse than no arrow.
 */

/*
 * The head is DARK, matching the bar it hangs from.
 *
 * It was `--bzf-surface-2`, which made the panel read as a second page floating
 * over the first. Carrying the bar's colour down into the header ties the two
 * together — the panel is the bar talking.
 */
.bzf-chatpanel__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-3);
	padding: var(--bzf-4) var(--bzf-5);
	background: var(--bzf-appbar-bg);
	border-bottom: 0;
	border-start-start-radius: var(--bzf-radius-xl);
	border-start-end-radius: var(--bzf-radius-xl);
}

.bzf-chatpanel__title {
	display: flex;
	flex-direction: column;
	gap: 1px;
	min-width: 0;
}

.bzf-chatpanel__title strong {
	font-size: var(--bzf-text-md);
	font-weight: 620;
	color: var(--bzf-appbar-fg);
}

.bzf-chatpanel__title span {
	font-size: var(--bzf-text-sm);
	color: var(--bzf-appbar-dim);
}

/* The close button sits on near-black and has to be legible there. */
.bzf-chatpanel__head .bzf-btn {
	background: rgba(255, 255, 255, .08);
	border-color: transparent;
	color: var(--bzf-appbar-fg);
	min-width: 34px;
	min-height: 34px;
}

.bzf-chatpanel__head .bzf-btn:hover { background: rgba(255, 255, 255, .16); }

.bzf-chatpanel__body {
	display: flex;
	flex-direction: column;
	gap: var(--bzf-3);
	padding: var(--bzf-4);
	overflow-y: auto;
	min-height: 0;
	background: var(--bzf-surface);
	border-end-start-radius: var(--bzf-radius-xl);
	border-end-end-radius: var(--bzf-radius-xl);
}

/*
 * A thread row.
 *
 * It was a flat 2px-gapped list where every row looked the same whether it had
 * an answer waiting or not. Now: an icon that carries the state, the subject,
 * a time under it, and the unread count as the only coloured thing in the row.
 */
.bzf-threadlist { display: flex; flex-direction: column; gap: var(--bzf-2); }

.bzf-threadlist__row {
	display: flex;
	align-items: center;
	gap: var(--bzf-3);
	min-height: 44px;
	padding: var(--bzf-3);
	border: 1px solid var(--bzf-border-soft);
	border-radius: var(--bzf-radius);
	background: var(--bzf-surface);
	cursor: pointer;
	transition: border-color var(--bzf-fast), background var(--bzf-fast);
}

.bzf-threadlist__row:hover {
	background: var(--bzf-surface-2);
	border-color: var(--bzf-border-strong);
}

/* A row with an unanswered message earns the brand edge. */
.bzf-threadlist__row.has-unread {
	border-color: var(--bzf-brand-200);
	background: var(--bzf-brand-50);
}

.bzf-threadlist__icon {
	display: grid;
	place-items: center;
	width: 34px;
	height: 34px;
	flex-shrink: 0;
	border-radius: var(--bzf-radius-sm);
	background: var(--bzf-surface-3);
	color: var(--bzf-text-muted);
}

.bzf-threadlist__row.has-unread .bzf-threadlist__icon {
	background: var(--bzf-brand-solid);
	color: var(--bzf-on-brand);
}

.bzf-threadlist__row.is-closed .bzf-threadlist__icon {
	background: var(--bzf-success-bg);
	color: var(--bzf-success-fg);
}

.bzf-threadlist__subject {
	font-weight: 600;
	font-size: var(--bzf-text-base);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* A promise the product can keep, under the list rather than inside it. */
.bzf-chatpanel__foot {
	padding: var(--bzf-3) var(--bzf-4);
	border-top: 1px solid var(--bzf-border-soft);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-subtle);
	text-align: center;
	background: var(--bzf-surface);
	border-end-start-radius: var(--bzf-radius-xl);
	border-end-end-radius: var(--bzf-radius-xl);
}

@media (max-width: 640px) {
	/* A sheet, not a card in a corner — 400px in a 375px window is not a panel. */
	.bzf-chatpanel {
		inset-inline: var(--bzf-3);
		width: auto;
		top: calc(var(--bzf-appbar-h-phone) + var(--bzf-2));
		max-height: calc(100vh - var(--bzf-appbar-h-phone) - var(--bzf-6));
	}

}

/* ------------------------------------------------------------------
   Checkout — full width, and the card form. 1.49.4
   ------------------------------------------------------------------ */

/*
 * FULL WIDTH, breaking out of the content column.
 *
 * `.bzf-shell__content` caps every screen at 1520px and centres it, which is
 * right for a dashboard — a stat card should not stretch to 400px to hold six
 * characters. Checkout is not a dashboard. It is one task, and on a wide
 * monitor the capped column left the card floating with dead space either side.
 * The owner's word for it was "ajeeb".
 *
 * The negative margin is measured against the same padding token the content
 * column uses, so the two can never drift apart.
 */
/*
 * checkout.js puts `is-flush` on the content area, which already drops the
 * 1520px cap, the auto margins and the padding — the same switch the till uses.
 * So there are no negative margins here: nothing needs cancelling, because
 * nothing is applied.
 */
.bzf-buy {
	border-radius: 0;
	box-shadow: none;
	/* Fill what is left of the window below the bar, so the page does not end
	   in a band of empty ground under a short card. */
	min-height: calc(100vh - var(--bzf-appbar-h));
}

/* The inner grid keeps its own comfortable measure — full width is for the
   SURFACE, not for stretching a form to 1900px. */
.bzf-buy__body {
	max-width: 1180px;
	margin-inline: auto;
	width: 100%;
}

.bzf-buy__bar {
	max-width: none;
	padding-inline: var(--bzf-7);
}

/*
 * The section heading sat 24px under the cycle boxes and read as belonging to
 * them. app.css resets margins on every heading in the app, so the shorthand
 * here was being overridden on the top edge only — which is exactly the kind of
 * half-applied spacing that reads as "no alignment".
 */
.bzf-buy__pane .bzf-buy__slab {
	margin-top: var(--bzf-7);
	margin-bottom: var(--bzf-3);
}

.bzf-buy__gets { padding-inline-start: 0; }

/* ------------------------------------------------------------------
   The card form
   ------------------------------------------------------------------ */

/*
 * Paddle.js renders its own iframe in here.
 *
 * A minimum height up front, so the panel does not jump when the frame arrives
 * — a form that grows under the cursor is how people mis-click.
 */
.bzf-cardframe {
	min-height: 420px;
	margin-top: var(--bzf-4);
	padding: var(--bzf-4);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius-lg);
	background: var(--bzf-surface);
	display: flex;
	flex-direction: column;
	justify-content: center;
}

/* Once Paddle owns the box, our padding would double up with its own. */
.bzf-cardframe.is-ready { padding: var(--bzf-2); display: block; }

.bzf-cardframe.is-failed {
	align-items: center;
	gap: var(--bzf-4);
	text-align: center;
	color: var(--bzf-text-muted);
}

.bzf-cardframe__wait {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--bzf-3);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

.bzf-spinner {
	width: 16px;
	height: 16px;
	border-radius: 50%;
	border: 2px solid var(--bzf-border);
	border-top-color: var(--bzf-brand-solid);
	animation: bzf-spin .7s linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
	.bzf-spinner { animation-duration: 2.4s; }
}

.bzf-buy__fallback {
	align-self: center;
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
	text-decoration: underline;
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}

.bzf-buy__fallback:hover { color: var(--bzf-brand-600); }

/* The processor's name, under the friendly label. */
.bzf-pay__provider {
	display: block;
	margin-top: 2px;
	font-size: var(--bzf-text-xs);
	color: var(--bzf-text-subtle);
}

@media (max-width: 900px) {
	.bzf-buy {
		margin-inline: calc(var(--bzf-4) * -1);
		min-height: 0;
	}

	.bzf-buy__bar { padding-inline: var(--bzf-4); }
}

/* ------------------------------------------------------------------
   LEGAL — the footer inside the app, and the public pages it opens.

   Not chrome. Paddle refuses to approve a domain whose site does not link
   through to its terms, privacy notice and refund policy, and an unapproved
   domain cannot take a card — so these two links are part of the payment
   path. They sit under the content on every screen of the app.
   ------------------------------------------------------------------ */
.bzf-legalbar {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--bzf-2);
	padding: var(--bzf-4) var(--bzf-4) calc(var(--bzf-4) + env(safe-area-inset-bottom));
	font-size: var(--bzf-text-xs);
}

.bzf-legalbar__link {
	color: var(--bzf-text-muted);
	text-decoration: none;
	/* 44px of target without 44px of visible bar — the row is quiet by design
	   but still has to be tappable on a phone. */
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding-inline: var(--bzf-1);
}

.bzf-legalbar__link:hover,
.bzf-legalbar__link:focus-visible { color: var(--bzf-brand-600); text-decoration: underline; }

.bzf-legalbar__dot { color: var(--bzf-text-subtle); }

/* The public page. Its own ground, because it renders with no app around it. */
.bzf-legal {
	min-height: 100vh;
	min-height: 100dvh;
	background: var(--bzf-ground);
	padding: var(--bzf-6) var(--bzf-4);
}

.bzf-legal__inner {
	max-width: 760px;
	margin-inline: auto;
}

.bzf-legal__brand {
	display: inline-flex;
	align-items: center;
	gap: var(--bzf-2);
	margin-bottom: var(--bzf-6);
	min-height: 44px;
	color: var(--bzf-text);
	font-weight: 650;
	text-decoration: none;
}

.bzf-legal__mark {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	border-radius: var(--bzf-radius-sm);
	background: var(--bzf-brand-solid);
	color: #FFFFFF;
}

.bzf-legal__title {
	margin: 0 0 var(--bzf-1);
	font-size: var(--bzf-text-2xl);
	color: var(--bzf-text);
}

.bzf-legal__meta {
	margin: 0 0 var(--bzf-6);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

/* Descendant rules: this body is written in the WordPress editor, so the
   markup is whatever the owner typed. Style the tags, not classes we control. */
.bzf-legal__body {
	background: var(--bzf-surface);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius);
	padding: var(--bzf-6);
	color: var(--bzf-text);
	line-height: 1.7;
}

.bzf-legal__body h2 {
	margin: var(--bzf-6) 0 var(--bzf-2);
	font-size: var(--bzf-text-lg);
}

.bzf-legal__body h2:first-child { margin-top: 0; }

.bzf-legal__body p { margin: 0 0 var(--bzf-3); }

.bzf-legal__body a { color: var(--bzf-brand-600); }

.bzf-legal__foot {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--bzf-2);
	margin-top: var(--bzf-6);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

.bzf-legal__foot a { color: var(--bzf-text-muted); }
.bzf-legal__foot a:hover { color: var(--bzf-brand-600); }

/* ------------------------------------------------------------------
   PRICING — the public page Paddle asks for during account review.

   Prices come from the live plans, so this stylesheet must not assume how
   many cards there are: an operator can add a third tier and the grid has
   to keep working.
   ------------------------------------------------------------------ */
.bzf-legal__inner--wide { max-width: 980px; }

.bzf-price {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
	gap: var(--bzf-4);
	margin-top: var(--bzf-6);
}

.bzf-price__card {
	display: flex;
	flex-direction: column;
	background: var(--bzf-surface);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius);
	padding: var(--bzf-5);
}

/* The paid tier reads as the recommendation without a coloured banner that
   would need its own contrast budget. */
.bzf-price__card.is-featured { border-color: var(--bzf-brand-solid); }

.bzf-price__name {
	margin: 0 0 var(--bzf-1);
	font-size: var(--bzf-text-lg);
	color: var(--bzf-text);
}

.bzf-price__blurb {
	margin: 0 0 var(--bzf-3);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

.bzf-price__amount {
	margin: var(--bzf-2) 0 0;
	font-size: var(--bzf-text-2xl);
	font-weight: 700;
	color: var(--bzf-text);
}

.bzf-price__per {
	display: block;
	margin-top: 2px;
	font-size: var(--bzf-text-sm);
	font-weight: 400;
	color: var(--bzf-text-muted);
}

.bzf-price__alt {
	margin: var(--bzf-2) 0 0;
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

/* The honest line about being charged in dollars. Quiet, but never hidden —
   charging someone in a currency they were not quoted is the thing this
   sentence exists to prevent. */
.bzf-price__fx {
	margin: var(--bzf-3) 0 0;
	font-size: var(--bzf-text-xs);
	line-height: 1.6;
	color: var(--bzf-text-muted);
}

.bzf-price__trial {
	margin: var(--bzf-2) 0 0;
	font-size: var(--bzf-text-sm);
	color: var(--bzf-success, #1F7A3D);
}

.bzf-price__cta {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/* margin-top:auto pins every button to the same line no matter how much
	   text sits above it — cards with different feature counts still align. */
	margin-top: auto;
	min-height: 44px;
	padding: 0 var(--bzf-4);
	border-radius: var(--bzf-radius-sm);
	background: var(--bzf-brand-solid);
	color: #FFFFFF;
	font-weight: 600;
	text-decoration: none;
}

.bzf-price__cta:hover { background: var(--bzf-brand-600); color: #FFFFFF; }

/* The public footer on the signed-out screens. Paddle's website approval
   requires the site to LINK to its terms, privacy notice and refund policy —
   having the pages is not enough if nothing points at them. */
.bzf-publicfoot {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--bzf-2);
	padding: var(--bzf-4) var(--bzf-4) calc(var(--bzf-4) + env(safe-area-inset-bottom));
	font-size: var(--bzf-text-xs);
}

.bzf-publicfoot__link {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding-inline: var(--bzf-1);
	color: var(--bzf-text-muted);
	text-decoration: none;
}

.bzf-publicfoot__link:hover,
.bzf-publicfoot__link:focus-visible { color: var(--bzf-brand-600); text-decoration: underline; }
