/*
 * cp-consumer.css — design system for the customer-facing (consumer) Customer Portal shell.
 *
 * Companion to cp-admin-modern.css, which serves the CSR/admin shell. This file is deliberately
 * standalone: it does not depend on AppStrap (csa-theme-style.min.css) or on the runtime-injected
 * UserTheme <style> block, so a view that opts into the consumer shell sheds both.
 *
 * Colours are the CSA brand palette. There is exactly one theme; per-tenant variation is the
 * utility logo and name only.
 */

/* ─── Barlow (self-hosted; no third-party font origin on the critical path) ───────────────── */
@font-face {
	font-family: 'Barlow';
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url('../fonts/barlow/barlow-400.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
		U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
		U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: 'Barlow';
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url('../fonts/barlow/barlow-500.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
		U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
		U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: 'Barlow';
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url('../fonts/barlow/barlow-600.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
		U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
		U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: 'Barlow';
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url('../fonts/barlow/barlow-700.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
		U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
		U+2215, U+FEFF, U+FFFD;
}

/* ─── Tokens ──────────────────────────────────────────────────────────────────────────────── */
:root {
	color-scheme: light;

	--cp-ink: #0b2239;
	--cp-ink-2: #476175;
	/* 4.94:1 on white, 4.59:1 on --cp-paper. Was #8aa0b2 -- 2.71:1 and 2.52:1, which failed WCAG AA's
	   4.5:1 and even the 3:1 large-text floor, on the phone tab labels, every stat-card eyebrow, table
	   column headers, chart ticks and the footer. Measured, not estimated. */
	--cp-ink-3: #5c7386;

	--cp-navy: #005c97;
	--cp-navy-deep: #002b50;
	--cp-azure: #0085ca;
	--cp-azure-ink: #0176a3;
	--cp-sky: #71c5e8;
	--cp-sky-tint: #eaf4fb;
	--cp-turquoise: #00b5ad;

	--cp-amber: #ffa300;
	--cp-amber-ink: #8a5a00;
	--cp-amber-tint: #fff7e8;
	--cp-amber-line: #ffe0a6;
	--cp-gold: #ffcd00;

	--cp-good: #036b67;
	--cp-good-tint: #e2f7f6;
	--cp-alert: #9c2a21;
	--cp-alert-tint: #fcebe9;

	--cp-paper: #f4f7fa;
	--cp-surface: #ffffff;
	--cp-line: #dee7ef;
	--cp-line-2: #edf2f7;
	/* 3.33:1 on white, 3.10:1 on --cp-paper, clearing WCAG 1.4.11's 3:1 for a UI-component boundary.
	   Was #c6d4e0 -- 1.51:1. Every input, select and textarea sits on --cp-surface, the same white as the
	   card behind it, so this border is the only thing that says where the field is. */
	--cp-field-line: #7690a6;

	--cp-sidebar-w: 232px;
	/* Matches .cp-topbar's own min-height, which is now constant whether or not .cp-acct renders. */
	--cp-topbar-h: 77px;

	--cp-r-sm: 9px;
	--cp-r-md: 16px;
	--cp-r-pill: 999px;

	--cp-sh-1: 0 1px 2px rgba(11, 34, 57, .06), 0 1px 3px rgba(11, 34, 57, .04);
	--cp-sh-2: 0 2px 4px rgba(11, 34, 57, .05), 0 8px 24px -8px rgba(11, 34, 57, .14);

	--cp-font: 'Barlow', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

	/*
	 * A type scale collapsing the ~34 distinct font-sizes this file had accumulated (e.g. 13/13.5/14
	 * for what was, in every audited case, the same "small text" role at three different call sites).
	 * The rounding policy is "up, not down" as the goal, not a strict guarantee: whole-pixel values
	 * always round up or land exactly; the handful of `rem`-based values (.7rem/.9rem/.95rem, ~8 call
	 * sites) round to the nearest whole-pixel step instead, which is down by at most 0.8px in a few
	 * cases -- an independent review confirmed zero visual consequence from those, so it wasn't worth
	 * re-deriving the scale around eighths of a rem to close a sub-pixel gap. Most steps stay within a
	 * pixel or two of what they replace, but a few widely-spaced values (e.g. 22px and 24px, or 25/26px
	 * and 28px) were genuinely equidistant or close to it between two steps and got grouped with the
	 * larger one deliberately, which can mean a 3-4px jump at that specific site (e.g. .cp-authcard h1
	 * and its sibling h3, both 25px before, kept at --cp-text-3xl rather than -2xl to preserve their
	 * narrow-breakpoint step-down relationship -- see the media query below .cp-authcard's base rule).
	 * Checked live rather than assumed harmless. One site is intentionally NOT on this scale and stays
	 * as a literal px value: the wizard step-chip label (.csa-progress-bar's 12.5px, tied to a fixed
	 * 34px box height and label-fit math measured down to single-digit pixels of headroom -- see the
	 * comment above .cp-wizardbar's breakpoint) and any other spot flagged with a similar "measured,
	 * not a scale candidate" comment at its call site.
	 */
	--cp-text-3xs: 11px;
	--cp-text-2xs: 12px;
	--cp-text-xs: 13px;
	--cp-text-sm: 14px;
	--cp-text-base: 15px;
	--cp-text-md: 16px;
	--cp-text-lg: 18px;
	--cp-text-xl: 20px;
	--cp-text-2xl: 24px;
	--cp-text-3xl: 28px;
	--cp-text-4xl: 32px;
	--cp-text-display: 52px;
	--cp-text-display-lg: 64px;
	/* The account-home balance figure only -- see .cp-balance__amt at the 760px breakpoint. */
	--cp-text-hero: 76px;

	/*
	 * A spacing scale for `gap` specifically -- covers every whole-pixel gap value in the file (2px
	 * through 20px) with an exact or round-up match; the one `rem`-based outlier (.4rem = 6.4px, a
	 * single call site) rounds down to 6px instead, by 0.4px, same as the sub-pixel rem cases in the
	 * type scale above. One gap value in the file, 34px, exceeds this scale's 20px ceiling and is left
	 * as a literal (see the comment at its call site) rather than added as a 10th token for one use.
	 * Padding and margin are
	 * NOT on this scale: both are near-universally multi-value shorthand here (`padding: 22px 28px`,
	 * `margin: 12px 0 0`), and auditing the data showed that variation is overwhelmingly real
	 * per-component box-model tuning (a button's horizontal padding differs from a card's for
	 * structural reasons), not arbitrary noise the way the single-value gap/font-size data was.
	 * Collapsing shorthand padding/margin onto a token scale is a separate, larger project needing
	 * its own component-by-component pass, not a mechanical value-to-token mapping.
	 */
	--cp-space-1: 2px;
	--cp-space-2: 4px;
	--cp-space-3: 6px;
	--cp-space-4: 8px;
	--cp-space-5: 10px;
	--cp-space-6: 12px;
	--cp-space-7: 14px;
	--cp-space-8: 16px;
	--cp-space-9: 20px;

	--cp-dur: 180ms;
	--cp-ease: cubic-bezier(.4, 0, .2, 1);
}

/*
 * Desktop reads three steps larger. The sidebar layout starts at 1100px, where the page sits at arm's length
 * on a monitor rather than in the hand, and the phone-tuned scale read small there. Every text size in
 * the consumer pages resolves through these tokens, so redefining them here scales the whole desktop
 * experience without touching a layout; phone and tablet keep the scale above. Display and hero sizes
 * are already large and stay put.
 */
@media (min-width: 1100px) {
	:root {
		--cp-text-3xs: 14px;
		--cp-text-2xs: 15px;
		--cp-text-xs: 16px;
		--cp-text-sm: 17px;
		--cp-text-base: 18px;
		--cp-text-md: 19px;
		--cp-text-lg: 21px;
		--cp-text-xl: 24px;
		--cp-text-2xl: 30px;
		--cp-text-3xl: 34px;
		--cp-text-4xl: 38px;
	}
}

/* ─── Base ────────────────────────────────────────────────────────────────────────────────── */
.cp-consumer {
	margin: 0;
	font-family: var(--cp-font);
	font-size: var(--cp-text-base);
	line-height: 1.5;
	color: var(--cp-ink);
	background: var(--cp-paper);
	-webkit-font-smoothing: antialiased;
}

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

.cp-consumer img {
	max-width: 100%;
}

.cp-skip {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 100;
	background: var(--cp-navy);
	color: #fff;
	padding: 12px 18px;
	border-radius: 0 0 var(--cp-r-sm) 0;
	font-weight: 600;
	text-decoration: none;
}

.cp-skip:focus {
	left: 0;
}

.cp-sr {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/*
 * Bootstrap's name, deliberately, because core_js.scala.html toggles this exact class to swap a
 * profile section between its read-only row (.profile-information) and its edit form (.profile-form).
 * Nine profile tags rely on that mechanism. AppStrap supplied the rule and the consumer shell does
 * not load AppStrap, so without this the class went on and off with no effect: both blocks rendered
 * at once and Cancel did nothing observable. Renaming it to a cp- token would mean touching core_js
 * and every one of those tags for no behavioural gain, so the shared sheet defines the name instead.
 */
.d-none {
	display: none !important;
}

/* A flex row that keeps a control and a trailing action aligned on one line -- the self-service
   file input's Remove button is the case that exists. Wraps on a narrow phone rather than squeezing
   the control down to unreadable width; give the control a min-width so it wraps before it shrinks. */
.cp-inline {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--cp-space-5);
}

.cp-inline > input {
	min-width: 200px;
}

/* A manually-placed required marker for a field whose validation is not the native `required`
   attribute (e.g. self-service's fiber voice-porting fields, which stay unrequired in markup and
   are validated by data-required + JS instead, so the ambient div:has(input[required]) asterisk
   rule in self_service/forms/tags/common.scala.html never fires for them). */
.cp-req {
	color: var(--cp-alert);
}

.cp-shell {
	min-height: 100vh;
	display: flex;
	flex-direction: column;
}

.cp-main {
	flex: 1 0 auto;
	min-width: 0;
}

.cp-wrap {
	width: min(100% - 2rem, 1180px);
	margin-inline: auto;
}

/*
 * .cp-content is a flex column, and a UA gives hr margin-inline:auto -- auto cross-axis margins disable
 * align-self:stretch, so a bare <hr> collapsed to zero content width and painted only its 2px border.
 * Three of them rendered as specks in the middle of the notifications page.
 */
hr {
	margin: 4px 0;
	align-self: stretch;
	width: 100%;
	border: 0;
	border-top: 1px solid var(--cp-line);
}

/*
 * A heading with no class fell through to the UA default -- 1.5em, about 22.5px against the system's
 * 15.5px. That shipped twice. A base rule means a bare heading is merely unstyled-for-its-context, not
 * 45% oversized.
 */
.cp-content h2,
.cp-content h3,
.cp-content h4 {
	margin: 0 0 14px;
	font-size: var(--cp-text-md);
	font-weight: 600;
	letter-spacing: -.01em;
	color: var(--cp-ink);
}

.cp-lbl {
	font-size: var(--cp-text-3xs);
	font-weight: 500;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--cp-ink-3);
}

/*
 * The balance hero's "Current balance" label is a real <h2> now (see current_balance_consumer.scala.html
 * -- it was a bare span, leaving no h2 anywhere in account home's heading outline before the stat cards'
 * h3s). .cp-content h2 above outranks .cp-lbl alone on specificity, so without this override promoting
 * the tag would also have silently promoted the look -- 600 weight, dark ink, a 14px bottom margin -- to
 * something that reads as a section heading rather than the small caps label it has always been.
 */
.cp-content h2.cp-lbl {
	margin: 0;
	font-size: var(--cp-text-3xs);
	font-weight: 500;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--cp-ink-3);
}

/* ─── Buttons ─────────────────────────────────────────────────────────────────────────────── */
.cp-btn {
	align-items: center;
	justify-content: center;
	gap: var(--cp-space-4);
	border: 0;
	border-radius: var(--cp-r-sm);
	padding: 13px 20px;
	min-height: 46px;
	font-family: inherit;
	font-weight: 600;
	font-size: var(--cp-text-base);
	cursor: pointer;
	text-decoration: none;
	transition: background var(--cp-dur) var(--cp-ease), border-color var(--cp-dur) var(--cp-ease),
		transform var(--cp-dur) var(--cp-ease), filter var(--cp-dur) var(--cp-ease);
}

/*
 * :not([hidden]) -- account_sync's #loader-reload button (and any other .cp-btn a page starts
 * hidden, e.g. revealed only after a timeout) needs to actually stay hidden. An unqualified
 * `display: inline-flex` here would win the specificity tie against [hidden]'s UA display:none,
 * same collision as .cp-check above -- worth scoping on the single most-used class in the sheet
 * rather than patching each caller.
 */
.cp-btn:not([hidden]) {
	display: inline-flex;
}

.cp-btn:focus-visible {
	outline: 2px solid var(--cp-azure);
	outline-offset: 2px;
}

.cp-btn:active {
	transform: translateY(1px);
	filter: brightness(.94);
}

/*
 * No pointer-events:none here -- it would win the cascade over cursor:not-allowed (pointer-events:none
 * takes the element out of hit-testing entirely, so the browser paints whatever cursor sits underneath
 * instead of this rule's own). A real <button disabled> already rejects clicks/hover natively, so
 * nothing further is needed to block interaction; an anchor-styled .cp-btn[disabled] still needs its
 * click handler to check the attribute itself, same as it would without this rule.
 */
/*
 * A solid muted fill, not opacity. Opacity blends the WHOLE button (background and text alike)
 * toward whatever sits behind it, so a white-on-azure primary button measured at 2.3:1 here --
 * failing WCAG's 4.5:1 -- at the exact moment (mid-submit, spinner showing) a customer most needs to
 * read it. A solid background keeps full, backdrop-independent contrast against the white text every
 * variant already uses.
 */
.cp-btn:disabled,
.cp-btn[disabled] {
	background: var(--cp-ink-3);
	color: #fff;
	opacity: 1;
	cursor: not-allowed;
	transform: none;
	filter: none;
	box-shadow: none;
}

/*
 * A reusable loading affordance -- add this class and prepend a .cp-spinner (matching the button's
 * text color via currentColor below) when a submit/advance action is in flight. Not yet wired into
 * any submit handler; the primitive exists so a future pass can add it to payment submit and
 * wizard-advance without inventing the CSS at the same time. Pair with `disabled` on the element
 * itself to actually block a repeat click -- see the .cp-btn:disabled comment above for why this
 * class alone (no pointer-events:none) doesn't block clicks by itself.
 */
.cp-btn--loading {
	cursor: wait;
}

.cp-btn .cp-spinner {
	border-color: currentColor;
	border-top-color: transparent;
	opacity: .85;
}

.cp-btn--loading .cp-spinner {
	margin-right: 8px;
}

.cp-btn--primary {
	background: var(--cp-azure-ink);
	color: #fff;
}

.cp-btn--primary:hover {
	background: var(--cp-navy);
	color: #fff;
}

/*
 * Restores this variant's own (already-passing) resting color instead of the generic .cp-btn:disabled
 * grey below. That grey exists for buttons with no brand color of their own to fall back on, but on a
 * payment submit it read as "this failed" at the exact moment -- mid-charge, spinner showing -- a
 * customer most needs to trust the button is still working. Same contrast as the enabled state; only
 * the cursor, spinner and "Processing..." label communicate that a click will not register.
 */
.cp-btn--primary:disabled,
.cp-btn--primary[disabled] {
	background: var(--cp-azure-ink);
	color: #fff;
}

/*
 * For an irreversible action (account deactivation, and any future one) -- .cp-btn--primary is the
 * same blue as "Save Changes" everywhere else on the profile page, giving a permanent, unrecoverable
 * action no more visual weight than a routine edit.
 */
.cp-btn--danger {
	background: var(--cp-alert);
	color: #fff;
}

.cp-btn--danger:hover {
	background: #7a201a;
	color: #fff;
}

/*
 * The outline equivalent of .cp-btn--danger, for a trigger that OPENS a destructive flow rather than
 * commits it -- "Deactivate User Profile" used plain .cp-btn--ghost, identical to "Update Name" and
 * "Update Email" right beside it, giving the one irreversible action on the page no more visual weight
 * than a routine edit until the confirm step (already .cp-btn--danger) revealed it. Full danger red
 * would overstate a button that only reveals a form; this reads as a warning without alarming early.
 */
.cp-btn--danger-ghost {
	background: var(--cp-surface);
	color: var(--cp-alert);
	border: 1px solid #f2c9c4;
}

.cp-btn--danger-ghost:hover {
	background: var(--cp-alert-tint);
	border-color: var(--cp-alert);
	color: var(--cp-alert);
}

/*
 * For the Cancel link beside a payment Submit button while a charge is in flight -- Cancel is a plain
 * <a>, which has no native disabled state, so PaymentForm.disableButton (payment_common.js) toggles
 * this class on it alongside the real disabled attribute on Submit. pointer-events:none alone hides
 * the cursor entirely rather than showing not-allowed (a known gotcha), so cursor is set explicitly.
 */
.cp-btn--blocked {
	pointer-events: none;
	cursor: not-allowed;
	opacity: .55;
}

/*
 * Amber is the product's transactional accent, reserved for the one action that actually moves money
 * (the CSA brand guide names orange the "Pay Now" color) -- but the orange glow this button used to
 * throw smeared into the amber-tint due-date chip and the amber meter fill on the SAME dashboard card,
 * reading as three unrelated amber elements rather than one clear CTA. Dropped in favor of a neutral
 * lift, so the solid amber fill itself stays the only saturated amber on the card.
 */
.cp-btn--pay {
	background: var(--cp-amber);
	color: #2a1a00;
	box-shadow: 0 1px 0 rgba(0, 0, 0, .06);
}

.cp-btn--pay:hover {
	background: #ffb12b;
	color: #2a1a00;
	transform: translateY(-1px);
}

.cp-btn--pay:active {
	background: #e08f00;
	color: #2a1a00;
	transform: translateY(0);
	filter: none;
}

/* Same reasoning as .cp-btn--primary:disabled above -- keep this variant's own resting color. */
.cp-btn--pay:disabled,
.cp-btn--pay[disabled] {
	background: var(--cp-amber);
	color: #2a1a00;
}

.cp-btn--ghost {
	background: var(--cp-surface);
	color: var(--cp-navy);
	border: 1px solid var(--cp-line);
}

/*
 * Same reasoning as .cp-btn--primary:disabled above, but the effect is worse here: the generic
 * .cp-btn:disabled fill is a solid dark slate, which on an outline/ghost button flips it from the
 * lightest-weight button on the page to the heaviest -- a disabled "Download CSV" read as the
 * prominent action next to the error message explaining why there's nothing to download.
 */
.cp-btn--ghost:disabled,
.cp-btn--ghost[disabled] {
	background: var(--cp-surface);
	color: var(--cp-ink-3);
	border-color: var(--cp-line);
}

.cp-btn--ghost:hover {
	background: var(--cp-sky-tint);
	border-color: var(--cp-sky);
	color: var(--cp-navy);
}

.cp-btn--block {
	width: 100%;
	min-height: 50px;
	font-size: var(--cp-text-md);
}

/*
 * A smaller footprint for a secondary action that sits beside a field or a heading rather than
 * anchoring a form -- self-service's Copy Service Address and signature Type/Clear buttons are the
 * case that exists. Pair with .cp-btn--ghost for the secondary/outline look; there is no separate
 * color variant for this because .cp-btn--ghost already reads as one.
 */
.cp-btn--sm {
	padding: 7px 14px;
	min-height: 34px;
	font-size: var(--cp-text-sm);
	gap: var(--cp-space-3);
}

/*
 * Backs the fiber wizard's stop-service "Confirm Termination" submit (was .btn-danger, reusing the
 * .cp-btn--danger defined above for the deactivate-account button) and the package card's
 * "Broadband Facts" action (was .btn-link.btn-sm) -- see the fiber component block below.
 * .cp-btn--ghost was considered for Broadband Facts but rejected: its own border + surface
 * background read as a second "Select" button sitting under the first, not the bare text link
 * Bootstrap's .btn-link gave it, so a true no-border/no-background variant was genuinely missing.
 */
.cp-btn--link {
	background: transparent;
	color: var(--cp-azure-ink);
	padding: 4px 6px;
	min-height: auto;
	font-weight: 600;
}

.cp-btn--link:hover {
	color: var(--cp-navy);
	text-decoration: underline;
}

.cp-btn__ico {
	width: 18px;
	height: 18px;
	flex: none;
	stroke: currentColor;
	fill: none;
	stroke-width: 1.7;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/*
 * ─── Popover (CSS-only) ────────────────────────────────────────────────────────────────────
 * No jQuery/Bootstrap involved -- :hover/:focus-within do the showing and hiding. Ported from
 * multipay.css's .cp-popover primitive (the one place on this shell it already worked), remapped
 * off that file's own --cp-ma-* token scope onto this file's --cp-ink/--cp-azure/--cp-turquoise/
 * --cp-r-sm/--cp-sh-2 -- copying the rules verbatim would have left every var() pointing at a
 * custom property this file never defines.
 */
.cp-popover {
	position: relative;
	display: inline-flex;
}

.cp-popover__trigger {
	background: none;
	border: none;
	padding: 0;
	cursor: help;
	font: inherit;
}

.cp-popover__trigger:focus-visible {
	outline: none;
}

.cp-popover__content {
	position: absolute;
	bottom: calc(100% + 10px);
	right: 0;
	min-width: 240px;
	max-width: 300px;
	padding: .85rem 1rem .95rem;
	background-color: var(--cp-surface);
	color: var(--cp-ink);
	border-radius: var(--cp-r-sm);
	box-shadow: var(--cp-sh-2), 0 0 0 1px rgba(11, 34, 57, .08);
	font-size: var(--cp-text-xs);
	line-height: 1.5;
	opacity: 0;
	visibility: hidden;
	transform: translateY(6px);
	transition: opacity .18s ease, transform .18s ease, visibility 0s linear .18s;
	z-index: 50;
	pointer-events: none;
}

.cp-popover__content::after {
	content: "";
	position: absolute;
	top: 100%;
	right: 14px;
	border: 6px solid transparent;
	border-top-color: var(--cp-surface);
	filter: drop-shadow(0 1px 0 rgba(11, 34, 57, .06));
}

.cp-popover--right .cp-popover__content {
	right: auto;
	left: 0;
}

.cp-popover--right .cp-popover__content::after {
	right: auto;
	left: 14px;
}

/*
 * Opens to the right of the trigger, vertically centered -- for a trigger sitting in a short,
 * densely-packed block where BOTH above and below collide with real content (a fee-note wedged
 * between the balance summary and the payment form; a form field whose own input sits directly
 * below its label). Side-opening sidesteps that regardless of how tight the vertical rhythm is, as
 * long as there's horizontal room, which every call site here has (a card body, not a narrow column).
 */
.cp-popover--aside .cp-popover__content {
	bottom: auto;
	top: 50%;
	right: auto;
	left: calc(100% + 10px);
	transform: translateY(-50%) translateX(-6px);
}

.cp-popover--aside .cp-popover__content::after {
	top: 50%;
	right: 100%;
	bottom: auto;
	transform: translateY(-50%);
	border-top-color: transparent;
	border-right-color: var(--cp-surface);
	filter: drop-shadow(-1px 0 0 rgba(11, 34, 57, .06));
}

.cp-popover:hover .cp-popover__content,
.cp-popover:focus-within .cp-popover__content {
	opacity: 1;
	visibility: visible;
	transform: translateY(0);
	transition-delay: 0s, 0s, 0s;
	pointer-events: auto;
}

/*
 * Must come after the generic hover rule above: same specificity (two classes each), so source order
 * decides which transform wins on hover -- this one needs to keep the vertical centering, not fall
 * back to the generic rule's translateY(0).
 */
.cp-popover--aside:hover .cp-popover__content,
.cp-popover--aside:focus-within .cp-popover__content {
	transform: translateY(-50%) translateX(0);
}

/*
 * Every direction variant anchors the box relative to its trigger with a fixed 240-300px width -- on
 * a narrow viewport, a trigger anywhere past roughly the left third of the screen pushes the box
 * past the right edge. Because the invisible (opacity:0) box is still in normal layout flow, that
 * overflow was silent: it never showed on hover, but it forced the WHOLE PAGE 150+px wider than the
 * viewport at all times, adding an unwanted horizontal scrollbar site-wide. Below this width, every
 * variant collapses to one fixed bottom-sheet panel instead -- viewport-relative, so it can never
 * overflow regardless of where the trigger sits, at the cost of no longer visually pointing at it
 * (an accepted tradeoff on a screen too narrow for a pointer-anchored box to ever fit reliably).
 */
@media (max-width: 599px) {
	/*
	 * .cp-popover .cp-popover__content (two classes) to match .cp-popover--aside .cp-popover__content's
	 * own specificity -- a bare .cp-popover__content here loses that fight regardless of the media
	 * query, since specificity is compared the same way whether or not either rule sits inside one.
	 */
	.cp-popover .cp-popover__content {
		position: fixed;
		top: auto;
		bottom: 16px;
		left: 16px;
		right: 16px;
		max-width: none;
		min-width: 0;
		transform: translateY(6px);
	}

	.cp-popover .cp-popover__content::after {
		display: none;
	}

	.cp-popover:hover .cp-popover__content,
	.cp-popover:focus-within .cp-popover__content,
	.cp-popover--aside:hover .cp-popover__content,
	.cp-popover--aside:focus-within .cp-popover__content {
		transform: translateY(0);
	}
}

.cp-popover__title {
	display: block;
	font-size: var(--cp-text-2xs);
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: .1em;
	color: var(--cp-azure);
	margin-bottom: .55rem;
}

.cp-popover__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: .4rem;
}

.cp-popover__list li {
	position: relative;
	padding-left: 1rem;
	color: var(--cp-ink);
}

.cp-popover__list li::before {
	content: "";
	position: absolute;
	left: 0;
	top: .55em;
	width: 4px;
	height: 4px;
	background-color: var(--cp-turquoise);
	border-radius: 50%;
}

/*
 * The worked-example clause on a percentage-based fee line ("about $2.14 on your $142.37 balance") --
 * secondary to the flat percentage itself, which stays var(--cp-ink) via the surrounding <b>.
 */
.cp-fee-estimate {
	color: var(--cp-ink-2);
}

.cp-popover__trigger-text {
	margin-left: .2rem;
}

/* ─── Landing / welcome ───────────────────────────────────────────────────────────────────── */
.cp-landing {
	position: relative;
	isolation: isolate;
	display: flex;
	flex-direction: column;
	min-height: 100vh;
	background: var(--cp-navy-deep);
}

/*
 * The utility's own photo (BACKGROUND_IMAGE) when one is configured, otherwise the stock artwork,
 * always full-bleed behind everything. The scrim is what keeps white text legible over an arbitrary
 * photograph -- it is heavier on the side the identity text sits on, and over the whole image on a
 * phone where the text and the card stack.
 */
.cp-bg {
	position: absolute;
	inset: 0;
	z-index: -1;
	overflow: hidden;
	background: var(--cp-navy-deep);
}

.cp-bg__photo {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}

.cp-bg__scrim {
	position: absolute;
	inset: 0;
	background: linear-gradient(to top, rgba(2, 16, 29, .88) 0%, rgba(2, 16, 29, .76) 55%, rgba(2, 16, 29, .3) 100%);
}

.cp-devbanner,
.cp-alertbar {
	position: relative;
}

.cp-alertbar {
	display: flex;
	align-items: flex-start;
	gap: var(--cp-space-6);
	flex-wrap: wrap;
	background: var(--cp-amber-tint);
	border-bottom: 1px solid var(--cp-amber-line);
	padding: 11px 18px;
}

.cp-alertbar__ico {
	width: 18px;
	height: 18px;
	flex: none;
	margin-top: 1px;
	stroke: #b47500;
	fill: none;
	stroke-width: 1.8;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.cp-alertbar p {
	margin: 0;
	flex: 1 1 200px;
	min-width: 0;
	font-size: var(--cp-text-sm);
	line-height: 1.45;
	color: #6b4700;
}

.cp-alertbar p b {
	color: #4a3100;
}

.cp-alertbar__act {
	margin-left: auto;
	flex: none;
	white-space: nowrap;
	font-size: var(--cp-text-xs);
	font-weight: 600;
	color: var(--cp-amber-ink);
	text-decoration: none;
	border: 1px solid rgba(180, 117, 0, .4);
	border-radius: var(--cp-r-pill);
	padding: 5px 13px;
	background: rgba(255, 255, 255, .7);
}

.cp-alertbar__act:hover {
	background: #fff;
	border-color: #b47500;
	color: var(--cp-amber-ink);
}

.cp-split {
	display: grid;
	grid-template-columns: 1fr;
	flex: 1;
}

.cp-hero {
	position: relative;
	min-height: clamp(260px, 40svh, 360px);
	display: flex;
	flex-direction: column;
}

.cp-hero__art {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
}

.cp-hero__top {
	position: relative;
	display: flex;
	align-items: center;
	gap: var(--cp-space-7);
	padding: 18px 22px 0;
}

.cp-lockup {
	display: flex;
	align-items: center;
	gap: var(--cp-space-6);
	min-width: 0;
	padding: 8px 12px;
	background: rgba(255, 255, 255, .96);
	border-radius: 12px;
	box-shadow: 0 1px 2px rgba(2, 16, 29, .14), 0 10px 28px -10px rgba(2, 16, 29, .55);
}

.cp-lockup img {
	max-height: 38px;
	max-width: 180px;
	width: auto;
	object-fit: contain;
}

.cp-lockup__txt {
	font-size: var(--cp-text-xs);
	line-height: 1.25;
	font-weight: 500;
	color: #a9c4da;
}

.cp-lockup__txt b {
	display: block;
	font-size: var(--cp-text-base);
	font-weight: 700;
	letter-spacing: -.01em;
	color: #fff;
}

.cp-hero__tel {
	margin-left: auto;
	display: inline-flex;
	align-items: center;
	gap: var(--cp-space-4);
	white-space: nowrap;
	font-size: var(--cp-text-sm);
	font-weight: 600;
	font-variant-numeric: tabular-nums;
	color: #c3d8e9;
	text-decoration: none;
	border: 1px solid rgba(255, 255, 255, .18);
	border-radius: var(--cp-r-pill);
	padding: 7px 14px;
	transition: background var(--cp-dur) var(--cp-ease);
}

.cp-hero__tel:hover {
	background: rgba(255, 255, 255, .1);
	color: #fff;
}

.cp-hero__body {
	position: relative;
	isolation: isolate;
	margin-top: auto;
	padding: 26px 22px;
}

/*
 * A soft dark pool that travels with the identity text, so the name stays legible however long it wraps
 * and whatever the utility's photo looks like behind it; the page scrim alone cannot know where the text ends.
 */
.cp-hero__body::before {
	content: '';
	position: absolute;
	inset: -72px 0 -48px;
	z-index: -1;
	background: linear-gradient(to bottom, rgba(2, 16, 29, 0), rgba(2, 16, 29, .66) 72px,
		rgba(2, 16, 29, .66) calc(100% - 48px), rgba(2, 16, 29, 0));
	pointer-events: none;
}

.cp-hero__eyebrow {
	margin: 0 0 12px;
	font-size: var(--cp-text-2xs);
	font-weight: 600;
	letter-spacing: .18em;
	text-transform: uppercase;
	color: #fff;
	text-shadow: 0 1px 8px rgba(2, 16, 29, .6);
}

.cp-hero h1 {
	margin: 0;
	max-width: 16ch;
	font-size: var(--cp-text-4xl);
	font-weight: 700;
	line-height: 1.04;
	letter-spacing: -.028em;
	color: #fff;
	text-wrap: balance;
	text-shadow: 0 1px 2px rgba(2, 16, 29, .35), 0 4px 28px rgba(2, 16, 29, .5);
}

.cp-hero__alt {
	margin: 8px 0 0;
	font-size: var(--cp-text-xl);
	font-weight: 600;
	color: #e3eef7;
	text-shadow: 0 1px 10px rgba(2, 16, 29, .6);
}

.cp-hero__tag {
	margin: 14px 0 0;
	max-width: 44ch;
	font-size: var(--cp-text-base);
	line-height: 1.5;
	color: #e3eef7;
	text-shadow: 0 1px 10px rgba(2, 16, 29, .6);
}

/* ─── Landing action pane ─────────────────────────────────────────────────────────────────── */
.cp-pane {
	position: relative;
	z-index: 1;
	display: flex;
	flex-direction: column;
	justify-content: center;
	margin: 6px 14px 28px;
	padding: 26px 22px 28px;
	background: #fff;
	border-radius: 20px;
	box-shadow: 0 2px 6px rgba(2, 16, 29, .12), 0 32px 80px -24px rgba(2, 16, 29, .65);
}

.cp-pane__inner {
	width: 100%;
	max-width: 400px;
	margin: 0 auto;
}

.cp-pane h2 {
	margin: 0;
	font-size: var(--cp-text-3xl);
	font-weight: 700;
	letter-spacing: -.025em;
}

.cp-pane__sub {
	margin: 5px 0 0;
	text-wrap: balance;
	font-size: var(--cp-text-base);
	color: var(--cp-ink-2);
}

.cp-notice {
	margin: 18px 0 20px;
	padding: 11px 13px;
	background: var(--cp-sky-tint);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-sm);
	font-size: var(--cp-text-xs);
	line-height: 1.5;
	color: var(--cp-ink-2);
	white-space: pre-line;
}

.cp-msg {
	margin: 0 0 16px;
	padding: 11px 13px;
	border-radius: var(--cp-r-sm);
	font-size: var(--cp-text-sm);
	line-height: 1.45;
}

.cp-msg--error {
	background: var(--cp-alert-tint);
	border: 1px solid #f2c9c4;
	color: var(--cp-alert);
}

.cp-msg--success {
	background: var(--cp-good-tint);
	border: 1px solid #b7e4e1;
	color: var(--cp-good);
}

.cp-msg--info {
	background: var(--cp-sky-tint);
	border: 1px solid #c3ddef;
	color: var(--cp-azure-ink);
}

.cp-msg--warning {
	background: var(--cp-amber-tint);
	border: 1px solid var(--cp-amber-line);
	color: var(--cp-amber-ink);
}

.cp-msg a {
	color: inherit;
	font-weight: 600;
}

/*
 * cp-consumer.css carries no global ul/ol reset, so a <ul> of disclosure/warning bullets inside a
 * cp-msg -- e.g. autopay_management.scala.html's fee and monthly-limit list -- otherwise renders on
 * the UA default box model (1em vertical margin, 40px indent), which reads as dead space above and
 * below the message and text sitting oddly far from its own border. Scoped to cp-msg rather than a
 * page-wide reset so it cannot change list rendering anywhere else in the app.
 */
.cp-msg ul,
.cp-msg ol {
	margin: 0;
	padding-left: 20px;
}

.cp-msg li + li {
	margin-top: 4px;
}

.cp-msg:empty {
	display: none;
}

/*
 * Bootstrap alert markup, styled for this shell.
 *
 * CustomerPortal.setNotification builds <div class="alert alert-<type>"> with a <a class="close">, and
 * 182 call sites across the payment, self-service and profile bundles still use it against the 45 that
 * use setConsumerNotification's cp-msg. Those are the notifications a customer sees after paying,
 * enrolling in autopay or saving a payment method, and with no .alert rule here they rendered as
 * unstyled black text on the page background -- a success message indistinguishable from a failure.
 *
 * Styling the markup rather than converting the call sites fixes every one of them at once, leaves the
 * JS alone, and keeps the dismiss affordance that functional tests click
 * (AddPaymentMethodTest waits on "#divNotification .alert-success .close"). The variants deliberately
 * match .cp-msg above so the two notification paths are visually one thing.
 */
.alert {
	position: relative;
	margin: 0 0 16px;
	padding: 11px 34px 11px 13px;
	border-radius: var(--cp-r-sm);
	font-size: var(--cp-text-sm);
	line-height: 1.45;
}

.alert:empty {
	display: none;
}

.alert-danger,
.alert-error {
	background: var(--cp-alert-tint);
	border: 1px solid #f2c9c4;
	color: var(--cp-alert);
}

.alert-success {
	background: var(--cp-good-tint);
	border: 1px solid #b7e4e1;
	color: var(--cp-good);
}

.alert-info {
	background: var(--cp-sky-tint);
	border: 1px solid #c3ddef;
	color: var(--cp-azure-ink);
}

.alert-warning {
	background: var(--cp-amber-tint);
	border: 1px solid var(--cp-amber-line);
	color: var(--cp-amber-ink);
}

.alert a {
	color: inherit;
	font-weight: 600;
}

/*
 * setNotification renders the dismiss as an <a> with no href, so it is not focusable by default and
 * carries no pointer. Both are restored here rather than in the JS, which would change the markup the
 * functional suite asserts on.
 */
.alert .close {
	position: absolute;
	top: 6px;
	right: 9px;
	padding: 2px 4px;
	font-size: var(--cp-text-lg);
	line-height: 1;
	color: inherit;
	opacity: 0.55;
	cursor: pointer;
	text-decoration: none;
}

.alert .close:hover,
.alert .close:focus-visible {
	opacity: 1;
}

.cp-pane form {
	margin-top: 22px;
}

.cp-notice + form {
	margin-top: 0;
}

.cp-field {
	margin-bottom: 13px;
}

.cp-field label {
	display: block;
	margin-bottom: 6px;
	font-size: var(--cp-text-xs);
	font-weight: 500;
	color: var(--cp-ink-2);
}

.cp-field__row {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	margin-bottom: 6px;
}

.cp-field input,
.cp-field select,
.cp-field textarea {
	width: 100%;
	min-height: 50px;
	padding: 13px 15px;
	font: inherit;
	font-size: var(--cp-text-md);
	color: var(--cp-ink);
	background: var(--cp-surface);
	border: 1px solid var(--cp-field-line);
	border-radius: var(--cp-r-sm);
	transition: border-color var(--cp-dur) var(--cp-ease), box-shadow var(--cp-dur) var(--cp-ease);
}

.cp-field input::placeholder {
	color: #93a9bc;
}

.cp-field input:hover,
.cp-field select:hover,
.cp-field textarea:hover {
	border-color: #a9bdce;
}

.cp-field input:focus,
.cp-field select:focus,
.cp-field textarea:focus {
	outline: 0;
	border-color: var(--cp-azure);
	box-shadow: 0 0 0 3px rgba(0, 133, 202, .18);
}

/*
 * The native control paints its own arrow in the platform's colours, which reads as a stray
 * widget next to the flat fields around it. appearance:none drops it, so the caret has to be
 * drawn here -- inline as a data URI, since the artifact CSP and the asset pipeline both make an
 * external file the wrong trade for 200 bytes.
 */
.cp-field select {
	appearance: none;
	-webkit-appearance: none;
	-moz-appearance: none;
	padding-right: 42px;
	background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2354708a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: right 14px center;
	background-size: 18px 18px;
}

.cp-field select:disabled {
	color: var(--cp-ink-2);
	background-color: var(--cp-line-2);
	cursor: not-allowed;
}

.cp-field textarea {
	min-height: 110px;
	resize: vertical;
}

/*
 * A grid of settings groups, each in its own bordered panel — the detailed notification preferences
 * are the case that exists. cp-cards is the wrong container for this: its items are expected to bring
 * their own chrome, so bare forms dropped into it render as unbounded columns whose rows stop lining
 * up as soon as two descriptions wrap to different heights. The panel supplies the boundary, and the
 * wider track keeps a readable column count instead of five cramped ones.
 */
.cp-optgrid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
	gap: var(--cp-space-7);
}

.cp-optcard {
	padding: 14px 16px;
	background: var(--cp-surface);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-sm);
}

