@charset "utf-8";
/* ============================================================================
   D·PAY, the money app. The stylesheet for the rebuild.

   WHY A NEW FILE RATHER THAN novapay-style.css. The audit in
   handoff/REBUILD-PLAN.md section 2 names the problem: the existing portal
   reads as a shop. Its stylesheet carries collection tiles, product grids, a
   cart drawer and a wishlist, 2000 rules of furniture a money app does not
   have. Reusing it would mean inheriting the thing the rebuild exists to
   remove. This is built from the brand tokens and nothing else.

   The tokens themselves are copied deliberately rather than imported. The two
   stylesheets stand side by side until M7 switches, and an import would make a
   change to the old one silently redraw the new app. docs/BRAND.md owns the
   values; if they move, they move here too, and tools/check-newapp.mjs holds
   the two files to the same numbers.

   NAMESPACED, per CLAUDE.md section 1 item 8. Every class is dpy-, so this
   file and novapay-style.css can both be on a page during the switch without
   one restyling the other.
   ========================================================================== */

:root {
	/* The estate mark's own colours. images/brand/mark.svg is the source. */
	--dpy-indigo: #4F46E5;
	--dpy-indigo-light: #7C74F0;
	--dpy-indigo-deep: #312E9E;
	--dpy-mint: #34D399;
	--dpy-mint-deep: #10B981;

	/* Cool neutrals. A money app is read for minutes at a time, so the page
	   is near white and the ink is near black rather than either extreme. */
	--dpy-ink: #0F172A;
	--dpy-ink-soft: #475069;
	--dpy-muted: #6B7392;
	--dpy-line: #E2E4F0;
	--dpy-surface: #FFFFFF;
	--dpy-page: #F6F7FC;
	--dpy-raised: #EEF0FA;

	/* Money has two directions and they are never the only signal: every
	   credit also carries a plus sign, because colour alone fails for the
	   one reader in twelve who cannot separate these two hues. */
	--dpy-in: #0E8A5F;
	--dpy-out: #0F172A;
	--dpy-warn: #B45309;
	--dpy-bad: #B91C1C;

	--dpy-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
	--dpy-display: 'Sora', 'Inter', -apple-system, sans-serif;

	/* A money column has to line up. Tabular figures are the whole reason. */
	--dpy-num: 'Inter', ui-monospace, 'SF Mono', Menlo, monospace;

	--dpy-r-sm: 8px;
	--dpy-r: 14px;
	--dpy-r-lg: 20px;
	--dpy-shadow: 0 1px 2px rgba(15, 23, 42, .06), 0 8px 24px rgba(15, 23, 42, .06);
	--dpy-shadow-sm: 0 1px 2px rgba(15, 23, 42, .07);
}

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

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

body {
	margin: 0;
	font-family: var(--dpy-font);
	font-size: 16px;
	line-height: 1.5;
	color: var(--dpy-ink);
	background: var(--dpy-page);
	-webkit-font-smoothing: antialiased;
}

/* The accessible name the lockup and the icon buttons need, out of sight.
   Named dpy- rather than reusing the public site's novapay-vh, because the
   two stylesheets are independent until the switch. */
.dpy-vh {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* Keyboard users have to be able to see where they are. Never remove this
   without putting something equivalent in its place. */
:focus-visible {
	outline: 3px solid var(--dpy-indigo);
	outline-offset: 2px;
	border-radius: 4px;
}

.dpy-skip {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 100;
	background: var(--dpy-surface);
	padding: 12px 18px;
	border-radius: 0 0 var(--dpy-r-sm) 0;
	font-weight: 600;
}
.dpy-skip:focus { left: 0; }

/* --------------------------------------------------------------- the lockup */
/* The same three spans the public site uses, so the brand is one drawing in
   CSS on every surface. The wordmark takes its container's colour and only the
   dot carries the accent. */
.dpy-logo {
	display: inline-flex;
	align-items: center;
	font-family: var(--dpy-display);
	font-size: 1.5rem;
	font-weight: 700;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	line-height: 1;
	color: inherit;
	text-decoration: none;
}
.dpy-logo-dot {
	display: inline-block;
	width: 0.3em;
	height: 0.3em;
	margin: 0 0.1em;
	border-radius: 50%;
	background: var(--dpy-mint);
	/* Capital height is about 0.7em, so the midpoint of the capitals sits
	   0.35em above the baseline. Raising a 0.3em dot by 0.2em centres it. */
	vertical-align: 0.2em;
}

/* ---------------------------------------------------------------- the chrome */
.dpy-head {
	position: sticky;
	top: 0;
	z-index: 20;
	background: var(--dpy-surface);
	border-bottom: 1px solid var(--dpy-line);
}
.dpy-head-in {
	max-width: 1100px;
	margin: 0 auto;
	padding: 0 20px;
	height: 64px;
	display: flex;
	align-items: center;
	gap: 28px;
}
.dpy-nav { display: flex; gap: 4px; margin-right: auto; }
.dpy-nav a {
	display: inline-block;
	padding: 8px 14px;
	border-radius: var(--dpy-r-sm);
	color: var(--dpy-ink-soft);
	text-decoration: none;
	font-weight: 500;
	font-size: 15px;
}
.dpy-nav a:hover { background: var(--dpy-raised); color: var(--dpy-ink); }
.dpy-nav a[aria-current="page"] { background: var(--dpy-raised); color: var(--dpy-indigo); font-weight: 600; }

.dpy-who {
	display: flex;
	align-items: center;
	gap: 10px;
	font-size: 14px;
	color: var(--dpy-muted);
}
.dpy-avatar {
	width: 32px;
	height: 32px;
	border-radius: 50%;
	background: var(--dpy-indigo);
	color: #fff;
	display: grid;
	place-items: center;
	font-weight: 700;
	font-size: 13px;
	letter-spacing: 0.02em;
}

/* ------------------------------------------------------------------- layout */
.dpy-main { max-width: 1100px; margin: 0 auto; padding: 28px 20px 80px; }
.dpy-cols { display: grid; gap: 20px; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); align-items: start; }
.dpy-stack { display: grid; gap: 20px; }

