/*
 * BizzFlow base layer.
 *
 * Document defaults, the sign-in screen, and the boot state. Design tokens live
 * in tokens.css; everything else lives in components.css.
 */

/* ------------------------------------------------------------------
   Reset — narrow on purpose. A full reset would fight WordPress admin
   for administrators who still visit it.
   ------------------------------------------------------------------ */

*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body.bizzflow-body {
	margin: 0;
	font-family: var(--bzf-font);
	font-size: var(--bzf-text-base);
	line-height: 1.55;
	background: var(--bzf-canvas);
	color: var(--bzf-text);
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	/* Stops iOS Safari bouncing the whole page while a POS list scrolls. */
	overscroll-behavior-y: none;
}

/*
 * Serif headings, sans body.
 *
 * The pairing is what stops the interface reading as an admin panel: a page
 * title in the same font as the table under it has no voice. Weight drops to
 * 600 because a serif at 700 looks shouted, and tracking loosens because serifs
 * are already tighter than a sans at the same size.
 */
/*
 * Headings are now the same family as body text (see tokens.css). A serif at
 * 600 looked considered; a sans at 600 just looks like bold body copy, so the
 * weight goes to 680 and the tracking tightens — that is where the difference
 * in voice now comes from.
 */
.bizzflow-body h1,
.bizzflow-body h2,
.bizzflow-body h3,
.bizzflow-body h4 {
	margin: 0;
	font-family: var(--bzf-font-heading);
	font-weight: 680;
	letter-spacing: var(--bzf-track-tight);
	color: var(--bzf-text);
	line-height: 1.25;
}

/*
 * Small headings sit inside cards and read as labels, not titles, so they keep
 * a lighter weight and looser tracking than the display sizes above. The
 * family override that used to be here is gone — headings and body are the same
 * family now, so it did nothing.
 */
.bizzflow-body h4,
.bzf-card__head h3 {
	font-weight: 620;
	letter-spacing: var(--bzf-track-snug);
}

.bizzflow-body h1 { font-size: var(--bzf-text-2xl); letter-spacing: var(--bzf-track-tight); }
.bizzflow-body h2 { font-size: var(--bzf-text-xl); letter-spacing: var(--bzf-track-tight); }
.bizzflow-body h3 { font-size: var(--bzf-text-md); }
.bizzflow-body h4 { font-size: var(--bzf-text-base); }

.bizzflow-body p { margin: 0; }
.bizzflow-body ul, .bizzflow-body ol { margin: 0; padding: 0; }

/*
 * THE RESETS BELOW ARE WRAPPED IN :where() AND MUST STAY THAT WAY.
 *
 * They used to read `.bizzflow-body a` and `.bizzflow-body button`, which is a
 * class plus an element — specificity 0,1,1. Every component class that sets a
 * colour is 0,1,0, so the reset WON, and:
 *
 *   - `.bzf-btn--primary { color: var(--bzf-on-brand) }` lost to
 *     `color: inherit`, so every filled button in the app rendered its label in
 *     body charcoal on a burnt-orange fill — 3.47:1, below AA — instead of
 *     white. Shipped that way from 1.9.0 and only found by measuring it.
 *   - `.bzf-imp__exit { color: #fff }` lost to the link colour, putting brand
 *     orange text on a dark brown bar at 1.12:1. Effectively invisible.
 *
 * :where() contributes ZERO specificity, so these are now 0,0,1 — a bare
 * element selector that any single class beats, which is what a reset should be.
 */
:where(.bizzflow-body) a { color: var(--bzf-brand-600); text-decoration: none; }
:where(.bizzflow-body) a:hover { text-decoration: underline; }

:where(.bizzflow-body) button,
:where(.bizzflow-body) input,
:where(.bizzflow-body) select,
:where(.bizzflow-body) textarea { font: inherit; color: inherit; }

.bizzflow-body img,
.bizzflow-body svg { max-width: 100%; }

/* Tabular figures. Money that does not line up looks like a bug. */
.bzf-num {
	font-variant-numeric: tabular-nums;
	font-feature-settings: "tnum" 1;
	letter-spacing: 0;
}

/* Visible focus is an accessibility requirement, not a style preference. */
.bizzflow-body :focus-visible {
	outline: 2px solid var(--bzf-brand-500);
	outline-offset: 2px;
	border-radius: var(--bzf-radius-xs);
}

.bizzflow-body :focus:not(:focus-visible) { outline: none; }