.cp-optcard > strong {
	display: block;
	margin-bottom: 2px;
}

.cp-optgrid + .cp-btn {
	margin-top: 16px;
}

/* A row of side-by-side fields that stacks on phones — the usage chart's three controls, and any
   form that wants two or three inputs on one line without reaching for a grid framework. */
.cp-fields {
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
	gap: var(--cp-space-6);
	align-items: end;
}

/*
 * :not([hidden]) -- fiber_wizard.js's voice-line port-detail fields carry cp-fields on the same
 * element it toggles `hidden` on. An unqualified `display: grid` here would win the specificity tie
 * against [hidden]'s UA display:none, same collision as .cp-btn/.cp-check/.cp-note/.cp-chip above --
 * scoped here so every .cp-fields, not just this caller, actually stays hidden.
 */
.cp-fields:not([hidden]) {
	display: grid;
}

/*
 * A 12-column row for splits .cp-fields can't express because the columns are not equal width --
 * self-service's street-address (8/4) and city/state/zip (5/4/3) rows are the case that exists.
 * Reach for .cp-fields first; this is only for a row that genuinely needs asymmetric widths.
 *
 * Single column below 600px so every row still stacks on a phone: .cp-row itself never gains the
 * 12-column track until the breakpoint, so a .cp-col-N child has nothing to span into and simply
 * takes the row's one column, full width, in source order.
 *
 * align-items:end matches .cp-fields: without it a field carrying a description (an extra line
 * above the input) pushes only its own input down, stair-stepping it against a neighbour with none.
 *
 * The breakpoint tracks viewport width, not the card's own content width, so a narrow card at a wide
 * viewport (e.g. the unauth sign-in card) gets less room than the same column would on a full-bleed
 * page at the same viewport. A short-content column (state, zip) needs a wide enough span that it
 * still reads at the card's narrowest, not just at the viewport's.
 */