.dpy-card {
	background: var(--dpy-surface);
	border: 1px solid var(--dpy-line);
	border-radius: var(--dpy-r);
	box-shadow: var(--dpy-shadow-sm);
}
.dpy-card-pad { padding: 20px 22px; }
.dpy-card-head {
	display: flex;
	align-items: baseline;
	gap: 12px;
	padding: 18px 22px 0;
}
.dpy-card-head h2 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: 0.01em; }
.dpy-card-head a { margin-left: auto; font-size: 14px; color: var(--dpy-indigo); text-decoration: none; font-weight: 500; }
.dpy-card-head a:hover { text-decoration: underline; }

/* --------------------------------------------------------- the balance card */
.dpy-balance {
	background: linear-gradient(135deg, var(--dpy-indigo-deep), var(--dpy-indigo));
	color: #fff;
	border: 0;
	border-radius: var(--dpy-r-lg);
	padding: 26px 26px 22px;
	box-shadow: var(--dpy-shadow);
}
.dpy-balance-label { font-size: 13px; opacity: .78; letter-spacing: .08em; text-transform: uppercase; margin: 0 0 6px; }
.dpy-balance-figure {
	font-family: var(--dpy-num);
	font-variant-numeric: tabular-nums;
	font-size: clamp(2.1rem, 6vw, 2.9rem);
	font-weight: 700;
	line-height: 1.05;
	letter-spacing: -0.02em;
	margin: 0;
}
.dpy-balance-split { display: flex; flex-wrap: wrap; gap: 22px; margin: 20px 0 0; padding: 16px 0 0; border-top: 1px solid rgba(255,255,255,.22); }
.dpy-balance-split div { font-size: 13px; opacity: .85; }
.dpy-balance-split b { display: block; font-family: var(--dpy-num); font-variant-numeric: tabular-nums; font-size: 1rem; font-weight: 600; opacity: 1; margin-top: 2px; }

/* ------------------------------------------------------------ account rows */
.dpy-rows { list-style: none; margin: 0; padding: 6px 0 8px; }
.dpy-row {
	display: flex;
	align-items: center;
	gap: 14px;
	padding: 12px 22px;
	border-top: 1px solid var(--dpy-line);
}
.dpy-rows .dpy-row:first-child { border-top: 0; }
.dpy-row-body { min-width: 0; flex: 1; }
/* BLOCK, because they are two lines and not one. As inline spans they ran
   together and the feed read "GreenfieldsGroceries · 23:37", which is the kind
   of fault a diff cannot show and a screenshot shows immediately. */
.dpy-row-title { display: block; font-weight: 500; font-size: 15px; }
.dpy-row-sub { display: block; font-size: 13px; color: var(--dpy-muted); }
.dpy-row-amount {
	font-family: var(--dpy-num);
	font-variant-numeric: tabular-nums;
	font-weight: 600;
	white-space: nowrap;
}
.dpy-in { color: var(--dpy-in); }

/* A category mark, drawn in CSS: a money app shows a glyph per row and an
   image per merchant would be a third party CDN, which non-negotiable 12
   forbids. The letter is the merchant's initial, which is what a real app
   falls back to when it has no logo. */