/* Respect a stated preference for reduced motion. */
@media (prefers-reduced-motion: reduce) {
	.bizzflow-body *,
	.bizzflow-body *::before,
	.bizzflow-body *::after {
		animation-duration: .01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: .01ms !important;
		scroll-behavior: auto !important;
	}
}

/* Screen-reader-only text. */
.bzf-sr {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* ------------------------------------------------------------------
   Sign-in
   ------------------------------------------------------------------ */

.bzf-auth {
	min-height: 100vh;
	min-height: 100dvh;
	display: grid;
	grid-template-columns: minmax(0, 460px) 1fr;
}

.bzf-auth__panel {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: var(--bzf-5);
	padding: var(--bzf-12) var(--bzf-10);
	background: var(--bzf-surface);
}

.bzf-auth__brand {
	display: flex;
	align-items: center;
	gap: var(--bzf-3);
	margin-bottom: var(--bzf-6);
}

.bzf-auth__mark {
	width: 42px;
	height: 42px;
	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-lg);
	font-weight: 700;
	box-shadow: var(--bzf-shadow-brand);
}

.bzf-auth__title {
	font-size: var(--bzf-text-md);
	font-weight: 620;
}

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

.bzf-auth__heading { font-size: var(--bzf-text-2xl); }

.bzf-auth__form {
	display: flex;
	flex-direction: column;
	gap: var(--bzf-4);
}

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

.bzf-auth__submit {
	width: 100%;
	justify-content: center;
	padding: 12px;
	font-size: var(--bzf-text-md);
	margin-top: var(--bzf-1);
}

.bzf-auth__error {
	padding: var(--bzf-3) var(--bzf-4);
	border-radius: var(--bzf-radius);
	background: var(--bzf-danger-bg);
	color: var(--bzf-danger-fg);
	font-size: var(--bzf-text-sm);
	border: 1px solid transparent;
}

/*
 * One sentence under a heading, explaining what the screen wants.
 *
 * Every recovery screen needs it and the sign-in screen does not: "Sign in"
 * over two labelled boxes explains itself, "Reset your password" over one box
 * does not say which address to use.
 */
.bzf-auth__lede {
	margin: calc(var(--bzf-2) * -1) 0 var(--bzf-1);
	font-size: var(--bzf-text-sm);
	line-height: 1.55;
	color: var(--bzf-text-muted);
}

/* A one-shot message carried across a redirect — see Security\Auth::notice().
 * Neutral by default: "check your inbox" is not a success and not an error,
 * and colouring it green would claim an email was sent to an address that may
 * not exist. */
.bzf-auth__note {
	padding: var(--bzf-3) var(--bzf-4);
	border-radius: var(--bzf-radius);
	font-size: var(--bzf-text-sm);
	line-height: 1.5;
	background: var(--bzf-surface);
	color: var(--bzf-text);
	border: 1px solid var(--bzf-border);
}

.bzf-auth__note--success {
	background: var(--bzf-success-bg);
	color: var(--bzf-success-fg);
	border-color: transparent;
}

.bzf-auth__note--error {
	background: var(--bzf-danger-bg);
	color: var(--bzf-danger-fg);
	border-color: transparent;
}

.bzf-field__hint {
	margin: var(--bzf-1) 0 0;
	font-size: var(--bzf-text-xs);
	color: var(--bzf-text-muted);
}

/*
 * The "you are viewing someone else's account" bar.
 *
 * Deliberately loud and deliberately not dismissible. An operator who forgets
 * they are inside a customer's account will eventually do something in it, and
 * every action is recorded against that customer. Sticky so it survives
 * scrolling, and above the app's own chrome.
 */
.bzf-imp {
	position: sticky;
	top: 0;
	z-index: 100;
	display: flex;
	align-items: center;
	gap: var(--bzf-3);
	padding: 9px var(--bzf-4);
	font-size: var(--bzf-text-sm);
	line-height: 1.4;
	color: #fff;
	background: #7A2E00;
	box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
}

.bzf-imp__dot {
	flex: none;
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: #FFB067;
}

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

.bzf-imp__exit {
	flex: none;
	padding: 5px 12px;
	font-weight: 600;
	color: #fff;
	text-decoration: none;
	background: rgba(255, 255, 255, 0.16);
	border: 1px solid rgba(255, 255, 255, 0.28);
	border-radius: var(--bzf-radius);
}

.bzf-imp__exit:hover {
	background: rgba(255, 255, 255, 0.26);
}

@media (max-width: 640px) {
	.bzf-imp {
		flex-wrap: wrap;
		font-size: var(--bzf-text-xs);
	}
}