.cp-row {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--cp-space-6);
}

@media (min-width: 600px) {
	.cp-row {
		grid-template-columns: repeat(12, 1fr);
		align-items: end;
	}

	.cp-row > .cp-col-2 {
		grid-column: span 2;
	}

	.cp-row > .cp-col-3 {
		grid-column: span 3;
	}

	.cp-row > .cp-col-4 {
		grid-column: span 4;
	}

	.cp-row > .cp-col-5 {
		grid-column: span 5;
	}

	.cp-row > .cp-col-6 {
		grid-column: span 6;
	}

	.cp-row > .cp-col-8 {
		grid-column: span 8;
	}
}

/* Native checkbox + label, no custom-drawn widget -- accent-color follows the shell's own palette,
   the same value already used for the payment-options checkboxes in one_time_payment_unauth.
   Also covers radio inputs (self-service's Ownership picker) -- same layout either way, and the
   browser already draws a round vs. square control off the input's own type. */
.cp-check {
	align-items: flex-start;
	gap: var(--cp-space-5);
	margin-bottom: 13px;
}

/*
 * :not([hidden]) -- detailed_notifications conditionally hides a category's SMS row until the
 * master SMS toggle is on (data-content="allowSms"). [hidden] { display: none } is a user-agent
 * default at the same specificity as a single class, and an author rule wins that tie regardless
 * of source order -- an unqualified `display: flex` here left the row (and its checkbox) visible
 * and clickable while still marked hidden.
 */
.cp-check:not([hidden]) {
	display: flex;
}

/*
 * min-height:0 overrides .cp-field input's 50px -- this round nested .cp-check inside .cp-field for
 * the first time (Ownership radios, service-type checkboxes), and without the override the 20px box
 * inherited the 50px floor, painting the control ~30px below the label it belongs to.
 */
.cp-check input[type="checkbox"],
.cp-check input[type="radio"] {
	flex: none;
	width: 20px;
	height: 20px;
	min-height: 0;
	margin-top: 1px;
	accent-color: var(--cp-azure-ink);
}

/*
 * font-size lives here, not on the <small> children -- self-service wraps checkbox copy in <small>
 * for its own reasons (matching the field-description convention), and <small>'s 0.833em default
 * would otherwise shrink it to ~11px against this component's own explicit size.
 */
.cp-check label {
	display: flex;
	flex-direction: column;
	gap: var(--cp-space-1);
	font-size: var(--cp-text-sm);
	color: var(--cp-ink);
}

.cp-check label small {
	font-size: inherit;
}

/*
 * A lighter-weight, form-local version of .cp-card / .cp-card__hd — a bordered sub-section inside a
 * form rather than a page-level panel. .cp-fieldset__hd is independently usable: it's a title + trailing
 * action row on its own (no descendant-selector dependency on .cp-fieldset), for a case that wants the
 * header without the bordered box (signature's heading + Type/Clear buttons), or a box with no header
 * (fiber_voice_number's plain bordered block). Where both are used together (self-service's mailing-
 * address block), __hd sits immediately before .cp-fieldset as a sibling, not nested inside it.
 */
.cp-fieldset {
	padding: 14px;
	margin: 10px 0 16px;
	background: var(--cp-surface);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-sm);
}

.cp-fieldset__hd {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--cp-space-6);
	margin-bottom: 12px;
}

.cp-fieldset__hd h5 {
	margin: 0;
	font-size: var(--cp-text-base);
	font-weight: 600;
	color: var(--cp-ink);
}

/*
 * A div that has to look and sit exactly like an input because a widget owns its contents — the
 * daterangepicker trigger is the case that exists. It is not focusable on its own, so it takes the
 * resting border only and leaves focus styling to whatever the widget opens.
 */
.cp-field .cp-fauxinput {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--cp-space-5);
	width: 100%;
	min-height: 50px;
	padding: 13px 15px;
	font-size: var(--cp-text-md);
	color: var(--cp-ink);
	background: var(--cp-surface);
	border: 1px solid var(--cp-field-line);
	border-radius: var(--cp-r-sm);
}

.cp-field .cp-fauxinput:not(.disabled):hover {
	border-color: #a9bdce;
}

.cp-field .cp-fauxinput.disabled {
	color: var(--cp-ink-2);
	background: var(--cp-line-2);
}

.cp-field__wrap {
	position: relative;
	display: flex;
	flex-wrap: wrap;
}

.cp-field__wrap input {
	padding-right: 48px;
}

/*
 * CustomerPortal.js's ensureErrorElement inserts .invalid-feedback as input.nextSibling, i.e. a flex
 * sibling of the input inside .cp-field__wrap. Without a wrap point that squeezed the input down to
 * whatever width the error text didn't need and put .cp-field__eye's absolute position on top of the
 * error's own text -- found on registration.scala.html's password fields, which are both wrapped AND
 * long enough (a strength-requirements message, a mismatch message) to make it visible; reset_password.
 * scala.html/set_password.scala.html's own single wrapped field carries the same underlying defect
 * with a short "This field is required." message, less visible but the same root cause.
 */
.cp-field__wrap .invalid-feedback {
	flex: 1 0 100%;
	margin-top: 0;
}

.cp-field__eye {
	position: absolute;
	right: 4px;
	top: 50%;
	transform: translateY(-50%);
	width: 44px;
	height: 44px;
	display: grid;
	place-items: center;
	border: 0;
	background: transparent;
	border-radius: var(--cp-r-sm);
	color: var(--cp-ink-3);
	cursor: pointer;
}

.cp-field__eye svg {
	width: 19px;
	height: 19px;
	stroke: currentColor;
	fill: none;
	stroke-width: 1.7;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.cp-field__eye:hover {
	color: var(--cp-navy);
	background: var(--cp-sky-tint);
}

/*
 * Struck through while the password is visible, so the control shows its state without swapping the
 * inline SVG for a second one. togglePasswordVisibility applies the class.
 */
.cp-field__eye.is-revealed {
	color: var(--cp-navy);
}

.cp-field__eye.is-revealed::after {
	content: '';
	position: absolute;
	left: 50%;
	top: 50%;
	width: 21px;
	height: 1.7px;
	background: currentColor;
	border-radius: 1px;
	transform: translate(-50%, -50%) rotate(-45deg);
}

.cp-field__eye:focus-visible {
	outline: 2px solid var(--cp-azure);
	outline-offset: 1px;
}

.cp-link {
	color: var(--cp-azure-ink);
	font-weight: 600;
	font-size: var(--cp-text-sm);
	text-decoration: none;
}

/*
 * .cp-link is used on 22 anchors and exactly one <button> (multi-autopay's Terms and Conditions
 * trigger) -- a <button> carries its own UA chrome (background, border, padding, font) that none of
 * the properties above touch, so that one instance rendered as a raw grey system button with the
 * link's color bleeding through rather than reading as a link at all.
 */
button.cp-link {
	background: none;
	border: 0;
	padding: 0;
	font-family: inherit;
	cursor: pointer;
}

.cp-link:hover {
	color: var(--cp-navy);
	text-decoration: underline;
}

/* The drawing surface for self-service's signature field -- a fixed-height bordered box; the canvas
   itself is styled inline to fill it (width/height:100%) so SignatureField.js can read its pixel
   size back off clientWidth/clientHeight. */
.cp-sigpad {
	height: 150px;
	margin: 14px 0;
	background: var(--cp-surface);
	border: 1px solid var(--cp-field-line);
	border-radius: var(--cp-r-sm);
}

.cp-pane form .cp-btn--primary {
	margin-top: 8px;
}

.cp-pane form .cp-btn--ghost {
	margin-top: 9px;
}

.cp-rule {
	display: flex;
	align-items: center;
	gap: var(--cp-space-7);
	margin: 26px 0 15px;
}

.cp-rule::before,
.cp-rule::after {
	content: '';
	flex: 1;
	height: 1px;
	background: var(--cp-line);
}

.cp-rule span {
	white-space: nowrap;
	font-size: var(--cp-text-3xs);
	font-weight: 500;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--cp-ink-3);
}

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

.cp-tiles--2 {
	grid-template-columns: 1fr 1fr;
}

.cp-tiles a {
	display: flex;
	flex-direction: column;
	gap: var(--cp-space-2);
	padding: 14px 13px;
	background: var(--cp-surface);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-sm);
	color: var(--cp-ink);
	text-decoration: none;
	transition: border-color var(--cp-dur) var(--cp-ease), background var(--cp-dur) var(--cp-ease),
		transform var(--cp-dur) var(--cp-ease);
}

.cp-tiles a svg {
	width: 20px;
	height: 20px;
	margin-bottom: 4px;
	color: var(--cp-azure);
	stroke: currentColor;
	fill: none;
	stroke-width: 1.7;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.cp-tiles a b {
	font-size: var(--cp-text-base);
	font-weight: 600;
	line-height: 1.25;
}

.cp-tiles a small {
	font-size: var(--cp-text-2xs);
	line-height: 1.35;
	color: var(--cp-ink-2);
}

.cp-pane .cp-tiles #oneTimePayment {
	grid-column: 1 / -1;
	flex-direction: row;
	align-items: center;
	flex-wrap: wrap;
	column-gap: var(--cp-space-6);
	padding: 16px 18px;
	background: var(--cp-amber);
	border-color: var(--cp-amber);
	color: #2a1a00;
	box-shadow: 0 1px 0 rgba(0, 0, 0, .06);
}

.cp-pane .cp-tiles #oneTimePayment svg {
	margin: 0;
	width: 24px;
	height: 24px;
	color: #2a1a00;
}

.cp-pane .cp-tiles #oneTimePayment b {
	font-size: var(--cp-text-md);
}

.cp-pane .cp-tiles #oneTimePayment small {
	flex-basis: 100%;
	padding-left: 36px;
	color: #5a3b00;
}

.cp-pane .cp-tiles #invoicePayment {
	grid-column: 1 / -1;
	flex-direction: row;
	align-items: center;
	flex-wrap: wrap;
	column-gap: var(--cp-space-6);
	padding: 12px 18px;
}

.cp-pane .cp-tiles #invoicePayment svg {
	margin: 0;
}

.cp-pane .cp-tiles #invoicePayment small {
	flex-basis: 100%;
	padding-left: 32px;
}

.cp-pane .cp-tiles #oneTimePayment:hover {
	background: #ffb12b;
	border-color: #ffb12b;
}

.cp-tiles a:hover {
	border-color: var(--cp-sky);
	background: var(--cp-sky-tint);
	transform: translateY(-1px);
	box-shadow: var(--cp-sh-1);
}

.cp-tiles a:focus-visible {
	outline: 2px solid var(--cp-azure);
	outline-offset: 2px;
}

.cp-rows {
	display: flex;
	flex-direction: column;
	margin-top: 10px;
}

.cp-rows a {
	display: flex;
	align-items: center;
	gap: var(--cp-space-6);
	padding: 12px 2px;
	font-size: var(--cp-text-sm);
	font-weight: 500;
	color: var(--cp-ink);
	text-decoration: none;
	border-bottom: 1px solid var(--cp-line-2);
	transition: color var(--cp-dur) var(--cp-ease);
}

.cp-rows a:last-child {
	border-bottom: 0;
}

.cp-rows a > svg:first-child {
	width: 17px;
	height: 17px;
	flex: none;
	color: var(--cp-azure);
	stroke: currentColor;
	fill: none;
	stroke-width: 1.7;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.cp-rows__arw {
	margin-left: auto;
	width: 15px;
	height: 15px;
	flex: none;
	stroke: var(--cp-ink-3);
	fill: none;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.cp-rows a:hover {
	color: var(--cp-navy);
}

.cp-rows a:hover .cp-rows__arw {
	stroke: var(--cp-navy);
}

.cp-rows a:focus-visible {
	outline: 2px solid var(--cp-azure);
	outline-offset: 1px;
}

.cp-utilinks {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--cp-space-4) var(--cp-space-8);
	margin-top: 22px;
	padding-top: 16px;
	border-top: 1px solid var(--cp-line);
}

.cp-utilinks .cp-lbl {
	width: 100%;
	margin-bottom: 2px;
	font-size: var(--cp-text-3xs);
}

.cp-utilinks a {
	font-size: var(--cp-text-sm);
	font-weight: 600;
	color: var(--cp-azure-ink);
	text-decoration: none;
}

.cp-utilinks a:hover {
	text-decoration: underline;
}

.cp-pane__foot {
	margin-top: 26px;
	text-align: center;
}

.cp-methods {
	margin-bottom: 18px;
	padding-bottom: 16px;
	border-bottom: 1px solid var(--cp-line);
}

.cp-methods .cp-lbl {
	display: block;
	margin-bottom: 7px;
	font-size: var(--cp-text-3xs);
}

.cp-methods__row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--cp-space-3);
	align-items: center;
	justify-content: center;
}

.cp-methods__row img {
	height: 26px;
	width: auto;
}

.cp-methods__more {
	position: relative;
	display: inline-flex;
}

.cp-methods__more b {
	position: absolute;
	right: -5px;
	bottom: -4px;
	padding: 0 5px;
	border-radius: 9px;
	background: var(--cp-navy);
	color: #fff;
	font-size: var(--cp-text-3xs);
	font-weight: 700;
	line-height: 14px;
}

.cp-methods__note {
	margin: 8px 0 0;
	font-size: var(--cp-text-2xs);
	color: var(--cp-ink-3);
}

.cp-badges {
	display: flex;
	gap: var(--cp-space-5);
	justify-content: center;
	margin-bottom: 14px;
}

.cp-badges img {
	height: 40px;
	width: auto;
}

.cp-legal {
	margin: 0 0 5px;
	font-size: var(--cp-text-xs);
}

.cp-legal a {
	color: var(--cp-ink-2);
	font-weight: 500;
	text-decoration: none;
}

.cp-legal a:hover {
	color: var(--cp-navy);
	text-decoration: underline;
}

.cp-legal span {
	margin: 0 7px;
	color: var(--cp-ink-3);
}

.cp-ver {
	margin: 0;
	font-size: var(--cp-text-2xs);
	color: var(--cp-ink-3);
	font-variant-numeric: tabular-nums;
}

.cp-devbanner {
	margin: 0;
	padding: 6px 12px;
	text-align: center;
	font-size: var(--cp-text-2xs);
	font-weight: 600;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: #fff;
	background: var(--cp-alert);
}

/* ─── Breakpoints ─────────────────────────────────────────────────────────────────────────── */
@media (min-width: 600px) {
	.cp-pane__inner {
		max-width: 520px;
	}
}

@media (min-width: 900px) {
	.cp-alertbar {
		align-items: center;
		flex-wrap: nowrap;
		padding: 12px 34px;
	}

	.cp-alertbar__ico {
		margin-top: 0;
	}

	.cp-split {
		grid-template-columns: 1fr minmax(400px, 460px);
		align-items: center;
		gap: 48px;
		padding: 0 56px;
	}

	/*
	 * Fixed on desktop only, where the hero is sticky, so the photo frames the viewport rather than the
	 * whole scrolling page and the scrim stays registered under the pinned text. On a phone the layer
	 * scrolls with the page, which keeps the text over the same part of the scrim at every scroll position.
	 */
	.cp-bg {
		position: fixed;
	}

	.cp-bg__scrim {
		background:
			linear-gradient(to top, rgba(2, 16, 29, .86) 0%, rgba(2, 16, 29, .58) 26%, rgba(2, 16, 29, .18) 50%,
				rgba(2, 16, 29, 0) 68%),
			linear-gradient(to right, rgba(2, 16, 29, .42) 0%, rgba(2, 16, 29, .12) 45%, rgba(2, 16, 29, 0) 65%),
			linear-gradient(to bottom, rgba(2, 16, 29, .32) 0%, rgba(2, 16, 29, 0) 20%);
	}

	.cp-hero {
		position: sticky;
		top: 0;
		align-self: start;
		min-height: 0;
		height: 100vh;
		height: 100svh;
	}

	.cp-lockup {
		padding: 10px 16px;
	}

	.cp-lockup img {
		max-height: 52px;
		max-width: 220px;
	}

	.cp-hero__top {
		padding: 40px 0 0;
	}

	.cp-hero__body {
		margin: auto 0 0;
		padding: 0 0 64px;
	}

	.cp-hero__body::before {
		inset: -96px -48px -64px -96px;
		background: radial-gradient(farthest-side, rgba(2, 16, 29, .7) 60%, rgba(2, 16, 29, 0));
	}

	.cp-hero h1 {
		font-size: clamp(var(--cp-text-4xl), 4.4vw, var(--cp-text-display-lg));
	}

	.cp-hero__alt {
		font-size: var(--cp-text-2xl);
	}

	.cp-hero__tag {
		font-size: var(--cp-text-md);
	}

	.cp-pane {
		margin: 40px 0;
		padding: 36px 40px;
	}

	.cp-pane__inner {
		max-width: 400px;
	}
}

@media (min-width: 1400px) {
	.cp-split {
		grid-template-columns: 1fr 480px;
		padding: 0 96px;
	}

	.cp-hero h1 {
		font-size: var(--cp-text-display-lg);
	}
}

@media (max-width: 759px) {
	.cp-btn {
		min-height: 44px;
	}
}

@media (prefers-reduced-motion: reduce) {
	.cp-consumer *,
	.cp-consumer *::before,
	.cp-consumer *::after {
		animation-duration: .01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: .01ms !important;
	}
}

/* ─── Authenticated app shell ─────────────────────────────────────────────────────────────── */
.cp-app {
	display: flex;
	flex-direction: column;
	min-height: 100vh;
	background: var(--cp-paper);
}

/*
 * min-height matches the taller variant (the two-line .cp-acct chip renders only when hasAccount is
 * true) so the bar is the same height whether or not that chip is present -- without it, crossing from
 * an account page into the account-less Profile family (nav_consumer.scala.html) visibly shrank the
 * topbar and shifted the whole page up, on top of the account identity already disappearing there.
 */
.cp-topbar {
	position: sticky;
	top: 0;
	z-index: 20;
	display: flex;
	align-items: center;
	gap: var(--cp-space-7);
	min-height: 77px;
	padding: 12px 16px;
	background: var(--cp-surface);
	border-bottom: 1px solid var(--cp-line);
	box-sizing: border-box;
}

.cp-topbar__brand {
	display: flex;
	align-items: center;
	gap: var(--cp-space-5);
	flex: none;
	text-decoration: none;
	color: inherit;
}

/*
 * The UserWay accessibility widget (public/views/common/accessibility_widget.scala.html) defaults to
 * the viewport's top-right corner, fixed -- meaning it never scrolls away, so ANY right-aligned control
 * that a page happens to scroll under that same band collides with it, not just the topbar. This was
 * chased for a while as a series of per-page/per-breakpoint push-downs (clearing .cp-topbar's own
 * account chip, then the tablet nav bar's Pay bill button) before it became clear those were the same
 * underlying bug recurring: every new right-aligned button close to the top of a page -- Profile's
 * Update Name/Update Email/Update Password/Edit Nicknames/Add Customer Account(s), all right-aligned
 * at the same x -- would eventually scroll into the same fixed y-band and get covered in turn.
 *
 * Bottom-right, the conventional placement for this exact widget, sidesteps the whole class: page
 * content scrolls vertically, so a fixed bottom-right element only ever shares space with whatever
 * currently sits at the very bottom of the viewport, which on this shell is empty page background or
 * the footer -- never an action button anchored to a card's top-right. :has() scopes this to the two
 * consumer shells' own root wrappers (.cp-topbar for main_consumer, .cp-auth for the public
 * main_unauth_consumer -- login, sign-up, reset, activation, one-time payment) so it does not reach
 * into an unrelated shell (admin, legacy). Originally .cp-topbar-only: the unauth shell has no topbar
 * control to collide with, but top-right is still the wrong resting place there too, and a widget that
 * jumps position between logged-out and logged-in pages is its own inconsistency. !important is
 * deliberate: the vendor's own stylesheet sets top/right and reasserts it, so a plain-specificity
 * override loses intermittently; top must be forced to auto or it fights bottom for which edge anchors
 * the element.
 */