.dpy-mark {
	width: 38px;
	height: 38px;
	flex: 0 0 38px;
	border-radius: 50%;
	display: grid;
	place-items: center;
	font-weight: 600;
	font-size: 14px;
	background: var(--dpy-raised);
	color: var(--dpy-indigo-deep);
}
.dpy-mark[data-cat="groceries"]     { background: #E3F5EC; color: #0E6B4A; }
.dpy-mark[data-cat="transport"]     { background: #E4EDFD; color: #1D4ED8; }
.dpy-mark[data-cat="eating_out"]    { background: #FDEEE3; color: #9A4B13; }
.dpy-mark[data-cat="subscriptions"] { background: #EFE8FD; color: #5B21B6; }
.dpy-mark[data-cat="bills"]         { background: #FCE9EC; color: #9F1239; }
.dpy-mark[data-cat="shopping"]      { background: #E8F0FB; color: #1E3A8A; }
.dpy-mark[data-cat="travel"]        { background: #E0F2FE; color: #075985; }
.dpy-mark[data-cat="cash"]          { background: #EDEFF5; color: #334155; }
.dpy-mark[data-cat="salary"]        { background: #DCF7EB; color: #0E8A5F; }

.dpy-daygroup { padding: 14px 22px 2px; font-size: 12px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--dpy-muted); border-top: 1px solid var(--dpy-line); }
.dpy-rows .dpy-daygroup:first-child { border-top: 0; }

/* ------------------------------------------------------------ empty states */
/* A money app with nothing in it is a screen this demonstration has to be able
   to show: one persona is deliberately pre KYC. An empty list that renders as
   nothing at all reads as a broken page. */
.dpy-empty { padding: 28px 22px; text-align: center; color: var(--dpy-muted); }
.dpy-empty strong { display: block; color: var(--dpy-ink); font-size: 15px; margin-bottom: 4px; }

/* ----------------------------------------------------------------- controls */
.dpy-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	min-height: 44px;
	padding: 11px 20px;
	border-radius: var(--dpy-r-sm);
	border: 1px solid transparent;
	font: inherit;
	font-weight: 600;
	cursor: pointer;
	text-decoration: none;
}
.dpy-btn-primary { background: var(--dpy-indigo); color: #fff; }
.dpy-btn-primary:hover { background: var(--dpy-indigo-deep); }
.dpy-btn-quiet { background: transparent; color: var(--dpy-ink-soft); border-color: var(--dpy-line); }
.dpy-btn-quiet:hover { background: var(--dpy-raised); color: var(--dpy-ink); }
.dpy-btn-block { width: 100%; }

.dpy-field { display: grid; gap: 6px; margin-bottom: 18px; }
.dpy-field label { font-weight: 600; font-size: 14px; }
.dpy-hint { font-size: 13px; color: var(--dpy-muted); }
.dpy-input {
	font: inherit;
	min-height: 48px;
	padding: 12px 14px;
	border: 1px solid var(--dpy-line);
	border-radius: var(--dpy-r-sm);
	background: var(--dpy-surface);
	color: var(--dpy-ink);
}
.dpy-input:focus-visible { border-color: var(--dpy-indigo); }

.dpy-alert {
	border-radius: var(--dpy-r-sm);
	padding: 12px 14px;
	font-size: 14px;
	margin-bottom: 18px;
	border: 1px solid;
}
.dpy-alert-bad { background: #FEF2F2; border-color: #FECACA; color: var(--dpy-bad); }
.dpy-alert-info { background: #EEF2FF; border-color: #C7D2FE; color: var(--dpy-indigo-deep); }

/* ------------------------------------------------------------- the sign in */
.dpy-auth { min-height: 100vh; display: grid; place-items: center; padding: 32px 20px; }
.dpy-auth-card { width: 100%; max-width: 420px; }
.dpy-auth-card h1 { font-family: var(--dpy-display); font-size: 1.6rem; margin: 0 0 6px; }
.dpy-auth-lede { color: var(--dpy-muted); margin: 0 0 24px; font-size: 15px; }
.dpy-auth-brand { display: block; margin-bottom: 28px; color: var(--dpy-ink); }
.dpy-personas { margin-top: 26px; padding-top: 20px; border-top: 1px solid var(--dpy-line); }
.dpy-personas h2 { font-size: 13px; letter-spacing: .07em; text-transform: uppercase; color: var(--dpy-muted); margin: 0 0 10px; }
.dpy-persona-list { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; margin: 0; padding: 0; }
.dpy-persona-list button {
	font: inherit;
	font-size: 13px;
	padding: 7px 12px;
	min-height: 36px;
	border-radius: 999px;
	border: 1px solid var(--dpy-line);
	background: var(--dpy-surface);
	color: var(--dpy-ink-soft);
	cursor: pointer;
}
.dpy-persona-list button:hover { border-color: var(--dpy-indigo); color: var(--dpy-indigo); }

/* ----------------------------------------------------------------- the foot */
.dpy-foot {
	border-top: 1px solid var(--dpy-line);
	background: var(--dpy-surface);
	padding: 22px 20px 40px;
	font-size: 13px;
	color: var(--dpy-muted);
}
.dpy-foot-in { max-width: 1100px; margin: 0 auto; display: grid; gap: 8px; }

/* The regulated disclaimer. CLAUDE.md section 6: it stays, because financial
   promotions are regulated and every figure here is invented. */
.dpy-disclaimer { max-width: 62ch; }

/* ------------------------------------------------------- the session notice */
.dpy-timeout-mask {
	position: fixed;
	inset: 0;
	z-index: 60;
	background: rgba(15, 23, 42, .55);
	display: grid;
	place-items: center;
	padding: 20px;
}
.dpy-timeout-card { background: var(--dpy-surface); border-radius: var(--dpy-r); max-width: 400px; padding: 24px; box-shadow: var(--dpy-shadow); }
.dpy-timeout-card h2 { margin: 0 0 8px; font-size: 1.15rem; }
.dpy-timeout-card p { margin: 0 0 18px; color: var(--dpy-ink-soft); }

/* ------------------------------------------------------------- the card */
/* THE CARD IS DRAWN, NOT PHOTOGRAPHED. CLAUDE.md non-negotiable 12 forbids an
   external image host, and a committed raster per card product is four files
   that go stale the moment a colour moves. Drawn in CSS it is one rule set,
   it is sharp at every density, and it can show a frozen card by going grey,
   which a photograph cannot. */
.dpy-card-face {
	position: relative;
	display: grid;
	grid-template-areas: "brand chip" ".  ." "number tier";
	grid-template-columns: 1fr auto;
	align-content: space-between;
	aspect-ratio: 1.586;
	max-width: 340px;
	padding: 18px 20px;
	border-radius: var(--dpy-r);
	color: #fff;
	box-shadow: var(--dpy-shadow);
	background: linear-gradient(135deg, var(--dpy-indigo) 0%, var(--dpy-indigo-deep) 100%);
}
/* One gradient per card product, so the room can see at a glance that the
   personas hold different cards. The ids are the dimension's own, from
   fintech_products.csv. */
.dpy-card-face[data-product="NPY-CRD-METAL"]  { background: linear-gradient(135deg, #3F4756 0%, #141A26 100%); }
.dpy-card-face[data-product="NPY-CRD-TRAVEL"] { background: linear-gradient(135deg, var(--dpy-mint) 0%, #0B6E52 100%); }
.dpy-card-face[data-product="NPY-CRD-BIZ"]    { background: linear-gradient(135deg, #5B6480 0%, #262C3C 100%); }

.dpy-card-brand {
	grid-area: brand;
	font-family: var(--dpy-display);
	font-size: 1.05rem;
	font-weight: 700;
	letter-spacing: 0.02em;
	line-height: 1;
	text-transform: uppercase;
}
/* The mint dot reads as mint on indigo and vanishes on the Travel card, which
   is mint itself, so on that one it goes white. */
.dpy-card-face[data-product="NPY-CRD-TRAVEL"] .dpy-logo-dot { background: #fff; }

/* The chip. Three lines across a rounded gold rectangle, which is the shape
   everybody recognises without it being any issuer's artwork. */
.dpy-card-chip {
	grid-area: chip;
	width: 34px;
	height: 26px;
	border-radius: 5px;
	background:
		linear-gradient(to bottom, transparent 30%, rgba(15, 23, 42, .35) 30%, rgba(15, 23, 42, .35) 36%, transparent 36%,
		                           transparent 62%, rgba(15, 23, 42, .35) 62%, rgba(15, 23, 42, .35) 68%, transparent 68%),
		linear-gradient(135deg, #E9D08A 0%, #C9A74E 100%);
}
.dpy-card-number {
	grid-area: number;
	font-family: var(--dpy-num);
	font-variant-numeric: tabular-nums;
	font-size: 1.05rem;
	letter-spacing: 0.06em;
	align-self: end;
}
.dpy-card-tier {
	grid-area: tier;
	align-self: end;
	justify-self: end;
	font-size: 12px;
	font-weight: 600;
	letter-spacing: 0.07em;
	text-transform: uppercase;
	opacity: .85;
}
/* A frozen card is grey AND says so. Colour alone is not a signal: the same
   rule as the credit and debit hues above. */
.dpy-card-face.is-frozen {
	background: linear-gradient(135deg, #9AA1B4 0%, #6B7392 100%);
	box-shadow: var(--dpy-shadow-sm);
}
.dpy-card-state {
	position: absolute;
	inset: auto 20px 50% auto;
	padding: 4px 10px;
	border-radius: 999px;
	background: rgba(15, 23, 42, .45);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/* A message box that has nothing to say takes no room. .dpy-card-pad draws
   its own padding whether or not anything inside it is visible, which showed
   as a band of empty white above the first row of a list. */
.dpy-card-note { padding: 0 22px; }
.dpy-card-note .dpy-alert { margin: 18px 0 0; }

/* --------------------------------------------------------------- the pots */
/* A progress bar is the one place a screen is tempted to work out a figure
   from two others. The width here is the percentage dfintech-profile already
   computed in numeric, so the bar is drawing a number rather than deriving
   one. */
.dpy-pots .dpy-pot { display: block; padding: 16px 22px; }
.dpy-pot-top { display: flex; align-items: flex-start; gap: 12px; }
.dpy-pot-top .dpy-row-body { flex: 1 1 auto; min-width: 0; }
.dpy-bar {
	height: 8px;
	margin: 12px 0 10px;
	border-radius: 999px;
	background: var(--dpy-raised);
	overflow: hidden;
}
.dpy-bar > span {
	display: block;
	height: 100%;
	border-radius: 999px;
	background: linear-gradient(90deg, var(--dpy-indigo-light), var(--dpy-indigo));
}
.dpy-bar.is-done > span { background: linear-gradient(90deg, var(--dpy-mint), var(--dpy-mint-deep)); }
.dpy-pot-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.dpy-pot-acts { display: flex; gap: 8px; }
.dpy-pot-fund { display: flex; gap: 8px; margin-top: 12px; }
.dpy-pot-fund .dpy-input { flex: 1 1 auto; min-width: 0; }
.dpy-btn-sm { min-height: 36px; padding: 7px 12px; font-size: 13px; }
.dpy-tag {
	display: inline-block;
	vertical-align: middle;
	margin-left: 6px;
	padding: 2px 8px;
	border-radius: 999px;
	background: var(--dpy-raised);
	color: var(--dpy-ink-soft);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
}

/* ------------------------------------------------------------- the credit */
.dpy-score { display: grid; gap: 2px; margin-bottom: 14px; }
.dpy-score-num {
	font-family: var(--dpy-display);
	font-variant-numeric: tabular-nums;
	font-size: 2.6rem;
	font-weight: 700;
	line-height: 1;
	color: var(--dpy-ink);
}
.dpy-score-band { font-weight: 600; color: var(--dpy-ink-soft); }
/* The direction is in the words as well as the colour, so the hue is a second
   signal and never the only one. */
.dpy-score-move { font-size: 13px; color: var(--dpy-muted); }
.dpy-score-move.is-up { color: var(--dpy-in); }
.dpy-score-move.is-down { color: var(--dpy-warn); }

.dpy-quote {
	display: grid;
	gap: 8px;
	margin-top: 16px;
	padding: 18px;
	border-radius: var(--dpy-r-sm);
	background: var(--dpy-raised);
}
.dpy-quote-num {
	font-family: var(--dpy-display);
	font-variant-numeric: tabular-nums;
	font-size: 1.8rem;
	font-weight: 700;
	line-height: 1;
	color: var(--dpy-indigo-deep);
}
.dpy-quote-num small { font-family: var(--dpy-font); font-size: 0.85rem; font-weight: 500; color: var(--dpy-muted); }

/* ---------------------------------------------------------------- the PIN */
/* The digits are the whole answer, so they are the size of one. Tabular
   figures, because four numerals that jiggle read as an animation. */
.dpy-pin { margin-top: 14px; }
.dpy-pin-ask { margin-top: 14px; }
.dpy-pin-out {
	margin-top: 14px;
	padding: 18px;
	border-radius: var(--dpy-r-sm);
	background: var(--dpy-raised);
	text-align: center;
	display: grid;
	gap: 8px;
}
.dpy-pin-digits {
	font-family: var(--dpy-num);
	font-variant-numeric: tabular-nums;
	font-size: 2rem;
	font-weight: 700;
	letter-spacing: 0.3em;
	/* The letter spacing pushes the last digit off centre by one gap. */
	text-indent: 0.3em;
	color: var(--dpy-indigo-deep);
}

/* ---------------------------------------------------------- the switches */
/* role="switch" with aria-checked, so it is a switch to a screen reader and
   not a button that has to be guessed at. The tick is drawn by the knob's
   position, never by colour alone. */
.dpy-switches { margin-top: 18px; }
.dpy-switch {
	flex: 0 0 48px;
	width: 48px;
	height: 28px;
	padding: 0;
	border: 1px solid var(--dpy-line);
	border-radius: 999px;
	background: var(--dpy-raised);
	cursor: pointer;
	position: relative;
	transition: background .14s ease, border-color .14s ease;
}
.dpy-switch::after {
	content: "";
	position: absolute;
	top: 3px;
	left: 3px;
	width: 20px;
	height: 20px;
	border-radius: 50%;
	background: var(--dpy-surface);
	box-shadow: var(--dpy-shadow-sm);
	transition: transform .14s ease;
}
.dpy-switch.is-on { background: var(--dpy-mint-deep); border-color: var(--dpy-mint-deep); }
.dpy-switch.is-on::after { transform: translateX(20px); }
.dpy-switch[disabled] { opacity: .45; cursor: not-allowed; }
.dpy-switch:focus-visible { outline: 2px solid var(--dpy-indigo); outline-offset: 2px; }

/* The two transitions above are the only motion in this stylesheet, and a
   visitor who has asked for less of it gets none. */
@media (prefers-reduced-motion: reduce) {
	.dpy-switch, .dpy-switch::after { transition: none; }
}

/* --------------------------------------------------------------- [hidden] */
/* [hidden] IS A REQUEST, NOT A GUARANTEE, and this cost a browser run to see.
   The user agent hides a [hidden] element with display: none at the lowest
   specificity there is, so any class here that declares a display of its own
   silently outranks it. .dpy-btn is inline-flex and .dpy-pin-out is grid, and
   both sat on screen with hidden set: the "Show my PIN" button stayed under
   the form it had just opened, and an empty grey panel sat below it waiting
   for digits.

   Nothing errors when this happens, which is what makes it worth a rule
   rather than a fix per element: the next class with a display and a hidden
   state would do it again.

   Scoped to this app's own namespace rather than written bare, because
   novapay-style.css stands beside this file until M7 and a bare [hidden]
   rule here would reach into the live site. */
[class^="dpy-"][hidden],
[class*=" dpy-"][hidden] { display: none; }

/* ----------------------------------------------------------------- the slot */
/* A Dengage inline target renders its own markup and is NOT sandboxed, so the
   page gives it a box and never a style. */
.dpy-slot:empty { display: none; }
/* A campaign wider than the screen, a Story rail on a phone, scrolls inside
   its slot rather than pushing the whole page sideways. Measured by
   tools/verify/slottest.js: a 900px rail made a 420px page 920px wide. */
.dpy-slot { max-width: 100%; min-width: 0; overflow-x: auto; }

@media (max-width: 860px) {
	.dpy-cols { grid-template-columns: minmax(0, 1fr); }
	.dpy-head-in { gap: 14px; }
	.dpy-nav { gap: 0; }
	.dpy-nav a { padding: 8px 10px; font-size: 14px; }
	/* The NAME goes, the avatar stays. The rule was `.dpy-who span`, and the
	   avatar is a span too, so a narrow window lost the one thing that says
	   which persona is signed in: in a demonstration that is the whole point
	   of the header, and it costs 32 pixels. */
	.dpy-who > span:not(.dpy-avatar) { display: none; }
}

/* THE HEADER HAS NO HORIZONTAL SLACK. CLAUDE.md non-negotiable 13 says room
   is made BEFORE a nav control is added, and M3's Cards entry is the fourth.
   Measured at 360px before this rule: the row wanted 420px, so the sign out
   button sat past the right edge and the lockup broke over two lines reading
   "D." above "PAY". Three items already overflowed; the fourth made it worse
   by sixty pixels.

   Room is made rather than taken. Below 640px the nav drops to a row of its
   own, where four items have the full width instead of what is left of it,
   and the lockup and the sign out button keep the row above to themselves. */
@media (max-width: 640px) {
	.dpy-head-in {
		flex-wrap: wrap;
		height: auto;
		min-height: 56px;
		padding-top: 10px;
		padding-bottom: 6px;
		row-gap: 2px;
	}
	.dpy-head-in > .dpy-logo { margin-right: auto; white-space: nowrap; }
	.dpy-nav {
		order: 3;
		width: 100%;
		margin-right: 0;
		overflow-x: auto;
	}
	.dpy-nav a { white-space: nowrap; }
}
/* ------------------------------------------------------- the public site */
/* The signed out half. It shares every token, the header, the footer, the
   buttons and the cards with the app above: one stylesheet, because two with
   two sets of breakpoints is two things to get wrong, and the media query
   that gives the nav a row of its own below 640px has to cover both. */

/* A marketing page is read at arm's length, so it is wider than a screen of
   figures and the measure is capped rather than the container. */
.dpy-wide { max-width: 1100px; }
.dpy-wide .dpy-lede { max-width: 62ch; font-size: 1.05rem; color: var(--dpy-ink-soft); }

.dpy-hero {
	display: grid;
	grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
	gap: 40px;
	align-items: center;
	padding: 48px 0 40px;
}
.dpy-hero h1 {
	font-family: var(--dpy-display);
	font-size: clamp(2rem, 1.2rem + 3vw, 3.2rem);
	line-height: 1.08;
	letter-spacing: -0.015em;
	margin: 0 0 16px;
}
.dpy-hero-words .dpy-lede { margin: 0 0 24px; }
.dpy-hero-acts { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 14px; }
/* The card in the hero is the same drawing as the one on the Cards screen,
   tilted. Non-negotiable 12 forbids an external image host and this needs no
   image at all. */
.dpy-hero-art { display: grid; place-items: center; }
.dpy-hero-art .dpy-card-face {
	width: 100%;
	transform: rotate(-4deg);
	box-shadow: 0 2px 4px rgba(15, 23, 42, .08), 0 24px 48px rgba(49, 46, 158, .18);
}

.dpy-band { padding: 40px 0; border-top: 1px solid var(--dpy-line); }
.dpy-band > h2 {
	font-family: var(--dpy-display);
	font-size: 1.5rem;
	margin: 0 0 10px;
}
.dpy-band > .dpy-lede { margin: 0 0 24px; }

.dpy-cols-3 {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
	gap: 20px;
	margin-top: 24px;
}
.dpy-point h3 { font-size: 1.05rem; margin: 0 0 8px; }
.dpy-point p { margin: 0; color: var(--dpy-ink-soft); font-size: 15px; }

/* ------------------------------------------------------------- the plans */
.dpy-plans {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
	gap: 14px;
}
.dpy-plan {
	display: grid;
	gap: 6px;
	align-content: start;
	padding: 20px;
	border-radius: var(--dpy-r);
	border: 1px solid var(--dpy-line);
	background: var(--dpy-surface);
	color: inherit;
	text-decoration: none;
}
.dpy-plan:hover { border-color: var(--dpy-indigo); box-shadow: var(--dpy-shadow-sm); }
.dpy-plan-name {
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--dpy-muted);
}
.dpy-plan-fee {
	font-family: var(--dpy-display);
	font-variant-numeric: tabular-nums;
	font-size: 1.5rem;
	font-weight: 700;
	line-height: 1.1;
}
.dpy-plan-fee small { font-family: var(--dpy-font); font-size: 0.8rem; font-weight: 500; color: var(--dpy-muted); }
.dpy-plan-promise { font-size: 14px; color: var(--dpy-ink-soft); }

/* ------------------------------------------------------------- the tiles */
.dpy-tiles {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
	gap: 14px;
}
.dpy-tile {
	display: grid;
	gap: 6px;
	align-content: start;
	padding: 20px;
	border-radius: var(--dpy-r);
	background: var(--dpy-surface);
	border: 1px solid var(--dpy-line);
	color: inherit;
	text-decoration: none;
}
.dpy-tile:hover { border-color: var(--dpy-indigo); box-shadow: var(--dpy-shadow-sm); }
.dpy-tile-name { font-family: var(--dpy-display); font-size: 1.1rem; font-weight: 600; }
.dpy-tile-line { font-size: 14px; color: var(--dpy-ink-soft); }
.dpy-tile-count { font-size: 12px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--dpy-muted); }

/* -------------------------------------------------------- the foot, wide */
.dpy-foot-cols {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
	gap: 20px;
	margin-bottom: 18px;
}
.dpy-foot-cols strong { display: block; color: var(--dpy-ink); margin-bottom: 8px; }
.dpy-foot-cols a { display: block; color: var(--dpy-muted); text-decoration: none; padding: 3px 0; }
.dpy-foot-cols a:hover { color: var(--dpy-indigo); }

/* --------------------------------------------------------- opening an account */
/* The funnel's own furniture. Every step is one row in
   fintech_onboarding_events and the step strip is the only thing that tells a
   visitor how much is left, so it is drawn rather than counted in prose. */
.dpy-steps {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 18px;
	list-style: none;
	margin: 0 0 24px;
	padding: 0;
	counter-reset: step;
}
.dpy-step { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--dpy-muted); }
.dpy-step-dot {
	width: 10px;
	height: 10px;
	border-radius: 50%;
	background: var(--dpy-raised);
	border: 2px solid var(--dpy-line);
}
.dpy-step.is-done { color: var(--dpy-ink-soft); }
.dpy-step.is-done .dpy-step-dot { background: var(--dpy-mint-deep); border-color: var(--dpy-mint-deep); }
.dpy-step.is-here { color: var(--dpy-indigo); font-weight: 600; }
.dpy-step.is-here .dpy-step-dot { background: var(--dpy-indigo); border-color: var(--dpy-indigo); }

.dpy-apply { max-width: 520px; }
.dpy-apply h2 { font-family: var(--dpy-display); margin: 0 0 8px; }
.dpy-apply p { color: var(--dpy-ink-soft); }

.dpy-check { display: flex; align-items: flex-start; gap: 10px; font-size: 14px; margin-bottom: 18px; color: var(--dpy-ink-soft); }
.dpy-check input { margin-top: 3px; }

.dpy-choices { display: grid; gap: 10px; margin-bottom: 8px; }
.dpy-choice {
	font: inherit;
	text-align: left;
	min-height: 52px;
	padding: 14px 16px;
	border-radius: var(--dpy-r-sm);
	border: 1px solid var(--dpy-line);
	background: var(--dpy-surface);
	color: var(--dpy-ink);
	font-weight: 600;
	cursor: pointer;
}
.dpy-choice:hover { border-color: var(--dpy-indigo); color: var(--dpy-indigo); }

/* A button that reads as a link, for the one control on the page that leaves
   it. It is a button and not an anchor because it fires an event first. */
.dpy-linkbtn {
	font: inherit;
	font-size: 13px;
	padding: 0;
	border: 0;
	background: none;
	color: var(--dpy-muted);
	text-decoration: underline;
	cursor: pointer;
}
.dpy-linkbtn:hover { color: var(--dpy-indigo); }

/* ------------------------------------------------------------ help and fraud */
.dpy-qas { display: grid; gap: 10px; max-width: 70ch; }
.dpy-qa {
	border: 1px solid var(--dpy-line);
	border-radius: var(--dpy-r-sm);
	background: var(--dpy-surface);
	padding: 14px 18px;
}
.dpy-qa summary { font-weight: 600; cursor: pointer; }
.dpy-qa p { margin: 10px 0 0; color: var(--dpy-ink-soft); }

/* The fraud steps are numbered because the ORDER is the advice. */
.dpy-ordered { max-width: 70ch; margin: 0; padding-left: 22px; display: grid; gap: 12px; }
.dpy-ordered li { color: var(--dpy-ink-soft); }
.dpy-ordered strong { color: var(--dpy-ink); }
.dpy-urgent {
	border-top: 0;
	border-left: 3px solid var(--dpy-bad);
	padding-left: 20px;
	margin-top: 8px;
}

/* A phone, drawn. Non-negotiable 12 forbids an external image host and this
   needs no image at all. */
.dpy-phone {
	width: 220px;
	aspect-ratio: 0.49;
	border-radius: 28px;
	padding: 10px;
	background: linear-gradient(160deg, #2A3040, #11141C);
	box-shadow: 0 2px 4px rgba(15, 23, 42, .1), 0 24px 48px rgba(15, 23, 42, .22);
}
.dpy-phone-screen {
	height: 100%;
	border-radius: 20px;
	background: linear-gradient(160deg, var(--dpy-indigo), var(--dpy-indigo-deep));
	display: grid;
	place-items: center;
	color: #fff;
}

/* ---------------------------------------------------- the catalogue pages */
.dpy-crumbs { padding: 24px 0 0; font-size: 14px; color: var(--dpy-muted); }
.dpy-crumbs a { color: var(--dpy-muted); text-decoration: none; }
.dpy-crumbs a:hover { color: var(--dpy-indigo); }

.dpy-plan-head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 24px;
	flex-wrap: wrap;
	margin-bottom: 20px;
}
.dpy-plan-head h2 { font-family: var(--dpy-display); font-size: 1.5rem; margin: 0 0 6px; }
.dpy-plan-head .dpy-lede { margin: 0 0 4px; }
.dpy-plan-price { display: grid; gap: 10px; justify-items: end; text-align: right; }

/* A product's two figures sit together, because a fee without the rate beside
   it is half a price. */
.dpy-product-figures { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.dpy-product-fee {
	font-family: var(--dpy-display);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
	font-size: 1.15rem;
}
.dpy-product-fee small { font-family: var(--dpy-font); font-size: 0.75rem; font-weight: 500; color: var(--dpy-muted); }
.dpy-product-rate {
	font-size: 12px;
	font-weight: 600;
	letter-spacing: 0.03em;
	padding: 3px 9px;
	border-radius: 999px;
	background: #ECFDF5;
	color: var(--dpy-in);
}
.dpy-product-figures-big { margin: 18px 0 20px; }
.dpy-product-figures-big .dpy-product-fee { font-size: 1.9rem; }

.dpy-features { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; max-width: 62ch; }
.dpy-features li { position: relative; padding-left: 28px; color: var(--dpy-ink-soft); }
/* A tick drawn in CSS rather than a character, so it is the brand's green at
   any size and a screen reader is not read a dingbat. */
.dpy-features li::before {
	content: "";
	position: absolute;
	left: 4px;
	top: 6px;
	width: 11px;
	height: 6px;
	border-left: 2px solid var(--dpy-mint-deep);
	border-bottom: 2px solid var(--dpy-mint-deep);
	transform: rotate(-45deg);
}

@media (max-width: 860px) {
	.dpy-plan-price { justify-items: start; text-align: left; }
	.dpy-hero { grid-template-columns: minmax(0, 1fr); gap: 28px; padding-top: 32px; }
	/* The card goes first on a narrow screen, because a product you can see is
	   a better opening than a paragraph you have to scroll past. */
	.dpy-hero-art { order: -1; max-width: 340px; }
}

/* The estimate on a product page that quotes a rate, assets/catalogue-pages.js. */
.dpy-estimate-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0 14px; max-width: 640px; }
.dpy-estimate-form .dpy-field { flex: 1 1 200px; margin-bottom: 14px; }
.dpy-estimate-form .dpy-btn { margin-bottom: 14px; }
.dpy-estimate-result {
	margin: 4px 0 12px;
	padding: 14px 18px;
	max-width: 640px;
	border-radius: var(--dpy-r);
	background: var(--dpy-raised);
	font-size: 16px;
	color: var(--dpy-ink);
}
.dpy-estimate-result strong { font-family: var(--dpy-display); color: var(--dpy-indigo-deep); }
.dpy-estimate-result.is-bad { background: #FEF2F2; color: var(--dpy-bad); }
@media (max-width: 520px) {
	.dpy-estimate-form .dpy-btn { width: 100%; }
}

/* Web push, asked only from a press. Drawn by js/novapayEvents.js into the
   footer of both halves and onto online banking Home. */
.dpy-push { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.dpy-push p { margin: 0; flex: 1 1 260px; color: var(--dpy-ink-soft); font-size: 14px; line-height: 1.45; }
.dpy-push p b { color: var(--dpy-ink); }
.dpy-push-slot { margin: 18px 0 14px; padding: 14px 16px; border: 1px solid var(--dpy-line); border-radius: 12px; background: #fff; }
.dpy-push-slot:empty { display: none; }
.dpy-push[data-push-state="granted"] p b::before { content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--dpy-mint-deep); margin-right: 8px; vertical-align: middle; }