/*
 * Brand-coloured, deliberately.
 *
 * This rule said `--bzf-text-muted` with brand on hover, and never once
 * rendered that way: `.bizzflow-body a` outranked it and painted these orange
 * always. Fixing the reset's specificity exposed the written intent — and the
 * written intent was worse. "Forgot your password?" in grey on a sign-in screen
 * is the one link on the page that has to be findable by someone who is
 * already locked out.
 */
/*
 * 44px of hit area, not 22px of text.
 *
 * These links measured 22px tall on a phone — half the product's tap-target
 * floor, and "Forgot your password?" is the one control a person reaches for
 * at exactly the moment they are already annoyed. The auth screens predate the
 * 44px rule and were never swept, because the responsive audit runs on app
 * routes and these are rendered by index.php for signed-OUT visitors.
 *
 * The padding is negative-margined back out so nothing below shifts; the link
 * still sits where it did, it is just catchable now.
 */
.bzf-auth__link {
	font-size: var(--bzf-text-sm);
	color: var(--bzf-brand-600);
	align-self: flex-start;
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding-block: var(--bzf-2);
	margin-block: calc(var(--bzf-2) * -1);
}

.bzf-auth__link:hover { color: var(--bzf-brand-700); }

/* ------------------------------------------------------------------
   1.48.0 — the label row, and the one footer line
   ------------------------------------------------------------------ */

/*
 * A field's label, with room for something on the far side of it.
 *
 * Only "Forgot?" uses the far side today. The row keeps its 44px height either
 * way so a labelled field is the same height whether or not it carries a link,
 * which is what stops sign-in and register drifting by a few pixels.
 */
.bzf-field__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bzf-3);
	min-height: 44px;
}

/*
 * Padded to a 44px target and negative-margined back out, so the link is
 * catchable with a thumb without pushing the field down. Measured at 375px
 * before this existed: a 19px target, which is the same class of bug as the
 * 22px one found on these screens in 1.46.0.
 */
.bzf-field__aside {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding-block: var(--bzf-2);
	margin-block: calc(var(--bzf-2) * -1);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-brand-600);
	white-space: nowrap;
}

.bzf-field__aside:hover { color: var(--bzf-brand-700); }

/*
 * Whatever is left over, as ONE sentence below a hairline.
 *
 * These screens used to end in a column of two or three separate links, which
 * is where the panel stopped reading as a designed screen. A rule and a single
 * line gives it a bottom edge.
 */
.bzf-auth__foot {
	margin-top: var(--bzf-1);
	padding-top: var(--bzf-4);
	border-top: 1px solid var(--bzf-border-soft);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
}

.bzf-auth__foot a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding-block: var(--bzf-2);
	margin-block: calc(var(--bzf-2) * -1);
	color: var(--bzf-brand-600);
	font-weight: 550;
}

.bzf-auth__foot a:hover { color: var(--bzf-brand-700); }

.bzf-auth__list {
	list-style: disc;
	padding-inline-start: var(--bzf-5);
	font-size: var(--bzf-text-sm);
	color: var(--bzf-text-muted);
	display: flex;
	flex-direction: column;
	gap: var(--bzf-1);
}