body:has(.cp-topbar, .cp-auth) .userway_buttons_wrapper {
	top: auto !important;
	bottom: 24px !important;
	right: 24px !important;
	/*
	 * The vendor's own width computation is keyed off its default top-right layout mode (data-position
	 * stays '1' -- its script never learns this is being repositioned to bottom-right by CSS alone), and
	 * forcing top:auto/bottom left it computing a 0px-wide flex box that only still looked right because
	 * overflow:visible let its 44px icon child render outside its own empty bounding box -- invisible in
	 * a screenshot, but a genuinely zero-size hit target and collision box. Pinned to its known real size.
	 */
	width: 44px !important;
	min-width: 44px !important;
}

/*
 * Below 600px the bottom tab bar (.cp-tabs, nav_consumer.scala.html) is itself fixed to the same edge --
 * bottom:24px would sit the widget right on top of its rightmost tab. .cp-content reserves 74px of
 * bottom padding for that bar, but clearing only that band (84px) still let the widget collide with
 * whatever full-width card button (e.g. "Bill summary") happened to be scrolled to the very bottom of a
 * narrow viewport -- on mobile, cards run near-full-width, so there is no horizontal room to dodge a
 * button the way the desktop fix dodges a topbar control. 140px clears the tab bar's 74px band plus a
 * full card-button's height (~48px) with margin -- not a guarantee for every account state's content
 * height, but it closes the reproduced case (a standard 390x844 phone, unscrolled, viewing the
 * dashboard's Previous Statement mini-card).
 */
@media (max-width: 599.98px) {
	body:has(.cp-topbar):has(.cp-tabs) .userway_buttons_wrapper {
		bottom: 140px !important;
	}

	/* The tab row's More panel opens upward from the same corner. It is transient, so the widget steps
	   aside while it is open rather than moving again -- see the note above on chasing this collision. */
	body:has(.cp-tabs__more[open]) .userway_buttons_wrapper {
		visibility: hidden !important;
	}
}

.cp-topbar__brand img {
	max-height: 34px;
	width: auto;
}

.cp-acct {
	display: flex;
	align-items: center;
	gap: var(--cp-space-5);
	flex: 1;
	min-width: 0;
	max-width: 420px;
	padding: 7px 12px;
	background: var(--cp-paper);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-sm);
	color: inherit;
	text-decoration: none;
	text-align: left;
	cursor: pointer;
	font: inherit;
	transition: border-color var(--cp-dur) var(--cp-ease), background var(--cp-dur) var(--cp-ease);
}

.cp-acct:hover {
	border-color: var(--cp-sky);
	background: var(--cp-sky-tint);
	color: inherit;
}

.cp-acct:focus-visible {
	outline: 2px solid var(--cp-azure);
	outline-offset: 2px;
}

.cp-acct__t {
	min-width: 0;
	line-height: 1.25;
}

.cp-acct__t b {
	display: block;
	font-size: var(--cp-text-sm);
	font-weight: 600;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.cp-acct__t small {
	font-size: var(--cp-text-2xs);
	color: var(--cp-ink-2);
	font-variant-numeric: tabular-nums;
}

.cp-topbar__user {
	margin-left: auto;
	display: flex;
	align-items: center;
	gap: var(--cp-space-5);
	flex: none;
}

.cp-topbar__user a {
	font-size: var(--cp-text-sm);
	font-weight: 600;
	color: var(--cp-ink-2);
	text-decoration: none;
	white-space: nowrap;
}

/*
 * Hidden on phones, where the topbar has to hold the brand, the account switcher and the logout link
 * in one row -- the avatar's initial carries the identity there instead.
 */
.cp-topbar__me {
	display: none;
	font-size: var(--cp-text-sm);
	font-weight: 600;
	color: var(--cp-ink);
	white-space: nowrap;
	max-width: 22ch;
	overflow: hidden;
	text-overflow: ellipsis;
}

@media (min-width: 600px) {
	.cp-topbar__me {
		display: inline-block;
	}
}

.cp-topbar__user a:hover {
	color: var(--cp-navy);
	text-decoration: underline;
}

.cp-avatar {
	width: 34px;
	height: 34px;
	flex: none;
	display: grid;
	place-items: center;
	border-radius: 50%;
	background: var(--cp-sky-tint);
	border: 1px solid var(--cp-line);
	color: var(--cp-navy);
	font-size: var(--cp-text-xs);
	font-weight: 700;
}

.cp-pill {
	display: inline-flex;
	align-items: center;
	gap: var(--cp-space-3);
	padding: 4px 11px;
	border-radius: var(--cp-r-pill);
	font-size: var(--cp-text-2xs);
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: .04em;
	white-space: nowrap;
}

.cp-pill--csr {
	background: var(--cp-gold);
	color: #4a3100;
}

.cp-pill--readonly {
	background: var(--cp-ink);
	color: #fff;
}

.cp-pill--sync {
	background: var(--cp-sky-tint);
	color: var(--cp-navy);
}

/* .cp-pill's display:inline-flex would otherwise beat the hidden attribute. */
.cp-pill--sync[hidden] {
	display: none;
}

/* top nav (tablet and up) */
.cp-nav {
	display: none;
	align-items: center;
	gap: var(--cp-space-1);
	padding: 0 16px;
	background: var(--cp-surface);
	border-bottom: 1px solid var(--cp-line);
	position: sticky;
	top: var(--cp-topbar-h);
	z-index: 19;
}

/*
 * The links scroll in their own sub-region, separate from Pay bill. At tablet widths (below the
 * 1100px sidebar breakpoint) five links plus the Pay bill button do not all fit; when this whole
 * row shared one overflow-x:auto, the flex item pushed to the end (Pay bill, via margin-left:auto
 * on .cp-nav__pay) was what ran off the edge -- and with scrollbar-width:none there was no visible
 * cue that scrolling would reveal it, so it just looked cut off. Scoping the scroll to the links
 * only, with Pay bill flex:none outside it, keeps Pay bill fully visible at every width down to 600px.
 */
.cp-nav__links {
	display: flex;
	align-items: center;
	gap: var(--cp-space-1);
	min-width: 0;
	flex: 1 1 auto;
	overflow-x: auto;
	scrollbar-width: none;
}

.cp-nav__links::-webkit-scrollbar {
	display: none;
}

/*
 * Scoped to .cp-nav__links, never a bare `.cp-nav a`: Pay bill is an <a> inside the same nav, and a
 * type selector on the nav itself out-ranks .cp-nav__pay .cp-btn's own styling on every property they
 * share. The sidebar breakpoint's border-bottom:0 (a link reset) stripped the button's bottom border
 * for a month while two fixes chased it as a clipping problem. Same scoping for the hover, active,
 * focus and svg rules below and in the 600px and 1100px blocks -- the button owns its own look.
 */
.cp-nav__links a {
	display: flex;
	align-items: center;
	gap: var(--cp-space-4);
	padding: 14px 14px 12px;
	margin-bottom: -1px;
	border-bottom: 3px solid transparent;
	color: var(--cp-ink-2);
	font-size: var(--cp-text-base);
	font-weight: 500;
	text-decoration: none;
	white-space: nowrap;
	transition: color var(--cp-dur) var(--cp-ease), border-color var(--cp-dur) var(--cp-ease);
}

.cp-nav__links a:hover {
	color: var(--cp-navy);
	border-bottom-color: var(--cp-line);
}

.cp-nav__links a[aria-current="page"] {
	color: var(--cp-navy);
	font-weight: 600;
	border-bottom-color: var(--cp-azure);
}

.cp-nav__links a:focus-visible {
	outline: 2px solid var(--cp-azure);
	outline-offset: -3px;
}

.cp-nav__links svg {
	width: 17px;
	height: 17px;
	flex: none;
	stroke: currentColor;
	fill: none;
	stroke-width: 1.7;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/*
 * display:contents below 1100px: the bar and tab-row layouts style .cp-nav__links/.cp-nav__pay
 * directly as flex children of .cp-nav, so this wrapper stays out of the way entirely until the
 * sidebar breakpoint gives it a job (see cp-consumer.css's 1100px block for what that job is).
 */
.cp-nav__inner {
	display: contents;
}

.cp-nav__pay {
	flex: none;
	margin-left: auto;
	padding-left: 14px;
}

.cp-nav__pay .cp-btn {
	min-height: 38px;
	padding: 8px 16px;
	font-size: var(--cp-text-sm);
	background: transparent;
	color: var(--cp-amber-ink);
	/* Solid --cp-amber-ink, not the previous 65%-alpha amber -- that composited to ~1.6:1 against the
	   white nav background, under the 3:1 WCAG minimum for a UI component's boundary. Matches the
	   button's own text color, which already passed. */
	border: 1.5px solid var(--cp-amber-ink);
	box-shadow: none;
}

.cp-nav__pay .cp-btn:hover {
	background: var(--cp-amber-tint);
	border-color: var(--cp-amber);
	transform: none;
}

/* bottom tab bar (phone) */
/*
 * Fixed, not sticky. nav_consumer emits this row BEFORE <main class="cp-content">, and .cp-content is
 * flex:1 0 auto, so the flexible length ate the free space before margin-top:auto could push the row
 * down, and sticky bottom:0 never engaged because an element already at the top of the scrollport
 * satisfies that constraint permanently. Measured on a phone viewport: the row sat at top:77 under the
 * topbar and scrolled clean off at top:-523, so the primary navigation disappeared the moment anyone
 * scrolled. .cp-content already reserves 74px of bottom padding for it, which is what this was always
 * meant to be. Only applies under 600px, where the row is the only nav -- above that it is display:none.
 */
.cp-tabs {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 20;
	display: grid;
	/* The row emits three to five items depending on hasAccount, canPay and isShowStatementHistory, so a
	   hard-coded five-column grid bunched three tabs into the left 60% with two empty columns on every
	   profile page and on any utility with payments or statement history off. */
	grid-auto-flow: column;
	grid-auto-columns: 1fr;
	padding: 7px 4px 9px;
	background: rgba(255, 255, 255, .94);
	backdrop-filter: blur(12px);
	border-top: 1px solid var(--cp-line);
}

.cp-tabs a {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--cp-space-2);
	padding: 4px 2px;
	border-radius: 9px;
	color: var(--cp-ink-3);
	font-size: var(--cp-text-3xs);
	font-weight: 500;
	text-decoration: none;
}

.cp-tabs a svg {
	width: 21px;
	height: 21px;
	stroke: currentColor;
	fill: none;
	stroke-width: 1.7;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.cp-tabs a[aria-current="page"] {
	color: var(--cp-navy);
}

.cp-tabs a:focus-visible {
	outline: 2px solid var(--cp-azure);
	outline-offset: -2px;
}

.cp-tabs a.cp-tabs__pay {
	color: var(--cp-amber-ink);
}

.cp-tabs a.cp-tabs__pay svg {
	width: 34px;
	height: 34px;
	margin-top: -9px;
	padding: 7px;
	border-radius: 11px;
	background: var(--cp-amber);
	color: #2a1a00;
	box-shadow: 0 5px 14px -4px rgba(255, 163, 0, .85);
}

.cp-content {
	flex: 1 0 auto;
	min-width: 0;
	padding: 18px 16px 74px;
	display: flex;
	flex-direction: column;
	gap: var(--cp-space-8);
}

.cp-pagehd h1 {
	margin: 0;
	font-size: var(--cp-text-2xl);
	font-weight: 700;
	letter-spacing: -.025em;
}

.cp-pagehd p {
	margin: 4px 0 0;
	font-size: var(--cp-text-sm);
	color: var(--cp-ink-2);
}

/*
 * Solid, not tinted -- unlike cp-chip--alert (a quiet detail-level note, e.g. the due-date chip on an
 * account card), this badge's whole job is to be seen before the reader scrolls, the way the legacy
 * shell's badge-danger pill sat right in the page heading. cp-pastdue below it stays the quieter,
 * detail-level explanation once they get there.
 */
.cp-badge {
	display: inline-flex;
	align-items: center;
	margin-left: 10px;
	padding: 4px 12px;
	border-radius: var(--cp-r-pill);
	font-size: var(--cp-text-xs);
	font-weight: 700;
	letter-spacing: .02em;
	text-transform: uppercase;
	vertical-align: middle;
}

.cp-badge--alert {
	background: var(--cp-alert);
	color: #fff;
}

.cp-banner {
	padding: 13px 15px;
	border-radius: var(--cp-r-sm);
	background: var(--cp-sky-tint);
	border: 1px solid var(--cp-line);
	font-size: var(--cp-text-base);
	color: var(--cp-ink);
}

@media (min-width: 600px) {
	.cp-nav {
		display: flex;
	}

	.cp-tabs {
		display: none;
	}

	.cp-content {
		padding-bottom: 24px;
	}

	/*
	 * Five links at the base 14px 14px 12px padding overflow .cp-nav__links' own width right at this
	 * breakpoint's narrow end (measured ~37px short at 601px) -- .cp-nav__links scrolls to compensate,
	 * exactly as designed, but scrollbar-width:none leaves no visual cue that there's more to reach, so
	 * the last link (Profile) just reads as cut off ("Pro") rather than reachable. Tightening padding
	 * here removes the overflow itself at the width it actually occurs, rather than mitigating a scroll
	 * affordance problem. Superseded by the sidebar breakpoint's own 10px 12px at 1100px, so this only
	 * ever applies in bar mode.
	 */
	.cp-nav__links a {
		padding: 10px 10px 12px;
	}
}

@media (min-width: 900px) {
	.cp-topbar,
	.cp-nav {
		padding-left: 28px;
		padding-right: 28px;
	}

	.cp-content {
		padding: 26px 28px;
		gap: var(--cp-space-9);
	}
}

/*
 * ─── Left sidebar navigation (wide screens) ───────────────────────────────────────────────────
 *
 * The portal's navigation lived in a left sidebar for years (the AppStrap quick_links panel), so
 * that is where people look for it. Below this width there is not enough room to give a sidebar its
 * column and still leave the content a readable measure, so the nav stays the horizontal bar down to
 * 600px and the bottom tab row below that. Same markup throughout -- only the layout changes, so no
 * nav item, id or route differs between the three modes.
 *
 * Grid areas rather than a DOM reorder: nav_consumer emits the topbar, the bar and the tab row as
 * siblings before <main>, and placing them by area leaves that order alone, which keeps the tab
 * order and the skip link honest.
 */
@media (min-width: 1100px) {
	.cp-app--nav {
		display: grid;
		grid-template-columns: var(--cp-sidebar-w) minmax(0, 1fr);
		grid-template-rows: auto 1fr auto;
		grid-template-areas:
			"topbar topbar"
			"nav    content"
			"nav    foot";
	}

	.cp-app--nav > .cp-topbar {
		grid-area: topbar;
	}

	.cp-app--nav > .cp-nav {
		grid-area: nav;
	}

	.cp-app--nav > .cp-content {
		grid-area: content;
	}

	.cp-app--nav > .cp-appfoot {
		grid-area: foot;
	}

	/*
	 * align-self:stretch, not start: this panel is what paints .cp-nav's background and right border,
	 * and it has to run the full height of the sidebar's grid area (nav+foot rows combined) -- which on
	 * any page taller than one viewport (nearly all of them; the Usage page's chart and table are a
	 * typical case) is taller than the viewport itself. A previous version capped this same element at
	 * min-height:100vh via align-self:start, on the reasoning that being sticky would keep it flush with
	 * the viewport as the page scrolled -- true for the sticky *offset*, but align-self:start also sizes
	 * the box itself to that capped height, so its white background and border stopped exactly one
	 * viewport down and left the page's own grey background showing through underneath for the rest of
	 * the scroll, with Pay bill stranded at that seam. Stretching this element removes the cap; the
	 * sticky, viewport-capped panel that used to be here moves to .cp-nav__inner below instead, one
	 * level in, where it can stick within *this* now-full-height box without also being what defines
	 * that box's own size.
	 */
	.cp-app--nav > .cp-nav {
		align-self: stretch;
		flex-direction: column;
		align-items: stretch;
		border-bottom: 0;
		border-right: 1px solid var(--cp-line);
		overflow-x: visible;
		/*
		 * Resets the tablet/bar-mode padding-left/right:28px (@media 900px, above) that .cp-nav still
		 * carries at this width -- harmless in bar mode where it pads a horizontal row, but stacked on
		 * top of .cp-nav__inner's own 14px/12px padding here it pushed every icon 40px further right
		 * than intended, reading as the nav column not actually starting at its own left edge.
		 */
		padding: 0;
	}

	/*
	 * The sticky, one-viewport-tall panel itself. Its containing block for sticky purposes is .cp-nav
	 * above, which is now stretched to the full (possibly page-length) sidebar column, so this has real
	 * room to travel: top:var(--cp-topbar-h) keeps it flush under the topbar as the page scrolls, and it
	 * only releases near the actual bottom of the page, not after one viewport.
	 */
	.cp-app--nav > .cp-nav > .cp-nav__inner {
		position: sticky;
		top: var(--cp-topbar-h);
		min-height: calc(100vh - var(--cp-topbar-h));
		display: flex;
		flex-direction: column;
		align-items: stretch;
		gap: var(--cp-space-1);
		padding: 14px 12px;
	}

	/*
	 * Undo the tablet-mode scrolling sub-region: in the sidebar column, links stack like anything else.
	 * align-items:stretch undoes the base rule's align-items:center too -- that rule vertically centers
	 * each link within the horizontal bar, correct there, but flex-direction:column rotates the cross
	 * axis to horizontal: unreset, the same "center" instead centered every link by its own text width
	 * within the column, giving a ragged edge (each icon starting at a different x) instead of a flush
	 * left edge. stretch, not flex-start, so each <a> still fills the column's full width -- its own
	 * display:flex + default justify-content:flex-start (see .cp-nav__links a below) is what actually left-
	 * aligns the icon+label inside that now-full-width link, and the hover/active background (also on
	 * the <a> itself) reaches the column's right edge instead of stopping at the text.
	 */
	.cp-app--nav > .cp-nav .cp-nav__links {
		flex: none;
		flex-direction: column;
		align-items: stretch;
		min-width: auto;
		overflow-x: visible;
	}

	.cp-app--nav > .cp-nav .cp-nav__links a {
		gap: var(--cp-space-6);
		padding: 10px 12px;
		margin-bottom: 0;
		border-bottom: 0;
		border-radius: var(--cp-r-sm);
		font-size: var(--cp-text-base);
	}

	.cp-app--nav > .cp-nav .cp-nav__links a:hover {
		background: var(--cp-line-2);
		border-bottom-color: transparent;
	}

	.cp-app--nav > .cp-nav .cp-nav__links a[aria-current="page"] {
		background: var(--cp-sky-tint);
		color: var(--cp-navy);
		border-bottom-color: transparent;
	}

	.cp-app--nav > .cp-nav .cp-nav__links svg {
		width: 18px;
		height: 18px;
	}

	/*
	 * Directly under the last link, not pinned to the foot of the panel: the links' flex:none above
	 * stops them growing to fill the column, which is what used to strand Pay bill a screen away from
	 * the menu it belongs to.
	 */
	.cp-app--nav > .cp-nav .cp-nav__pay {
		margin-left: 0;
		margin-top: 12px;
		padding-left: 0;
	}

	.cp-app--nav > .cp-nav .cp-nav__pay .cp-btn {
		width: 100%;
	}
}

@media (min-width: 1600px) {
	.cp-content {
		width: 100%;
		max-width: 1400px;
		margin-inline: auto;
	}
}

/*
 * The bottom padding clears the fixed phone tab row. .cp-content reserves 74px for it, but the footer
 * is .cp-content's SIBLING, not its child, so that reservation never covered it -- the tab row sat on
 * top of the footer and hid every glyph in it, leaving only the top padding visible. That is Terms,
 * Privacy, Contact us, the copyright and the version string unreachable on every phone page: the legal
 * links a payments product has to keep reachable, and the escape hatch for exactly the customer who is
 * stuck. Only under 600px, which is the only width where the row exists.
 */
.cp-appfoot {
	flex: none;
	padding: 20px 16px calc(24px + 74px);
	text-align: center;
	border-top: 1px solid var(--cp-line);
	background: var(--cp-surface);
}

@media (min-width: 600px) {
	.cp-appfoot {
		padding-bottom: 24px;
	}
}

@media (min-width: 900px) {
	.cp-appfoot {
		padding: 22px 28px;
	}
}

/* ─── Balance hero + due-date meter ───────────────────────────────────────────────────────── */
.cp-card {
	background: var(--cp-surface);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-md);
	box-shadow: var(--cp-sh-1);
}

/*
 * The one card on account home that outranks every other card on the page -- it's the number a
 * customer opens the app to check. --cp-sh-2 (used elsewhere only for popovers) gives it real
 * elevation against the flat --cp-sh-1 every other card uses; the top accent is a brand cue, not a
 * status color, so it stays --cp-navy/--cp-azure regardless of due-date tone (see [data-tone] below
 * for the actual urgency signal).
 */
.cp-card--hero {
	position: relative;
	overflow: hidden;
	box-shadow: var(--cp-sh-2), 0 0 0 1px rgba(11, 34, 57, .06);
}

.cp-card--hero::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: 4px;
	background: linear-gradient(90deg, var(--cp-navy), var(--cp-azure));
}

