/*
 * Application shell + page components for the modernized UI.
 *
 * Consumes the --es-* tokens from tokens.css; loaded last, after custom.css,
 * which now holds only the pieces shared with server-rendered markup (the
 * deterministic badge palette, avatars, member-row icons).
 */

/* ------------------------------------------------------------------ shell */

.app-shell {
	display: flex;
	min-height: 100vh;
	align-items: stretch;
}

.app-main {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
}

.app-content {
	flex: 1 1 auto;
	padding: 1.75rem 1.75rem 4rem;
	max-width: 1400px;
	width: 100%;
}

@media (max-width: 767.98px) {
	.app-content {
		padding: 1.25rem 1rem 3rem;
	}
}

/* ---------------------------------------------------------------- sidebar */

/*
 * .offcanvas-lg (Bootstrap 5.3's responsive offcanvas) means this element is
 * a normal in-flow block at lg and up, and a slide-in offcanvas below it -
 * so the same markup serves desktop and mobile with no duplicate nav.
 * The desktop-only rules therefore live inside the lg media query; below it
 * Bootstrap's own offcanvas positioning must win.
 */
.app-sidebar {
	background: var(--es-surface);
	border-right: 1px solid var(--es-border);
}

@media (min-width: 992px) {
	.app-sidebar {
		flex: 0 0 var(--es-sidebar-w);
		width: var(--es-sidebar-w);
		position: sticky;
		top: 0;
		height: 100vh;
		overflow-y: auto;
	}
}

.app-sidebar .offcanvas-header {
	border-bottom: 1px solid var(--es-border);
}

.sidebar-brand {
	display: flex;
	align-items: center;
	gap: .625rem;
	padding: 0 1.25rem;
	height: var(--es-topbar-h);
	text-decoration: none;
	color: var(--es-text);
	border-bottom: 1px solid var(--es-border);
}

.sidebar-brand:hover {
	color: var(--es-text);
}

.sidebar-brand-mark {
	width: 32px;
	height: 32px;
	min-width: 32px;
	border-radius: var(--es-r-sm);
	background: var(--es-primary);
	color: #fff;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 1rem;
}

.sidebar-brand-text {
	font-weight: 750;
	letter-spacing: -.015em;
}

.sidebar-nav {
	padding: 1rem .75rem;
	display: flex;
	flex-direction: column;
	gap: .125rem;
}

.sidebar-heading {
	font-size: .68rem;
	font-weight: 600;
	letter-spacing: .09em;
	text-transform: uppercase;
	color: var(--es-text-subtle);
	padding: 1rem .75rem .35rem;
}

.sidebar-link {
	display: flex;
	align-items: center;
	gap: .7rem;
	padding: .55rem .75rem;
	border-radius: var(--es-r-sm);
	color: var(--es-text-muted);
	text-decoration: none;
	font-size: .925rem;
	font-weight: 500;
	transition: background-color .12s ease, color .12s ease;
}

.sidebar-link i {
	font-size: 1.05rem;
	line-height: 1;
}

.sidebar-link:hover {
	background: var(--es-surface-hover);
	color: var(--es-text);
}

/*
 * Was a hardcoded bright true-orange (#fb923c), deliberately not
 * --es-accent, because the old --es-accent (#c2410c) read reddish/brick at
 * full saturation with white text. That reasoning no longer applies now
 * that --es-accent itself is the app's own accent (tokens.css) - reusing it
 * here keeps the sidebar's active highlight in the same family as every
 * other accent touch instead of its own one-off colour. Text is
 * --es-accent-on (dark bronze), not white - the current gold accent fails
 * contrast with white text (see tokens.css).
 */
.sidebar-link.is-active {
	background: var(--es-accent);
	color: var(--es-accent-on);
	font-weight: 700;
}

/*
 * Nav entries for sections that have no backend yet. Rendered as <span>, not
 * <a> (see fragments.html), so they are already unfocusable and unclickable -
 * this only makes that state visible.
 */
.sidebar-link.is-disabled {
	color: var(--es-text-subtle);
	cursor: default;
	opacity: .75;
}

.sidebar-link.is-disabled:hover {
	background: transparent;
	color: var(--es-text-subtle);
}

.sidebar-soon {
	margin-left: auto;
	font-size: .62rem;
	font-weight: 600;
	letter-spacing: .06em;
	text-transform: uppercase;
	color: var(--es-text-subtle);
	background: var(--es-surface-sunk);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-pill);
	padding: .1rem .4rem;
}

.sidebar-footer {
	margin-top: auto;
	padding: 1rem .75rem 1.25rem;
}

/* ----------------------------------------------------------------- topbar */

.app-topbar {
	position: sticky;
	top: 0;
	z-index: 1020;
	display: flex;
	align-items: center;
	gap: 1rem;
	min-height: var(--es-topbar-h);
	padding: .75rem 1.75rem;
	background: rgba(255, 255, 255, .88);
	backdrop-filter: blur(8px);
	border-bottom: 1px solid var(--es-border);
}

@media (max-width: 767.98px) {
	.app-topbar {
		padding: .75rem 1rem;
	}
}

.topbar-toggle {
	border: 1px solid var(--es-border-strong);
	background: var(--es-surface);
	color: var(--es-text-muted);
	border-radius: var(--es-r-sm);
	width: 38px;
	height: 38px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

.topbar-search {
	position: relative;
	flex: 1 1 auto;
	max-width: 420px;
}

.topbar-search i {
	position: absolute;
	left: .75rem;
	top: 50%;
	transform: translateY(-50%);
	color: var(--es-text-subtle);
	pointer-events: none;
	font-size: .95rem;
}

.topbar-search input {
	padding-left: 2.25rem;
	background: var(--es-surface-sunk);
	border-radius: var(--es-r-pill);
}

.topbar-search input:focus {
	background: var(--es-surface);
}

.topbar-user {
	display: flex;
	align-items: center;
	gap: .55rem;
	padding: .3rem .55rem .3rem .3rem;
	border-radius: var(--es-r-pill);
	text-decoration: none;
	color: var(--es-text);
	font-size: .9rem;
	font-weight: 550;
	white-space: nowrap;
}

.topbar-user:hover {
	background: var(--es-surface-hover);
	color: var(--es-text);
}

.dropdown-menu {
	border-color: var(--es-border);
	border-radius: var(--es-r-md);
	box-shadow: var(--es-shadow-md);
	padding: .375rem;
}

.dropdown-item {
	border-radius: var(--es-r-sm);
	font-size: .9rem;
	padding: .5rem .625rem;
}

/* ------------------------------------------------------------ page header */

.page-head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
	margin-bottom: 1.5rem;
}

.page-title {
	font-size: 1.85rem;
	font-weight: 800;
	letter-spacing: -.025em;
	margin: 0;
}

.page-subtitle {
	color: var(--es-text-muted);
	font-size: .9rem;
	margin: .2rem 0 0;
}

/*
 * ------------------------------------------------------- groups overview
 *
 * Gen-Z redesign of the Groups list page (confirmed via static preview -
 * groups-genz-preview.html - before implementation): the old flat
 * stat-grid/stat-tile pair (Total groups/Owned by you/Shared with you) is
 * replaced entirely by three gradient "headline" cards (Total balance/You
 * owe/You're owed - real figures, see GroupsOverviewService), and the old
 * single group-grid is replaced by a two-column layout (featured group +
 * card grid on the left, Quick actions/Recent activity/Upcoming settlements
 * on the right - see .groups-main below). stat-grid/stat-tile themselves are
 * gone: nothing else in the app referenced them.
 */

.groups-summary {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 1.1rem;
	margin-bottom: .85rem;
}

/* Small screens: two cards side by side, the third full width below (Groups page). */
@media (max-width: 767.98px) {
	.groups-summary {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: .75rem;
	}

	.groups-summary > .groups-summary-card:nth-child(3):last-child {
		grid-column: 1 / -1;
	}

	.groups-summary-card {
		padding: .9rem 1rem;
		column-gap: .7rem;
	}
}

/*
 * Muted-palette revision: these three tiles used to be solid, fully-
 * saturated colour blocks (green / orange / a blue-purple gradient) with
 * white text - explicit feedback called that "overstimulating" and asked
 * for a more cohesive system built from shades of the app's own green/
 * orange rather than 3-4 competing hues. Rebuilt as calm white cards with
 * a small tinted icon badge and colour only in the value text - the same
 * "tile background stays pale, saturation reserved for small accents"
 * rule the whole muted-palette pass follows elsewhere (Expenses/Balances
 * total-spent tile, status pills). Markup (icon/label/value as three plain
 * sibling elements) is unchanged from the original design - this is a
 * grid layout on the card itself rather than a flex row, specifically so
 * the icon can span both text rows without needing an extra wrapper div
 * around label+value in the template.
 */
.groups-summary-card {
	position: relative;
	display: grid;
	/* minmax(0, 1fr), not 1fr: a plain 1fr track never shrinks below its
	   content, so a long amount would push past the card edge. */
	grid-template-columns: 42px minmax(0, 1fr);
	grid-template-rows: auto auto;
	column-gap: .9rem;
	border-radius: var(--es-r-lg);
	padding: 1.1rem 1.3rem;
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	box-shadow: var(--es-shadow-sm);
}

.groups-summary-card .gs-icon {
	grid-row: 1 / 3;
	grid-column: 1;
	align-self: center;
	width: 42px;
	height: 42px;
	border-radius: 12px;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 1.1rem;
}

.groups-summary-card .gs-label {
	grid-row: 1;
	grid-column: 2;
	align-self: end;
	font-size: .8rem;
	font-weight: 650;
	color: var(--es-text-muted);
}

.groups-summary-card .gs-value {
	grid-row: 2;
	grid-column: 2;
	align-self: start;
	font-family: var(--font-heading);
	font-size: 1.3rem;
	font-weight: 700;
	margin-top: .15rem;
	font-variant-numeric: tabular-nums;
	color: var(--es-text);
	/* Never cut an amount off: a value too long for the card wraps onto a
	   second line instead (no ellipsis, no overflow). */
	min-width: 0;
	overflow-wrap: anywhere;
	line-height: 1.2;
}

.gs-total .gs-icon { background: var(--es-primary-soft); color: var(--es-primary); }
.gs-owe .gs-icon { background: var(--es-accent-soft); color: var(--es-accent-text); }
.gs-owe .gs-value { color: var(--es-accent-text); }
.gs-owed .gs-icon { background: var(--es-primary-soft); color: var(--es-primary); }
.gs-owed .gs-value { color: var(--es-primary); }

/* Smaller on the two-up phone layout so typical amounts stay on one line.
   Placed after the base .gs-value rule above so it wins at equal specificity. */
@media (max-width: 767.98px) {
	.groups-summary-card .gs-value {
		font-size: 1.05rem;
	}
}

/* Smaller muted line under a summary card's value, e.g. "6 expenses" under
   the Settlements page's Total expenses amount. The icon spans rows 1-2 and
   stays centred on the label + value. */
.groups-summary-card .gs-sub {
	grid-row: 3;
	grid-column: 2;
	font-size: .82rem;
	font-weight: 500;
	color: var(--es-text-muted);
	margin-top: .1rem;
}

/* Two labelled amounts in one summary card (Settlements: "Your settlements"
   - Paid to others / Received from others). Sits where .gs-value would. */
.groups-summary-card .gs-pair {
	grid-row: 2;
	grid-column: 2;
	min-width: 0;
	margin: .2rem 0 0;
}

.groups-summary-card .gs-pair > div {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: .5rem;
	line-height: 1.35;
}

.groups-summary-card .gs-pair dt {
	font-size: .78rem;
	font-weight: 500;
	color: var(--es-text-muted);
}

.groups-summary-card .gs-pair dd {
	margin: 0;
	font-family: var(--font-heading);
	font-size: .95rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	color: var(--es-text);
	text-align: right;
	/* Keep an amount in one piece ("19,982.00" never splits); the label
	   beside it wraps instead. */
	white-space: nowrap;
}

.groups-summary-note {
	font-size: .8rem;
	color: var(--es-text-muted);
	margin: 0 0 1.5rem;
}

/* --------------------------------------------------------------- main grid */

.groups-main {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 360px;
	gap: 1.5rem;
	align-items: start;
}