/* The right-hand panel is decoration; it collapses first on small screens. */
.bzf-auth__aside {
	position: relative;
	display: grid;
	place-items: center;
	padding: var(--bzf-12);
	/* Literal oranges rather than tokens: this panel is dark in both themes, so
	   it must not follow the surface tokens that flip. */
	background:
		radial-gradient(120% 100% at 100% 0%, #EA6412 0%, transparent 62%),
		linear-gradient(160deg, #8E3507, #3A1503 72%);
	overflow: hidden;
}

/*
 * The diagram sits above the claim, not beside it.
 *
 * Side by side, the two halve each other's width on a laptop and the receipt
 * becomes unreadable — and the pitch is the caption for the drawing, which is
 * a relationship the eye only reads top to bottom.
 */
/*
 * Sized against the ASIDE, not against a guessed pixel width.
 *
 * The form column is capped at 460px and the aside takes the rest — 980px on a
 * 1440 laptop, and more on a wide monitor. A fixed 460px drawing in the middle
 * of that reads as a small sticker on a large orange wall. `min(…)` lets it
 * grow with the panel and stops it turning into a poster at 2560px, where the
 * receipt's 5px text bars would be the size of real text and start looking
 * like something you are meant to read.
 */
.bzf-auth__show {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: clamp(var(--bzf-6), 4vh, var(--bzf-10));
	align-items: flex-start;
	width: 100%;
	max-width: min(620px, 82%);
}

/* The pitch shrinks once it is a caption rather than the whole panel. */
.bzf-auth__show .bzf-auth__pitch-line { font-size: clamp(24px, 3.2vw, 38px); }

/* Short viewports lose the drawing before they lose the words. */
@media (max-height: 620px) {
	.bzf-auth__show .bzf-motion { display: none; }
}

.bzf-auth__pitch { position: relative; }

/*
 * The two quiet lines.
 *
 * Raised from .42 to .52 in 1.48.0. At .42 these measured 2.64:1 against the
 * part of the aside they actually sit on (#8E3507), and large text needs 3.0 —
 * so two of the first three words anyone reads of BizzFlow were under the
 * floor. Nothing caught it because the contrast sweeps run app routes and these
 * render for signed-OUT visitors. At .52 they measure 3.28:1 there and 5.20:1
 * further down the panel, and they still read as clearly secondary to the
 * accent line.
 */
.bzf-auth__pitch-line {
	font-size: clamp(28px, 4.4vw, 52px);
	font-weight: 680;
	line-height: 1.12;
	letter-spacing: var(--bzf-track-tight);
	color: rgba(255, 255, 255, .52);
}

/*
 * Three claims at the foot of the orange panel.
 *
 * It gives the aside a base — the pitch used to float in the middle of a large
 * empty rectangle — and it is the only place a first-time visitor is told what
 * the product does before they have an account.
 */
.bzf-auth__trust {
	display: flex;
	flex-wrap: wrap;
	gap: var(--bzf-2) var(--bzf-5);
	margin: 0;
	padding-top: var(--bzf-5);
	border-top: 1px solid rgba(255, 226, 206, .22);
	list-style: none;
}

.bzf-auth__trust li {
	display: flex;
	align-items: center;
	gap: var(--bzf-2);
	font-size: var(--bzf-text-sm);
	/* 5.9:1 on the darkest part of the aside, 4.6:1 on the brightest. */
	color: rgba(255, 226, 206, .88);
}

.bzf-auth__trust svg { flex-shrink: 0; }

/* The one line that gets the accent. Always on the dark orange aside, so the
   pale wash is the readable choice in both themes. */
.bzf-auth__pitch-line--accent { color: #FFE2CE; }

@media (max-width: 900px) {
	.bzf-auth { grid-template-columns: 1fr; }
	.bzf-auth__aside { display: none; }
	.bzf-auth__panel { padding: var(--bzf-8) var(--bzf-5); }
}

/* ------------------------------------------------------------------
   Boot state
   ------------------------------------------------------------------ */

.bizzflow-app { min-height: 100vh; min-height: 100dvh; }

.bzf-boot {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--bzf-4);
	min-height: 70vh;
	color: var(--bzf-text-muted);
}

/*
 * The full starting screen — see the note in templates/app-shell.php for why
 * it was redrawn. `.bzf-boot` on its own stays a plain centred column, because
 * router.js reuses it for the between-views loading state with nothing but a
 * `__text` child.
 */
.bzf-boot--start {
	position: relative;
	min-height: 100vh;
	min-height: 100dvh;
	gap: var(--bzf-4);
	background:
		radial-gradient(80% 58% at 50% 0%, var(--bzf-brand-50) 0%, transparent 68%),
		radial-gradient(70% 50% at 50% 100%, #F1EEF6 0%, transparent 62%);
}

/* The same tile the auth screens use, so sign-in → starting → dashboard is one
   continuous product rather than three unrelated screens. */
.bzf-boot__tile {
	position: relative;
	width: 62px;
	height: 62px;
	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;
	box-shadow: var(--bzf-shadow-brand);
}

/*
 * A ring expanding outward, rather than the logo fading in and out.
 *
 * The old screen pulsed the mark itself, which dims the brand every 1.8s. This
 * says the same "still working" with the mark staying solid.
 */
.bzf-boot__tile::after {
	content: '';
	position: absolute;
	inset: -10px;
	border-radius: 22px;
	border: 1.5px solid var(--bzf-brand-200);
	opacity: 0;
	animation: bzf-halo 2.4s var(--bzf-ease) infinite;
}

.bzf-boot__tile svg { width: 30px; height: 30px; overflow: visible; }

/* The B still draws itself — that part was right and it stays. */
.bzf-boot__tile svg path {
	stroke-dasharray: 64;
	stroke-dashoffset: 64;
	animation: bzf-draw 1.1s var(--bzf-ease) forwards;
}

.bzf-boot__tile svg path:nth-child(2) { animation-delay: .18s; }
.bzf-boot__tile svg path:nth-child(3) { animation-delay: .36s; }

.bzf-boot__name {
	font-size: var(--bzf-text-xl);
	font-weight: 650;
	letter-spacing: var(--bzf-track-tight);
	color: var(--bzf-text);
}

.bzf-boot__text { font-size: var(--bzf-text-base); margin-top: calc(var(--bzf-3) * -1); }

/*
 * Three segments, one per real boot stage. app.js stamps data-stage as each
 * completes; the segment currently in flight shimmers, finished ones are solid,
 * and ones not reached yet stay empty. A stall is therefore readable: whichever
 * segment is shimmering is the step that did not come back.
 */
.bzf-boot__stages { display: flex; gap: 5px; }

.bzf-boot__seg {
	position: relative;
	width: 62px;
	height: 3px;
	border-radius: var(--bzf-radius-full);
	background: var(--bzf-surface-3);
	overflow: hidden;
}

.bzf-boot__seg i {
	display: block;
	height: 100%;
	width: 0;
	border-radius: inherit;
	background: var(--bzf-brand-solid);
	transition: width var(--bzf-slow);
}

.bzf-boot[data-stage="1"] .bzf-boot__seg:nth-child(1) i,
.bzf-boot[data-stage="2"] .bzf-boot__seg:nth-child(-n+2) i,
.bzf-boot[data-stage="3"] .bzf-boot__seg i { width: 100%; }

/* The segment being worked on right now. */
.bzf-boot[data-stage="0"] .bzf-boot__seg:nth-child(1)::after,
.bzf-boot[data-stage="1"] .bzf-boot__seg:nth-child(2)::after,
.bzf-boot[data-stage="2"] .bzf-boot__seg:nth-child(3)::after {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: inherit;
	background: linear-gradient(90deg, transparent, var(--bzf-brand-300), transparent);
	animation: bzf-slide 1.4s var(--bzf-ease) infinite;
}

.bzf-boot__hint {
	max-width: 34ch;
	text-align: center;
	font-size: var(--bzf-text-sm);
	line-height: 1.5;
	padding: var(--bzf-3) var(--bzf-4);
	border-radius: var(--bzf-radius-sm);
	background: var(--bzf-warning-bg);
	color: var(--bzf-warning-fg);
}

.bzf-boot__hint[hidden] { display: none; }

/*
 * A floor for the screen, so the cluster reads as composed rather than floating
 * in a void. text-muted, not text-subtle: subtle measures 4.24:1 against the
 * cool wash at the bottom of this gradient, which is under AA.
 */
.bzf-boot__foot {
	position: absolute;
	inset-inline: 0;
	bottom: var(--bzf-5);
	text-align: center;
	font-size: var(--bzf-text-xs);
	letter-spacing: var(--bzf-track-wide);
	text-transform: uppercase;
	color: var(--bzf-text-muted);
}

@keyframes bzf-draw {
	to { stroke-dashoffset: 0; }
}

@keyframes bzf-slide {
	0%   { transform: translateX(-100%); }
	100% { transform: translateX(250%); }
}

@keyframes bzf-halo {
	0%   { transform: scale(.86); opacity: 0; }
	38%  { opacity: .85; }
	100% { transform: scale(1.16); opacity: 0; }
}

/* Everything here is decoration over a state that is already written in text
   and in aria-valuenow. None of it is needed to understand the screen. */
@media (prefers-reduced-motion: reduce) {
	.bzf-boot__tile::after,
	.bzf-boot__tile svg path,
	.bzf-boot__seg::after,
	.bzf-boot__seg i { animation: none; transition: none; }

	.bzf-boot__tile svg path { stroke-dashoffset: 0; }
}

/* ------------------------------------------------------------------
   Fatal / no-JS
   ------------------------------------------------------------------ */

.bzf-fatal,
.bzf-noscript {
	max-width: 520px;
	margin: var(--bzf-16) auto;
	padding: var(--bzf-8);
	background: var(--bzf-surface);
	border: 1px solid var(--bzf-border);
	border-radius: var(--bzf-radius-lg);
	box-shadow: var(--bzf-shadow);
	text-align: center;
}

.bzf-fatal h2, .bzf-noscript h2 { margin-bottom: var(--bzf-3); }

.bzf-fatal p, .bzf-noscript p {
	color: var(--bzf-text-muted);
	margin-bottom: var(--bzf-4);
}

.bzf-fatal__ref {
	display: inline-block;
	font-family: var(--bzf-font-mono);
	font-size: var(--bzf-text-xs);
	padding: var(--bzf-1) var(--bzf-2);
	background: var(--bzf-surface-3);
	border: 1px solid var(--bzf-border-soft);
	border-radius: var(--bzf-radius-sm);
	color: var(--bzf-text-subtle);
	word-break: break-all;
}

/* ------------------------------------------------------------------
   Buttons — base. Variants live in components.css.
   ------------------------------------------------------------------ */

.bzf-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--bzf-2);
	padding: 8px 14px;
	font-weight: 550;
	font-size: var(--bzf-text-base);
	line-height: 1.2;
	border-radius: var(--bzf-radius);
	border: 1px solid transparent;
	cursor: pointer;
	white-space: nowrap;
	text-decoration: none;
	transition: background var(--bzf-fast), border-color var(--bzf-fast),
		box-shadow var(--bzf-fast), transform var(--bzf-fast), color var(--bzf-fast);
	user-select: none;
}

.bzf-btn:hover { text-decoration: none; }
.bzf-btn:active { transform: translateY(.5px); }

.bzf-btn[disabled] {
	opacity: .45;
	cursor: not-allowed;
	transform: none;
}

.bzf-btn__icon {
	display: inline-flex;
	width: 15px;
	height: 15px;
	flex-shrink: 0;
}

.bzf-btn__icon svg { width: 100%; height: 100%; }

/* ------------------------------------------------------------------
   RTL (Urdu)
   ------------------------------------------------------------------ */

.bizzflow-body[dir="rtl"],
.bizzflow-app[dir="rtl"] { text-align: right; }

/* Numerals and money stay left-to-right even in an RTL layout —
   mirroring a price is not a translation, it is a bug. */
.bizzflow-body[dir="rtl"] .bzf-num,
.bizzflow-app[dir="rtl"] .bzf-num {
	direction: ltr;
	unicode-bidi: embed;
	display: inline-block;
}

/* ------------------------------------------------------------------
   The sign-in aside's motion diagram
   ------------------------------------------------------------------
   A till in use, on an 8s loop: tap a product, the scan line sweeps, a line
   lands on the receipt, the total climbs, Charge goes green, reset.

   ONLY `transform` and `opacity` are animated. Those are the two properties a
   phone GPU composites without repainting the layer, and this sits behind a
   sign-in form on a shop's cheap Android. The same reasoning turned the
   mockup's 200px blur into a gradient in 1.37.0.

   The timeline is one 8s clock shared by every element; each part takes its
   slice through keyframe percentages rather than through animation-delay, so
   the whole scene stays in step after any number of loops. Delays drift when
   a tab is backgrounded and animations are throttled; percentages of one
   shared duration cannot.
   ------------------------------------------------------------------ */
.bzf-motion {
	width: 100%;
	height: auto;
	overflow: visible;
}

/* --- resting appearance --- */
.bzf-motion__tile {
	fill: rgba(255, 255, 255, .10);
	stroke: rgba(255, 255, 255, .20);
	stroke-width: 1;
}

.bzf-motion__pip   { fill: rgba(255, 255, 255, .26); }
.bzf-motion__beam  { fill: #FFD9BE; opacity: .9; }
.bzf-motion__paper { fill: rgba(255, 255, 255, .95); }
.bzf-motion__head  { fill: #B34309; }
.bzf-motion__faint { fill: #D9D5D0; }
.bzf-motion__ink   { fill: #55575E; }
.bzf-motion__tear  { fill: #D9D5D0; }
.bzf-motion__total { fill: #EA6412; }

.bzf-motion__charge { fill: #EA6412; }

.bzf-motion__tick {
	fill: none;
	stroke: #FFFFFF;
	stroke-width: 2.4;
	stroke-linecap: round;
	stroke-linejoin: round;
	opacity: 0;
}

/* --- the clock --- */
.bzf-motion__slip,
.bzf-motion__scan,
.bzf-motion__tile,
.bzf-motion__line,
.bzf-motion__total,
.bzf-motion__charge,
.bzf-motion__tick {
	animation-duration: 8s;
	animation-iteration-count: infinite;
	animation-timing-function: linear;
}

/* The receipt arrives once, then holds. */
.bzf-motion__slip { animation-name: bzf-motion-slip; }

@keyframes bzf-motion-slip {
	0%       { opacity: 0; transform: translateY(14px); }
	5%, 88%  { opacity: 1; transform: translateY(0); }
	96%, 100%{ opacity: 0; transform: translateY(-8px); }
}

/*
 * The beam sweeps down the catalogue four times, once per tap, and is invisible
 * between passes — a beam that idles on screen reads as a divider, not a scan.
 */
.bzf-motion__scan { animation-name: bzf-motion-scan; }

@keyframes bzf-motion-scan {
	0%,  4%  { opacity: 0; transform: translateY(34px); }
	6%       { opacity: 1; }
	13%      { opacity: 1; transform: translateY(146px); }
	15%, 100%{ opacity: 0; transform: translateY(146px); }
}

/* Each tile lights in turn. transform-box keeps the scale centred on the tile
   itself rather than on the SVG's origin — without it they leap across. */
.bzf-motion__tile {
	transform-box: fill-box;
	transform-origin: center;
	animation-name: bzf-motion-tap;
}

@keyframes bzf-motion-tap {
	0%, 100% { fill: rgba(255, 255, 255, .10); transform: scale(1); }
	48%      { fill: rgba(255, 255, 255, .10); transform: scale(1); }
	52%      { fill: rgba(255, 226, 206, .34); transform: scale(.94); }
	58%      { fill: rgba(255, 255, 255, .10); transform: scale(1); }
}

.bzf-motion__tile--1 { animation-name: bzf-motion-tap-1; }
.bzf-motion__tile--2 { animation-name: bzf-motion-tap-2; }
.bzf-motion__tile--3 { animation-name: bzf-motion-tap-3; }
.bzf-motion__tile--4 { animation-name: bzf-motion-tap-4; }

@keyframes bzf-motion-tap-1 {
	0%, 6%, 14%, 100% { fill: rgba(255,255,255,.10); transform: scale(1); }
	9%                { fill: rgba(255,226,206,.38); transform: scale(.93); }
}
@keyframes bzf-motion-tap-2 {
	0%, 17%, 25%, 100% { fill: rgba(255,255,255,.10); transform: scale(1); }
	20%                { fill: rgba(255,226,206,.38); transform: scale(.93); }
}
@keyframes bzf-motion-tap-3 {
	0%, 28%, 36%, 100% { fill: rgba(255,255,255,.10); transform: scale(1); }
	31%                { fill: rgba(255,226,206,.38); transform: scale(.93); }
}
@keyframes bzf-motion-tap-4 {
	0%, 39%, 47%, 100% { fill: rgba(255,255,255,.10); transform: scale(1); }
	42%                { fill: rgba(255,226,206,.38); transform: scale(.93); }
}

/* A line lands just after its tile is tapped. */
.bzf-motion__line { animation-name: bzf-motion-line; opacity: 0; }

@keyframes bzf-motion-line {
	0%, 10%   { opacity: 0; transform: translateX(-6px); }
	14%, 88%  { opacity: 1; transform: translateX(0); }
	94%, 100% { opacity: 0; transform: translateX(0); }
}

.bzf-motion__line--2 { animation-name: bzf-motion-line-2; }
.bzf-motion__line--3 { animation-name: bzf-motion-line-3; }
.bzf-motion__line--4 { animation-name: bzf-motion-line-4; }

@keyframes bzf-motion-line-2 {
	0%, 21%   { opacity: 0; transform: translateX(-6px); }
	25%, 88%  { opacity: 1; transform: translateX(0); }
	94%, 100% { opacity: 0; }
}
@keyframes bzf-motion-line-3 {
	0%, 32%   { opacity: 0; transform: translateX(-6px); }
	36%, 88%  { opacity: 1; transform: translateX(0); }
	94%, 100% { opacity: 0; }
}
@keyframes bzf-motion-line-4 {
	0%, 43%   { opacity: 0; transform: translateX(-6px); }
	47%, 88%  { opacity: 1; transform: translateX(0); }
	94%, 100% { opacity: 0; }
}

/*
 * The total grows from its LEFT edge, so the bar lengthens the way a number
 * gets longer. Scaling from centre would make it creep out of both sides of
 * the slip.
 */
.bzf-motion__total {
	transform-box: fill-box;
	transform-origin: left center;
	animation-name: bzf-motion-total;
}

@keyframes bzf-motion-total {
	0%, 12%   { transform: scaleX(.08); opacity: 0; }
	14%       { transform: scaleX(.22); opacity: 1; }
	25%       { transform: scaleX(.46); opacity: 1; }
	36%       { transform: scaleX(.72); opacity: 1; }
	47%, 88%  { transform: scaleX(1);   opacity: 1; }
	94%, 100% { transform: scaleX(1);   opacity: 0; }
}

/* Charge turns green when the sale lands. Green means money in this product. */
.bzf-motion__charge { animation-name: bzf-motion-charge; }

@keyframes bzf-motion-charge {
	0%, 52%   { fill: #EA6412; }
	56%, 88%  { fill: #2F7D46; }
	94%, 100% { fill: #EA6412; }
}

.bzf-motion__tick { animation-name: bzf-motion-tick; }

@keyframes bzf-motion-tick {
	0%, 55%   { opacity: 0; transform: scale(.7); }
	60%, 88%  { opacity: 1; transform: scale(1); }
	94%, 100% { opacity: 0; }
}

.bzf-motion__tick {
	transform-box: fill-box;
	transform-origin: center;
}

/*
 * Reduced motion FREEZES the scene on a finished sale — it does not hide it.
 *
 * An empty orange panel reads as a page that failed to load. The still frame
 * says the same thing the animation does, which is the whole point of drawing
 * a diagram rather than playing a video.
 */
@media (prefers-reduced-motion: reduce) {
	.bzf-motion__slip,
	.bzf-motion__scan,
	.bzf-motion__tile,
	.bzf-motion__line,
	.bzf-motion__total,
	.bzf-motion__charge,
	.bzf-motion__tick {
		animation: none;
	}

	.bzf-motion__scan   { opacity: 0; }
	.bzf-motion__line   { opacity: 1; }
	.bzf-motion__total  { transform: none; }
	.bzf-motion__charge { fill: #2F7D46; }
	.bzf-motion__tick   { opacity: 1; }
}

/* ------------------------------------------------------------------
   Signed-out screens: reveal, strength, submit state
   ------------------------------------------------------------------
   Everything here is progressive enhancement — auth.js adds the markup, and
   without JavaScript none of these selectors match anything. The forms work
   either way.
   ------------------------------------------------------------------ */

.bzf-inputwrap { position: relative; display: block; }

/*
 * The Show/Hide control.
 *
 * `inset-inline-end`, not `right` — on the Urdu build the field is mirrored and
 * a hardcoded right edge would put this on top of the caret.
 *
 * The input gets padding on that side so a long password never runs underneath
 * the button; 74px covers "Hide" and "Show" at 12.5px with room either side.
 */
.bzf-inputwrap .bzf-input { padding-inline-end: 74px; }

.bzf-reveal {
	position: absolute;
	inset-block: 0;
	inset-inline-end: 0;
	display: inline-flex;
	align-items: center;
	padding-inline: var(--bzf-3);
	border: 0;
	background: none;
	cursor: pointer;
	font-family: inherit;
	font-size: var(--bzf-text-xs);
	font-weight: 600;
	color: var(--bzf-brand-600);
}

.bzf-reveal:hover { color: var(--bzf-brand-700); }
.bzf-reveal:focus-visible { outline: 2px solid var(--bzf-brand-500); outline-offset: -2px; border-radius: var(--bzf-radius-xs); }

/* --- strength meter --- */
.bzf-strength {
	display: flex;
	gap: 5px;
	margin-top: var(--bzf-2);
}

.bzf-strength__bar {
	flex: 1;
	height: 4px;
	border-radius: 2px;
	background: var(--bzf-border);
	transition: background var(--bzf-fast);
}

/*
 * Amber until it is actually decent, then green.
 *
 * A meter that goes green on the first bar is a meter that lies. Green means
 * money everywhere else in this product, and it should not be spent on a
 * six-character password.
 */
.bzf-strength__bar.is-on { background: var(--bzf-warning); }
.bzf-strength[data-score="3"] .bzf-strength__bar.is-on,
.bzf-strength[data-score="4"] .bzf-strength__bar.is-on { background: var(--bzf-success); }

/* --- submit state --- */
.bzf-btn.is-busy,
.bzf-auth__submit.is-busy {
	gap: var(--bzf-2);
	cursor: progress;
}

.bzf-btn__busy { font-weight: inherit; }

.bzf-spinner {
	inline-size: 16px;
	block-size: 16px;
	border-radius: 50%;
	border: 2.2px solid rgba(255, 255, 255, .42);
	border-top-color: #FFFFFF;
	animation: bzf-spin 700ms linear infinite;
	flex-shrink: 0;
}

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

/*
 * Reduced motion keeps the spinner as a STATIC ring rather than hiding it.
 * It is the only thing on screen saying the tap registered; removing it would
 * leave a button that looks like it did nothing.
 */
@media (prefers-reduced-motion: reduce) {
	.bzf-spinner { animation: none; border-top-color: rgba(255, 255, 255, .82); }
}