.cp-card__hd {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--cp-space-6);
	padding: 16px 18px 0;
}

.cp-card__hd h2,
.cp-card__hd h3 {
	margin: 0;
	font-size: var(--cp-text-md);
	font-weight: 600;
	letter-spacing: -.01em;
}

/*
 * A heading for one section inside a card, where several sections share a single cp-card__bd -- the
 * profile page stacks six of them. Not a plain h2: that is the page-level size, which reads as a new
 * page rather than a subdivision. Four profile tags had each defined this identical rule privately
 * and two had not, which is exactly why their headings did not match.
 */
.cp-sectionhd {
	margin: 0 0 14px;
	font-size: var(--cp-text-md);
	font-weight: 600;
	letter-spacing: -.01em;
	color: var(--cp-ink);
}

.cp-card__bd {
	padding: 14px 18px 18px;
}

/*
 * For a card whose only content is a long-form legal-prose fragment (terms_and_conditions(),
 * privacy_policy()) that opens with its own <h3> -- the same fragment renders inside
 * .cp-authcard--wide on the unauth shell, which gives it a readable measure and a page-title-sized
 * first heading via its own three rules below; .cp-content's global h2/h3/h4 rule (cp-consumer.css,
 * tuned for a card-section heading, not a page title) is all an unmodified .cp-card gave it, and an
 * unconstrained .cp-card__bd runs prose the full width of the content column. Mirrors
 * .cp-authcard--wide's rules exactly so the authenticated and public versions of the same fragment
 * read as one design instead of two eras.
 */
.cp-card--prose .cp-card__bd {
	max-width: 820px;
}

.cp-card--prose .cp-card__bd h2,
.cp-card--prose .cp-card__bd h3 {
	margin: 26px 0 8px;
	font-size: var(--cp-text-lg);
	line-height: 1.3;
	color: var(--cp-ink);
}

.cp-card--prose .cp-card__bd > h3:first-child {
	margin-top: 0;
	font-size: var(--cp-text-3xl);
	letter-spacing: -.015em;
}

.cp-card--prose .cp-card__bd p,
.cp-card--prose .cp-card__bd li {
	font-size: var(--cp-text-base);
	line-height: 1.65;
}

.cp-balance {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--cp-space-9);
	padding: 22px;
}

.cp-balance__amt {
	display: block;
	margin: 10px 0 0;
	font-size: var(--cp-text-display);
	font-weight: 600;
	line-height: 1;
	letter-spacing: -.035em;
	font-variant-numeric: tabular-nums;
}

.cp-balance__side {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: var(--cp-space-6);
	padding-top: 18px;
	border-top: 1px solid var(--cp-line);
}

.cp-balance__side .cp-btn {
	width: 100%;
	min-height: 52px;
	font-size: var(--cp-text-md);
}

.cp-balance__row {
	display: flex;
	justify-content: space-between;
	gap: var(--cp-space-6);
	padding: 3px 0;
	font-size: var(--cp-text-sm);
	color: var(--cp-ink-2);
}

.cp-balance__row b,
.cp-balance__row a {
	font-variant-numeric: tabular-nums;
	font-weight: 600;
	color: var(--cp-ink);
}

.cp-balance__row a {
	color: var(--cp-azure-ink);
	text-decoration: none;
}

/*
 * "Total due" sat at the exact same size/weight as "Account Number" and every other row around it on
 * the payment summary -- the one figure the whole card exists to confirm read as just more metadata.
 * The hr already above it separates it structurally; this gives it the visual weight to match.
 */
.cp-balance__row--total {
	padding-top: 8px;
}

.cp-balance__row--total b {
	font-size: var(--cp-text-lg);
}

.cp-balance__row a:hover {
	text-decoration: underline;
}

.cp-chips {
	display: flex;
	flex-wrap: wrap;
	gap: var(--cp-space-4);
	margin-top: 18px;
}

.cp-chip {
	align-items: center;
	gap: var(--cp-space-3);
	padding: 4px 11px;
	border-radius: var(--cp-r-pill);
	background: var(--cp-sky-tint);
	color: var(--cp-navy);
	font-size: var(--cp-text-xs);
	font-weight: 600;
	white-space: nowrap;
}

/*
 * :not([hidden]) -- fiber_voice_number's "Will be removed" badge starts hidden and fiber_wizard.js
 * toggles it. An unqualified `display: inline-flex` here would win the specificity tie against
 * [hidden]'s UA display:none, same collision as .cp-btn/.cp-check/.cp-note above -- scoped here so
 * every .cp-chip, not just this caller, actually stays hidden.
 */
.cp-chip:not([hidden]) {
	display: inline-flex;
}

.cp-chip--good {
	background: var(--cp-good-tint);
	color: var(--cp-good);
}

.cp-chip--warn {
	background: var(--cp-amber-tint);
	color: var(--cp-amber-ink);
}

.cp-chip--alert {
	background: var(--cp-alert-tint);
	color: var(--cp-alert);
}

.cp-meter {
	margin-top: 18px;
}

.cp-meter__track {
	position: relative;
	height: 8px;
	border-radius: var(--cp-r-pill);
	background: linear-gradient(90deg, var(--cp-line-2), #e4ebf2);
}

.cp-meter__fill {
	position: absolute;
	inset: 0 auto 0 0;
	width: 0;
	border-radius: var(--cp-r-pill);
	background: linear-gradient(90deg, var(--cp-sky), var(--cp-azure));
	transition: width 1.1s cubic-bezier(.2, .8, .2, 1);
}

.cp-meter[data-tone="due"] .cp-meter__fill {
	background: linear-gradient(90deg, var(--cp-gold), var(--cp-amber));
}

.cp-meter[data-tone="past"] .cp-meter__fill {
	background: linear-gradient(90deg, var(--cp-amber), #ce3b2f);
}

/*
 * The due-date tick doubles as an urgency badge once account_home's inline script sets data-tone --
 * same three states the meter fill above already colors, so this reads as one signal rather than two
 * competing ones. "ok" stays visually quiet on purpose: most balances aren't due soon, and a badge on
 * every single one would just be noise.
 */
#statementDueDateWrapper {
	display: inline-flex;
	align-items: baseline;
	gap: 4px;
	padding: 2px 10px;
	border-radius: var(--cp-r-pill);
	transition: background var(--cp-dur) var(--cp-ease), color var(--cp-dur) var(--cp-ease);
}

/*
 * No tint fill here (unlike the "past" tone below) -- this is the third amber element on the same
 * dashboard card as the solid .cp-btn--pay button and the meter's own gold-to-amber fill. The ink-
 * colored text still carries the due-soon signal; a matching tint chip on top of it just crowded amber
 * three times over on the one card a customer opens the app to check.
 */
.cp-meter[data-tone="due"] #statementDueDateWrapper #statementDueDate {
	color: var(--cp-amber-ink);
}

.cp-meter[data-tone="past"] #statementDueDateWrapper {
	background: var(--cp-alert-tint);
}

.cp-meter[data-tone="past"] #statementDueDateWrapper #statementDueDate {
	color: var(--cp-alert);
}

.cp-meter__ticks {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: var(--cp-space-5) var(--cp-space-6);
	margin-top: 9px;
}

/*
 * nowrap so a narrow viewport (e.g. 390px) wraps the whole "8 days to pay" tick onto its own line
 * (flex-wrap above) instead of breaking mid-phrase into "8 days to" / "pay" -- three flex items sharing
 * one row with no wrap allowance had nowhere else to shrink to.
 */
.cp-meter__ticks span {
	font-size: var(--cp-text-2xs);
	font-weight: 500;
	color: var(--cp-ink-3);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

.cp-meter__ticks b {
	color: var(--cp-ink-2);
	font-weight: 600;
}

/* display:block matters when this lands on a <small> (self-service's field descriptions do): <small>
   is inline by default, and an inline box's vertical margin is a no-op, so the note would otherwise
   butt straight up against the label above it with no gap. */
.cp-note {
	margin: 12px 0 0;
	font-size: var(--cp-text-xs);
	line-height: 1.5;
	color: var(--cp-ink-2);
}

/*
 * :not([hidden]) -- self-service's signature field description (data-role="description") starts
 * hidden until its section is relevant. An unqualified `display: block` here would win the
 * specificity tie against [hidden]'s UA display:none, same collision as .cp-check above.
 */
.cp-note:not([hidden]) {
	display: block;
}

/*
 * fiber_wizard.js writes a validation failure (not routine help text) into #voiceLineMessage, which
 * needs the same alert-red every other invalid-feedback message on this shell carries -- .cp-note's
 * own ink-2 grey reads as ordinary guidance, not an error.
 */
.cp-note--alert {
	color: var(--cp-alert);
}

/*
 * A saved payment method row (Payment Methods page): a brand image, a growing detail column, and a
 * delete action, in a divided list -- the same border-bottom/last-child pattern as .cp-rows. Built
 * from JS (manage_payment_methods.js constructs one per saved card/bank/PayPal method), so the class
 * names here are a contract with that file, not just this stylesheet.
 */
.cp-pm-row {
	display: flex;
	align-items: center;
	gap: var(--cp-space-7);
	padding: 14px 2px;
	border-bottom: 1px solid var(--cp-line-2);
}

.cp-pm-row:last-child {
	border-bottom: 0;
}

.cp-pm-row__img {
	flex: none;
	width: 44px;
}

.cp-pm-row__img img {
	display: block;
	max-width: 100%;
}

.cp-pm-row__detail {
	flex: 1 1 auto;
	min-width: 0;
}

.cp-pm-row__title {
	font-size: var(--cp-text-base);
	font-weight: 600;
	color: var(--cp-ink);
}

.cp-pm-row__sub {
	margin-top: 2px;
	font-size: var(--cp-text-xs);
	color: var(--cp-ink-2);
}

.cp-pm-row__sub .cp-chip {
	margin-left: 8px;
	vertical-align: middle;
}

.cp-pm-row__del {
	flex: none;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 34px;
	height: 34px;
	border: 0;
	border-radius: var(--cp-r-pill);
	background: transparent;
	color: var(--cp-ink-3);
	cursor: pointer;
	transition: background var(--cp-dur) var(--cp-ease), color var(--cp-dur) var(--cp-ease);
}

.cp-pm-row__del:hover,
.cp-pm-row__del:focus-visible {
	background: var(--cp-alert-tint);
	color: var(--cp-alert);
}

/*
 * Overrides toggle-switch.css's own flat "No / Yes" split-bar treatment (its markup, third-party,
 * ships no width on .switch-light and renders as a control the full width of its container -- on this
 * shell's wider content column that is most of a card) with a compact iOS/Material-style pill and
 * sliding knob, the same direction cp-admin-modern.css already took for the identical vendor markup
 * (see its own "Modern toggle switch" block) -- ported here with this shell's own tokens rather than
 * the admin palette. The <label class="switch-light switch-candy switch-candy-green"> markup and its
 * <input>/<span><span>No</span><span>Yes</span><a></a></span> structure stay exactly as the vendor
 * library and every page using it already expect; only the paint changes.
 */
.switch-light.switch-candy,
.switch-light.switch-candy.switch-candy-green {
	display: inline-block;
	width: 52px;
	height: 28px;
	min-width: 52px;
	padding: 0;
	border: 0;
	border-radius: var(--cp-r-pill);
	background: transparent;
	cursor: pointer;
	vertical-align: middle;
}

.switch-light.switch-candy > span,
.switch-light.switch-candy > span:first-of-type {
	display: block;
	position: relative;
	width: 52px;
	height: 28px;
	padding: 0;
	margin: 0;
	background: var(--cp-line-2);
	border: 1px solid var(--cp-field-line);
	border-radius: var(--cp-r-pill);
	box-shadow: none;
	overflow: visible;
	transition: background-color var(--cp-dur) var(--cp-ease), border-color var(--cp-dur) var(--cp-ease);
}

.switch-light.switch-candy > span > span {
	display: none !important;
}

.switch-light.switch-candy > span > a {
	display: block;
	position: absolute;
	top: 3px;
	left: 3px;
	width: 22px;
	height: 22px;
	margin: 0;
	background: var(--cp-surface);
	border-radius: 50%;
	box-shadow: var(--cp-sh-1);
	transition: left var(--cp-dur) var(--cp-ease);
}

.switch-light.switch-candy > input:checked ~ span,
.switch-light.switch-candy > input:checked + span {
	background: var(--cp-azure);
	border-color: var(--cp-azure);
}

.switch-light.switch-candy > input:checked ~ span > a,
.switch-light.switch-candy > input:checked + span > a {
	left: 27px;
}

.switch-light.switch-candy > input:focus ~ span,
.switch-light.switch-candy > input:focus + span {
	box-shadow: 0 0 0 3px rgba(0, 133, 202, .25);
}

.switch-light.switch-candy > input:disabled ~ span,
.switch-light.switch-candy > input:disabled + span {
	background: var(--cp-line-2);
	border-color: var(--cp-line);
	cursor: not-allowed;
}

/*
 * A class-based mute, not the disabled attribute, for the same toggle when a past-due/deposit balance
 * blocks enrollment (autopay_management.scala.html): disabling the input would also drop the click event
 * that shows the customer WHY it's blocked (autopay_management.js's Sweet.showError on #chkActive), since
 * a disabled control never fires click. The unchecked switch-candy OFF state already uses this same
 * background/border (see input:disabled above, which is nearly indistinguishable from resting OFF for
 * the same reason) -- opacity is the actual signal here, matching .cp-btn--blocked's own treatment for a
 * control that reads as blocked rather than merely unset.
 */
.cp-toggle-row--blocked .cp-toggle-row__control {
	opacity: .55;
}

.cp-toggle-row--blocked .switch-light.switch-candy > span {
	background: var(--cp-line-2);
	border-color: var(--cp-line);
	cursor: not-allowed;
}

/*
 * .cp-toggle-row wraps the vendor switch-candy control on autopay_management.scala.html -- previously
 * a bare label + linebreak floating with no card-row treatment, the only unstyled control on a page
 * whose every other element got the redesign pass. Label + description on the left, control + a
 * plain-language state word on the right, matching the label/value rhythm .cp-balance__row already
 * uses elsewhere -- so this reads as part of the same design system instead of a leftover control.
 */
.cp-toggle-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--cp-space-6);
	padding: 14px 0;
}

.cp-toggle-row p.cp-note {
	margin: 2px 0 0;
}

.cp-toggle-row__control {
	display: flex;
	align-items: center;
	gap: var(--cp-space-4);
	flex-shrink: 0;
}

.cp-toggle-row__state {
	min-width: 28px;
	font-size: var(--cp-text-sm);
	font-weight: 600;
	color: var(--cp-ink-2);
}

/*
 * The csa-progress-bar/csa-progress-bar-selected/csa-progress-bar-completed classes are the wizard
 * step indicator (payment_auth.js's step-1/2/3, registration.scala.html's own, and the self-service
 * multi-step forms) -- their base look is defined only in the AppStrap theme's custom-style.css, which
 * the consumer shell does not load. Multiple JS files (payment_common.js's completeStep/clearStep/
 * selectStep and registration.scala.html's own inline script) toggle these three class NAMES via
 * classList add/remove with no assumption about how they're painted, so the fix is the same shape as
 * the switch-light component above: real rules for the existing class names here, no JS or markup
 * change needed.
 */
/*
 * registration.scala.html originally carried "Account Verification" as its step-1 label, longer than
 * every cp-step-full label this component was tested against elsewhere (payment_auth.scala.html's
 * "Payment Details"/"Review Payment"/"Confirmation", one_time_payment.scala.html's "Verify Account"/
 * etc.) -- it has since been renamed to match one_time_payment.scala.html's "Verify Account" and no
 * longer clips, but the box-level truncation below is left in place: at the fixed 34px height, a label
 * that doesn't fit the flex-distributed width used to wrap onto a second line that rendered past the
 * box, overlapping the content below it -- white-space/overflow/text-overflow here truncates with an
 * ellipsis instead, on the box itself rather than the inline cp-step-full/cp-step-short spans inside
 * it (text-overflow has no effect on a display:inline element). Correct for any label this component
 * might carry, present or future, not specific to any one string.
 */
.csa-progress-bar,
.csa-progress-bar-selected,
.csa-progress-bar-completed {
	height: 34px;
	line-height: 34px;
	padding: 0 4px;
	border-radius: var(--cp-r-sm);
	/* Not --cp-text-xs (13px) -- the 6-cell .cp-wizardbar variant's label-fit math below is measured
	   down to single-digit pixels of headroom at this exact 12.5px; see the --cp-text-* token comment. */
	font-size: 12.5px;
	font-weight: 600;
	letter-spacing: .01em;
	text-align: center;
	color: var(--cp-ink-3);
	background: var(--cp-line-2);
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
	transition: background var(--cp-dur) var(--cp-ease), color var(--cp-dur) var(--cp-ease);
}

.csa-progress-bar-selected {
	color: #fff;
	background: var(--cp-azure);
}

.csa-progress-bar-completed {
	color: var(--cp-good);
	background: var(--cp-good-tint);
}

.csa-progress-bar-completed::before {
	content: "\f00c";
	font-family: "Font Awesome 5 Free";
	font-weight: 900;
	font-size: 10.5px;
	margin-right: 5px;
}

/*
 * The full/short step-label swap at narrow widths -- payment_auth.scala.html's own
 * .payment-auth-step-full/.payment-auth-step-short and one_time_payment.scala.html's own
 * .otp-step-full/.otp-step-short each reinvented this identical pair as a page-local rule. Promoted
 * here as the third page (registration.scala.html) needed it, so it is defined once rather than a
 * fourth time; the two existing page-local pairs are left as they are; this is additive only.
 */
.cp-step-full {
	display: inline;
}

.cp-step-short {
	display: none;
}

@media (max-width: 480px) {
	.cp-step-full {
		display: none;
	}

	.cp-step-short {
		display: inline;
	}
}

/*
 * Ported from public/css/cp/self_service/service_wizard.css, the step-wizard chrome shared by all
 * five non-fiber self-service request types (stop_service, misc_request, transfer_service,
 * start_service and start_service_new_construction all use it now -- round 4 converted the last of
 * the five and the source file was deleted). That file's .csa-progress-bar padding override is not
 * repeated here -- the base rule above already carries the same padding (0 4px == 0 .25rem). Its
 * cursor:default IS carried forward below, scoped to .cp-wizardbar -- the source file set it on all
 * three progress-bar classes and it was dropped when the callers stopped linking that stylesheet; the
 * base .csa-progress-bar rule (used by payment/registration wizards too) is left alone since those
 * callers never had it.
 */
.service-wizard-step {
	padding: .25rem 0;
}

/*
 * A generic wizard progress row -- N equal-width csa-progress-bar cells in one horizontal bar.
 * payment_auth.scala.html/one_time_payment.scala.html/registration.scala.html each own a page-local
 * flex wrapper around the same csa-progress-bar cells rather than sharing one; this bar is different
 * because all five non-fiber self-service request types share it, so centralising it here means each
 * reuses it instead of re-deriving its own copy. Four render 5 cells; start_service_new_construction
 * renders 6 (it adds a Location step) -- flex:1 1 0 below distributes the row's width evenly across
 * however many cells there are, so no per-page override is needed for that. It says nothing about
 * whether a cell's own label still fits at that width -- see the short-label swap point below, which
 * does need re-checking whenever the cell count or a label changes.
 */