@media (max-width: 1080px) {
	.groups-main {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ----------------------------------------------------------- featured card */

.groups-featured {
	display: block;
	border-radius: 24px;
	padding: 1.75rem;
	color: #fff;
	position: relative;
	overflow: hidden;
	text-decoration: none;
	box-shadow: var(--es-shadow-lg);
	margin-bottom: 1.25rem;
	transition: transform .14s ease, box-shadow .14s ease;
}

.groups-featured:hover {
	transform: translateY(-2px);
	box-shadow: var(--es-shadow-lg), 0 0 0 1px rgba(255, 255, 255, .12) inset;
	color: #fff;
	text-decoration: none;
}

.groups-featured::after {
	content: "";
	position: absolute;
	inset: 0;
	background: radial-gradient(circle at 85% 15%, rgba(255, 255, 255, .18), transparent 55%);
}

/*
 * Was four different hue variants (green/teal/purple/orange) cycled by a
 * hash of the group's own name - dropped per the muted-palette revision's
 * "stick to green/orange, not a rainbow of competing hues" feedback. All
 * four gf-N classes now render the same blended green-to-terracotta
 * gradient the dashboard hero uses (see .dashboard-hero below) - the class
 * names/hashing in the template are left as-is (harmless now that they're
 * all identical) rather than touching groups/list.html for a purely visual
 * change.
 */
.gf-0, .gf-1, .gf-2, .gf-3 {
	background: linear-gradient(135deg, var(--es-primary) 0%, var(--es-primary-mid) 45%, var(--es-accent) 130%);
}

.groups-featured-tag {
	display: inline-flex;
	align-items: center;
	gap: .4rem;
	background: rgba(255, 255, 255, .2);
	font-size: .75rem;
	font-weight: 700;
	padding: .3rem .7rem;
	border-radius: var(--es-r-pill);
	margin-bottom: 1rem;
	position: relative;
	z-index: 1;
}

.groups-featured-top {
	display: flex;
	align-items: center;
	gap: 1rem;
	position: relative;
	z-index: 1;
}

.groups-featured-avatar {
	width: 58px;
	height: 58px;
	min-width: 58px;
	border-radius: 16px;
	background: rgba(255, 255, 255, .22);
	display: flex;
	align-items: center;
	justify-content: center;
	font-family: var(--font-heading);
	font-weight: 800;
	font-size: 1.3rem;
}

.groups-featured-name {
	font-family: var(--font-heading);
	font-size: 1.5rem;
	font-weight: 800;
	margin: 0;
	word-break: break-word;
}

.groups-featured-meta {
	font-size: .85rem;
	font-weight: 600;
	opacity: .96;
	margin-top: .2rem;
}

.groups-featured-stats {
	display: flex;
	gap: 2.2rem;
	margin-top: 1.4rem;
	position: relative;
	z-index: 1;
}

.groups-featured-stats .gfs-val {
	font-family: var(--font-heading);
	font-size: 1.3rem;
	font-weight: 800;
	font-variant-numeric: tabular-nums;
}

.groups-featured-stats .gfs-lbl {
	font-size: .78rem;
	font-weight: 600;
	opacity: .94;
}

.groups-featured-foot {
	margin-top: 1.4rem;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: .75rem;
	flex-wrap: wrap;
	position: relative;
	z-index: 1;
}

.groups-featured-last {
	font-size: .8rem;
	font-weight: 600;
	opacity: .94;
}

.groups-featured[data-href] {
	cursor: pointer;
}

.groups-featured-btn {
	text-decoration: none;
	background: rgba(255, 255, 255, .95);
	color: var(--es-primary);
	font-weight: 700;
	font-size: .82rem;
	padding: .55rem 1.1rem;
	border-radius: var(--es-r-pill);
	white-space: nowrap;
}

/* ------------------------------------------------------------ group cards */

.group-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
	gap: 1rem;
}

.group-card {
	position: relative;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	gap: .85rem;
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	box-shadow: var(--es-shadow-sm);
	padding: 1.15rem 1.25rem;
	cursor: pointer;
	transition: box-shadow .14s ease, border-color .14s ease, transform .14s ease;
}

/*
 * Blended green-to-terracotta glow along the top edge, revealed on hover -
 * replaces a flat mint-green hover border per explicit feedback ("green
 * bgcolor looks odd... replace with mixed muted green and terracotta").
 * A blurred pseudo-element positioned just above the card's own top edge,
 * clipped by .group-card's overflow: hidden, rather than a gradient
 * border-image (which fights border-radius) - the same technique the
 * approved preview used.
 */
.group-card::before {
	content: "";
	position: absolute;
	left: -10%;
	right: -10%;
	top: -28px;
	height: 44px;
	border-radius: 50%;
	background: linear-gradient(90deg, var(--es-primary-mid), var(--es-accent));
	filter: blur(13px);
	opacity: 0;
	transition: opacity .18s ease;
	pointer-events: none;
}

.group-card:hover {
	box-shadow: var(--es-shadow-md);
	border-color: transparent;
	transform: translateY(-2px);
}

.group-card:hover::before {
	opacity: .55;
}

.group-card:active {
	transform: translateY(-1px);
	box-shadow: var(--es-shadow-sm);
}

.group-card:focus-visible {
	outline: 2px solid var(--es-primary);
	outline-offset: 2px;
}

.group-card.is-open {
	border-color: var(--es-primary);
	box-shadow: var(--es-shadow-md);
}

.group-card.is-archived {
	opacity: .62;
}

.group-card-top {
	display: flex;
	align-items: flex-start;
	gap: .75rem;
}

.group-card-title {
	font-weight: 720;
	font-size: 1.05rem;
	line-height: 1.3;
	letter-spacing: -.015em;
	word-break: break-word;
}

/*
 * "Total spent" tile - sits as the third flex child of .group-card-top,
 * pushed to the top-right via margin-left: auto (that row is already a flex
 * container, so no absolute positioning is needed). No background/color of
 * its own here - a badge-color-N class is added in list.html (same hash of
 * group.name the avatar already uses, via T(java.lang.Math).abs(...) % 7,
 * kept in sync client-side after an inline edit via badgeColorClass() in
 * groups.js) - so the tile always matches its own card's avatar colour
 * rather than being a fixed colour across every card.
 */
.group-spend-tile {
	margin-left: auto;
	flex-shrink: 0;
	display: inline-flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 1px;
	padding: .3rem .6rem;
	border-radius: var(--es-r-sm);
}

.group-spend-amount {
	font-size: .82rem;
	font-weight: 750;
	line-height: 1.2;
	white-space: nowrap;
}

.group-spend-count {
	font-size: .65rem;
	font-weight: 500;
	line-height: 1.2;
	opacity: .85;
	white-space: nowrap;
}

/*
 * Modifier for the same tile placed beside the Balances/Expenses pages'
 * group selector (see balances.html/expenses.html) instead of inside a
 * .group-card-top flex row. margin-left: auto only makes sense in the
 * card, where it pushes the tile to that row's own far edge - reused
 * as-is here, it would instead push the tile all the way to the page's
 * far right edge (the selector row itself isn't width-constrained the
 * way a card is), recreating the same "amount stranded far from its
 * label" gap already fixed elsewhere on these pages. Cancelled here so
 * the tile just sits directly after the selector instead.
 */
/*
 * On the Expenses/Balances pages this tile has no group card/avatar next
 * to it to visually match, so unlike the card version above it shouldn't
 * ride whatever badge-color-N the selected group's name happens to hash
 * to (expenses.js/balances.js both add one from the same helper the card
 * uses, purely by re-using that shared helper - not because a matching
 * colour meant anything here). That's what put a bright badge-color-0
 * green tile next to "Add expense" instead of the muted-palette look
 * every other tile on this page has - fixed with a two-class selector
 * (higher specificity than the single-class badge-color-N rules above, so
 * this always wins regardless of which badge-color-N JS also added).
 */
.group-spend-tile.group-spend-tile-inline {
	margin-left: 0;
	background: var(--es-accent-soft);
	color: var(--es-accent-text);
}

/* Member count line under the name - Gen-Z redesign; real count, see GroupsOverviewService.memberCountsFor(). */
.group-card-members {
	font-size: .78rem;
	color: var(--es-text-muted);
	margin-top: .1rem;
}

/*
 * The viewer's own outstanding balance in this group - real figure (see
 * GroupBalanceChip/GroupsOverviewService.balanceChipsFor()), never the raw
 * expense-derived one, so this reads SETTLED the moment every transaction
 * involving the viewer is marked Settled elsewhere in the app.
 */
.group-balance-chip {
	display: inline-flex;
	margin-top: .85rem;
	font-size: .78rem;
	font-weight: 700;
	padding: .28rem .7rem;
	border-radius: var(--es-r-pill);
}

.group-balance-chip.chip-owes {
	background: var(--es-accent-soft);
	color: var(--es-accent-text);
}

.group-balance-chip.chip-owed {
	background: var(--es-primary-soft);
	color: var(--es-primary);
}

.group-balance-chip.chip-settled {
	background: var(--es-surface-sunk);
	color: var(--es-text-muted);
}

.group-card-last {
	font-size: .76rem;
	color: var(--es-text-muted);
	margin-top: .7rem;
}

/*
 * Owner block - .owner-lines is still used by groups/dashboard.html's own
 * hero (name over "· email"); .group-card-meta/.group-card-owner/.owner-email/
 * .group-card-foot/.group-quicklinks/.group-quicklink* were only ever used by
 * this page's own OLD card layout (owner block + Expenses/Balances chips +
 * a bottom foot row) and are removed along with it - the Gen-Z redesign's
 * card spec doesn't show owner/description, and the hover action icon now
 * reuses the Delete/Archive modal instead of separate quick-link chips (see
 * list.html).
 */
.owner-lines {
	display: flex;
	flex-direction: column;
	min-width: 0;
	line-height: 1.35;
}

/*
 * Card actions stay in the DOM at all times (they carry the disabled state
 * the server decided) and are only faded out at rest, so hovering or tabbing
 * into the card reveals them without any layout shift. Pinned to the right
 * of .group-card-top via margin-left: auto (that row is already a flex
 * container) now that this button lives there instead of in a bottom foot
 * row.
 */
.group-card-actions {
	display: flex;
	gap: .3rem;
	margin-left: auto;
	flex-shrink: 0;
	align-self: flex-start;
	opacity: 0;
	transition: opacity .14s ease;
}

.group-card:hover .group-card-actions,
.group-card:focus-within .group-card-actions {
	opacity: 1;
}

@media (hover: none) {
	.group-card-actions {
		opacity: 1;
	}
}

/*
 * Used to inherit badge-color-N (currency hashed into the same 7-hue
 * avatar palette, custom.css) - landed on arbitrary, unrelated hues per
 * group (mint/peach/lime...) with no connection to the app's own theme.
 * Currency is just an informational tag, not something that needs a
 * unique colour per group the way an avatar does, so it's now one
 * consistent soft-sage treatment everywhere .group-pill (not
 * .group-pill-muted, which keeps its own neutral grey below) appears -
 * group cards, the dashboard hero pill row (both server-rendered and the
 * one groups-dashboard.js patches after an inline edit).
 */
.group-pill {
	display: inline-flex;
	align-items: center;
	font-size: .7rem;
	font-weight: 650;
	letter-spacing: .03em;
	padding: .2rem .55rem;
	border-radius: var(--es-r-pill);
	background: var(--es-primary-soft);
	color: var(--es-primary-hover);
}

.group-pill-muted {
	background: var(--es-surface-sunk);
	color: var(--es-text-subtle);
	border: 1px solid var(--es-border);
}

/* ------------------------------------------------------------ empty state */

.empty-state {
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	box-shadow: var(--es-shadow-sm);
	padding: 3.5rem 1.5rem;
	text-align: center;
}

.empty-mark {
	width: 72px;
	height: 72px;
	margin: 0 auto 1.25rem;
	border-radius: 50%;
	background: var(--es-primary-soft);
	color: var(--es-primary);
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 2rem;
}

.empty-title {
	font-size: 1.1rem;
	font-weight: 640;
	margin-bottom: .35rem;
}

.empty-text {
	color: var(--es-text-muted);
	font-size: .9rem;
	max-width: 34rem;
	margin: 0 auto 1.5rem;
}

/* ------------------------------------------------------------ right rail */

/*
 * Quick actions/Recent activity/Upcoming settlements - same radius/shadow/
 * hover-lift language as .group-card, plus a faint gradient tint so the
 * rail reads as part of the same card family rather than a flatter, plainer
 * panel (per explicit feedback on the confirmed preview).
 */
.groups-panel {
	background: linear-gradient(160deg, var(--es-surface-hover) 0%, var(--es-surface) 40%);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	padding: 1.25rem;
	box-shadow: var(--es-shadow-sm);
	margin-bottom: 1.1rem;
	transition: transform .14s ease, box-shadow .14s ease;
}

.groups-panel:hover {
	transform: translateY(-2px);
	box-shadow: var(--es-shadow-md);
}

.groups-panel-title {
	font-family: var(--font-heading);
	font-weight: 700;
	font-size: .95rem;
	color: var(--es-text);
	margin: 0 0 1rem;
}

.groups-panel-empty {
	font-size: .82rem;
	color: var(--es-text-muted);
	margin: 0;
}

.groups-actions {
	display: flex;
	flex-direction: column;
	gap: .7rem;
}

/*
 * Muted-palette revision: these two Quick-action buttons used to be solid
 * gradient blocks with white text - restyled as soft tinted pills (colour
 * only in the text/icon badge, per the same "tile background stays pale"
 * rule the rest of this pass follows) rather than another pair of loud
 * saturated fills sitting right under the already-toned-down summary tiles.
 */
.groups-action-btn {
	display: flex;
	align-items: center;
	gap: .8rem;
	border: none;
	border-radius: var(--es-r-lg);
	padding: .9rem 1.1rem;
	font-weight: 700;
	font-size: .9rem;
	text-decoration: none;
	transition: background-color .12s ease;
}

.groups-action-btn:hover {
	text-decoration: none;
}

.groups-action-btn .ga-icon {
	width: 36px;
	height: 36px;
	border-radius: 11px;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 1rem;
	flex-shrink: 0;
}

.ga-add { background: var(--es-accent-soft); color: var(--es-accent-text); }
.ga-add .ga-icon { background: rgba(215, 181, 109, .28); color: var(--es-accent-text); }
.ga-add:hover { background: #ecdbb0; color: var(--es-accent-text); }

.ga-new { background: var(--es-primary-soft); color: var(--es-primary-hover); }
.ga-new .ga-icon { background: rgba(46, 94, 138, .18); color: var(--es-primary); }
.ga-new:hover { background: #d7e0ea; color: var(--es-primary-hover); }

.groups-feed-row {
	display: flex;
	align-items: flex-start;
	gap: .75rem;
	padding: .6rem 0;
	border-bottom: 1px solid var(--es-border);
}

.groups-feed-row:last-child {
	border-bottom: none;
	padding-bottom: 0;
}

/*
 * Used to inherit the same badge-color-N hash palette (custom.css) as the
 * avatar badges, forced white (color: #fff) on top - white barely reads
 * against several of those backgrounds, since most badge-color-N tones are
 * pale pastels (badge-color-N carries per-*person/group* meaning there,
 * which doesn't apply here: this is the same receipt icon on every row
 * regardless of which group it's for). Replaced with a fixed two-tone
 * sage/terracotta pairing, alternated per row (is-sage/is-terracotta, set
 * in groups/list.html via itemStat.index % 2) - same soft-bg/dark-icon
 * treatment as the "Add expense"/"New group" quick-action icons and the
 * Expenses/Balances Total spent tile, so contrast is always solid and the
 * feed stays on the app's own two brand tones instead of pulling in
 * unrelated hues from the avatar palette.
 */
.groups-feed-icon {
	width: 32px;
	height: 32px;
	border-radius: 9px;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: .85rem;
	flex-shrink: 0;
}

.groups-feed-icon.is-sage {
	background: var(--es-primary-soft);
	color: var(--es-primary-hover);
}

.groups-feed-icon.is-terracotta {
	background: var(--es-accent-soft);
	color: var(--es-accent-text);
}

.groups-feed-text {
	font-size: .83rem;
	line-height: 1.35;
	color: var(--es-text);
}

.groups-feed-time {
	font-size: .74rem;
	font-weight: 600;
	color: var(--es-text-muted);
	margin-top: .15rem;
}

.groups-settle-row {
	display: flex;
	align-items: center;
	gap: .7rem;
	padding: .6rem 0;
	border-bottom: 1px solid var(--es-border);
}

.groups-settle-row:last-child {
	border-bottom: none;
	padding-bottom: 0;
}

.groups-settle-avatar {
	width: 34px;
	height: 34px;
	border-radius: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
	font-weight: 700;
	font-size: .78rem;
	flex-shrink: 0;
}

.groups-settle-name {
	font-size: .85rem;
	font-weight: 700;
	color: var(--es-text);
}

.groups-settle-sub {
	font-size: .75rem;
	font-weight: 600;
	color: var(--es-text-muted);
}

.groups-settle-amt {
	margin-left: auto;
	font-family: var(--font-heading);
	font-weight: 700;
	font-size: .88rem;
	white-space: nowrap;
}

.groups-settle-amt.is-owed { color: var(--es-primary); }
.groups-settle-amt.is-owe { color: var(--es-accent-text); }

/* Shown by groups.js when the topbar search filters every card out. */
.no-results {
	display: none;
	text-align: center;
	color: var(--es-text-muted);
	font-size: .9rem;
	padding: 3rem 1rem;
}

/* ----------------------------------------------------------------- drawer */

.group-drawer {
	--bs-offcanvas-width: 460px;
}

.drawer-head {
	display: flex;
	align-items: flex-start;
	gap: .85rem;
	padding: 1.25rem 1.35rem;
	border-bottom: 1px solid var(--es-border);
}

.drawer-title {
	font-size: 1.1rem;
	font-weight: 650;
	letter-spacing: -.01em;
	line-height: 1.3;
	word-break: break-word;
}

.drawer-sub {
	font-size: .8rem;
	color: var(--es-text-subtle);
	margin-top: .15rem;
}

.drawer-body {
	padding: 1.1rem 1.35rem 2rem;
	overflow-y: auto;
}

/* Bottom-right action bar (Close, Save) - stays visible while the body scrolls. */
.drawer-foot {
	display: flex;
	justify-content: flex-end;
	gap: .5rem;
	margin-top: auto;
	padding: .85rem 1.35rem;
	border-top: 1px solid var(--es-border);
}

.nav-tabs {
	--bs-nav-tabs-border-color: var(--es-border);
	--bs-nav-tabs-link-active-color: var(--es-primary);
	--bs-nav-tabs-link-active-border-color: transparent transparent var(--es-primary);
	--bs-nav-tabs-link-hover-border-color: transparent;
	--bs-nav-link-color: var(--es-text-muted);
	--bs-nav-link-hover-color: var(--es-text);
	--bs-nav-tabs-link-active-bg: transparent;
	padding: 0 1.35rem;
	gap: .25rem;
}

.nav-tabs .nav-link {
	border-width: 0 0 2px;
	border-radius: 0;
	padding: .65rem .35rem;
	margin-bottom: -1px;
	font-size: .9rem;
	font-weight: 550;
}

.nav-tabs .nav-link.disabled {
	color: var(--es-text-subtle);
	opacity: .6;
}

/* ------------------------------------------------------------ auth layout */

.auth-shell {
	display: flex;
	min-height: 100vh;
	background: var(--es-bg);
}

/*
 * Gradient brand panel - CSS only, no image asset, keeping the "zero external
 * network dependency at runtime" rule intact. Hidden below lg, where the form
 * card simply centres on the tinted background instead.
 */
.auth-brand {
	display: none;
	flex: 0 0 42%;
	position: relative;
	overflow: hidden;
	/* Muted-palette revision: was a bold #0f5228→#15803d→#22a355 sweep - now the same three-stop shape in the muted sage family. */
	background: linear-gradient(150deg, var(--es-primary-active) 0%, var(--es-primary) 45%, var(--es-primary-mid) 100%);
	color: #fff;
	padding: 3rem;
	flex-direction: column;
	justify-content: space-between;
}

@media (min-width: 992px) {
	.auth-brand {
		display: flex;
	}
}

/*
 * Decorative circle - orange instead of a plain translucent white, so the
 * accent colour makes a small appearance even on the page that has no
 * buttons/pills of its own to carry it.
 */
.auth-brand::after {
	content: "";
	position: absolute;
	right: -22%;
	bottom: -28%;
	width: 78%;
	aspect-ratio: 1;
	border-radius: 50%;
	background: rgba(215, 181, 109, .28);
}

.auth-brand-inner {
	position: relative;
	z-index: 1;
	max-width: 26rem;
}

.auth-brand h2 {
	font-size: 2.2rem;
	font-weight: 800;
	letter-spacing: -.025em;
	line-height: 1.15;
	text-wrap: balance;
}

.auth-brand p {
	color: rgba(255, 255, 255, .82);
	font-size: .95rem;
	line-height: 1.6;
}

.auth-points {
	list-style: none;
	padding: 0;
	margin: 2rem 0 0;
	display: flex;
	flex-direction: column;
	gap: .85rem;
}

.auth-points li {
	display: flex;
	align-items: flex-start;
	gap: .65rem;
	font-size: .9rem;
	color: rgba(255, 255, 255, .9);
}

.auth-points i {
	margin-top: .12rem;
}

.auth-brand-foot {
	position: relative;
	z-index: 1;
	color: rgba(255, 255, 255, .6);
}

.auth-panel {
	flex: 1 1 auto;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: 2rem 1.25rem;
	min-width: 0;
}

.auth-card {
	width: 100%;
	max-width: 26rem;
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	box-shadow: var(--es-shadow-md);
	padding: 2rem;
}

/* join.html's invite card carries more content than the login forms do. */
.auth-card-wide {
	max-width: 30rem;
}

.auth-mark {
	display: inline-flex;
	align-items: center;
	gap: .55rem;
	margin-bottom: 1.5rem;
	font-weight: 650;
	color: var(--es-text);
	text-decoration: none;
}

.auth-title {
	font-size: 1.45rem;
	font-weight: 760;
	letter-spacing: -.02em;
	margin-bottom: .3rem;
}

.auth-sub {
	color: var(--es-text-muted);
	font-size: .9rem;
	margin-bottom: 1.5rem;
}

/*
 * "Signing you in..." panel on login.html, shown by google-signin.js after
 * Google returns until the next page loads. min-height roughly matches the
 * login form so the card doesn't collapse when the form is swapped out.
 * The spinner reuses Bootstrap's spinner-border keyframes but not its class,
 * so the page never renders a .spinner-border at rest.
 */
.auth-signing-in {
	min-height: 16rem;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 1rem;
	text-align: center;
}

.auth-signing-in.d-none {
	display: none;
}

.auth-spinner {
	width: 2.5rem;
	height: 2.5rem;
	border: .25rem solid var(--es-border);
	border-top-color: var(--es-primary);
	border-radius: 50%;
	animation: spinner-border .75s linear infinite;
}

/* First-sign-in account notice under the login buttons (login.html). */
.auth-notice {
	margin: 1.5rem 0 0;
	padding-top: 1rem;
	border-top: 1px solid var(--es-border);
	font-size: .78rem;
	line-height: 1.55;
	color: var(--es-text-muted);
	text-align: center;
}

.auth-notice a {
	color: var(--es-primary);
	text-decoration: none;
	white-space: nowrap;
}

.auth-notice a:hover,
.auth-notice a:focus-visible {
	text-decoration: underline;
}

.auth-signing-in-title {
	margin: 0;
	font-weight: 650;
	color: var(--es-text);
}

/*
 * Separates the email/OTP form from the "Sign in with Google" button below
 * it on login.html - both are self-contained login methods, not steps in one
 * flow, so this reads as "or" rather than a section divider within a form.
 * Kept local to the login page rather than reusing quickjoin's divider,
 * which is a distinct component (invite-link copy affordances) with no
 * conceptual overlap here.
 */
.auth-divider {
	display: flex;
	align-items: center;
	gap: .75rem;
	margin: 1.5rem 0;
	color: var(--es-text-muted);
	font-size: .8rem;
}

.auth-divider::before,
.auth-divider::after {
	content: "";
	flex: 1 1 auto;
	height: 1px;
	background: var(--es-border);
}

/* ----------------------------------------------------------------- shared */

/* Bootstrap 5.3 has no min-width utility; flex children need one to shrink. */
.min-w-0 {
	min-width: 0;
}

.section-card {
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	box-shadow: var(--es-shadow-sm);
	padding: 1.5rem;
}

.form-narrow {
	max-width: 34rem;
}

@media (prefers-reduced-motion: reduce) {
	.group-card,
	.group-card-actions,
	.sidebar-link,
	.btn,
	.btn-icon {
		transition: none;
	}

	.group-card:hover,
	.btn:hover,
	.btn:active,
	.btn-icon:hover {
		transform: none;
	}
}

/*
 * Add Expense tab (UI only) - see the template comment above
 * #tabAddExpense in groups/list.html and the JS section in groups.js.
 */

.add-expense-icon {
	width: 36px;
	height: 36px;
	min-width: 36px;
	border-radius: var(--es-r-md, 12px);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 1rem;
	background: var(--es-primary-soft);
	color: var(--es-primary);
}

.expense-participant-row {
	display: flex;
	align-items: center;
	gap: .6rem;
	padding: .4rem 0;
	border-bottom: 1px solid var(--es-border);
}

.expense-participant-row:last-child {
	border-bottom: none;
}

.expense-participant-avatar {
	width: 26px;
	height: 26px;
	min-width: 26px;
	border-radius: 50%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 10px;
	font-weight: 650;
}

.expense-participant-name {
	flex: 1;
	font-size: .85rem;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.expense-participant-value {
	font-size: .85rem;
	color: var(--es-text-subtle);
	text-align: right;
}

.expense-participant-input {
	width: 90px;
	text-align: right;
}

/*
 * Participant selection chips (renderExpenseParticipantChips() in
 * groups.js) - "All", one per visible member, and a trailing "Add more".
 * Selected = filled var(--es-primary-soft) with a checkmark; unselected =
 * plain outline, same border/background pairing .btn-outline-secondary
 * already uses elsewhere in this drawer.
 */
.expense-chip-row {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

.expense-chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 6px 10px 6px 6px;
	border-radius: var(--es-r-pill);
	border: 1px solid var(--es-border-strong);
	background: var(--es-surface);
	color: var(--es-text);
	font-size: 12px;
	font-weight: 500;
	line-height: 1.2;
	cursor: pointer;
	transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}

.expense-chip:hover {
	background: var(--es-surface-hover);
}

.expense-chip.is-selected {
	background: var(--es-primary-soft);
	border-color: var(--es-primary-border);
	color: var(--es-primary-active);
}

.expense-chip-avatar {
	width: 20px;
	height: 20px;
	min-width: 20px;
	border-radius: 50%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 9px;
	font-weight: 650;
}

.expense-chip-check {
	font-size: 13px;
	color: var(--es-primary);
}

/* No avatar - just bold text, so it reads as a distinct "select everyone"
   action rather than one more person in the row. */
.expense-chip-all {
	padding: 6px 12px;
	font-weight: 650;
}

.expense-chip-add-more {
	border-style: dashed;
	color: var(--es-text-muted);
}

/*
 * Deliberately NOT .alert-success: this app's Bootstrap alert tokens are
 * a muted soft-bg/dark-text pairing (see --bs-alert-bg above), which is
 * not what was asked for here - a solid green with genuinely white text.
 */
.expense-split-preview {
	background: var(--es-success);
	color: #ffffff;
	border: none;
}

/*
 * Expenses tab (UI only) - see the template comment above #tabExpenses in
 * groups/list.html and the JS section in groups.js.
 */

.expenses-success-banner {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	background: var(--es-success-soft);
	color: var(--es-success);
	border-radius: var(--es-r-md, 12px);
	padding: 10px 12px;
	margin-bottom: 12px;
}

.expenses-success-banner i {
	font-size: 1.1rem;
	margin-top: 1px;
}

.expense-row {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 10px 4px;
	border-bottom: 1px solid var(--es-border);
	border-radius: var(--es-r-sm);
	transition: background-color .12s ease;
}

.expense-row:last-child {
	border-bottom: none;
}

/* Light green on hover - distinct from the row's default (transparent)
   background. Same --es-surface-hover token .sidebar-link:hover already
   uses, so it reads as one consistent "hover" colour app-wide. */
.expense-row:hover {
	background: var(--es-surface-hover);
}

.expense-row-new {
	background: var(--es-primary-soft);
	border-radius: var(--es-r-md, 12px);
	border-bottom-color: transparent;
}

.expense-row-date {
	width: 52px;
	min-width: 52px;
	font-size: 11px;
	color: var(--es-text-subtle);
	white-space: nowrap;
}

.expense-row-icon {
	width: 34px;
	height: 34px;
	min-width: 34px;
	border-radius: 50%;
	background: var(--es-primary-soft);
	color: var(--es-primary);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 15px;
}

/*
 * max-width caps how far this column stretches on a wide page (this list
 * now lives on its own full-width Expenses page, not the narrow drawer it
 * was designed for originally) - without it, flex: 1 fills every bit of
 * remaining row width even for a short description, dragging
 * .expense-row-end (payer/amount) all the way to the card's far right
 * edge with a huge, unreadable gap in between. Capped instead of
 * removing flex/growth entirely, so a genuinely long description still
 * gets real room (up to the cap) before wrapping/clamping.
 */
.expense-row-main {
	flex: 1;
	max-width: 340px;
	min-width: 0;
}

/* 3-line clamp, not the single-line nowrap+ellipsis truncation this used
   to have - a long description now wraps up to 3 lines before an
   ellipsis cuts it off, instead of a taller row expanding indefinitely.
   Same -webkit-line-clamp technique .group-card-desc already uses,
   just 3 lines here instead of 2. */
.expense-row-title {
	font-size: 13px;
	font-weight: 600;
	display: -webkit-box;
	-webkit-line-clamp: 3;
	line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
	word-break: break-word;
}

.expense-row-sub {
	font-size: 11px;
	color: var(--es-text-subtle);
}

.expense-badge-new {
	background: var(--es-primary);
	color: #ffffff;
	font-weight: 500;
	font-size: 10px;
	vertical-align: middle;
}

/*
 * Fixed width, not just text-align: right - without a fixed width this
 * div sizes to its own widest child (the payer line or the amount line,
 * whichever is longer for that particular row), so a row with a short
 * payer name and a long amount ends up narrower than a row with a long
 * payer name ("Karthikeyan G +2") and a short amount, and the whole
 * two-line block shifts left/right row to row even though each is
 * individually right-aligned - the "Amount" column visually zigzags
 * instead of lining up. A fixed width (matching .expenses-sort-btn-end's
 * own, so the header lines up too) fixes that; flex-shrink: 0 keeps it
 * from being squeezed by .expense-row-main. Content past the fixed width
 * ellipsizes (see .expense-row-payer/.expense-row-amount below) rather
 * than wrapping or re-widening the column.
 */
.expense-row-end {
	text-align: right;
	flex-shrink: 0;
	width: 140px;
}

.expense-row-payer {
	font-size: 11px;
	color: var(--es-text-subtle);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.expense-row-amount {
	font-size: 13px;
	font-weight: 600;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/*
 * Hover-reveal edit/bin icons per expense row - same opacity-only reveal
 * .btn-remove-member/.group-members-table already use for the Members
 * table, just keyed off .expense-row instead. flex-shrink: 0 keeps each a
 * fixed 32px regardless of how long the description/payer text next to
 * it gets. .btn-edit-expense sits before .btn-delete-expense in markup
 * (renderExpenseRow() in groups.js), so it reveals to its left.
 */
.btn-edit-expense,
.btn-delete-expense {
	opacity: 0;
	flex-shrink: 0;
	transition: opacity 0.1s ease;
}

.expense-row:hover .btn-edit-expense,
.expense-row:focus-within .btn-edit-expense,
.expense-row:hover .btn-delete-expense,
.expense-row:focus-within .btn-delete-expense {
	opacity: 1;
}

.expenses-pagination {
	border-top: 1px solid var(--es-border);
}

/*
 * Balances card on the dedicated /balances page (see balances.html) - a
 * two-part summary: "Your balances" (viewer-perspective, "you are owed" /
 * "you owe") and "Other balances" (every other settlement transaction in
 * the group, third-person, full transparency). Built by
 * loadBalances()/renderBalancesCard() in balances.js.
 */
.balances-card {
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	box-shadow: var(--es-shadow-sm);
	padding: 1.5rem;
}

/*
 * Narrower than the card itself - a long group name shouldn't stretch the
 * dropdown to the full content width. Widened from 320px to 420px (a
 * short name like "Travel To Greece" still sits comfortably left-aligned;
 * a longer one like "Our long roadway trip to entire India" now fits
 * without colliding with the chevron) - see the select's own
 * text-overflow rule below for names that still exceed even that.
 */
.balances-group-select-wrap {
	/* Never wider than 420px or than the screen, so the ellipsis rule below can kick in. */
	max-width: min(420px, 100%);
	min-width: 0;
}

/*
 * Truncates the *closed* select's own text with an ellipsis rather than
 * letting it visually collide with Bootstrap's chevron background-image
 * (which reserves space via the select's own padding-right, so the
 * ellipsis lands before the icon, never under it). The open dropdown list
 * itself is unaffected - each <option> still renders its full text
 * natively - and balances.js/expenses.js set a title attribute on the
 * select synced to the selected option's full name, so the full name is
 * still available as a hover tooltip when truncated.
 */
.balances-group-select-wrap select {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
 * Expenses page's own card only (not .balances-card generally - the
 * Balances page's own card is fine at full width). Capped instead of
 * stretching edge-to-edge on a wide screen: with .expense-row-main now
 * max-width: 340px (see its own comment), a full-width card just left a
 * lot of empty space after the Amount column - shrinking the card itself
 * down to roughly what a row actually needs (date + icon + description +
 * amount + the hover-icon spacer, plus padding) removes that dead space
 * instead of leaving it there. Search box/Filters button narrow along
 * with it, which reads as intentional rather than as a layout bug.
 */
#expensesCard {
	max-width: 760px;
}

.balance-entry {
	display: flex;
	align-items: center;
	gap: .65rem;
	padding: .4rem 0;
}

/*
 * "Your balances" table (Person / Amount / Date / Mode / Settled action) -
 * "Other balances" below it keeps the plain .balance-entry row style (no
 * table), since it has no per-row controls of its own.
 *
 * Lives only on the dedicated Balances page now (see balances.html/
 * balances.js) - this used to also back a squeezed version of the same
 * table inside the group drawer's Balances tab, which needed much smaller
 * fonts/avatars and no Mode column to avoid a horizontal scrollbar at the
 * drawer's narrower width. Moving the whole feature to its own full-width
 * page (see README) removed that constraint, so this is back to
 * comfortable, normal-sized styling and the Mode column is restored.
 * table-layout: fixed with the column widths set via <colgroup> in
 * balances.html keeps the columns predictable regardless of content
 * length.
 */
.balances-mine-table {
	width: 100%;
	table-layout: fixed;
	border-collapse: collapse;
}

.balances-mine-table thead th {
	font-size: .78rem;
	font-weight: 600;
	color: var(--es-text-subtle);
	text-align: left;
	padding: .35rem .5rem;
	border-bottom: 1px solid var(--es-border);
	white-space: nowrap;
}

.balances-mine-table tbody td {
	padding: .5rem;
	vertical-align: middle;
	border-bottom: 1px solid var(--es-border);
}

.balances-mine-table tbody tr:last-child td {
	border-bottom: none;
}

/*
 * Left-aligned, not right-aligned - matching the "Amount" header (see
 * .balances-mine-table thead th, also left-aligned) and every other
 * column header (Member/Date/Mode) in this table. A right-aligned value
 * under a left-aligned header read as visually disconnected from its own
 * column - confirmed via preview widget/screenshot before this fix.
 */
.balances-amount-cell {
	text-align: left;
	white-space: nowrap;
}

.balances-mine-table .balance-entry {
	padding: 0;
}

.balances-mine-table .balance-entry-name {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.balances-date-input,
.balances-mode-select {
	width: 100%;
}

/*
 * Status column (between Amount and Date) - a small colored pill when a
 * settlement has actually been recorded ("Paid" - the only status this
 * feature ever writes, see GroupBalance.STATUS_PAID), an em dash otherwise
 * (no settlement recorded yet for that relationship). Used identically by
 * both "Your balances" rows (buildMineEntryRow(), sourced from
 * mySettlementsByOtherUserId) and "Other balances" rows
 * (buildOtherTransactionRow(), sourced from settlementStatusByPairKey) -
 * see balances.js's loadSettlementStatuses()/README for why the latter is a
 * separate, not-party-scoped lookup.
 */
.balance-status-pill {
	display: inline-block;
	font-size: 11px;
	font-weight: 650;
	padding: .15rem .55rem;
	border-radius: 999px;
	background: var(--es-success-soft);
	color: var(--es-success);
}

.balance-status-empty {
	color: var(--es-text-subtle);
}

/* "₹X paid so far" / "Last payment ₹X on 14 Oct" under an amount in Your balances. */
.balance-paid-so-far {
	font-size: .75rem;
	margin-top: .15rem;
	white-space: nowrap;
}

/* Amount being paid now (Your balances "Paying" column). */
.balances-pay-input {
	width: 100%;
	max-width: 9rem;
	font-variant-numeric: tabular-nums;
}

/*
 * "Pending" - the Other balances section's own unsettled state, per
 * explicit follow-up ("change '--' to 'pending' and highlight in
 * yellow"). A modifier on .balance-status-pill rather than a whole
 * separate pill class, so it only needs to override background/color -
 * same warning tokens .stat-icon-shared already uses elsewhere in this
 * file. The Your balances table's own unsettled state is intentionally
 * left as the plain .balance-status-empty em dash - not touched here,
 * this follow-up was scoped to Other balances only.
 */
.balance-status-pending {
	background: var(--es-warning-soft);
	color: var(--es-warning);
}

/*
 * Your balances row's own Settled/Cancel action (balances.js's actionBtn) -
 * previously plain Bootstrap .btn-outline-primary/.btn-outline-danger,
 * which rendered as a blue/red pair unrelated to the app's own green/
 * orange theme. Per explicit follow-up ("change settled and cancel
 * buttons to match with the existing theme like 'Add expense' button"),
 * restyled as solid pills in the same sage/terracotta pair every other
 * primary/accent action already uses, rather than Bootstrap's default
 * outline-primary/outline-danger colours.
 */
.btn-balance-settle {
	background: var(--es-primary);
	color: #fff;
	border: none;
}

.btn-balance-settle:hover {
	background: var(--es-primary-hover);
	color: #fff;
}

.btn-balance-cancel {
	background: var(--es-accent);
	color: var(--es-accent-on);
	border: none;
}

.btn-balance-cancel:hover {
	background: var(--es-accent-hover);
	color: var(--es-accent-on);
}

/*
 * File column (between Status and Payment reference) - originally a
 * filename label stacked above a text "Upload"/"Replace" button, which
 * squeezed both into the column's narrow width and forced an aggressive
 * truncation plus a taller row than every other one in this table
 * (reported via screenshot, circled - see README follow-up). Rebuilt as a
 * single line: a small icon button first (upload arrow, one consistent
 * icon covering both "upload" and "replace" - see its title/aria-label in
 * balances.js for which), then the filename link with the rest of the
 * column's width to itself. No remove/delete control - considered and
 * explicitly not wanted. Icon/spinner are swapped via .d-none during an
 * upload - see balances.js's uploadFile(). Disabled (along with the
 * Payment reference input and the Settled/Cancel button) for the
 * duration of any one of those three actions on this row - see
 * setRowBusy() in balances.js's buildMineEntryRow().
 */
.balance-file-wrap {
	display: flex;
	align-items: center;
	gap: .4rem;
	min-width: 0;
}

.balance-file-name {
	font-size: .8rem;
	font-weight: 600;
	color: var(--es-text-subtle);
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	text-decoration: none;
}

/*
 * Only downloadable once the file is actually persisted server-side (an
 * href is set) - see balances.js's fileNameEl for when that is/isn't the
 * case. Without an href it's just an inert <a> displaying text, same look
 * as before this was made a link.
 */
.balance-file-name[href] {
	color: var(--es-primary);
	cursor: pointer;
}

.balance-file-name[href]:hover {
	text-decoration: underline;
}

.balance-file-btn {
	width: 26px;
	height: 26px;
	min-width: 26px;
	padding: 0;
	border: none;
	border-radius: var(--es-r-sm);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background: var(--es-primary-soft);
	color: var(--es-primary-hover);
	font-size: .8rem;
	flex-shrink: 0;
	transition: background-color .12s ease, color .12s ease;
}

.balance-file-btn:hover:not(:disabled) {
	background: var(--es-primary);
	color: #fff;
}

.balance-file-btn:disabled,
.balances-payment-reference-input:disabled {
	opacity: .6;
}

.balances-payment-reference-input {
	width: 100%;
}

/* Same palette/shape family as .group-avatar/.navbar-avatar (custom.css) - circular here since these represent people, not groups. */
.balance-avatar {
	width: 32px;
	height: 32px;
	min-width: 32px;
	border-radius: 50%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 12px;
	font-weight: 650;
	letter-spacing: .02em;
}

/* Two overlapping avatars for an "Other balances" row (debtor then
   creditor) - smaller than a single .balance-avatar since two now share
   the same row width, with a surface-colored ring so the overlap reads as
   two distinct circles rather than one merged blob. */
.balance-avatar-pair {
	display: inline-flex;
	flex-shrink: 0;
}

.balance-avatar-pair .balance-avatar-stacked {
	width: 26px;
	height: 26px;
	min-width: 26px;
	font-size: 11px;
	box-shadow: 0 0 0 2px var(--es-surface);
}

.balance-avatar-pair .balance-avatar-stacked + .balance-avatar-stacked {
	margin-left: -8px;
}

/*
 * "Other balances" - a single flex row per transaction, no header and no
 * fixed-width columns at all. Went through three rounds before landing
 * here, each confirmed via preview/screenshot before implementing:
 *  1. A fixed-percentage 3-column div-grid, sized to visually align with
 *     the Your balances table two sections above it.
 *  2. A 1fr-based div-grid once this section got its own header row - but
 *     a 1fr first column stretches to fill nearly the whole card width
 *     regardless of how short the content is, leaving a large gap before
 *     Status/Amount ("looks really bad").
 *  3. A real <table> with an explicit colgroup (60/15/25) - fixed the
 *     stretching, but also hit a CSS specificity bug (the Amount header
 *     and its own value didn't line up - see git history for the fix)
 *     and, per explicit follow-up ("remove column header and fit status
 *     and amount in the same first column - it should just follow the
 *     text"), turned out to be more structure than this single line of
 *     text per row actually needed.
 * Avatars, name, Status pill, and amount are now just flex children in
 * document order - each sized to its own content, no column to stretch
 * or misalign against.
 */
.balance-entry-other {
	display: flex;
	align-items: center;
	gap: .625rem;
	padding: .5rem 0;
	border-bottom: 1px solid var(--es-border);
}

.balance-entry-other:last-child {
	border-bottom: none;
}

.balance-entry-other .balance-avatar-pair {
	flex-shrink: 0;
}

.balance-entry-other .balance-entry-name {
	white-space: nowrap;
}

.balance-entry-other .balance-status-pill {
	flex-shrink: 0;
}

.balance-entry-other .balance-entry-amount {
	flex-shrink: 0;
}

.balance-entry-info {
	flex: 1 1 auto;
	min-width: 0;
}

.balance-entry-name {
	font-size: 13px;
	font-weight: 600;
}

.balance-entry-label {
	font-size: 11px;
}

.balance-entry-amount {
	font-size: 13px;
	font-weight: 720;
	white-space: nowrap;
}

.text-balance-positive {
	color: var(--es-success);
}

.text-balance-negative {
	color: var(--es-danger);
}

/*
 * "Other balances" row amount - neither party is the viewer, so this is
 * deliberately not green/red (that pair means "good/bad for the viewer"
 * elsewhere in this card). It used to be the app's primary blue; now that
 * --es-success and --es-primary share the same bold green (see tokens.css),
 * that would collide with text-balance-positive's "you're owed" green, so
 * this now uses the orange accent instead - a real three-way distinction
 * (green = you're owed, red = you owe, orange = not your row) instead of a
 * green/orange pair that looks identical at this size.
 */
.text-balance-neutral {
	color: var(--es-accent-text);
}

/*
 * Expenses tab sortable header row - server-side sort per
 * GET /api/groups/{groupId}/expenses (see groups.js's expensesState).
 */

/* Whole row gets a light green band (var(--es-primary-soft), same token
   .sidebar-link.is-active already uses for its own highlighted state)
   instead of a plain border-bottom - the header row itself is now the
   highlight, not just its active column. */
.expenses-header-row {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 8px 10px;
	background: var(--es-primary-soft);
	border-radius: var(--es-r-sm);
	margin-bottom: 6px;
}

.expenses-header-icon-spacer {
	width: 34px;
	min-width: 34px;
}

/* Blue, bold, init-cap headers (Date/Description/Amount) - was
   var(--es-text-subtle) grey with text-transform: uppercase at 11px/600;
   the label strings themselves (SORT_LABELS in groups.js) were already
   init-cap, uppercase was purely a CSS transform on top of them. Sized
   up to 13px/700 to read clearly against the header row's own green band
   (see .expenses-header-row). */
.expenses-sort-btn {
	background: none;
	border: none;
	padding: 0;
	font-size: 13px;
	font-weight: 700;
	color: var(--es-primary);
	letter-spacing: .01em;
	white-space: nowrap;
}

.expenses-sort-btn-date {
	width: 52px;
	min-width: 52px;
	text-align: left;
}

/* Same max-width cap as .expense-row-main (see its own comment) - keeps
   the "Description" header aligned with the values in the rows below it,
   now that they no longer stretch to fill the whole width. */
.expenses-sort-btn-main {
	flex: 1;
	max-width: 340px;
	text-align: left;
}

/* margin-left: auto removed - it used to push "Amount" to the header
   row's far right edge, which is exactly the gap being closed here. With
   .expenses-sort-btn-main now capped, the row's own flex gap (10px,
   see .expenses-header-row) is enough spacing on its own. Fixed width
   matching .expense-row-end's own (see its comment) so the header lines
   up with the column below it rather than just sitting near it. */
.expenses-sort-btn-end {
	text-align: right;
	flex-shrink: 0;
	width: 140px;
}

/*
 * Reserves the same trailing width every actual .expense-row's two
 * hover-reveal icon buttons occupy (32px .btn-icon each, plus the row's own
 * 10px gap between them - see .btn-edit-expense/.btn-delete-expense above)
 * even though those buttons don't exist in the header row itself. Without
 * this, "Amount" sits flush against the header's own right edge while
 * every row's real amount value is pushed further left by those two
 * icons, making the column look misaligned/overlapping the row above it.
 */
.expenses-header-actions-spacer {
	width: 74px;
	min-width: 74px;
}

/* No underline - the header row's own green band is the highlight now.
   A slightly darker green than the row's other two columns is still
   enough of a cue on top of the ↑/↓ arrow updateSortIndicators()
   already appends to the active label. */
.expenses-sort-active {
	color: var(--es-primary-active);
}

/*
 * Expense Type - searchable dropdown (#expenseTypeInput/#expenseTypeOptions
 * in list.html). Positioned absolute against the field's own
 * .position-relative wrapper so it overlays the Split section below it
 * instead of pushing it down while open.
 */
.expense-type-dropdown {
	position: absolute;
	top: 100%;
	left: 0;
	right: 0;
	z-index: 5;
	margin-top: 2px;
	max-height: 220px;
	overflow-y: auto;
	background: #fff;
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-md);
	box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
	padding: 4px;
}

.expense-type-option {
	padding: 6px 10px;
	border-radius: var(--es-r-sm);
	cursor: pointer;
	font-size: 0.875rem;
}

.expense-type-option:hover,
.expense-type-option.is-active {
	background: var(--es-surface-hover);
}

/* Currency picker (currency-combobox.js) - reuses the Expense Type dropdown
   look, with a mark on the current choice. */
.expense-type-option.is-selected {
	font-weight: 650;
	color: var(--es-primary-active);
}

.expense-type-option-empty {
	padding: 6px 10px;
	font-size: 0.875rem;
	color: var(--es-text-subtle);
}

/* ================================================== settlements page */
/*
 * Settlements Report page (settlements.html/settlements.js) - group
 * selector + "most active group" note, four summary tiles (reusing
 * .groups-summary/.groups-summary-card as-is, see their own comment
 * above), the settlement records list (with its own totals footer row),
 * and the export row.
 */

.most-active-note {
	display: inline-flex;
	align-items: center;
	gap: .3rem;
	font-size: .74rem;
	font-weight: 650;
	color: var(--es-accent-text);
	margin-bottom: .35rem;
}

/*
 * Four tiles (Total balances, Total expenses, Total settled, Your net
 * settled), not three - the only override .groups-summary itself needs.
 * Drops to 2-up at tablet width and 1-up at mobile, same breakpoint style
 * as the rest of this page's responsive rules - four
 * .groups-summary-card's worth of numbers never get cramped into a row
 * too narrow to read.
 */
.settlements-summary {
	grid-template-columns: repeat(4, 1fr);
}

@media (max-width: 991.98px) {
	.settlements-summary {
		grid-template-columns: repeat(2, 1fr);
	}
}

@media (max-width: 575.98px) {
	.settlements-summary {
		grid-template-columns: 1fr;
	}
}

/*
 * Header row - same green-band/bold-blue-label treatment as
 * .expenses-header-row/.expenses-sort-btn (see their own comments), six
 * columns instead of three: Date, From/To, Amount, Mode, Status,
 * Reference. .settlement-row below repeats the exact same
 * grid-template-columns so header and rows always line up.
 */
.settlements-header-row,
.settlement-row {
	display: grid;
	grid-template-columns: 84px 1.7fr 110px 110px 120px 1.3fr;
	gap: 12px;
	align-items: center;
}

.settlements-header-row {
	padding: 8px 12px;
	background: var(--es-primary-soft);
	border-radius: var(--es-r-sm);
	margin-bottom: 6px;
}

.settlements-header-row > * {
	font-size: 13px;
	font-weight: 700;
	color: var(--es-primary);
	letter-spacing: .01em;
	white-space: nowrap;
}

.settlements-sort-btn {
	background: none;
	border: none;
	padding: 0;
	text-align: left;
	font: inherit;
	color: inherit;
}

/* No underline - the header row's own green band is the highlight, same
   convention as .expenses-sort-active. */
.settlements-sort-active {
	color: var(--es-primary-active);
}

.settlement-row {
	padding: 12px;
	border-bottom: 1px solid var(--es-border);
}

.settlements-list .settlement-row:last-child {
	border-bottom: none;
}

.settlement-row:hover {
	background: var(--es-surface-hover);
}

.settlement-row-date {
	font-size: .8rem;
	color: var(--es-text-muted);
}

.settlement-row-parties {
	display: flex;
	align-items: center;
	gap: .4rem;
	font-weight: 650;
	font-size: .88rem;
	min-width: 0;
}

.settlement-row-parties .settlement-party {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	max-width: 45%;
}

.settlement-row-parties i {
	color: var(--es-text-subtle);
	font-size: .72rem;
	flex-shrink: 0;
}

.settlement-row-amount {
	font-weight: 700;
	color: var(--es-primary);
	text-align: right;
	font-variant-numeric: tabular-nums;
}

.mode-pill,
.status-pill {
	display: inline-block;
	font-size: .72rem;
	font-weight: 650;
	padding: .22rem .6rem;
	border-radius: var(--es-r-pill);
	white-space: nowrap;
}

.mode-pill {
	background: var(--es-surface-sunk);
	color: var(--es-text-muted);
}

.status-pill.status-paid {
	background: var(--es-primary-soft);
	color: var(--es-primary);
}

.status-pill.status-partial {
	background: var(--es-accent-soft);
	color: var(--es-accent-text);
}

.settlement-row-ref {
	font-size: .78rem;
	color: var(--es-text-subtle);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
 * Table footer "Total" row - sum of settleAmount across every row
 * matching the current search (see SettlementListResponse.totalAmount's
 * own Javadoc), not styled as another .settlement-row (it isn't one - no
 * date/parties/mode/status/reference columns to align with, just a
 * label+value pair right-aligned under the Amount column's general
 * position). Sits between the list and the pagination footer, toggled
 * d-none in lockstep with #settlementsPagination by settlements.js.
 */
.settlements-totals-row {
	display: flex;
	align-items: center;
	justify-content: flex-end;
	gap: .6rem;
	padding: 10px 12px;
	margin-top: 2px;
	border-top: 2px solid var(--es-border);
	background: var(--es-primary-soft);
	border-radius: var(--es-r-sm);
}

.settlements-totals-label {
	font-size: .8rem;
	font-weight: 700;
	color: var(--es-primary);
	text-transform: uppercase;
	letter-spacing: .03em;
}

.settlements-totals-value {
	font-weight: 700;
	font-size: 1rem;
	color: var(--es-primary);
	font-variant-numeric: tabular-nums;
}

.settlements-export-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
	margin-top: 1.1rem;
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	padding: .95rem 1.2rem;
	box-shadow: var(--es-shadow-sm);
}

.settlements-export-btns {
	display: flex;
	gap: .5rem;
	flex-wrap: wrap;
}

/*
 * Below 767.98px, the six-column grid would cram Mode/Status/Reference
 * into unreadably narrow slivers next to a long From/To name - explicit
 * requirement (confirmed with the user) that the list never crams columns
 * on a small screen. Each .settlement-row instead becomes a stacked card:
 * one label/value line per field, using the same data-label attribute
 * settlements.js already writes on every cell (renderSettlementRow()) so
 * no separate mobile-only markup/JS branch is needed - one row template
 * serves both layouts, CSS alone decides how it reads.
 */
@media (max-width: 767.98px) {
	.settlements-header-row {
		display: none;
	}

	.settlement-row {
		display: flex;
		flex-direction: column;
		/* The desktop grid rule's align-items: center would shrink each
		   label/value line to its content and centre it in the card. */
		align-items: stretch;
		gap: .5rem;
		padding: .9rem .95rem;
		border: 1px solid var(--es-border);
		border-radius: var(--es-r-md);
		margin-bottom: .6rem;
		box-shadow: var(--es-shadow-sm);
	}

	.settlements-list .settlement-row:last-child {
		margin-bottom: 0;
		border-bottom: 1px solid var(--es-border);
	}

	.settlement-row > * {
		display: flex;
		align-items: baseline;
		justify-content: space-between;
		gap: .75rem;
		font-size: .85rem;
	}

	.settlement-row > *::before {
		content: attr(data-label);
		font-size: .68rem;
		font-weight: 700;
		text-transform: uppercase;
		letter-spacing: .03em;
		color: var(--es-text-subtle);
		flex-shrink: 0;
	}

	.settlement-row-parties {
		flex-wrap: wrap;
		justify-content: flex-end;
		text-align: right;
	}

	/* Keep the FROM -> TO label pinned left like every other line; the
	   names stay right-aligned (and wrap to the right) after it. */
	.settlement-row-parties::before {
		margin-right: auto;
	}

	.settlement-row-parties .settlement-party {
		max-width: none;
	}

	.settlement-row-amount,
	.settlement-row-ref {
		text-align: right;
	}

	/* Full width like the other lines (label left); only the text wraps,
	   right-aligned, in the space the label leaves. Previously max-width +
	   margin-left: auto shrank the whole line - label included - to the
	   right-hand side. */
	.settlement-row-ref {
		white-space: normal;
		overflow: visible;
		text-overflow: clip;
	}

	.settlements-export-row {
		flex-direction: column;
		align-items: stretch;
	}

	.settlements-export-btns {
		justify-content: stretch;
	}

	.settlements-export-btns .btn {
		flex: 1 1 auto;
	}
}

/* ============================================= group dashboard (Phase 2.1) */
/*
 * Gen-Z refresh Phase 2 ("Option C" - see README): the per-group dashboard
 * page (groups/dashboard.html) that replaces the Groups list page's old
 * shared tabbed offcanvas drawer. One page per group instead of one drawer
 * for all of them - no tabs, everything either shown directly or one
 * "View all"/modal away.
 *
 * Phase 2.1 (this section, current) restyles that same page as a gradient
 * "Hero" banner + a 2-card "Bento" grid, per the redesign spec in README -
 * see dashboard.html's own comment above .dashboard-hero for what moved
 * where and why. Flat cards with a soft shadow only throughout (confirmed
 * over neumorphism/glassmorphism via AskUserQuestion) - the one deliberate
 * exception is the hero itself, which is the "personality" surface the
 * spec asked for.
 */

.dashboard-back {
	display: inline-flex;
	align-items: center;
	gap: .4rem;
	font-size: .85rem;
	font-weight: 600;
	text-decoration: none;
}

/* -------------------------------------------------------------- hero ---- */

.dashboard-hero {
	position: relative;
	overflow: hidden;
	border-radius: 0 0 28px 28px;
	margin: 0 -0.25rem 1.5rem;
	padding: 1.75rem 1.5rem 1.5rem;
	/* Mid-stop was a hardcoded brighter green (#1f9a4a); now the shared
	   --es-primary-mid token (tokens.css) so this blend, the featured group
	   card (.gf-N above) and the group-card hover glow (below) all use the
	   exact same middle tone rather than three slightly different greens. */
	background: linear-gradient(135deg, var(--es-primary) 0%, var(--es-primary-mid) 45%, var(--es-accent) 130%);
	color: #fff;
	box-shadow: var(--es-shadow-md);
}

/* Purely decorative - the soft circle the approved preview used to keep the
   gradient from reading as a flat rectangle. aria-hidden not needed, it's a
   background pseudo-element with no content. */
.dashboard-hero::after {
	content: "";
	position: absolute;
	right: -12%;
	top: -35%;
	width: 55%;
	aspect-ratio: 1;
	border-radius: 50%;
	background: rgba(255, 255, 255, .08);
	pointer-events: none;
}

/*
 * Title row: avatar/name/pills on the left, "All groups" + lock + gear
 * together on the right - all one line now (previously "All groups" +
 * icons had their own row above the avatar/name; moved here per in-chat
 * follow-up, previewed first as dashboard-hero-alignment-preview.html).
 * .dashboard-hero-nav is reused below for the amount row's Invite/View all
 * pair too - it's just a flex-row layout wrapper, not tied to icon buttons
 * specifically - but only THIS usage (as the toprow's second child) should
 * get pushed to the far right, hence the child-combinator margin-left
 * rather than putting it on .dashboard-hero-nav itself.
 */
.dashboard-hero-toprow {
	position: relative;
	z-index: 1;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: .75rem;
	flex-wrap: wrap;
}

.dashboard-hero-nav {
	display: flex;
	align-items: center;
	gap: .6rem;
}

.dashboard-hero-toprow > .dashboard-hero-nav {
	margin-left: auto;
	flex-shrink: 0;
}

.dashboard-hero .dashboard-back {
	color: rgba(255, 255, 255, .88);
}

.dashboard-hero .dashboard-back:hover {
	color: #fff;
}

/*
 * The lock/gear buttons reuse the shared .btn-icon markup (see custom.css)
 * for their structure/click target, restyled here as translucent-white
 * circles so they read against the gradient instead of .btn-icon's default
 * white-square-on-light-page look.
 */
.dashboard-hero .btn-icon {
	background: rgba(255, 255, 255, .16);
	border-color: transparent;
	border-radius: var(--es-r-pill);
	color: #fff;
}

.dashboard-hero .btn-icon:hover:not(:disabled) {
	background: rgba(255, 255, 255, .26);
	border-color: transparent;
	color: #fff;
}

.dashboard-hero .btn-icon:disabled {
	color: rgba(255, 255, 255, .6);
}

.dashboard-hero-identity {
	display: flex;
	align-items: center;
	gap: .75rem;
	min-width: 0;
}

.dashboard-hero-avatar {
	width: 52px;
	height: 52px;
	min-width: 52px;
	font-size: 1.05rem;
	border: 2px solid rgba(255, 255, 255, .55);
}

.dashboard-hero-title {
	margin: 0;
	font-family: var(--font-heading);
	font-weight: 700;
	font-size: 1.4rem;
	letter-spacing: -.01em;
}

/* Pills reuse .group-pill/.group-pill-muted's structure but need a
   consistent light-on-gradient treatment regardless of which badge-color-N
   class Thymeleaf (or groups-dashboard.js, for pills it builds client-side)
   happened to assign - a saturated badge colour would clash with the
   gradient rather than sit on top of it. */
.dashboard-hero .group-pill,
.dashboard-hero .group-pill-muted {
	background: rgba(255, 255, 255, .2);
	color: #fff;
	border-color: transparent;
}

.dashboard-hero-desc {
	position: relative;
	z-index: 1;
	color: rgba(255, 255, 255, .85);
	font-size: .88rem;
	margin: .85rem 0 0;
	/* A description with no spaces (an unbroken run of characters) can't
	   wrap at a word boundary and would otherwise force this element - and
	   the hero itself - wider than intended. */
	overflow-wrap: break-word;
	word-break: break-word;
}

.dashboard-hero-owner {
	position: relative;
	z-index: 1;
	display: flex;
	align-items: flex-start;
	gap: .4rem;
	color: rgba(255, 255, 255, .78);
	font-size: .82rem;
	margin-top: .35rem;
}

.dashboard-hero-owner > .bi {
	margin-top: .15rem;
}

/*
 * Amount row: Invite/View all (via .dashboard-hero-nav) on the left, the
 * amount on the right. margin-left: auto + flex-shrink: 0 keeps the amount
 * pinned to the right edge even if this row ever wraps on a narrow screen -
 * the same defensive pattern .dashboard-hero-toprow's nav uses above, and
 * the fix for the earlier bug where a lone wrapped flex item landed left
 * instead of right.
 */
.dashboard-hero-amount-row {
	position: relative;
	z-index: 1;
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
	margin-top: 1.1rem;
}

.dashboard-hero-amount-block {
	text-align: right;
	margin-left: auto;
	flex-shrink: 0;
}

.dashboard-hero-amount {
	font-family: var(--font-heading);
	font-weight: 800;
	font-size: 2.5rem;
	line-height: 1;
	letter-spacing: -.02em;
}

.dashboard-hero-amount-label {
	font-size: .78rem;
	font-weight: 550;
	color: rgba(255, 255, 255, .82);
	margin-top: .35rem;
}

/*
 * Stat links on the gradient heroes (dashboard Total spent; Groups page
 * Most active card's Total spent / Your balance) and the group cards'
 * balance chip. White (or the chip's own colour) at rest with a small
 * arrow; link blue + underline on hover/focus. On the dark hero the blue is
 * a light link blue - Bootstrap's #0d6efd would be close to unreadable
 * there; the chip sits on a white card, so it uses the standard link blue.
 */
.hero-stat-link {
	display: block;
	color: inherit;
	text-decoration: none;
	border-radius: var(--es-r-sm);
}

.hero-stat-arrow {
	display: inline-block;
	margin-left: .2rem;
	font-size: .75em;
	transition: transform .15s ease;
}

.hero-stat-link:hover,
.hero-stat-link:focus-visible,
.hero-stat-link:hover .dashboard-hero-amount-label,
.hero-stat-link:focus-visible .dashboard-hero-amount-label {
	color: #8ab4f8;
}

.hero-stat-link:hover .hero-stat-value,
.hero-stat-link:focus-visible .hero-stat-value {
	text-decoration: underline;
	text-decoration-thickness: 2px;
	text-underline-offset: .3em;
}

.hero-stat-link:hover .hero-stat-arrow,
.hero-stat-link:focus-visible .hero-stat-arrow,
.group-chip-link:hover .hero-stat-arrow,
.group-chip-link:focus-visible .hero-stat-arrow {
	transform: translateX(3px);
}

.hero-stat-link:focus-visible {
	outline: 2px solid #8ab4f8;
	outline-offset: 4px;
}

.group-chip-link {
	align-items: center;
	text-decoration: none;
}

.group-chip-link:hover,
.group-chip-link:focus-visible {
	color: #0d6efd;
	text-decoration: underline;
	text-underline-offset: .2em;
}

/* Settlements page: Total balances / Total expenses tiles. Same light link
   blue as the Groups page / dashboard hero links (asked for consistency).
   The whole tile is the link. */
.summary-tile-link {
	color: inherit;
	text-decoration: none;
	transition: box-shadow .14s ease;
}

.summary-tile-link:hover,
.summary-tile-link:focus-visible {
	color: inherit;
	box-shadow: var(--es-shadow-md);
}

.summary-tile-link:hover .gs-value,
.summary-tile-link:focus-visible .gs-value {
	color: #8ab4f8;
	text-decoration: underline;
	text-decoration-thickness: 2px;
	text-underline-offset: .25em;
}

.summary-tile-link:hover .hero-stat-arrow,
.summary-tile-link:focus-visible .hero-stat-arrow {
	transform: translateX(3px);
}

.summary-tile-link:focus-visible {
	outline: 2px solid #8ab4f8;
	outline-offset: 3px;
}

/* --------------------------------------------------- hero member preview */

/* Just the avatar stack now - Invite/View all moved up onto the amount
   row above (see .dashboard-hero-amount-row), so this is a single-child
   row and doesn't need to negotiate alignment with anything else. */
.dashboard-hero-members {
	position: relative;
	z-index: 1;
	margin-top: 1.4rem;
	padding-top: 1rem;
	border-top: 1px solid rgba(255, 255, 255, .22);
}

/* Horizontal-scrolling row of avatars, per the redesign spec ("move members
   to a horizontal scrolling row... at the top") - overrides the base
   .member-avatar-stack's wrap behaviour (used unchanged elsewhere, if this
   class is ever reused outside the hero) rather than changing that class
   itself. Scrollbar hidden cross-browser since the row is short and the
   affordance is obvious from the overlapping avatars themselves. */
/*
 * Base member-avatar-stack styling. This was accidentally dropped entirely
 * in the Phase 2.1 rewrite (the old "Members" bento card's copy of these
 * rules was removed along with that card, but never re-added when the
 * stack itself moved into the hero) - .member-avatar-stack had no
 * display: flex at all, so its <span> children never got blockified and
 * every width/height/border-radius on .member-stack-avatar was silently
 * ignored. That is what "member icons not rendered properly" actually was
 * - restored here, unscoped, since this element only lives in the hero
 * today but there is no reason its base shape should live inside a
 * .dashboard-hero-scoped block.
 */
.member-avatar-stack {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
}

.member-stack-avatar {
	width: 34px;
	height: 34px;
	min-width: 34px;
	border-radius: 50%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 11px;
	font-weight: 720;
	border: 2px solid var(--es-surface);
	box-shadow: 0 0 0 1px var(--es-border);
}

/* Overlapping "joined" avatar stack, same pattern as the standalone
   Balances page's own .balance-avatar-pair - each avatar after the first
   pulls left under its neighbour, and the solid border above is what turns
   the overlap into a visible ring rather than two circles just touching. */
.member-stack-avatar + .member-stack-avatar {
	margin-left: -10px;
}

.member-stack-more {
	background: var(--es-surface-sunk);
	color: var(--es-text-muted);
}

.member-stack-summary {
	margin-left: .6rem;
	font-size: .8rem;
	color: var(--es-text-subtle);
}

.dashboard-hero .member-avatar-stack {
	flex-wrap: nowrap;
	overflow-x: auto;
	scrollbar-width: none;
	/* Letting a flex container scroll (overflow != visible) drops each
	   item's automatic minimum size to 0 per spec, so without an explicit
	   floor every avatar below gets crushed toward zero width instead of
	   actually overflowing into the scroll area - this is what "rounded
	   icons not rendered properly" turned out to be. min-width: 0 here is
	   just letting the scroll container itself shrink in its own row. */
	min-width: 0;
	/*
	 * Setting overflow-x also forces overflow-y to a non-visible value per
	 * spec (a scroll container clips both axes, not just the one you set),
	 * and .member-stack-avatar's box-shadow ring extends past its own box -
	 * with zero padding here that ring's top/bottom edge was getting
	 * clipped by this container's own box, i.e. avatars looked like they
	 * had a flat side instead of a full circle. This padding gives the
	 * ring room to render without adding visible whitespace (the hero's
	 * own gradient shows through).
	 */
	padding: 3px 2px;
}

.dashboard-hero .member-avatar-stack::-webkit-scrollbar {
	display: none;
}

.dashboard-hero .member-avatar-stack > * {
	flex-shrink: 0;
}

.dashboard-hero-members-loading {
	font-size: .82rem;
	color: rgba(255, 255, 255, .75);
}

.dashboard-hero .member-stack-more {
	background: rgba(255, 255, 255, .22);
	color: #fff;
}

.dashboard-hero .member-stack-summary {
	color: rgba(255, 255, 255, .85);
}

.dashboard-hero-chip {
	display: inline-flex;
	align-items: center;
	gap: .35rem;
	font-size: .78rem;
	font-weight: 650;
	color: #fff;
	background: rgba(255, 255, 255, .18);
	border: none;
	border-radius: var(--es-r-pill);
	padding: .4rem .8rem;
	transition: background-color .12s ease;
}

.dashboard-hero-chip:hover {
	background: rgba(255, 255, 255, .3);
}

/*
 * WhatsApp quick-share - same pill as Invite/View all above, just
 * icon-only/square instead of icon+label (a third full-width label here
 * would crowd this row at narrow widths). Hover tints toward WhatsApp's own
 * brand green rather than the generic white overlay every other chip uses,
 * as a small hint of what it does.
 */
.dashboard-hero-chip-wa {
	width: 34px;
	height: 34px;
	padding: 0;
	justify-content: center;
	flex-shrink: 0;
}

.dashboard-hero-chip-wa:hover {
	background: #25D366;
}

@media (max-width: 575.98px) {
	.dashboard-hero-amount-row {
		align-items: flex-start;
	}

	.dashboard-hero-amount-block {
		text-align: left;
		margin-left: 0;
	}
}

/* ------------------------------------------------------------- bento grid */

.dashboard-bento {
	display: grid;
	grid-template-columns: 1.4fr 1fr;
	gap: 1rem;
	margin-bottom: 6rem; /* room for the floating Add Expense button below */
}

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

.dashboard-card {
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	box-shadow: var(--es-shadow-sm);
	padding: 1.1rem 1.25rem;
}

.dashboard-card-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: .75rem;
	margin-bottom: .75rem;
}

.dashboard-card-title {
	font-family: var(--font-heading);
	font-weight: 700;
	font-size: .95rem;
}

.dashboard-card-viewall {
	font-size: .82rem;
	font-weight: 600;
	color: var(--es-primary);
	text-decoration: none;
	border: none;
	background: none;
}

.dashboard-card-viewall:hover {
	text-decoration: underline;
}

/* --------------------------------------------------------- recent expenses */

.feed-row {
	display: flex;
	align-items: center;
	gap: .75rem;
	padding: .6rem 0;
	border-bottom: 1px solid var(--es-border);
}

.feed-row:last-child {
	border-bottom: none;
}

/* One shared icon-circle style; the glyph inside changes per expense
   category (see EXPENSE_TYPE_ICONS in groups-dashboard.js). A per-category
   background colour was considered and dropped - 17 expense types is too
   many to give each a distinct, legible pastel without it turning into
   noise, and the icon glyph itself already carries the "what kind of
   expense is this" signal the spec asked for. */
.feed-icon {
	width: 38px;
	height: 38px;
	min-width: 38px;
	border-radius: var(--es-r-md);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 1rem;
	background: var(--es-accent-soft);
	color: var(--es-accent-text);
}

/*
 * A flex ITEM (of .feed-row) isn't automatically a flex CONTAINER for its
 * own children - .feed-desc/.feed-sub are plain inline <span>s underneath
 * it, so without this they flow side-by-side on one line instead of
 * stacking (the "Fuel For Car28-Aug-26 · ..." run-together bug). display:
 * flex here blockifies them as its own flex items instead.
 */
.feed-main {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: .1rem;
}

.feed-desc {
	min-width: 0;
	font-weight: 600;
	font-size: .87rem;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.feed-sub {
	font-size: .76rem;
	color: var(--es-text-subtle);
}

.feed-amount {
	font-weight: 700;
	font-size: .88rem;
	white-space: nowrap;
}

/* ----------------------------------------------------------------- balances */

.mini-balance-summary {
	font-size: .88rem;
	font-weight: 600;
	margin-bottom: .8rem;
}

.balance-row {
	display: flex;
	align-items: center;
	gap: .6rem;
	padding: .55rem 0;
	border-top: 1px solid var(--es-border);
}

.balance-row:first-of-type {
	border-top: none;
}

.balance-avatar {
	width: 30px;
	height: 30px;
	min-width: 30px;
	border-radius: 50%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: 10px;
	font-weight: 720;
}

/* Same fix, same reason as .feed-main above - .balance-name/.balance-bar-
   track are plain <span>s under a flex ITEM, not a flex container, so
   without this they sat on one line instead of stacking and the bar's
   height:6px was silently ignored (height has no effect on a non-flex,
   non-replaced inline element) - the "amount share bar is missing" bug. */
.balance-info {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: .3rem;
}

.balance-name {
	min-width: 0;
	font-size: .82rem;
	font-weight: 600;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
 * The "debt bubble" bar. Its fill % is relative magnitude within the
 * viewer's own set of relationships in this group (see
 * BALANCE_BAR_BASELINE's comment in groups-dashboard.js) - not literal
 * progress toward zero, since a GroupBalance row has no partial-settlement
 * fraction to read that from - a judgment call documented in README.
 */
.balance-bar-track {
	height: 6px;
	border-radius: var(--es-r-pill);
	background: var(--es-surface-sunk);
	overflow: hidden;
}

/*
 * .balance-bar-fill is a <span> nested inside .balance-bar-track (also a
 * <span>), which is a plain block box, not a flex/grid container - so the
 * fill never got blockified the way .balance-bar-track itself did once its
 * own parent (.balance-info) became a flex column. A non-replaced inline
 * element ignores both width and height entirely (they compute to "auto"),
 * so the width:NN% JS sets inline and this height: 100% were both silently
 * no-ops - this is what "amount share bar is missing" actually was (the
 * track was visible as a thin fixed-height sliver; the coloured fill inside
 * it never had any size at all). display: block is the fix.
 */
.balance-bar-fill {
	display: block;
	height: 100%;
	border-radius: var(--es-r-pill);
}

.balance-bar-fill.owed {
	background: var(--es-success);
}

.balance-bar-fill.owe {
	background: var(--es-danger);
}

.balance-amt {
	font-size: .82rem;
	font-weight: 700;
	white-space: nowrap;
	text-align: right;
}

.balance-amt.owed {
	color: var(--es-success);
}

.balance-amt.owe {
	color: var(--es-danger);
}

/* Pastel, not banking-app saturated - the *-soft tokens (tokens.css) rather
   than the full-strength success/danger colours, per the spec's explicit
   "muted, pastel tones... to keep it from looking like a banking app". */
.settle-btn {
	font-size: .72rem;
	font-weight: 650;
	padding: .35rem .65rem;
	border-radius: var(--es-r-pill);
	border: none;
	white-space: nowrap;
	flex-shrink: 0;
}

.settle-btn.remind {
	background: var(--es-success-soft);
	color: var(--es-success);
}

.settle-btn.remind:hover:not(:disabled) {
	background: var(--es-primary-border);
}

.settle-btn.pay {
	background: var(--es-danger-soft);
	color: var(--es-danger);
}

.settle-btn.pay:hover:not(:disabled) {
	background: #f3c8bd;
}

.settle-btn:disabled {
	opacity: .6;
}

/* -------------------------------------------------- floating Add Expense */

/*
 * Bold orange accent (.btn-accent, tokens.css), not the primary green -
 * this is the one action on the page meant to draw the eye above
 * everything else. Fixed to the viewport (not .dashboard-bento) so it stays
 * reachable regardless of scroll position, same reasoning a mobile app's
 * own FAB would use. The glow (fab-glow below) is the "tactile" animation
 * the redesign spec asked for.
 */
.fab-add-expense {
	position: fixed;
	right: 1.75rem;
	bottom: 1.75rem;
	z-index: 1030;
	border-radius: var(--es-r-pill);
	padding: .85rem 1.4rem;
	box-shadow: 0 0 0 0 rgba(215, 181, 109, .45), var(--es-shadow-lg);
	font-weight: 700;
	animation: fab-glow 2.4s ease-in-out infinite;
}

@keyframes fab-glow {
	0% {
		box-shadow: 0 0 0 0 rgba(215, 181, 109, .45), var(--es-shadow-lg);
	}

	70% {
		box-shadow: 0 0 0 14px rgba(215, 181, 109, 0), var(--es-shadow-lg);
	}

	100% {
		box-shadow: 0 0 0 0 rgba(215, 181, 109, 0), var(--es-shadow-lg);
	}
}

@media (prefers-reduced-motion: reduce) {
	.fab-add-expense {
		animation: none;
	}
}

@media (max-width: 767.98px) {
	.fab-add-expense {
		right: 1rem;
		bottom: 1rem;
		padding: .85rem 1rem;
	}
}

/* ------------------------------------------------- quick join (WhatsApp) */
/*
 * Invite modal's WhatsApp group-link section (below the existing email
 * form) - previewed as whatsapp-invite-preview.html before implementation.
 * join_link/join_code are read-only display values (Phase 1 already
 * generated them at group creation), so nothing here is a form control.
 */
.quickjoin-divider {
	display: flex;
	align-items: center;
	gap: .75rem;
	margin: 1.1rem 0 .85rem;
	color: var(--es-text-subtle);
	font-size: .7rem;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: .04em;
}

.quickjoin-divider::before, .quickjoin-divider::after {
	content: "";
	flex: 1;
	height: 1px;
	background: var(--es-border);
}

.quickjoin-message {
	display: flex;
	align-items: flex-start;
	gap: .55rem;
	background: var(--es-primary-soft);
	color: var(--es-primary-active);
	border-radius: var(--es-r-sm);
	padding: .6rem .75rem;
	margin-bottom: .9rem;
	font-size: .83rem;
	line-height: 1.4;
}

.quickjoin-field {
	margin-bottom: .9rem;
}

.quickjoin-field-last {
	margin-bottom: 1.1rem;
}

.quickjoin-row {
	display: flex;
	align-items: center;
	gap: .5rem;
	background: var(--es-surface-sunk);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-sm);
	padding: .5rem .6rem;
}

.quickjoin-value {
	flex: 1;
	min-width: 0;
	font-size: .83rem;
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	color: var(--es-text);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.quickjoin-copy {
	flex-shrink: 0;
	width: 28px;
	height: 28px;
	border: none;
	border-radius: 8px;
	background: transparent;
	color: var(--es-primary);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	transition: background-color .12s ease, color .12s ease;
}

.quickjoin-copy:hover {
	background: var(--es-primary-soft);
}

.wa-share-btn {
	width: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: .5rem;
	background: #25D366;
	color: #fff;
	border: none;
	border-radius: var(--es-r-sm);
	padding: .6rem .9rem;
	font-weight: 700;
	font-size: .85rem;
	transition: filter .12s ease, transform .12s ease;
}

.wa-share-btn:hover {
	filter: brightness(1.06);
}

.wa-share-btn:active {
	transform: translateY(1px);
}

.quickjoin-wa-hint {
	text-align: center;
	margin-top: .5rem;
}

/* ====================================================== reports page */
/*
 * Reports Tab (reports.html/reports.js) - group selector (reuses
 * .balances-group-select-wrap/.group-spend-tile-inline/.most-active-note
 * as-is, same pattern as Settlements/Expenses), the filter bar below, two
 * Chart.js chart cards, and the filtered expense table underneath (reuses
 * .balances-card/.expenses-header-row/.expense-row/.expenses-pagination
 * verbatim - same list styling as the Expenses page, just fed by
 * GET .../reports/expenses instead of the free-text-search-driven
 * GET .../expenses).
 */

.reports-filter-card {
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	padding: 1rem 1.1rem;
	margin-bottom: 1rem;
}

.reports-filter-grid {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: 1rem;
}

@media (max-width: 991.98px) {
	.reports-filter-grid {
		grid-template-columns: repeat(2, 1fr);
	}
}

@media (max-width: 575.98px) {
	.reports-filter-grid {
		grid-template-columns: 1fr;
	}
}

/* min-height keeps an empty Paid by/Shared with chip row (a brand-new
   group with only the viewer in it) from collapsing to a sliver next to
   its taller siblings. */
.reports-filter-group .expense-chip-row {
	min-height: 34px;
}

/* Per-filter header: label + count on the left, Select all · Clear right. */
.reports-filter-head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: .25rem .5rem;
	margin-bottom: .4rem;
}

.reports-filter-count {
	font-size: .78rem;
	font-weight: 500;
	color: var(--es-text-muted);
}

.reports-filter-actions {
	display: inline-flex;
	align-items: baseline;
	gap: .3rem;
	font-size: .78rem;
	color: var(--es-text-subtle);
	white-space: nowrap;
}

.reports-filter-action {
	padding: 0;
	border: 0;
	font-size: .78rem;
	font-weight: 600;
	line-height: inherit;
	vertical-align: baseline;
	color: var(--es-primary);
	text-decoration: none;
}

.reports-filter-action:hover,
.reports-filter-action:focus-visible {
	color: var(--es-primary-active);
	text-decoration: underline;
}

.reports-filter-action:disabled {
	color: var(--es-text-subtle);
	opacity: 1;
}

.reports-filter-search {
	position: relative;
	margin-bottom: .5rem;
}

.reports-filter-search .bi-search {
	position: absolute;
	left: .6rem;
	top: 50%;
	transform: translateY(-50%);
	font-size: .75rem;
	color: var(--es-text-subtle);
	pointer-events: none;
}

.reports-filter-search input {
	padding-left: 1.75rem;
}

.reports-filter-nomatch {
	font-size: .78rem;
	color: var(--es-text-muted);
	margin: .25rem 0 0;
}

.reports-filter-footer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: .75rem;
	margin-top: 1rem;
	padding-top: 1rem;
	border-top: 1px solid var(--es-border);
}

.reports-groupby-label {
	font-size: .8rem;
	font-weight: 650;
	color: var(--es-text-muted);
}

/*
 * Segmented Day/Week/Month control - a single bordered pill split into
 * three buttons (border-radius on the group, not each button, so it reads
 * as one control), same visual family as .btn-group without pulling in
 * Bootstrap's own btn-group JS/markup for something this simple.
 */
.reports-groupby-toggle {
	display: inline-flex;
	border: 1px solid var(--es-border-strong);
	border-radius: var(--es-r-pill);
	overflow: hidden;
}

.reports-groupby-btn {
	border: none;
	background: var(--es-surface-sunk);
	color: var(--es-text-muted);
	font-size: .8rem;
	font-weight: 600;
	padding: .35rem .85rem;
	cursor: pointer;
	transition: background-color .12s ease, color .12s ease;
}

.reports-groupby-btn + .reports-groupby-btn {
	border-left: 1px solid var(--es-border-strong);
}

.reports-groupby-btn:hover {
	background: var(--es-surface-hover);
}

.reports-groupby-btn.is-active {
	background: var(--es-primary);
	color: #fff;
}

.reports-groupby-auto-note {
	font-size: .74rem;
	color: var(--es-text-subtle);
}

.reports-charts-grid {
	display: grid;
	grid-template-columns: 1fr 1.3fr;
	gap: 1rem;
	margin-bottom: 1rem;
}

@media (max-width: 991.98px) {
	.reports-charts-grid {
		grid-template-columns: 1fr;
	}
}

.reports-chart-card {
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	padding: 1rem 1.1rem;
}

.reports-chart-head {
	display: flex;
	align-items: baseline;
	gap: .4rem;
	margin-bottom: .75rem;
}

.reports-chart-title {
	font-size: .85rem;
	font-weight: 700;
}

.reports-chart-subtitle {
	font-size: .78rem;
	color: var(--es-text-subtle);
}

.reports-chart-canvas-wrap {
	position: relative;
	height: 220px;
}

.reports-chart-empty {
	text-align: center;
	color: var(--es-text-subtle);
	font-size: .85rem;
	padding: 3rem 0;
}

.reports-expenses-head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	margin-bottom: .5rem;
}

/*
 * Export row (confirmed via AskUserQuestion: "filtered expense list +
 * summary", respecting whatever's currently applied) - same visual shape as
 * .settlements-export-row/.settlements-export-btns, duplicated under its own
 * .reports-* prefix rather than shared, matching this page's existing
 * convention of its own class prefix throughout (reports.html/reports.js/
 * this file's own "reports page" section) rather than reaching into another
 * page's feature-named classes.
 */
.reports-export-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
	margin-top: 1rem;
	background: var(--es-surface);
	border: 1px solid var(--es-border);
	border-radius: var(--es-r-lg);
	padding: .95rem 1.2rem;
	box-shadow: var(--es-shadow-sm);
}

.reports-export-btns {
	display: flex;
	gap: .5rem;
	flex-wrap: wrap;
}

@media (max-width: 575.98px) {
	.reports-export-row {
		flex-direction: column;
		align-items: stretch;
	}

	.reports-export-btns {
		justify-content: stretch;
	}

	.reports-export-btns .btn {
		flex: 1 1 auto;
	}
}


/* ---- SplytEazy brand ------------------------------------------------- */
.brand-lockup {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	text-decoration: none;
}

.brand-icon {
	display: block;
	flex: none;
}

.brand-wordmark {
	font-family: var(--font-heading);
	font-weight: 600;
	letter-spacing: -.3px;
	line-height: 1;
	color: #16233F;
}

.brand-wordmark .eazy {
	color: #E8702E;
}

.brand-lockup-on-dark .brand-wordmark {
	color: #fff;
}

/* White rounded tile that holds the icon on dark / blue backgrounds. */
.brand-tile {
	display: inline-flex;
	background: #fff;
	border-radius: 9px;
	padding: 4px;
}

.auth-brand .brand-lockup {
	gap: 8px;
}

.auth-brand .brand-tile {
	padding: 6px;
	border-radius: 12px;
}

.auth-brand .brand-tile .brand-icon {
	width: 32px;
	height: 32px;
}

.auth-brand .brand-wordmark {
	font-size: 1.5rem;
}

.sidebar-brand .brand-wordmark {
	font-size: 1.1rem;
}

.brand-strip {
	display: flex;
	align-items: center;
	padding: .5rem 1rem;
	background: #16233F;
	font-family: var(--font-heading);
}

.brand-strip .brand-tile {
	padding: 3px;
	border-radius: 7px;
}

.brand-strip .brand-tile .brand-icon {
	width: 20px;
	height: 20px;
}

.brand-strip .brand-wordmark {
	font-size: 1rem;
}

.brand-tagline {
	margin-left: .875rem;
	padding-left: .875rem;
	border-left: 1px solid #2C4B7C;
	font-size: .8125rem;
	font-weight: 500;
	color: #cbd3e6;
}

/* Brand strip is the desktop top bar: pinned, with a little space above it. */
@media (min-width: 992px) {
	.app-main {
		padding-top: var(--es-strip-gap);
	}

	/* Sidebar logo row ends on the same line as the strip's bottom edge. */
	.sidebar-brand {
		height: calc(var(--es-strip-gap) + var(--es-strip-h));
		padding-top: var(--es-strip-gap);
	}

}

@media (max-width: 991.98px) {
	.brand-strip {
		margin-top: 10px;
	}
}

.brand-strip {
	flex-wrap: wrap;
	min-height: var(--es-strip-h);
	/* Pinned at every size: below lg it also carries the menu button. */
	position: sticky;
	top: 0;
	z-index: 1020;
}

/* Menu button inside the dark strip (below lg). */
.brand-strip .strip-toggle {
	width: 34px;
	height: 34px;
	margin-right: .75rem;
	background: transparent;
	color: #fff;
	border-color: rgba(255, 255, 255, .3);
	font-size: 1.15rem;
}

.brand-strip .strip-toggle:hover,
.brand-strip .strip-toggle:focus-visible {
	background: rgba(255, 255, 255, .12);
	color: #fff;
}

/* Groups search lives in the strip (dark, translucent field). */
.brand-strip .topbar-search {
	flex: 0 1 300px;
	margin-left: 1.4rem;
	max-width: none;
}

.brand-strip .topbar-search i {
	color: #cbd3e6;
}

.brand-strip .topbar-search input {
	height: 32px;
	font-size: .8125rem;
	color: #fff;
	background: rgba(255, 255, 255, .12);
	border: 1px solid rgba(255, 255, 255, .22);
}

.brand-strip .topbar-search input::placeholder {
	color: #cbd3e6;
}

.brand-strip .topbar-search input:focus {
	background: rgba(255, 255, 255, .18);
	border-color: rgba(255, 255, 255, .5);
	box-shadow: none;
	color: #fff;
}

/* Narrow screens: search drops to its own full-width row under logo + avatar. */
@media (max-width: 767.98px) {
	.brand-strip .topbar-search {
		order: 3;
		flex: 1 1 100%;
		margin: .5rem 0 0;
	}
}

.brand-strip .topbar-user {
	color: #fff;
}

.brand-strip .topbar-user:hover {
	background: rgba(255, 255, 255, .12);
	color: #fff;
}

/* Groups page never scrolls sideways (clip, not hidden, so the sticky strip keeps working). */
.groups-page {
	overflow-x: clip;
}

/* ------------------------------------------------------------------ footer */

/* Support contact, on every page (fragments/footer.html). */
.app-footer {
	padding: 1rem 1.25rem;
	border-top: 1px solid var(--es-border);
	text-align: center;
	font-size: .8125rem;
	color: var(--es-text-muted);
}

.app-footer .bi {
	margin-right: .25rem;
}

/* SplytEazy % mark before the support text, in a small white circle with a
   thin border so it reads as a badge on the grey page background. */
.app-footer-badge {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 22px;
	height: 22px;
	border-radius: 50%;
	background: #fff;
	border: 1px solid var(--es-border);
	margin-right: 4px;
	/* Centre the circle on the text's middle (x-height), not its baseline;
	   the 1px lift corrects for the footer font's tall ascenders. */
	vertical-align: middle;
	position: relative;
	top: -1px;
}

.app-footer-logo {
	display: block;
	width: 13px;
	height: 13px;
}

.app-footer a {
	color: var(--es-primary);
	font-weight: 600;
	text-decoration: underline;
	text-underline-offset: 2px;
	word-break: break-all;
}

/* Sign-in pages: card stays centred, footer sits at the bottom of the panel. */
.auth-panel > :first-child {
	margin-top: auto;
}

.auth-panel > .app-footer {
	margin-top: auto;
	width: 100%;
	border-top: 0;
	padding-bottom: 0;
}

/* ------------------------------------------------- Your balances: card layout */

/*
 * Below 1200px the 8-column "Your balances" table cannot fit (with the sidebar
 * open it is already tight on small laptops), so each row becomes a card:
 *   member ............ amount
 *   Status   | File
 *   Payment reference (full width)
 *   Date     | Mode
 *   action button (full width)
 * Cells keep their order and behaviour; only the layout changes. Labels come
 * from each cell's data-label (set in balances.js), the table header is hidden.
 */
@media (max-width: 1199.98px) {
	.balances-mine-table,
	.balances-mine-table tbody {
		display: block;
		width: 100%;
	}

	.balances-mine-table colgroup,
	.balances-mine-table thead {
		display: none;
	}

	.balances-mine-table tbody tr {
		display: grid;
		grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
		grid-template-areas:
			"member member"
			"amount amount"
			"status file"
			"ref    ref"
			"date   mode"
			"action action";
		column-gap: .75rem;
		row-gap: .6rem;
		padding: .85rem;
		margin-bottom: .75rem;
		border: 1px solid var(--es-border);
		border-radius: var(--es-r-md, 12px);
		background: var(--es-surface);
	}

	.balances-mine-table tbody tr:last-child {
		margin-bottom: 0;
	}

	.balances-mine-table tbody td {
		display: block;
		padding: 0;
		border: 0;
		min-width: 0;
	}

	/* Field labels above each value (not for member/amount/action). */
	.balances-mine-table tbody td[data-label]:not(.bm-member):not(.bm-amount)::before {
		content: attr(data-label);
		display: block;
		font-size: .72rem;
		font-weight: 600;
		color: var(--es-text-subtle);
		margin-bottom: .2rem;
	}

	.balances-mine-table .bm-member { grid-area: member; }
	.balances-mine-table .bm-amount { grid-area: amount; }
	.balances-mine-table .bm-status { grid-area: status; }
	.balances-mine-table .bm-file   { grid-area: file; }
	.balances-mine-table .bm-ref    { grid-area: ref; }
	.balances-mine-table .bm-date   { grid-area: date; }
	.balances-mine-table .bm-mode   { grid-area: mode; }
	.balances-mine-table .bm-action { grid-area: action; }

	/* Full name can wrap on a card instead of being cut to "Kart...". */
	.balances-mine-table .balance-entry-name {
		white-space: normal;
		overflow: visible;
	}

	.balances-mine-table .bm-amount .balance-entry-amount {
		font-size: 1.05rem;
	}

	.balances-mine-table .bm-file .balance-file-name {
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.balances-mine-table .bm-ref input,
	.balances-mine-table .bm-date input,
	.balances-mine-table .bm-mode select {
		width: 100%;
	}

	.balances-mine-table .bm-action .btn {
		width: 100%;
	}
}

/* Wider cards: member and amount share the top line. */
@media (min-width: 576px) and (max-width: 1199.98px) {
	.balances-mine-table tbody tr {
		grid-template-areas:
			"member amount"
			"status file"
			"ref    ref"
			"date   mode"
			"action action";
	}

	.balances-mine-table .bm-amount {
		justify-self: end;
		align-self: center;
	}
}

/* Very narrow phones: date and mode each get a full row so the date isn't cut off. */
@media (max-width: 419.98px) {
	.balances-mine-table tbody tr {
		grid-template-areas:
			"member member"
			"amount amount"
			"status file"
			"ref    ref"
			"date   date"
			"mode   mode"
			"action action";
	}
}

/* ------------------------------------------------------ Expenses: small screens */

/*
 * Below 768px the one-line expense row (date 52px + icon + description + 140px
 * amount + 74px of action icons) and its header no longer fit, so:
 *   - the header keeps only the three sort buttons, each sized to its text,
 *     with no fixed-width spacers (no more titles running past the edge);
 *   - each row becomes a two-line grid:
 *       [icon] Description (wraps) ........ payer / amount
 *       [icon] date ....................... [edit] [delete]
 *   - the edit/delete icons are always visible on touch screens (no hover).
 */
@media (max-width: 767.98px) {
	#expensesCard {
		max-width: none;
	}

	.expenses-header-row {
		gap: 6px;
		padding: 6px 10px;
	}

	.expenses-header-icon-spacer,
	.expenses-header-actions-spacer {
		display: none;
	}

	.expenses-sort-btn-date,
	.expenses-sort-btn-end {
		width: auto;
		min-width: 0;
	}

	.expenses-sort-btn-main {
		max-width: none;
	}

	.expense-row {
		display: grid;
		grid-template-columns: 34px minmax(0, 1fr) auto auto;
		grid-template-areas:
			"icon main end    end"
			"icon date edit   delete";
		column-gap: 10px;
		row-gap: 2px;
		align-items: center;
		padding: 10px 6px;
	}

	.expense-row-icon { grid-area: icon; align-self: start; }
	.expense-row-main { grid-area: main; max-width: none; }
	.expense-row-end  { grid-area: end; width: auto; max-width: 45vw; }
	.expense-row-date { grid-area: date; width: auto; min-width: 0; }
	.expense-row .btn-edit-expense   { grid-area: edit; }
	.expense-row .btn-delete-expense { grid-area: delete; }

	.expense-row .btn-edit-expense,
	.expense-row .btn-delete-expense {
		width: 30px;
		height: 30px;
		justify-self: end;
	}
}

/* Touch screens have no hover: keep the edit/delete icons visible. */
@media (hover: none) {
	.btn-edit-expense,
	.btn-delete-expense {
		opacity: 1;
	}
}

/* Invite dialog: hint + counter on the left, Send right under the email box
   (dashboard.html); Send drops below the hint on a narrow screen. */
.invite-send-row {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: .5rem .75rem;
	margin-top: .4rem;
}

.invite-send-row .btn {
	margin-left: auto;
	flex-shrink: 0;
}