.cp-wizardbar {
	display: flex;
	gap: var(--cp-space-4);
	margin-top: 10px;
	margin-bottom: 16px;
}

.cp-wizardbar .csa-progress-bar {
	flex: 1 1 0;
	cursor: default;
}

/*
 * The shared .cp-step-full/.cp-step-short swap (above) defaults to full labels above 480px, tuned for
 * the 3-cell bars it was built for (payment/registration/one_time_payment). This bar's worst case is
 * now 6 cells (start_service_new_construction) with labels up to "Review & Submit" -- measured clipping
 * from 481px clear up to ~660px, i.e. crossing 480px going up made a many-cell bar WORSE, not better.
 * Scoped here rather than moving the shared swap point, since 3-cell callers are fine at 480px and
 * shouldn't lose that room. 680px holds at 6 cells with real margin at every cell except the two
 * longest labels ("Service Type", "Contact Info"), which clear it by under 15px -- a 7th cell or a
 * longer label on either of those two would need the bound re-measured, not assumed to still hold.
 */
@media (max-width: 680px) {
	.cp-wizardbar .cp-step-full {
		display: none;
	}

	.cp-wizardbar .cp-step-short {
		display: inline;
	}
}

/*
 * Service-type selector cards (start_service's step 1) -- a radio group where the whole labelled card
 * is the clickable control, so this targets bare input/label elements, NOT .cp-check (that component's
 * checkbox-beside-label layout is the opposite of what this wants). Markup contract:
 *
 *   <div class="ss-type-option selected">
 *       <input type="radio" id="serviceTypeResidential" name="...">
 *       <label for="serviceTypeResidential">Residential</label>
 *   </div>
 *
 * No `hidden` attribute on the input -- the visually-hidden rule below keeps it out of sight while
 * staying focusable; `hidden` forces display:none via the UA stylesheet with !important, which nothing
 * here can override, and an unfocusable input is unreachable by keyboard/screen-reader entirely.
 */
.ss-type-options {
	display: flex;
	flex-wrap: wrap;
	gap: var(--cp-space-8);
}

/* No padding here -- it lives on the label below, which is the only in-flow child (the input is
   absolutely positioned out of flow) and must fill this card's whole clickable/paintable area itself,
   not just the text it wraps. */
.ss-type-option {
	position: relative;
	flex: 1 1 14rem;
	border: 2px solid var(--cp-line);
	border-radius: var(--cp-r-sm);
	background: var(--cp-surface);
	margin: 0;
	text-align: center;
	transition: border-color var(--cp-dur) var(--cp-ease), background-color var(--cp-dur) var(--cp-ease),
		box-shadow var(--cp-dur) var(--cp-ease);
}

.ss-type-option:hover {
	border-color: var(--cp-azure);
	box-shadow: 0 2px 8px rgba(0, 133, 202, .1);
}

.ss-type-option.selected {
	border-color: var(--cp-azure);
	background: var(--cp-sky-tint);
}

/*
 * Visually hidden, not display:none -- a display:none input is removed from the tab order and never
 * announced, so a keyboard/screen-reader user could not tell this card group was a radio group at all,
 * let alone choose Residential vs. Commercial. This keeps the input focusable while the card supplies
 * the visible affordance; the rule below gives the card itself a visible focus ring when its input is
 * keyboard-focused, since the input itself is invisible.
 */
.ss-type-option input[type="radio"] {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

.ss-type-option:has(input[type="radio"]:focus-visible) {
	outline: 2px solid var(--cp-azure);
	outline-offset: 2px;
}

.ss-type-option label {
	display: block;
	padding: 1rem 1.25rem;
	font-weight: 600;
	font-size: var(--cp-text-md);
	cursor: pointer;
}

/* Review-step summary blocks -- ServiceWizard.reviewBlock() in service_wizard.js builds this markup
   as an HTML string; the class names here are a contract with that function, not just this file. */
.ss-review-block {
	margin-bottom: 1rem;
}

.ss-review-block:last-child {
	margin-bottom: 0;
}

.ss-review-label {
	font-size: var(--cp-text-2xs);
	letter-spacing: .05em;
	text-transform: uppercase;
	color: var(--cp-ink-3);
	font-weight: 500;
}

.ss-review-value {
	font-size: var(--cp-text-md);
	color: var(--cp-ink);
}

/*
 * Ported from public/css/cp/self_service/fiber.css, the fiber self-service wizard's component set
 * (package carousel, package cards, add-on rows, review summary, stop-service termination cards) --
 * fiber_start_service, fiber_stop_service and fiber_change_service all use it now, and the source
 * file is deleted. fiber-* class names are kept as-is rather than renamed to cp-fiber-*: they were
 * already well-named and not Bootstrap-derived, and fiber_wizard.js (~1094 lines) hardcodes every one
 * of them at runtime, so a rename would have meant touching the JS purely for a prefix.
 *
 * Raw hex colors are mapped to the closest existing custom property: #125aa8 (the fiber wizard's own
 * accent blue) -> var(--cp-azure-ink), the same token .cp-btn--primary already uses; #dee2e6/#ced4da
 * -> var(--cp-line); #f5f9ff -> var(--cp-sky-tint); #fff -> var(--cp-surface); #212529 -> var(--cp-ink);
 * #6c757d -> var(--cp-ink-3) (closer than -ink-2 by channel distance); #1a7a3a (the "current plan"
 * green) -> var(--cp-good), the same token already used for a completed wizard step and an alert-
 * success; #dc3545 -> var(--cp-alert); #f8f9fa -> var(--cp-paper); #cfe0f5 -> var(--cp-line) (no closer
 * blue-tinted border token exists). Box-shadow tints keyed to the accent blue or to ink use the literal
 * RGB decomposition of the matching token (rgba(1, 118, 163, ...) for azure-ink, rgba(11, 34, 57, ...)
 * for ink), the same convention --cp-sh-1/--cp-sh-2 already use, since a custom property cannot be
 * partially consumed inside an rgba() component list. The original .fiber-voice-config rule (background
 * + border override) was NOT ported: its selector never matched anything even before this round --
 * fiber_voice_number.scala.html's equivalent element is an id (#voiceNumberConfig), not this class -- so
 * it was already dead CSS, not a casualty of this port.
 */
.fiber-pkg-section {
	padding: .5rem 0;
	margin-bottom: 1.5rem;
}

/*
 * The background tint only -- border/padding/border-radius come from pairing this with .cp-fieldset
 * on the same element (class="cp-fieldset fiber-addon-section"), which also supplies the bottom margin
 * that replaces the original's mb-3, so no margin is repeated here.
 */
.fiber-addon-section {
	background-color: rgba(0, 0, 0, .02);
}

/* A flex row of .cp-check groups (fiber service-type picker) -- replaces the Bootstrap d-flex/flex-
   wrap/inline-gap utility trio, which do not exist on this shell. Each .cp-check's own 13px bottom
   margin is reset to 0 since the row's own gap already spaces both axes when it wraps. */
.fiber-servicetypes {
	display: flex;
	flex-wrap: wrap;
	gap: var(--cp-space-8);
}

.fiber-servicetypes .cp-check {
	margin-bottom: 0;
}

/* ===== Add-on rows ===== */
.fiber-addon-list {
	max-height: 24rem;
	overflow-y: auto;
	padding: .25rem;
	margin-top: .5rem;
}

.fiber-addon-row {
	display: flex;
	align-items: center;
	gap: var(--cp-space-6);
	padding: .75rem 1rem;
	margin-bottom: .5rem;
	border: 2px solid var(--cp-line);
	border-radius: .5rem;
	background: var(--cp-surface);
	cursor: pointer;
	transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
}

.fiber-addon-row:last-child {
	margin-bottom: 0;
}

.fiber-addon-row:hover {
	border-color: var(--cp-azure-ink);
	box-shadow: 0 2px 8px rgba(1, 118, 163, .1);
}

.fiber-addon-row.selected {
	border-color: var(--cp-azure-ink);
	background: var(--cp-sky-tint);
}

/*
 * fiber_wizard.js builds each row's checkbox+label as a bare .cp-check (see renderAddOns), not a
 * Bootstrap .form-check -- this only resets what fiber-addon-row's own flex layout needs reset:
 * .cp-check's 13px bottom margin (the row itself, not the check, controls spacing) and its own
 * default flex-basis so it shares the row with the qty stepper and price instead of stacking under
 * them. font-weight/cursor restore the emphasis the original .form-check-label carried; .cp-check's
 * own ink color token already matches the original's #212529.
 */
.fiber-addon-row .cp-check {
	margin: 0;
	flex: 1 1 auto;
	min-width: 0;
}

.fiber-addon-row .cp-check label {
	font-weight: 600;
	cursor: pointer;
}

.fiber-addon-row .fiber-addon-price {
	color: var(--cp-azure-ink);
	font-weight: 700;
	font-size: var(--cp-text-base);
	white-space: nowrap;
}

.fiber-addon-row .fiber-addon-qty {
	width: 4.5rem;
	text-align: center;
}

/*
 * :not([hidden]) is load-bearing, not decorative -- fiber_wizard.js toggles this wrapper's visibility
 * via the `hidden` property (qtyWrap.hidden = !isChecked), which only works through the UA stylesheet's
 * [hidden]{display:none}, and ANY author rule setting display beats a UA rule regardless of specificity.
 * A bare `display: inline-flex` here unconditionally overrode `hidden`, so every add-on's qty stepper
 * showed on every row whether checked or not -- pre-existing in the original Bootstrap file (confirmed
 * the same bug existed there too, ported forward silently), caught and fixed here since this round
 * already touches this exact rule.
 */
.fiber-addon-row .fiber-addon-qty-wrap:not([hidden]) {
	display: inline-flex;
	align-items: center;
	gap: var(--cp-space-3);
}

.fiber-addon-row .fiber-addon-qty-label {
	margin: 0;
	font-size: var(--cp-text-3xs);
	color: var(--cp-ink-3);
	font-weight: 500;
	text-transform: uppercase;
	letter-spacing: .05em;
}

/*
 * A standalone small number input -- used both for each add-on row's quantity stepper (nested inside
 * the .fiber-addon-list that itself sits inside a .cp-field) and, outside any .cp-field, for the
 * "Number of Static IPs" box in the add-ons step. It carries its own complete chrome (a scaled-down
 * version of .cp-field input's own values) rather than relying on Bootstrap's .form-control.form-
 * control-sm. The `input.` prefix is load-bearing, not decorative: .cp-field input (a class + element
 * selector, specificity 0-1-1) would otherwise beat a bare .fiber-addon-qty (0-1-0) for every row
 * nested inside a .cp-field, silently reverting min-height/padding/font-size/border back to the
 * 50px-tall default field chrome. input.fiber-addon-qty ties .cp-field input's specificity (0-1-1) and
 * wins on source order instead, the same reasoning applied to :hover/:focus/.is-invalid below against
 * .cp-field's own state rules.
 */
input.fiber-addon-qty {
	min-height: 32px;
	padding: 6px 8px;
	font: inherit;
	font-size: var(--cp-text-sm);
	color: var(--cp-ink);
	background: var(--cp-surface);
	border: 1px solid var(--cp-field-line);
	border-radius: var(--cp-r-sm);
	transition: border-color var(--cp-dur) var(--cp-ease), box-shadow var(--cp-dur) var(--cp-ease);
}

input.fiber-addon-qty:hover {
	border-color: #a9bdce;
}

input.fiber-addon-qty:focus {
	outline: 0;
	border-color: var(--cp-azure);
	box-shadow: 0 0 0 3px rgba(0, 133, 202, .18);
}

input.fiber-addon-qty.is-invalid {
	border-color: var(--cp-alert);
	background: var(--cp-alert-tint);
}

/* Spacing for the static-IP notice/input block (add-ons step, data service) -- both elements already
   carry their own id for fiber_wizard.js and are otherwise unstyled by this shell, so nothing but the
   top margin (replacing the original's mt-3) needs restating here. */
.fiber-static-ip-block {
	margin-top: 1rem;
}

/* dataStaticIpInput's <label> sits outside any .cp-field (its input is deliberately not wrapped in
   one -- see input.fiber-addon-qty above), so .cp-field label never reaches it; without this it stays
   UA-default inline and, whenever no admin description is configured, renders on the same line as the
   number box instead of stacked above it like every other field on the page. */
.fiber-static-ip-block label {
	display: block;
	margin-bottom: 6px;
	font-size: var(--cp-text-xs);
	font-weight: 500;
	color: var(--cp-ink-2);
}

/* The live static-IP cost breakdown (fiber_wizard.js's showStaticIpCost); its bold total line is
   built the same way, styled by the child rule below rather than a Bootstrap font-weight-bold/mt-1
   pair. */
.fiber-static-ip-cost {
	font-size: var(--cp-text-sm);
}

.fiber-static-ip-cost-total {
	margin-top: .25rem;
	font-weight: 700;
}

/* "No packages available for this service" -- rendered inside the carousel track, filling its flex:1
   row, left-aligned like the rest of the track's content rather than centered. */
.fiber-pkg-empty {
	color: var(--cp-ink-2);
	padding: .5rem 0;
}

/* "No add-ons available for this service" -- smaller and tighter than fiber-pkg-empty since it sits
   inside the already-compact add-on list rather than a full carousel row. */
.fiber-addon-empty {
	color: var(--cp-ink-2);
	font-size: var(--cp-text-xs);
	padding: .25rem 0;
}

/* "No services selected" (Twirl's #addOnsEmptyState, shown when nothing is checked on the packages
   step) -- centered and roomier, since it is the sole content of the whole add-ons step in that case
   rather than one row among several. */
.fiber-empty-state {
	color: var(--cp-ink-2);
	text-align: center;
	padding: 1.5rem 0;
}

/* renderRemovedAddOns's "these add-ons will be removed" notice, built entirely in JS -- replaces
   font-weight-bold/mb-1 on the header, mb-0/pl-3 on the list, and text-muted on each row's qty span. */
.fiber-removed-addons-header {
	margin-bottom: .25rem;
	font-weight: 700;
}

.fiber-removed-addons-list {
	margin-bottom: 0;
	padding-left: 1.1rem;
}

.fiber-removed-addons-qty {
	color: var(--cp-ink-2);
}

/* ===== Package carousel (AT&T Fiber style) ===== */
.fiber-pkg-carousel {
	position: relative;
	padding: 0 2.5rem;
}

.fiber-pkg-carousel-viewport {
	overflow: hidden;
	padding: .75rem 0;
}

.fiber-pkg-carousel-track {
	display: flex;
	gap: var(--cp-space-8);
	transition: transform .35s ease;
	will-change: transform;
}

.fiber-pkg-carousel-arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	width: 2.25rem;
	height: 2.25rem;
	border-radius: 50%;
	border: 1px solid var(--cp-line);
	background: var(--cp-surface);
	color: var(--cp-azure-ink);
	align-items: center;
	justify-content: center;
	cursor: pointer;
	z-index: 2;
	box-shadow: 0 2px 6px rgba(11, 34, 57, .08);
	transition: background-color .15s ease, color .15s ease;
}

/*
 * :not([hidden]) -- fiber_start_service/fiber_change_service hide these arrows until the package
 * carousel has more than one page. An unqualified `display: flex` here would win the specificity
 * tie against [hidden]'s UA display:none, same collision as .cp-check above.
 */
.fiber-pkg-carousel-arrow:not([hidden]) {
	display: flex;
}

.fiber-pkg-carousel-arrow:hover {
	background: var(--cp-azure-ink);
	color: #fff;
}

.fiber-pkg-carousel-arrow:disabled {
	opacity: .4;
	cursor: not-allowed;
}

.fiber-pkg-carousel-arrow-prev {
	left: 0;
}

.fiber-pkg-carousel-arrow-next {
	right: 0;
}

.fiber-pkg-carousel-dots {
	display: flex;
	justify-content: center;
	gap: var(--cp-space-4);
	margin-top: .75rem;
}

.fiber-pkg-carousel-dot {
	width: .6rem;
	height: .6rem;
	border-radius: 50%;
	background: var(--cp-line);
	border: 0;
	padding: 0;
	cursor: pointer;
	transition: background-color .15s ease, transform .15s ease;
}

.fiber-pkg-carousel-dot.active {
	background: var(--cp-azure-ink);
	transform: scale(1.25);
}

/* ===== Package card ===== */
.fiber-pkg-card {
	border: 2px solid var(--cp-line);
	border-radius: .75rem;
	padding: 1.25rem 1rem;
	background: var(--cp-surface);
	flex: 0 0 calc((100% - 2rem) / 3);
	max-width: calc((100% - 2rem) / 3);
	display: flex;
	flex-direction: column;
	transition: border-color .2s ease, box-shadow .2s ease, transform .15s ease;
	cursor: pointer;
	position: relative;
}

@media (max-width: 991.98px) {
	.fiber-pkg-card {
		flex-basis: calc((100% - 1rem) / 2);
		max-width: calc((100% - 1rem) / 2);
	}
}

@media (max-width: 767.98px) {
	.fiber-pkg-carousel {
		padding: 0 2rem;
	}

	.fiber-pkg-card {
		flex-basis: 100%;
		max-width: 100%;
	}
}

.fiber-pkg-card:hover {
	border-color: var(--cp-azure-ink);
	box-shadow: 0 6px 16px rgba(1, 118, 163, .12);
	transform: translateY(-2px);
}

.fiber-pkg-card.selected {
	border-color: var(--cp-azure-ink);
	box-shadow: 0 6px 16px rgba(1, 118, 163, .2);
	background: var(--cp-sky-tint);
}

.fiber-pkg-card.current {
	border-color: var(--cp-good);
}

.fiber-pkg-card-badge {
	position: absolute;
	top: -.65rem;
	left: 50%;
	transform: translateX(-50%);
	background: var(--cp-good);
	color: #fff;
	font-size: var(--cp-text-3xs);
	font-weight: 700;
	letter-spacing: .05em;
	text-transform: uppercase;
	padding: .2rem .6rem;
	border-radius: var(--cp-r-pill);
}

.fiber-pkg-card-tagline {
	font-size: var(--cp-text-2xs);
	letter-spacing: .05em;
	text-transform: uppercase;
	color: var(--cp-ink-3);
	font-weight: 500;
	margin-bottom: .5rem;
}

.fiber-pkg-card-title {
	font-size: var(--cp-text-xl);
	font-weight: 700;
	color: var(--cp-ink);
	margin-bottom: .5rem;
	line-height: 1.2;
}

.fiber-pkg-card-price {
	font-size: var(--cp-text-3xl);
	font-weight: 700;
	color: var(--cp-azure-ink);
	margin-bottom: .25rem;
}

.fiber-pkg-card-price-unit {
	font-size: var(--cp-text-sm);
	font-weight: 400;
	color: var(--cp-ink-3);
}

.fiber-pkg-card-spacer {
	flex: 1 1 auto;
}

.fiber-pkg-card-btn {
	width: 100%;
	margin-top: 1rem;
}

.fiber-pkg-card-facts {
	width: 100%;
	margin-top: .5rem;
}

.fiber-pkg-card.selected .fiber-pkg-card-btn {
	background-color: var(--cp-azure-ink);
	color: #fff;
	border-color: var(--cp-azure-ink);
}

/* ===== Review summary blocks (FiberWizard.reviewBlock/reviewBlockHtml) ===== */
.fiber-review-block {
	margin-bottom: 1rem;
}

.fiber-review-block:last-child {
	margin-bottom: 0;
}

.fiber-review-label {
	font-size: var(--cp-text-2xs);
	letter-spacing: .05em;
	text-transform: uppercase;
	color: var(--cp-ink-3);
	font-weight: 500;
}

.fiber-review-value {
	font-size: var(--cp-text-md);
	color: var(--cp-ink);
}

/* The add-ons line the review summary appends inside a .fiber-review-value -- replaces the Bootstrap
   text-muted this <small> used to carry. */
.fiber-review-value small {
	color: var(--cp-ink-2);
}

/* fiber_change_service's "Modified" tag next to a review block's label -- replaces
   badge/badge-warning/ml-2. */
.fiber-review-badge {
	margin-left: .4rem;
	padding: .15rem .5rem;
	border-radius: var(--cp-r-pill);
	background: var(--cp-amber-tint);
	color: var(--cp-amber-ink);
	font-size: var(--cp-text-3xs);
	font-weight: 700;
	letter-spacing: .05em;
	text-transform: uppercase;
}

/* ===== Stop service termination cards ===== */
.fiber-stop-card {
	border: 1px solid var(--cp-line);
	border-left: 4px solid var(--cp-alert);
	border-radius: .5rem;
	overflow: hidden;
	background: var(--cp-surface);
}

.fiber-stop-card-header {
	padding: .75rem 1rem;
	background-color: var(--cp-paper);
	font-weight: 600;
	border-bottom: 1px solid var(--cp-line);
	display: flex;
	align-items: center;
	gap: var(--cp-space-4);
}

.fiber-stop-service-name {
	font-size: var(--cp-text-md);
}

/* Replaces the header's Bootstrap text-danger icon color and badge/badge-danger/ml-2 pill. */
.fiber-stop-card-icon {
	color: var(--cp-alert);
}

.fiber-stop-badge {
	margin-left: auto;
	padding: .2rem .6rem;
	border-radius: var(--cp-r-pill);
	background: var(--cp-alert-tint);
	color: var(--cp-alert);
	font-size: var(--cp-text-3xs);
	font-weight: 700;
	letter-spacing: .05em;
	text-transform: uppercase;
}

.fiber-stop-card-body {
	padding: 1rem;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
	gap: var(--cp-space-8);
}

.fiber-stop-detail .fiber-review-label {
	margin-bottom: .25rem;
}

.fiber-stop-addon-list {
	list-style: none;
	padding-left: 0;
	margin-bottom: 0;
}

.fiber-stop-addon-list li {
	padding: .15rem 0;
	font-size: var(--cp-text-base);
}

/* Heading inside a package/add-on section (no .cp-fieldset__hd wrapper here -- these sit directly in
   the section, not beside a bordered box) -- folds in what was the Bootstrap mb-3 utility. */
.fiber-pkg-section h5,
.fiber-addon-section h5 {
	margin: 0 0 12px;
	font-size: var(--cp-text-base);
	font-weight: 600;
	color: var(--cp-ink);
}

/* ===== Voice lines (multiple phone lines on the fiber voice package) ===== */
/*
 * Development's Bootstrap markup spaced these three with mb-2/mb-2/mt-2; the consumer-shell port dropped
 * them with no replacement (no bare label/button/div rule on this shell provides it), so the "Phone
 * Lines" label, the existing-lines list and the Add button all butted directly against their neighbours.
 * ID-scoped rather than a class, since none of the three otherwise needs one.
 */
#voiceNumberConfig > label {
	display: block;
	margin-bottom: var(--cp-space-4);
}

#voiceExistingLines {
	margin-bottom: var(--cp-space-4);
}

#voiceAddLine {
	margin-top: var(--cp-space-4);
}

/*
 * One row per existing line on the account, built server-side by fiber_voice_number.scala.html; a
 * removed one gets fiber-voice-line-removed (toggled by fiber_wizard.js's setExistingVoiceLineRemoved)
 * instead of being deleted from the DOM, since Remove is reversible up until the form actually submits.
 */
.fiber-voice-line {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--cp-space-6);
	padding: var(--cp-space-5) var(--cp-space-6);
	margin-bottom: var(--cp-space-5);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-sm);
	background: var(--cp-surface);
}

.fiber-voice-line:last-child {
	margin-bottom: 0;
}

.fiber-voice-line-main {
	display: flex;
	align-items: center;
	gap: var(--cp-space-4);
	min-width: 0;
}

.fiber-voice-line-number {
	font-weight: 600;
}

.fiber-voice-line-removed {
	border-color: #f2c9c4;
	background: var(--cp-alert-tint);
}

.fiber-voice-line-removed .fiber-voice-line-number {
	text-decoration: line-through;
	color: var(--cp-ink-3);
}

/* A brand-new line the customer is adding (fiber_wizard.js's addVoiceLine) -- same card treatment as
   an existing line, plus its own header row for the "Additional line" label and Remove button. */
.fiber-voice-new-line {
	padding: var(--cp-space-5) var(--cp-space-6);
	margin-bottom: var(--cp-space-5);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-sm);
	background: var(--cp-surface);
}

.fiber-voice-new-line-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--cp-space-6);
	margin-bottom: var(--cp-space-5);
}

.fiber-voice-new-line-head label {
	margin: 0;
}

/* The port-or-new choice select and, once porting is chosen, its phone/PIN/provider fields -- reuses
   .cp-fields' own row layout rather than a dedicated rule, the same way the single-line version of
   this form already grouped its three port-detail fields. */
.fiber-voice-line-port {
	margin-top: var(--cp-space-5);
}

.cp-pastdue {
	margin-top: 14px;
	padding: 12px 14px;
	border-radius: var(--cp-r-sm);
	background: var(--cp-alert-tint);
	border: 1px solid #f2c9c4;
}

.cp-pastdue b {
	font-size: var(--cp-text-lg);
	font-variant-numeric: tabular-nums;
	color: var(--cp-alert);
}

.cp-pastdue p {
	margin: 2px 0 0;
	font-size: var(--cp-text-xs);
	color: var(--cp-ink-2);
}

@media (min-width: 760px) {
	.cp-balance {
		grid-template-columns: 1.35fr 1fr;
		/* Not on the --cp-space-* scale (tops out at 20px) -- this is the one column gap in the file
		   past that ceiling, and it's structurally paired with .cp-balance__side's 34px left padding
		   two rules below, not a value worth a 10th token for its single call site. */
		gap: 34px;
		padding: 26px 28px;
	}

	.cp-balance__amt {
		font-size: var(--cp-text-hero);
	}

	.cp-balance__side {
		border-top: 0;
		border-left: 1px solid var(--cp-line);
		padding: 0 0 0 34px;
	}
}

/* ─── Status cards ────────────────────────────────────────────────────────────────────────── */
.cp-cards {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
	gap: var(--cp-space-8);
}

/*
 * :not([hidden]) -- #outageCard is the one .cp-statcard ever hidden client-side (renderOutageStatus
 * sets card.hidden = true for NO_ELECTRIC_SERVICE). [hidden] { display: none } is a user-agent default
 * at the same specificity as a single class, and an author rule wins that tie regardless of source
 * order -- an unqualified `display: flex` here would silently keep the card visible.
 */
.cp-statcard:not([hidden]) {
	display: flex;
	flex-direction: column;
	padding: 18px;
	background: var(--cp-surface);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-md);
	box-shadow: var(--cp-sh-1);
}

.cp-statcard h3 {
	margin: 0 0 10px;
	font-size: var(--cp-text-xs);
	font-weight: 600;
	letter-spacing: .06em;
	text-transform: uppercase;
	color: var(--cp-ink-3);
}

.cp-statcard__figure {
	margin: 0;
	font-size: var(--cp-text-3xl);
	font-weight: 600;
	letter-spacing: -.02em;
	font-variant-numeric: tabular-nums;
	line-height: 1.15;
}

.cp-statcard__figure--text {
	font-size: var(--cp-text-xl);
	letter-spacing: -.01em;
}

.cp-statcard__meta {
	margin: 0 0 6px;
	font-size: var(--cp-text-sm);
	line-height: 1.45;
	color: var(--cp-ink-2);
}

.cp-statcard__note {
	margin: 8px 0 0;
	font-size: var(--cp-text-2xs);
	line-height: 1.45;
	color: var(--cp-ink-3);
}

.cp-statcard__act {
	margin-top: auto;
	padding-top: 12px;
	width: 100%;
	min-height: 42px;
	font-size: var(--cp-text-sm);
}

.cp-statcard__act ~ .cp-statcard__act {
	margin-top: 14px;
}

/*
 * account_home.js sets #outageCard's data-outage-tone to ok/alert/muted on every load. The top accent
 * bar is reserved for "alert" only -- a real, confirmed outage is the one state on this card worth
 * interrupting the dashboard's visual rhythm for. "ok" and "muted" used to paint the same 3px bar (in
 * teal and grey respectively), which meant the card looked structurally different from its three
 * siblings (Previous statement, Autopay, Scheduled payments -- none of which ever get an accent bar)
 * on every load regardless of whether there was actually anything to flag, including "we don't know"
 * (muted) and "nothing's wrong" (ok) -- a customer asked directly why this one card looked different.
 * The icon still tints per tone (a smaller, contained signal, not a structural one) so "all clear" and
 * "unavailable" remain distinguishable at a glance without the card itself looking like an outlier.
 */
.cp-statcard[data-outage-tone="alert"] {
	position: relative;
	overflow: hidden;
}

.cp-statcard[data-outage-tone="alert"]::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: 3px;
	background: var(--cp-alert);
}

.cp-statcard[data-outage-tone="ok"] #outageStatusIcon {
	color: var(--cp-good);
}

.cp-statcard[data-outage-tone="alert"] #outageStatusIcon {
	color: var(--cp-alert);
}

.cp-statcard[data-outage-tone="muted"] #outageStatusIcon {
	color: var(--cp-ink-3);
}

.cp-statcard[data-outage-tone="alert"] #outageStatusText {
	color: var(--cp-alert);
	font-weight: 600;
}

/* ─── Data table (statement history and friends) ──────────────────────────────────────────── */

/*
 * Nucleus.Matrix (CSACore) writes its own "Displaying Record(s) X To Y Of Z" / "No Records To Display"
 * text into a page-specific display container -- the string itself is core, shared framework text and
 * stays as-is; only the container is ours to style. is-empty is toggled by each page's own `success`
 * callback (Matrix has no built-in empty/non-empty distinction to hang a class on) since filteredCount
 * isn't knowable from CSS. [id$="display"] rather than a plain #display id, because a page can host more
 * than one Matrix table and each needs its own container id (prepay.scala.html has #activity-display
 * and #agreement-display alongside its own #display-less siblings).
 */
.cp-matrix-display {
	font-size: var(--cp-text-sm);
	color: var(--cp-ink-2);
}

[id$="display"].is-empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 10px;
	padding: 40px 20px;
	text-align: center;
}

[id$="display"].is-empty .cp-matrix-display {
	font-size: var(--cp-text-sm);
	color: var(--cp-ink-2);
}

[id$="display"].is-empty::before {
	content: "\f01c";
	font-family: "Font Awesome 5 Free";
	font-weight: 900;
	font-size: 28px;
	color: var(--cp-line);
}

/*
 * A column-header row above zero data rows reads as two competing "nothing here" signals instead of
 * one -- the icon+message already says it plainly. ~ (general sibling) rather than a parent/child
 * relationship: the display container and .cp-table-wrap are siblings, with an optional <nav> pager
 * between them on some pages (payments.scala.html) and not others (statement_history.scala.html).
 */
[id$="display"].is-empty ~ .cp-table-wrap thead {
	display: none;
}

.cp-table-wrap {
	overflow-x: auto;
}

.cp-table {
	/* width: 100% would let the table shrink to fit a narrow .cp-table-wrap instead of overflowing it,
	 * defeating the wrap's overflow-x: auto -- columns just compressed and clipped with no scroll
	 * affordance. max-content lets the table keep its natural width and overflow (scroll) on narrow
	 * viewports, while min-width: 100% still fills wide containers exactly as before. */
	width: max-content;
	min-width: 100%;
	border-collapse: collapse;
}

.cp-table th {
	padding: 0 12px 10px 0;
	text-align: left;
	font-size: var(--cp-text-3xs);
	font-weight: 500;
	letter-spacing: .09em;
	text-transform: uppercase;
	color: var(--cp-ink-3);
	border-bottom: 1px solid var(--cp-line);
	white-space: nowrap;
}

.cp-table td {
	padding: 13px 12px 13px 0;
	font-size: var(--cp-text-sm);
	font-variant-numeric: tabular-nums;
	border-bottom: 1px solid var(--cp-line-2);
}

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

.cp-table a {
	color: var(--cp-azure-ink);
	font-weight: 600;
	text-decoration: none;
}

.cp-table a:hover {
	text-decoration: underline;
}

/*
 * Cancel Payment reverses money already scheduled; it was a bare <a> in .cp-table__acts, pixel-
 * identical to a purely navigational row link like "Bill summary" or "PDF". Text, not a full
 * .cp-btn treatment, since it still belongs among the row's other lightweight actions -- just
 * colored to read as consequential before the confirmation dialog it opens.
 */
.cp-table a.cp-link--danger {
	color: var(--cp-alert);
}

.cp-table a.cp-link--danger:hover {
	color: #7a201a;
}

.cp-table__num {
	text-align: right;
}

/*
 * .cp-table is width: max-content so a table with short, fixed-format cells (dates, amounts) can
 * overflow and scroll on a narrow viewport instead of compressing and clipping -- but max-content
 * sizes a free-text column (no natural length limit) as if its text never wraps, so one long value
 * drags the whole table far wider than its card. max-width on the <td>/<th> itself does NOT fix
 * this: the browser's auto table-layout algorithm computes each column's preferred width from its
 * content's unwrapped intrinsic width and ignores max-width on the cell during that pass. Wrapping
 * the text in this block-level child does work: a div's max-width bounds ITS OWN intrinsic width (a
 * block-formatting-context rule, unrelated to table layout), so the cell's preferred width -- and so
 * the column's -- is bounded by the div's, and normal text-wrapping takes over inside it.
 */
.cp-table__desc {
	display: block;
	max-width: 420px;
	white-space: normal;
	overflow-wrap: break-word;
}

/*
 * .cp-table th already carries text-align: left at (0,1,1) specificity (element + class), which beats
 * .cp-table__num's (0,1,0) on a <th class="cp-table__num"> -- every already-shipped numeric-column
 * header (payments.scala.html, statement_history.scala.html, prepay.scala.html, and now
 * invoice.js's multi-invoice table) has silently rendered left-aligned instead of right-aligned over
 * its right-aligned data column. Found via adversarial review of the invoice conversion.
 */
.cp-table th.cp-table__num {
	text-align: right;
}

/*
 * text-align:right above only ever affected the <th>'s own text -- Nucleus.Matrix wraps the label and
 * sort icon in its own inline `display:flex; justify-content:space-between` span (Nucleus-Matrix.js
 * renderHead), which positions its two children by flex rules, not text-align. On a numeric column
 * whose header cell is much wider than "Amount"/"Actions" (sized to fit the data below, e.g. dollar
 * figures or action links), space-between stranded the label at the cell's left edge while the sort
 * icon sat at the right -- nowhere near the right-aligned data underneath. flex-end keeps label and
 * icon together at the right, matching the column's data alignment. !important is required: the
 * conflicting justify-content is an inline style, which no stylesheet rule can outrank otherwise.
 */
.cp-table th.cp-table__num > span {
	justify-content: flex-end !important;
	gap: var(--cp-space-3);
}

/*
 * The same Nucleus.Matrix space-between strands the icon away from the label on every OTHER sortable
 * column too, not just numeric ones -- any header whose cell is wider than its own label text (Source,
 * Status once it renders a chip, Confirmation Number sized to a long value below) puts daylight between
 * a short label and its sort icon. Left-aligned columns read left-to-right, so the icon belongs right
 * after the label (flex-start), not pinned to the cell's far right edge. th.cp-table__num above is more
 * specific (extra class) and still wins there, so this is additive, not a replacement.
 */
.cp-table th > span {
	justify-content: flex-start !important;
	gap: var(--cp-space-3);
}

.cp-table__acts {
	display: flex;
	gap: var(--cp-space-7);
	justify-content: flex-end;
	flex-wrap: wrap;
}

/*
 * A table too wide for a phone opts into this: each row becomes a block of label/value lines read from
 * the cells' data-label attributes, so nothing sits off-screen behind a horizontal scroll with no cue --
 * on Payment activity that hid Status, Confirmation # and the Cancel Payment action. The header stays as
 * a wrapped row of sort controls, so sorting still works; the unsortable Actions header is dropped.
 */
@media (max-width: 599.98px) {
	.cp-table--stack {
		width: 100%;
	}

	.cp-table--stack thead tr {
		display: flex;
		flex-wrap: wrap;
		gap: var(--cp-space-4) var(--cp-space-8);
		padding-bottom: 10px;
		border-bottom: 1px solid var(--cp-line);
	}

	.cp-table--stack th {
		padding: 0;
		border-bottom: 0;
	}

	.cp-table--stack th:empty,
	.cp-table--stack th.cp-table__num:last-child {
		display: none;
	}

	.cp-table--stack tbody tr {
		display: block;
		padding: 10px 0;
		border-bottom: 1px solid var(--cp-line-2);
	}

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

	.cp-table--stack td {
		display: flex;
		justify-content: space-between;
		align-items: baseline;
		gap: var(--cp-space-6);
		padding: 3px 0;
		border-bottom: 0;
		text-align: right;
	}

	.cp-table--stack td::before {
		content: attr(data-label);
		flex: none;
		font-size: var(--cp-text-xs);
		color: var(--cp-ink-3);
		text-align: left;
	}

	.cp-table--stack td:empty {
		display: none;
	}

	.cp-table--stack td:not([data-label]) {
		justify-content: flex-end;
		padding-top: 6px;
	}
}

.cp-pager {
	margin-top: 14px;
	margin-bottom: 14px;
	/*
	 * First/Previous/1-5/Next/Last (9 items at a 34px min-width each) doesn't fit one row under
	 * ~600px. A first pass wrapped to a second line -- functional, but two uneven, still-centered
	 * rows read as a layout accident rather than a design. Scrolling in place keeps the one-row pager
	 * the design uses everywhere else, matching the same horizontal-scroll treatment the payments
	 * table already uses for the same class of overflow (cp-table-wrap): the row scrolls to reach
	 * First/Last instead of wrapping or being cut off with no way to reach them.
	 */
	overflow-x: auto;
	/*
	 * overflow-x:auto computes overflow-y to auto too (the CSS spec has no way to scroll one axis
	 * only), and this element had no overflow at all before -- a keyboard focus ring on a
	 * .cp-pager__link flush against the top/bottom edge would otherwise get clipped by the box that
	 * now clips overflow, when it never did before this element could scroll.
	 */
	padding-block: 3px;
}

.cp-pager:empty {
	margin: 0;
}

.cp-pager__list {
	display: flex;
	gap: var(--cp-space-2);
	justify-content: center;
	width: max-content;
	min-width: 100%;
	list-style: none;
	margin: 0;
	padding: 0;
}

.cp-pager__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 34px;
	min-height: 34px;
	padding: 0 9px;
	border-radius: var(--cp-r-sm);
	border: 1px solid var(--cp-line);
	background: var(--cp-surface);
	color: var(--cp-ink-2);
	font-size: var(--cp-text-sm);
	font-weight: 600;
	text-decoration: none;
}

.cp-pager__link:hover {
	background: var(--cp-sky-tint);
	border-color: var(--cp-sky);
	color: var(--cp-navy);
}

.cp-pager__item.is-active .cp-pager__link {
	background: var(--cp-azure-ink);
	border-color: var(--cp-azure-ink);
	color: #fff;
}

.cp-pager__item.is-disabled .cp-pager__link {
	opacity: .45;
	pointer-events: none;
}

/*
 * First/Previous/five pages/Next/Last needs ~420px; a phone card has ~320. Rather than scroll the ends
 * of the pager out of sight, a phone drops First/Last and shows Previous/Next as arrows. The words stay
 * in the link for screen readers, and Nucleus.Matrix titles each item, which the selectors key on.
 */
@media (max-width: 599.98px) {
	.cp-pager__item[title="First Page"],
	.cp-pager__item[title="Last Page"] {
		display: none;
	}

	.cp-pager__item[title="Previous Page"] .cp-pager__link,
	.cp-pager__item[title="Next Page"] .cp-pager__link {
		font-size: 0;
	}

	.cp-pager__item[title="Previous Page"] .cp-pager__link::before,
	.cp-pager__item[title="Next Page"] .cp-pager__link::before {
		font-size: var(--cp-text-lg);
		line-height: 1;
	}

	.cp-pager__item[title="Previous Page"] .cp-pager__link::before {
		content: "\2039";
	}

	.cp-pager__item[title="Next Page"] .cp-pager__link::before {
		content: "\203A";
	}
}

/* ---------------------------------------------------------------------------
 * Public shell (main_unauth_consumer): utility lockup, one centred card, footer.
 * The landing page is not built on this -- it has a two-pane layout of its own.
 * ------------------------------------------------------------------------- */

.cp-auth {
	display: flex;
	flex-direction: column;
	min-height: 100vh;
	background: var(--cp-paper);
}

.cp-auth__top {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 6px;
	padding: 26px 20px 4px;
}

.cp-auth__lockup {
	display: inline-flex;
	align-items: center;
}

.cp-auth__name {
	margin: 0;
	font-size: var(--cp-text-md);
	font-weight: 600;
	color: var(--cp-ink);
	text-align: center;
}

.cp-auth__lockup img {
	max-height: 54px;
	max-width: 240px;
	width: auto;
}

.cp-auth__body {
	flex: 1 0 auto;
	display: flex;
	/* safe: a card taller than the viewport falls back to start, rather than overflowing past the top
	 * where the beginning of it cannot be scrolled to. */
	align-items: safe center;
	justify-content: center;
	padding: 22px 20px 34px;
}

.cp-authcard {
	width: 100%;
	max-width: 470px;
	padding: 30px 32px 32px;
	background: var(--cp-surface);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-md);
	box-shadow: var(--cp-sh-2);
}

/* Long-form prose (privacy, terms) -- the sign-in measure is far too narrow to read a policy in. */
.cp-authcard--wide {
	max-width: 820px;
}

/* privacy_policy() and terms_and_conditions() open with their own h3 and use h3 throughout. */
.cp-authcard--wide h2,
.cp-authcard--wide h3 {
	margin: 26px 0 8px;
	font-size: var(--cp-text-lg);
	line-height: 1.3;
	color: var(--cp-ink);
}

.cp-authcard--wide > h3:first-child {
	margin-top: 0;
	font-size: var(--cp-text-3xl);
	letter-spacing: -.015em;
}

.cp-authcard--wide p,
.cp-authcard--wide li {
	font-size: var(--cp-text-base);
	line-height: 1.65;
}

.cp-authcard h1 {
	margin: 0 0 6px;
	font-size: var(--cp-text-3xl);
	line-height: 1.2;
	letter-spacing: -.015em;
	color: var(--cp-ink);
}

.cp-authcard__sub {
	margin: 0 0 22px;
	font-size: var(--cp-text-sm);
	line-height: 1.5;
	color: var(--cp-ink-2);
}

.cp-authcard p {
	color: var(--cp-ink-2);
	line-height: 1.55;
}

.cp-authcard form {
	margin-top: 20px;
}

.cp-authcard .cp-btn--block {
	margin-top: 18px;
}

/* The wizard nav row supplies its own spacing via .cp-row's gap -- the 18px above was written for a
   single full-width submit under a form and, stacked on top of .cp-row's own 12px gap, left the
   self-service Back/Next pair reading as two unrelated buttons rather than one nav group on mobile. */
.cp-authcard .cp-row .cp-btn--block {
	margin-top: 0;
}

.cp-authcard__foot {
	margin: 20px 0 0;
	padding-top: 16px;
	border-top: 1px solid var(--cp-line);
	font-size: var(--cp-text-sm);
	text-align: center;
	color: var(--cp-ink-2);
}

@media (max-width: 520px) {
	.cp-auth__top {
		padding-top: 18px;
	}

	.cp-authcard {
		padding: 24px 20px 26px;
		border-radius: var(--cp-r-sm);
	}

	.cp-authcard h1 {
		font-size: var(--cp-text-2xl);
	}
}

/*
 * CustomerPortal.bindValidatedSubmit injects these two class names from JS -- they are a shared
 * contract with the Selenium suite, which reads #<field>-error, so the consumer shell styles them
 * rather than forking the validator.
 */
.cp-authcard input.is-invalid,
.cp-field input.is-invalid,
.cp-field select.is-invalid,
.cp-field textarea.is-invalid {
	border-color: var(--cp-alert);
	background: var(--cp-alert-tint);
}

.invalid-feedback {
	display: block;
	margin-top: 5px;
	font-size: var(--cp-text-xs);
	color: var(--cp-alert);
}

/*
 * block-cursor is a runtime classList.add/remove/toggle target (payment_auth.js's express-pay range
 * guard and terms-acceptance blocker) -- appstrap/custom-style.css carried the only definition, which
 * the consumer shell never loads. Kept as the exact class name the JS already hardcodes.
 */
.block-cursor {
	cursor: not-allowed !important;
}

.block-cursor > * {
	pointer-events: none !important;
}

/*
 * Sweet (SweetAlert2) dialogs are appended to <body>, outside any page container, so a page's own
 * scoped bridge CSS can never reach markup built into a Sweet `html:` string. Several such dialogs
 * (registration/secondary_account_option.scala.html's account lookup prompt among them) build raw
 * Bootstrap-class form controls this way. The consumer shell loads no Bootstrap, so without this those
 * controls render bare inside an otherwise-styled Sweet popup. Kept as the exact class names the shared
 * JS already hardcodes, scoped to Sweet's own popup class so nothing outside a dialog is affected.
 */
.swal2-popup .form-control {
	width: 100%;
	min-height: 46px;
	padding: 11px 13px;
	font: inherit;
	font-size: var(--cp-text-base);
	color: var(--cp-ink);
	background: var(--cp-surface);
	border: 1px solid var(--cp-field-line);
	border-radius: var(--cp-r-sm);
}

.swal2-popup .form-control:focus {
	outline: 0;
	border-color: var(--cp-azure);
	box-shadow: 0 0 0 3px rgba(0, 133, 202, .18);
}

.swal2-popup .d-block {
	display: block;
}

/*
 * Beyond the form-control bridge above, a Sweet dialog otherwise renders in SweetAlert2's own stock
 * look -- 5px popup corners and its default #3085d6 confirm blue -- rather than reading as part of
 * this app's design system, because Sweet-Icing.js (show/submit/#submitEx) always passes a
 * confirmColor/cancelColor option and SweetAlert2 applies each one as an inline
 * `element.style.backgroundColor`, not a class. An inline declaration outranks any stylesheet rule
 * regardless of specificity, and it isn't removed or replaced on hover -- SweetAlert2's own hover
 * effect is a separate `:hover` rule that layers a translucent dark gradient over whatever
 * background-color is already there -- so the same inline color keeps winning in both the resting
 * and hover states unless we go to !important here too. The popup and button corners aren't set this
 * way: SweetAlert2's base CSS writes its border-radius inside a `:where(...)` selector, which
 * contributes zero specificity, so a plain rule below already beats it.
 */
.swal2-popup {
	border-radius: var(--cp-r-md);
}

.swal2-confirm {
	/* Matches .cp-btn--primary's own resting color and corner radius. */
	background: var(--cp-azure-ink) !important;
	border-radius: var(--cp-r-sm);
}

.swal2-confirm:hover {
	/* Matches .cp-btn--primary:hover exactly, rather than leaning on Sweet's generic darken-overlay. */
	background: var(--cp-navy) !important;
}

/*
 * Reads as a secondary/ghost action, the same role .cp-btn--ghost plays beside a primary button
 * elsewhere in the app, instead of SweetAlert2's stock mid-grey Cancel. cancelButtonColor is
 * inline-styled the same way confirmButtonColor is above, so it needs the same !important treatment;
 * text color isn't set inline by SweetAlert2 for this button, so it doesn't.
 */
.swal2-cancel {
	background: var(--cp-line-2) !important;
	color: var(--cp-navy);
	border-radius: var(--cp-r-sm);
}

.swal2-cancel:hover {
	background: var(--cp-line) !important;
	color: var(--cp-navy);
}

.cp-spinner {
	display: inline-block;
	width: 16px;
	height: 16px;
	vertical-align: -2px;
	border: 2px solid var(--cp-line);
	border-top-color: var(--cp-azure);
	border-radius: 50%;
	animation: cp-spin 700ms linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
	.cp-spinner {
		animation-duration: 2.4s;
	}
}

/* Contact Us detail rows — shared by the public and authenticated pages via tags.view_contact. */
.cp-contact__row {
	display: flex;
	align-items: flex-start;
	gap: var(--cp-space-6);
	padding: 10px 0;
	border-top: 1px solid var(--cp-line-2);
}

.cp-contact__row:first-child {
	border-top: 0;
	padding-top: 0;
}

.cp-contact__ico {
	width: 18px;
	height: 18px;
	flex: none;
	margin-top: 3px;
	stroke: var(--cp-azure-ink);
	fill: none;
	stroke-width: 1.7;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.cp-contact__row p {
	margin: 0;
	color: var(--cp-ink-2);
}

.cp-contact__row strong {
	color: var(--cp-ink);
}

.cp-contact__row a {
	color: var(--cp-azure-ink);
	font-weight: 600;
	text-decoration: none;
	overflow-wrap: anywhere;
}

.cp-contact__row a:hover {
	text-decoration: underline;
}

/*
 * verify_account.scala.html's three optional-verification rows (SSN, phone, statement amount -- "at
 * least one of the following") used to render as plain stacked rows indistinguishable from the two
 * REQUIRED fields above them. This wraps just the sentence and the three option partials, so a
 * customer sees them grouped and separated from the required pair, and adds an "or" divider between
 * whichever of the three actually render (each utility can enable/disable each independently). The
 * host bridges (registration.scala.html's #step-1-content, one_time_payment_unauth's #step-1-content,
 * add_customer.scala.html's #addCustomerSection) each scope `.row { margin-bottom: 13px }` at (1,1,0);
 * no class-only rule here can cancel the last row's trailing margin, so it is absorbed into the
 * wrapper's own bottom padding instead of adding a fourth override to three separate bridge blocks.
 */
.cp-verify-options {
	padding: 14px 14px 1px;
	margin-bottom: 13px;
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-sm);
	background: var(--cp-sky-tint);
}

.cp-verify-options > .cp-verify-options__hd {
	margin: 0 0 12px;
	font-size: var(--cp-text-xs);
	font-weight: 600;
	color: var(--cp-ink-2);
}

/*
 * Left-aligned with the fields it separates, trailing a hairline across the panel: the fields are narrow
 * and flush left, so a centred "or" floated in empty space away from both of them.
 */
.cp-verify-options > .row + .row::before {
	content: "or";
	display: block;
	margin: 0 0 12px;
	text-align: left;
	background: linear-gradient(var(--cp-line), var(--cp-line)) right center / calc(100% - 3em) 1px no-repeat;
	font-size: var(--cp-text-xs);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: .08em;
	color: var(--cp-ink-3);
}

/*
 * ssn_option.scala.html and phone_number_option.scala.html size their inputs off a Bootstrap
 * col-sm-6 wrapper shared with the label beside it, so a 4-digit SSN field and a 10-digit phone field
 * both stretched to half the card's width -- ~680px on a wide screen for 4 characters. Sized to their
 * actual content instead; the wrapper column still reserves the layout space, so this only pulls the
 * visible field in, leaving it left-aligned rather than moving or restacking anything.
 */
.cp-verify-options input#ssn {
	max-width: 90px;
}

.cp-verify-options input#phoneNumberTemplate {
	max-width: 170px;
}

/*
 * statement_amount_option.scala.html's Bootstrap input-group/input-group-prepend/input-group-text
 * combo for the "$" sign read as a separate box next to the amount field rather than a fused prefix,
 * because each host's own bridge gave .input-group a flex `gap`. Fused here once, in the shared sheet,
 * rather than in each of the three host bridges (registration.scala.html, one_time_payment_unauth.scala.html,
 * add_customer.scala.html) -- confirmed via the account-verification-fragment audit that .input-group
 * only ever matches this one field within any of those three pages' scope.
 */
.cp-amount-group {
	display: flex;
	flex-wrap: wrap;
	align-items: stretch;
}

.cp-amount-group .input-group-prepend {
	display: flex;
}

.cp-amount-group .input-group-text {
	display: flex;
	align-items: center;
	padding: 0 12px;
	border: 1px solid var(--cp-field-line);
	border-right: 0;
	border-radius: var(--cp-r-sm) 0 0 var(--cp-r-sm);
	background: var(--cp-sky-tint);
	font-size: var(--cp-text-md);
	font-weight: 600;
	color: var(--cp-ink-2);
}

.cp-amount-group .form-control {
	flex: 1 1 160px;
}

/*
 * Each host bridge declares `#<host-id> .form-control { border-radius: ... }` at (1,1,0) -- no class-
 * only rule can out-specify an id. `#amount` is unique on every page this partial renders on, so
 * `.cp-amount-group #amount.form-control` at (1,2,0) beats all three bridges from this one shared rule
 * instead of three separate id-scoped overrides.
 */
.cp-amount-group #amount.form-control {
	border-top-left-radius: 0;
	border-bottom-left-radius: 0;
}

.cp-amount-group .invalid-feedback {
	flex: 1 0 100%;
	margin-top: 0;
}

.cp-amount-group:focus-within .input-group-text {
	border-color: var(--cp-azure);
}

.cp-amount-group:has(.form-control.is-invalid) .input-group-text {
	border-color: var(--cp-alert);
	background: var(--cp-alert-tint);
}

/*
 * The larger desktop scale lifts body text, but the secondary text around a card's main figure -- labels,
 * meta lines, table headers, card headings -- kept reading small beside it, most of all on Account Home.
 * One more step for those roles on desktop only.
 */
@media (min-width: 1100px) {
	.cp-card__hd h2,
	.cp-card__hd h3,
	.cp-content .cp-sectionhd {
		font-size: var(--cp-text-xl);
	}

	.cp-lbl {
		font-size: var(--cp-text-xs);
	}

	.cp-note {
		font-size: var(--cp-text-sm);
	}

	.cp-table th {
		font-size: var(--cp-text-2xs);
	}

	.cp-balance h2.cp-lbl,
	.cp-statcard h3 {
		font-size: var(--cp-text-sm);
	}

	.cp-meter__ticks span,
	.cp-balance .cp-note {
		font-size: var(--cp-text-sm);
	}

	.cp-balance__row,
	.cp-statcard__meta,
	.cp-pagehd p,
	.cp-matrix-display {
		font-size: var(--cp-text-base);
	}

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

	/* Nucleus.Matrix sizes its sort glyph with width/height attributes, which any CSS size overrides. */
	.cp-table th svg {
		width: 26px;
		height: 19px;
	}

	/* p.cp-usagefoot, not the bare class: usage_chart's own <style> sets the base size later in the page. */
	p.cp-usagefoot {
		font-size: var(--cp-text-2xs);
	}

	/*
	 * The balance column is taller than the side column beside it; spread the side's content over the
	 * full height -- the payment button level with the balance label, the detail rows level with the note
	 * under the meter -- rather than floating a short stack in the middle of empty space.
	 */
	.cp-balance__side {
		justify-content: space-between;
	}

	/* The wizard chip's 12.5px is measured for narrow screens; a desktop step box has room to spare. */
	.csa-progress-bar,
	.csa-progress-bar-selected,
	.csa-progress-bar-completed {
		font-size: var(--cp-text-3xs);
	}
}

/*
 * With the sidebar open and the larger text, a 1100-1399px content column fits only three 250px tiles,
 * so Account Home's four stranded one alone on a second row. Two columns keep them in even pairs until
 * there is room for all four across.
 */
@media (min-width: 1100px) and (max-width: 1399.98px) {
	.cp-cards {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/*
 * consumertags/shortcuts_consumer: the legacy menu's destinations the consumer nav does not carry.
 * Wraps rather than scrolls, so every link stays visible at phone width.
 */
.cp-shortcuts {
	display: flex;
	flex-wrap: wrap;
	gap: var(--cp-space-3);
	margin: var(--cp-space-6) 0;
}

/*
 * nav_consumer's phone "More" tab: a <details> styled as one of the .cp-tabs items, whose panel opens
 * upward from the fixed tab row (the row is its containing block).
 */
.cp-tabs__more > summary {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--cp-space-2);
	padding: 4px 2px;
	border-radius: 9px;
	color: var(--cp-ink-3);
	font-size: var(--cp-text-3xs);
	font-weight: 500;
	list-style: none;
	cursor: pointer;
}

.cp-tabs__more > summary::-webkit-details-marker {
	display: none;
}

.cp-tabs__more > summary svg {
	width: 21px;
	height: 21px;
	stroke: currentColor;
	fill: currentColor;
	stroke-width: 1.7;
}

.cp-tabs__more > summary:focus-visible {
	outline: 2px solid var(--cp-azure);
	outline-offset: -2px;
}

.cp-tabs__more--current > summary,
.cp-tabs__more[open] > summary {
	color: var(--cp-navy);
}

.cp-tabs__panel {
	position: absolute;
	right: 8px;
	bottom: calc(100% + 6px);
	display: flex;
	flex-direction: column;
	min-width: 200px;
	padding: 6px;
	background: var(--cp-surface);
	border: 1px solid var(--cp-line);
	border-radius: var(--cp-r-sm);
	box-shadow: 0 10px 30px -10px rgba(0, 0, 0, .35);
}

.cp-tabs .cp-tabs__panel a {
	flex-direction: row;
	padding: 11px 12px;
	color: var(--cp-ink);
	font-size: var(--cp-text-sm);
}

.cp-tabs .cp-tabs__panel a[aria-current="page"] {
	color: var(--cp-navy);
	font-weight: 700;
}

/* ─── Landing: task order, targets, footer band ──────────────────────────────────────────── */
.cp-alertbar,
.cp-devbanner {
	z-index: 1;
}

.cp-pane .cp-pane__kick {
	margin: 0 0 12px;
	font-size: var(--cp-text-lg);
	letter-spacing: -.01em;
}

.cp-pane .cp-rule {
	margin: 22px 0 14px;
}

.cp-pane .cp-rule + h2 {
	font-size: var(--cp-text-2xl);
}

.cp-pane .cp-tiles a {
	min-height: 48px;
}

.cp-pane .cp-tiles a b {
	flex: 1 1 0;
	min-width: 0;
}

.cp-field__help {
	display: flex;
	justify-content: flex-end;
}

.cp-field__help .cp-link,
.cp-utilinks a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}

.cp-field__help .cp-link {
	margin-bottom: -10px;
}

.cp-utilinks {
	gap: 0 var(--cp-space-9);
}

.cp-field input:focus,
.cp-field select:focus,
.cp-field textarea:focus {
	outline: 2px solid transparent;
	outline-offset: 2px;
}

@media (forced-colors: active) {
	.cp-field input:focus,
	.cp-field select:focus,
	.cp-field textarea:focus {
		outline: 2px solid Highlight;
	}

	.cp-tiles a,
	.cp-lockup {
		border: 2px solid CanvasText;
	}
}

.cp-landing__foot {
	position: relative;
	padding: 20px 16px 24px;
	background: var(--cp-paper);
	text-align: center;
	border-top: 1px solid var(--cp-line);
}

.cp-landing__foot .cp-methods {
	display: inline-flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--cp-space-4) var(--cp-space-6);
	margin: 0 0 12px;
	padding: 0;
	border: 0;
}

.cp-landing__foot .cp-methods .cp-lbl,
.cp-landing__foot .cp-methods__note {
	margin: 0;
}

.cp-landing__foot .cp-methods__note {
	flex-basis: 100%;
}

.cp-landing__foot .cp-methods__row img {
	height: 22px;
}

.cp-landing__foot .cp-badges img {
	height: 36px;
}

.cp-landing__foot .cp-legal a {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding: 0 4px;
}

@media (max-width: 899.98px) {
	.cp-hero {
		min-height: 0;
	}

	.cp-hero__body::before {
		display: none;
	}

	.cp-hero__top {
		padding: 14px 16px 0;
	}

	.cp-lockup {
		padding: 5px 10px;
	}

	.cp-lockup img {
		max-height: 30px;
	}

	.cp-hero__body {
		padding: 14px 16px 30px;
	}

	.cp-hero__eyebrow {
		margin-bottom: 4px;
	}

	.cp-hero h1 {
		max-width: none;
		font-size: var(--cp-text-2xl);
	}

	.cp-hero__tag {
		display: none;
	}

	.cp-pane {
		flex: 1;
		justify-content: flex-start;
		margin: -18px 0 0;
		padding: 22px 16px 24px;
		border-radius: 20px 20px 0 0;
	}

	.cp-landing__foot {
		border-top: 0;
		background: var(--cp-surface);
		padding-top: 0;
	}
}

@media (min-width: 900px) {
	.cp-pane {
		margin: 32px 0;
		padding: 32px 36px;
	}

	.cp-landing__foot {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		justify-content: center;
		gap: 8px 32px;
		padding: 14px 56px;
	}

	.cp-landing__foot .cp-methods,
	.cp-landing__foot .cp-badges,
	.cp-landing__foot .cp-legal {
		margin: 0;
	}

	.cp-landing__foot .cp-methods__note {
		flex-basis: auto;
	}
}
