/* ==========================================================================
   FourteenRockets Hosting — Cart Component
   Structural styles only — the theme owns all typography.
   Panel framing matches the /services/ order page sections.
   ========================================================================== */

[x-cloak] { display: none !important; }

/* --------------------------------------------------
   Panel
   -------------------------------------------------- */

/* The panel carries no border itself: on desktop it is taller than its content
   so the sticky header + footer have travel (see base.css), and a border there
   would box in the empty space below the footer. Instead each child carries the
   side borders, the header carries the top, and the footer carries the bottom —
   so the framed box ends at the footer and the extra height stays invisible. */
.fr-cart__panel > * {
	background-color: var( --wp--preset--color--white, #fff );
	border-left: 1px solid var( --fr-line-major );
	border-right: 1px solid var( --fr-line-major );
}

.fr-cart__header {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 1rem;
	padding: 1.25rem 2rem;
	border-top: 1px solid var( --fr-line-major );
	border-bottom: 1px solid var( --fr-line-major );
}

/* Right-hand header group: the Empty cart link sits right of "Prices in EUR" */
.fr-cart__header-meta {
	display: flex;
	align-items: baseline;
	gap: 1rem;
}

/* Empty cart — a real <button> stripped to plain clickable text (chrome
   removed); reads as a text link. Size comes from its inner <small>, the
   underline from the theme (base.css), matching the item Remove links. */
button.fr-cart__clear {
	padding: 0;
	background: transparent;
	border: none;
	color: inherit;
	cursor: pointer;
	white-space: nowrap;
}

button.fr-cart__clear:focus-visible {
	outline: 2px solid var( --wp--preset--color--foreground, #000 );
	outline-offset: 2px;
}

/* Closes the box bottom when the cart is empty (no footer is shown). On the grid
   wrapper (not the translating slide, where it flickered at the clip edge) and
   present in ALL states, so it stays crisp throughout the slide in both
   directions. A fully-collapsed 0fr box would stack its 1px border under the
   header and read as 2px, so a collapsed box is pulled up 1px (margin-top) to
   merge that border with the one above; the margin is transitioned with the
   slide so there is no 1px hop. */
.fr-cart__empty {
	margin: 0;
	border-bottom: 1px solid var( --fr-line-major );
}

.fr-cart__empty:not( .is-open ) {
	margin-top: -1px;
}

.fr-cart__empty-slide {
	margin: 0;
	padding: 1.25rem 2rem;
}

/* --------------------------------------------------
   Reveal / empty — slide behind the sticky header
   -------------------------------------------------- */

/* The empty hint and the cart body (items + footer) HIDE by sliding straight
   up behind the opaque sticky header, and SHOW by sliding back down from behind
   it — the same motion the content makes when the page scrolls (not a collapse).

   Each box is two coupled transitions on the same curve:
   - the wrapper animates its height via grid-template-rows (0fr <-> 1fr), which
     frees / reserves the layout space, so the panel never leaves a gap and the
     footer never pops; and
   - the inner slide translates by its own full height in lockstep, so the
     content moves as a rigid block (a slide) instead of clipping bottom-up.
   The track's overflow:hidden clips the part that rises above the header; the
   opaque sticky header (z-index 2, base.css) paints over it as it goes.

   Transitions are gated on .fr-cart--ready (added one tick after init) so the
   initial open/closed state paints instantly — no entrance animation on load. */
.fr-cart__empty,
.fr-cart__items,
.fr-cart__footer {
	display: grid;
	grid-template-rows: 0fr;
}

.fr-cart--ready .fr-cart__empty,
.fr-cart--ready .fr-cart__items,
.fr-cart--ready .fr-cart__footer {
	transition: grid-template-rows 0.4s ease, margin-top 0.4s ease;
}

.fr-cart__empty.is-open,
.fr-cart__items.is-open,
.fr-cart__footer.is-open {
	grid-template-rows: 1fr;
}

/* Track = the single grid row. min-height:0 lets the 0fr row collapse fully
   (grid items default to min-height:auto, which would otherwise hold the
   content's height open); overflow:hidden clips the slide as it rises. */
.fr-cart__empty-track,
.fr-cart__items-track,
.fr-cart__footer-track {
	min-height: 0;
	overflow: hidden;
}

/* Slide = the moving content. Hidden = translated up by its own full height
   (behind the header, clipped by the track); shown = translateY( 0 ). */
.fr-cart__empty-slide,
.fr-cart__items-slide,
.fr-cart__footer-slide {
	transform: translateY( -100% );
}

.fr-cart--ready .fr-cart__empty-slide,
.fr-cart--ready .fr-cart__items-slide,
.fr-cart--ready .fr-cart__footer-slide {
	transition: transform 0.4s ease;
}

.fr-cart__empty.is-open .fr-cart__empty-slide,
.fr-cart__items.is-open .fr-cart__items-slide,
.fr-cart__footer.is-open .fr-cart__footer-slide {
	transform: translateY( 0 );
}

@media ( prefers-reduced-motion: reduce ) {
	.fr-cart--ready .fr-cart__empty,
	.fr-cart--ready .fr-cart__items,
	.fr-cart--ready .fr-cart__footer,
	.fr-cart--ready .fr-cart__empty-slide,
	.fr-cart--ready .fr-cart__items-slide,
	.fr-cart--ready .fr-cart__footer-slide {
		transition: none;
	}
}

/* --------------------------------------------------
   Cart item rows
   -------------------------------------------------- */

/* Three columns: the Remove '×' | the name + meta | the price. Explicit
   grid-column on each child, NOT auto-placement — the setup-fee row carries no
   Remove (it is intrinsic to its product), and auto-placement would slide its
   name into the empty '×' column and break the alignment the column exists for.
   The '×' column is a fixed width for the same reason: an `auto` track collapses
   to 0 on those rows and the name would shift left.

   align-items: baseline sits the '×' on the NAME's baseline (it is optically
   centred in its em box and reads low otherwise) — the price opts back out via
   align-self: start below, so it keeps the top alignment it has always had.

   Pointer devices override all of this back to two columns (see Price / Remove),
   where '×' returns to the price cell and the row hover swaps the two. */
.fr-cart__item {
	/* Row geometry, as two numbers every dependent rule derives from — the child
	   connector's position has to follow BOTH of them, across pointer/touch and
	   both breakpoints, and hard-coding that product was six values that silently
	   drifted apart. Overridden (not recalculated) below:
	     --fr-row-pad  the row's own side padding        (1rem below sm, else 2rem)
	     --fr-x-col    what the '×' column adds before the info column
	                   (0.75rem track + 0.5rem gap; 0 where there is no '×' —
	                    pointer devices and the receipt)

	   The '×' track is only as wide as the glyph — its 24×24 tap target is painted
	   by a pseudo-element that overflows the track, so the column costs the layout
	   nothing more than the '×' itself (see button.fr-cart__remove::before). */
	--fr-row-pad: 2rem;
	--fr-x-col: 1.25rem;
	display: grid;
	grid-template-columns: 0.75rem 1fr auto;
	align-items: baseline;
	column-gap: 0.5rem;
	padding: 1.25rem var( --fr-row-pad );
	border-bottom: 1px solid var( --fr-line-minor );
}

/* last-of-type, not last-child: the totals <dl> follows the item <div>s */
.fr-cart__item:last-of-type {
	border-bottom: none;
}

button.fr-cart__remove {
	grid-column: 1;
}

.fr-cart__info {
	grid-column: 2;
	min-width: 0;
}

/* The receipt (cart-receipt.php) reuses these row classes but has NO Remove on
   any row — it is the proof of payment, nothing is removable. Drop the '×'
   column there so its names keep their original indent instead of being pushed
   right by an empty track. */
.fr-cart--receipt .fr-cart__item {
	--fr-x-col: 0rem;
	grid-template-columns: 1fr auto;
}

.fr-cart--receipt .fr-cart__info {
	grid-column: 1;
}

/* Badges shown beside the domain name: the Identity Digital registry logo
   (.fr-cart__id-logo, keeps its own colours) and the Premium-domain icon
   (.fr-cart__name-icon, follows the heading colour via fill-current). Both are sized to
   the heading text: 1em tall (so they track the h3 font-size in any context) with width
   following each glyph's own aspect ratio. Display-only.

   display:block drops the inline-SVG baseline gap. The small downward translate does
   two jobs: it sits each badge on the text optically (a centred 1em box reads slightly
   high against cap-height text), and a non-zero transform promotes it to its own paint
   layer — so the price↔Remove opacity swap in the action column on row hover can no
   longer re-rasterise it a sub-pixel off (the visible "jitter"). */
.fr-cart__id-logo,
.fr-cart__name-icon {
	display: block;
	height: 1em;
	width: auto;
	transform: translateY( 0.1em );
}

/* The Premium badge is portrait (taller than wide), so at the shared 1em it reads
   small next to the landscape Identity Digital logo — give it a touch more height.
   The ID logo stays at 1em. */
.fr-cart__name-icon {
	height: 1.4em;
}

/* --------------------------------------------------
   Grouped items — a domain plus its attached products
   -------------------------------------------------- */

/* A group = one domain (head) plus the hosting/extras assigned to it. The
   divider sits on the group, so the domain and its products read as one
   block; the per-item dividers inside a group are removed. */
.fr-cart__group {
	border-bottom: 1px solid var( --fr-line-minor );
}

.fr-cart__group .fr-cart__item {
	border-bottom: none;
}

/* The last block inside the items slide (a group, or an orphan fallback
   item) meets the footer's top border cleanly — drop its own divider. */
.fr-cart__items-slide > div:last-of-type {
	border-bottom: none;
}

/* When products follow, the domain head sits tight above them. */
.fr-cart__group:has( .fr-cart__children ) > .fr-cart__item:first-child {
	padding-bottom: 0.5rem;
}

/* Attached products: joined to the domain by a dashed vertical connector with
   a node dot per product. The indent lives on the child row itself (not the
   container) so each child spans the full cart width — its background (and the
   added-confirmation flash) covers the whole row, not just the indented part. */
.fr-cart__children {
	position: relative;
	padding: 0 0 1rem 0;
}

/* Child row: full width, with the nesting indent and the same right padding as
   the head so the price column lines up.

   The indent moved OFF the row (was padding-left: 3.5rem) and ONTO the info
   column: the row's left padding must now match the head's, or the child's '×'
   would sit 1.5rem right of the head's and the single '×' column would step in
   and out. The 1.5rem the row gave up is handed to .fr-cart__info below, so the
   NAME still lands at 3.5rem exactly as before — and the gap that opens between
   the '×' and the name is the connector's gutter. */
.fr-cart__child {
	position: relative;
	padding: 0.625rem var( --fr-row-pad );
}

.fr-cart__child .fr-cart__info {
	padding-left: 1.5rem;
}

/* Connector segment: each child draws the dashed line down to the next
   child's dot (its own row height = the centre-to-centre distance). The last
   child draws none, so the line spans only from the first dot to the last —
   no overhang above the top dot or below the bottom dot. Positioned within the
   child's left padding (the indent gutter). */
.fr-cart__child:not( :last-child )::after {
	content: "";
	position: absolute;
	left: var( --fr-connector-x );
	top: calc( 1.15rem + 3.5px ); /* the dot's centre (top 1.15rem + half its 7px) */
	width: 0;
	height: 100%;
	border-left: 1px dashed var( --wp--preset--color--secondary, #918CC3 );
}

/* Node dot, centred on the connector line. */
.fr-cart__child::before {
	content: "";
	position: absolute;
	left: var( --fr-connector-x );
	top: 1.15rem;
	width: 7px;
	height: 7px;
	margin-left: -3px;
	border-radius: 50%;
	background: var( --wp--preset--color--secondary, #918CC3 );
}

/* Where the connector sits, derived once instead of hard-coded per case. It must
   land on the domain icon's centre — the icon opens the head row's meta line, so
   that centre is (info column start + half a 1.5rem icon). The info column starts
   after the row padding and the '×' column, giving one expression that is also,
   conveniently, the centre of the child's own indent gutter:

     touch,   >= sm   2 + 1.75 + 0.75 = 4.50rem
     touch,   <  sm   1 + 1.75 + 0.75 = 3.50rem
     pointer, >= sm   2 + 0    + 0.75 = 2.75rem   (unchanged from before the '×' column)
     pointer, <  sm   1 + 0    + 0.75 = 1.75rem   (unchanged)
     receipt, >= sm   2 + 0    + 0.75 = 2.75rem   (unchanged)
     receipt, <  sm   1 + 0    + 0.75 = 1.75rem   (unchanged)

   So only the two touch layouts move, which is the point of the change. */
.fr-cart__child {
	--fr-connector-x: calc( var( --fr-row-pad ) + var( --fr-x-col ) + 0.75rem );
}

/* --------------------------------------------------
   Row entrance (newly added item)
   -------------------------------------------------- */

/* A new row opens first (max-height), then its contents fade in — a two-beat
   entrance so items don't jump into existence. The class is added on insert by
   Alpine (only after the cart has booted, so rows already present on page load
   don't animate) and removed on animationend, so the temporary max-height /
   overflow never linger to clip a row later. */
@keyframes fr-cart-row-in {
	0%   { max-height: 0; opacity: 0; }
	55%  { max-height: 10rem; opacity: 0; }
	100% { max-height: 10rem; opacity: 1; }
}

/* The entrance also carries the "added" flash, as a SECOND animation in the same
   declaration rather than a separately-toggled class. Both fr-cart__row-in and
   fr-category-flash set the `animation` property, so as two classes on one
   element they conflict and only one runs — which is why a grouped child or a
   switched-in row (entrance + flash on the same element) stopped flashing once
   the flash was retimed to overlap the still-running entrance. Listed together
   they both run. The 0.12s delay makes the pulse peak just as the row becomes
   visible (the row is opacity:0 until the open phase ends at ~55%), its ramp-up
   masked behind that opacity. (A first-added row has no entrance — it rides in on
   the body slide — and is flashed from JS instead; see flashCartRow.) */
.fr-cart__row-in {
	overflow: hidden;
	animation: fr-cart-row-in 0.55s ease both, fr-category-flash 0.6s ease-in-out 0.12s both;
}

@media ( prefers-reduced-motion: reduce ) {
	.fr-cart__row-in {
		animation: none;
	}
}

/* Row leave (the reverse of the entrance — fade the contents, then collapse the
   row) is driven from removeRow() in cart.php with inline transitions: it must
   animate the row's MEASURED height to 0, which a CSS keyframe can't do without
   a max-height cap (and the cap leaves a dead zone where the collapse stalls). */

/* --------------------------------------------------
   Price / Remove
   -------------------------------------------------- */

/* Base layout (touch and keyboard-only devices): the '×' sits in the row's left
   column, on the product name's baseline, and the price is alone on the right.
   There is no hover on these devices, so the control has to be permanently
   visible — but a full 'Remove' link on every row made the sheet read as noise,
   so the compact glyph carries it instead (the accessible name still comes from
   the button's aria-label). One column for every '×' in the cart, head rows and
   nested product rows alike.

   align-self: start keeps the price top-aligned in its cell, opting it out of
   the row's baseline group — so it stays exactly where it has always been while
   the '×' baselines to the name. */
.fr-cart__action {
	grid-column: 3;
	align-self: start;
	display: flex;
	align-items: baseline;
	justify-content: flex-end;
	gap: 0.5rem;
	flex-shrink: 0;
	text-align: right;
	/* The row's column-gap is tuned for the tight '×'-to-name step, so top it up
	   here to keep a long product name off the price — 0.5rem gap + 0.75rem =
	   the 1.25rem this row has always kept between the two. */
	padding-left: 0.75rem;
}

/* Receipt: two columns, no '×' — the price is the second, not the third. */
.fr-cart--receipt .fr-cart__action {
	grid-column: 2;
}

.fr-cart__price {
	white-space: nowrap;
}

/* Which label shows is decided here, not in the markup: touch gets the '×',
   pointer devices get the 'Remove' text revealed by the row hover (below). */
.fr-cart__remove-text {
	display: none;
}

/* The glyph is a <small>, so the theme gives it the lighter, smaller treatment
   (.fr-order-flow small). Nudged up a hair: '×' is optically centred in its em
   box and sits low against the price's digits. */
small.fr-cart__remove-x {
	display: inline-block;
	line-height: 1;
	transform: translateY( -0.05em );
}

/* Remove is a real <button> for keyboard and screen-reader access but
   reads as plain clickable text — all visual button chrome is stripped.
   color/background are reset to inherit/transparent (not set) so the
   theme keeps ownership of the text colour; underline comes from the
   theme (base.css). */
button.fr-cart__remove {
	position: relative;
	padding: 0;
	background: transparent;
	border: none;
	color: inherit;
	cursor: pointer;
	white-space: nowrap;
	transition: opacity 0.15s ease;
}

/* Tap target, decoupled from the glyph. WCAG 2.2 AA (2.5.8 Target Size Minimum)
   wants 24×24 CSS px, but the '×' is ~12px and its column is deliberately no
   wider — padding the button would push the whole name column right on every row.
   So the hit area is a transparent pseudo instead: absolutely positioned, centred
   on the button, free to overflow the track into dead space on both sides (the
   row's own left padding, and the gap before the name — it stops ~2px short of
   the info column at every breakpoint). Layout is untouched; only the hit area
   grows.

   It inherits the button's pointer-events, so on pointer devices — where Remove
   is opacity:0 until the row is hovered — it is just as unclickable as the button
   itself and cannot swallow a click meant for the row. */
button.fr-cart__remove::before {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	width: 24px;
	height: 24px;
	transform: translate( -50%, -50% );
}

button.fr-cart__remove:focus-visible {
	outline: 2px solid var( --wp--preset--color--foreground, #000 );
	outline-offset: 2px;
	opacity: 1;
}

/* Pointer devices: price and Remove share one grid cell; hovering the
   row (or focusing the button) swaps the price for the button. The
   button stays in the accessibility tree and focus order throughout.

   pointer-events is essential here, not decorative: opacity < 1 promotes
   an element into its own stacking context, painted ABOVE normal-flow
   siblings — without pointer-events: none the invisible price span sits
   on top of the Remove button and swallows its clicks. */
@media ( hover: hover ) and ( pointer: fine ) {

	/* Drop the touch '×' column: two columns, name then price, exactly as before
	   the column existed. Remove is placed back INTO the price's cell below, so
	   the two overlay and the row hover can swap them. Zeroing --fr-x-col walks
	   the child connector back with it (no '×' column to clear) at BOTH
	   breakpoints — see .fr-cart__child. */
	.fr-cart__item {
		--fr-x-col: 0rem;
		grid-template-columns: 1fr auto;
	}

	.fr-cart__info {
		grid-column: 1;
	}

	/* Both land in cell (2, 1) — the overlay the swap needs. It used to come from
	   a nested grid inside .fr-cart__action (Remove was a child of it); Remove is
	   now a direct child of the row, so the row's own grid does the overlaying. */
	.fr-cart__action,
	.fr-cart__item > .fr-cart__remove {
		grid-column: 2;
		grid-row: 1;
	}

	.fr-cart__item > .fr-cart__remove {
		justify-self: end;
		align-self: start;
	}

	/* Only the price is left inside .fr-cart__action here, right-aligned. */
	.fr-cart__action {
		display: grid;
		justify-items: end;
		gap: 0.5rem;
	}

	/* Pointer devices reveal the full 'Remove' text on hover, so the compact touch
	   glyph is redundant here — swap which label is rendered. */
	.fr-cart__remove-text {
		display: inline;
	}

	small.fr-cart__remove-x {
		display: none;
	}

	.fr-cart__action .fr-cart__price,
	.fr-cart__item > .fr-cart__remove {
		/* Put each on its own compositor layer so the hover opacity swap is composited
		   in isolation: it no longer repaints the info column, which had been re-
		   rasterising every domain/product icon a sub-pixel off (the "jitter"). This is
		   the central fix — the per-badge transforms only isolated those two badges. */
		transform: translateZ( 0 );
	}

	.fr-cart__item .fr-cart__remove {
		opacity: 0;
		pointer-events: none;
	}

	.fr-cart__item:hover .fr-cart__remove,
	.fr-cart__item:focus-within .fr-cart__remove {
		opacity: 1;
		pointer-events: auto;
	}

	.fr-cart__item:hover .fr-cart__price,
	.fr-cart__item:focus-within .fr-cart__price {
		opacity: 0;
		pointer-events: none;
	}
}

/* --------------------------------------------------
   Totals
   -------------------------------------------------- */

dl.fr-cart__totals {
	margin: 0;
	border-top: 1px solid var( --fr-line-major );
}

/* Subtotal + VAT grouped into one block, padded like a cart item row. */
.fr-cart__totals-group {
	display: flex;
	flex-direction: column;
	gap: 0.375rem;
	padding: 1.25rem 2rem;
}

.fr-cart__total-row {
	display: flex;
	justify-content: space-between;
	gap: 1rem;
}

.fr-cart__total-row dt,
.fr-cart__total-row dd {
	margin: 0;
}

/* Total row: its own block, padded like a cart item row, with a light
   divider (same token as the cart-item dividers) above it. */
.fr-cart__total-row--total {
	padding: 1.25rem 2rem;
	border-top: 1px solid var( --fr-line-minor );
}

/* Next invoice: padded like a cart item row, thin divider above. */
.fr-cart__next-invoice {
	padding: 1.25rem 2rem;
	border-top: 1px solid var( --fr-line-minor );
}

/* Country-dependent VAT note: a full-width footnote under the totals — the
   cart estimates at the NL rate, the charged VAT is confirmed at checkout.
   Structural only (padding + divider); the <small> carries its own theme
   typography. */
.fr-cart__vat-note {
	padding: 1.25rem 2rem;
	border-top: 1px solid var( --fr-line-minor );
}

/* --------------------------------------------------
   Footer — totals + action buttons (pinned together)
   -------------------------------------------------- */

/* Closes the framed box at the bottom (side borders come from the panel-child
   rule; the totals' own top border is the divider from the items above). On the
   grid wrapper and present in all states, like the empty hint; a collapsed 0fr
   footer is pulled up 1px so its border merges with the one above instead of
   doubling. */
.fr-cart__footer {
	border-bottom: 1px solid var( --fr-line-major );
}

.fr-cart__footer:not( .is-open ) {
	margin-top: -1px;
}

/* Buttons sit inside the footer, below the totals, with a light divider. */
.fr-cart__actions {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	padding: 1.25rem 2rem;
	border-top: 1px solid var( --fr-line-minor );
}

/* Always black with white text, invert on hover — global button rule */
button.fr-cart__btn {
	display: block;
	width: 100%;
	padding: 0.625rem 1rem;
	background-color: var( --wp--preset--color--foreground, #000 );
	color: var( --wp--preset--color--background, #fff );
	border: 1px solid var( --wp--preset--color--foreground, #000 );
	cursor: pointer;
	text-align: center;
	transition: background-color 0.15s ease, color 0.15s ease;
}

button.fr-cart__btn:focus-visible {
	outline: 2px solid var( --wp--preset--color--foreground, #000 );
	outline-offset: 2px;
}

/* Secondary actions (Back, Add hosting or support):
   inverted — white with black text, fills on hover */
button.fr-cart__btn--secondary {
	background-color: var( --wp--preset--color--white, #fff );
	color: var( --wp--preset--color--foreground, #000 );
}

/* Hover inversion is POINTER-ONLY. On a touch device :hover latches on the last
   element tapped and stays there until something else is tapped — so tapping the
   bar's Continue left it stuck in its inverted (white-on-black → black-on-white)
   state on the very next screen, reading as "greyed out" while still working.
   Most visibly on a screen reached BY tapping Continue (e.g. Add hosting, before
   a product is added); a screen reached by tapping an Add button left it black.
   The CTA must always read black, so the inversion is gated to devices that have
   a real hover — the same guard the row's price↔Remove swap already uses above.
   Ordered after the base rules (equal specificity, source order decides), so the
   secondary inversion still wins over the primary one. */
@media ( hover: hover ) and ( pointer: fine ) {

	button.fr-cart__btn:hover {
		background-color: var( --wp--preset--color--background, #fff );
		color: var( --wp--preset--color--foreground, #000 );
	}

	button.fr-cart__btn--secondary:hover {
		background-color: var( --wp--preset--color--foreground, #000 );
		color: var( --wp--preset--color--background, #fff );
	}
}

@media ( prefers-reduced-motion: reduce ) {

	button.fr-cart__remove,
	button.fr-cart__btn {
		transition: none;
	}
}

/* --------------------------------------------------
   Receipt — static, read-only copy of the cart
   -------------------------------------------------- */

/* Shown on a completed Mollie return in place of the live cart (cart-receipt.php).
   It reuses every cart class so it looks identical, but is plain server-rendered
   markup — no Alpine, no reveal animation, no buttons. These rules undo the
   hide-by-default reveal mechanics so the body shows statically, tint the panel a
   touch to set the receipt apart, and keep each price visible (there is no Remove
   button to swap to). */
.fr-cart--receipt .fr-cart__items,
.fr-cart--receipt .fr-cart__footer {
	display: block;
}

.fr-cart--receipt .fr-cart__items-track,
.fr-cart--receipt .fr-cart__footer-track {
	overflow: visible;
}

.fr-cart--receipt .fr-cart__items-slide,
.fr-cart--receipt .fr-cart__footer-slide {
	transform: none;
}

/* A subtle tint on each framed panel child sets the receipt apart from the page
   without introducing a new colour — adjust the 3% to taste. */
.fr-cart--receipt .fr-cart__panel > * {
	background-color: color-mix( in srgb, var( --wp--preset--color--foreground, #000 ) 3%, var( --wp--preset--color--white, #fff ) );
}

/* No Remove button to swap to, so keep the price visible on hover (the live cart
   fades the price out to reveal Remove). */
@media ( hover: hover ) and ( pointer: fine ) {
	.fr-cart--receipt .fr-cart__item:hover .fr-cart__price {
		opacity: 1;
	}
}

/* The receipt is NOT a sheet: cart-receipt.php renders .fr-cart > .fr-cart__panel
   with no .fr-cart__sheet wrapper, no bar and no Alpine — so it never gets
   .is-expanded. Without this it inherits the mobile panel collapse below and
   disappears completely on a phone, which is the one screen that must always
   render (it is the proof of payment on the Mollie return). The sheet's fixed
   positioning no longer reaches it, since that now lives on .fr-cart__sheet. */
@media ( max-width: 1023.98px ) {
	.fr-cart--receipt .fr-cart__panel {
		max-height: none;
		overflow: visible;
		visibility: visible;
		transition: none;
	}

	/* The receipt has no floating sheet to frame it, so the mobile rules further
	   down that strip the composed panel borders (for the sheet cart) would leave
	   it borderless. Restore its outside frame: side borders on every panel child,
	   top on the header, bottom on the footer (matching the desktop composition). */
	.fr-cart--receipt .fr-cart__panel > * {
		border-left: 1px solid var( --fr-line-major );
		border-right: 1px solid var( --fr-line-major );
	}

	.fr-cart--receipt .fr-cart__header {
		border-top: 1px solid var( --fr-line-major );
	}

	.fr-cart--receipt .fr-cart__footer {
		border-bottom: 1px solid var( --fr-line-major );
	}
}

/* ---- Mobile ( < sm / 640px ): tighter box padding, matching the order/product
   boxes (16px horizontal / 12px vertical). The child row keeps a reduced indent. ---- */
@media ( max-width: 639.98px ) {
	.fr-cart__header,
	.fr-cart__empty-slide,
	.fr-cart__item,
	.fr-cart__totals-group,
	.fr-cart__total-row--total,
	.fr-cart__next-invoice,
	.fr-cart__vat-note,
	.fr-cart__actions {
		padding: 0.75rem 1rem;
	}

	/* Pull the row geometry in with the padding: the child's indent and the
	   connector both derive from --fr-row-pad, so this one value moves them (the
	   connector used to need its own hard-coded 1.75rem here — see
	   .fr-cart__child). The .fr-cart__item padding above is deliberately literal:
	   it sets the value the var mirrors. */
	.fr-cart__item {
		--fr-row-pad: 1rem;
	}

	.fr-cart__child {
		padding: 0.5rem var( --fr-row-pad );
	}
}

/* --------------------------------------------------
   Mobile floating cart sheet ( < lg / 1024px )
   -------------------------------------------------- */

/* On small screens the cart becomes a floating bottom sheet: a rounded, shadowed
   card inset from the screen edges. Collapsed it shows only the bar (summary +
   CTA); tapping the bar expands the cart contents upward, scrollable, with the bar
   pinned as the sheet's footer. The rounded corners + shadow are a deliberate,
   app-style exception to the brutalist square/no-shadow rule, applied only to this
   control. Hidden from lg up, where the sticky cart column takes over. */

/* The bar is desktop-hidden; on mobile it is the sheet's always-visible footer. */
.fr-cart__bar {
	display: none;
}

/* The scrim is mobile-only; from lg up the cart is a normal column with nothing
   to dim. Declared outside the media query so the default is off everywhere. */
.fr-cart__overlay {
	display: none;
}

/* From lg up the sheet wrapper must NOT exist as a box: the theme's sticky
   header/footer travel (base.css) depends on .fr-cart__panel being a direct flex
   child of .fr-cart, so the wrapper dissolves into its parent. */
@media ( min-width: 1024px ) {
	.fr-cart__sheet {
		display: contents;
	}
}

@media ( max-width: 1023.98px ) {

	/* The sheet — not the root — is the fixed floating card. The root stays an
	   ordinary (untransformed, unclipped) box so the scrim can be a fixed sibling:
	   a transform makes an element the containing block for fixed descendants, and
	   overflow:hidden would clip them, so a scrim nested inside the card could
	   never cover the page.

	   z-index: above the masthead. #masthead is position:relative with no z-index,
	   so it creates no stacking context and its children (hamburger + logo z-20,
	   nav z-10) compete directly with the sheet in the root stacking context — at
	   the old z-index:5 the hamburger painted straight over the cart. */
	.fr-cart__sheet {
		position: fixed;
		left: 0.75rem;
		right: 0.75rem;
		bottom: 0.75rem;
		z-index: 50;
		display: flex;
		flex-direction: column;
		max-height: 85vh;
		background-color: var( --wp--preset--color--white, #fff );
		border: 1px solid var( --fr-line-major );
		border-radius: 0.75rem;
		box-shadow: 0 0.25rem 0.625rem rgba( 0, 0, 0, 0.25 );
		overflow: hidden; /* clip children to the rounded corners */
	}

	/* Scrim behind the expanded sheet: dims the page so the cart reads as a modal
	   layer instead of floating over live content. Sits between the page and the
	   sheet (z 40 < 50). Not display-toggled — it fades, and stays unclickable
	   while collapsed so it can never swallow a tap on the page beneath. */
	.fr-cart__overlay {
		display: block;
		position: fixed;
		inset: 0;
		z-index: 40;
		background-color: rgba( 0, 0, 0, 0.5 );
		opacity: 0;
		pointer-events: none;
	}

	.fr-cart--ready .fr-cart__overlay {
		transition: opacity 0.4s ease;
	}

	.fr-cart__overlay.is-shown {
		opacity: 1;
		pointer-events: auto;
	}

	/* The mobile menu overlay is a full-screen panel under the header; now that the
	   sheet outranks the masthead it would cover both the menu and its hamburger.
	   The masthead mirrors its `open` state onto <body> (header-content.php) purely
	   so this rule can stand the cart down while the menu is open — the same intent
	   as the services hanger's x-show="! open". */
	body.fr-menu-open .fr-cart__sheet,
	body.fr-menu-open .fr-cart__overlay {
		display: none;
	}

	/* Panel = the collapsible cart contents. Collapsed to zero height (and removed
	   from the a11y tree / tab order via visibility) until the sheet is expanded,
	   then it grows and scrolls within the sheet. */
	.fr-cart__panel {
		max-height: 0;
		overflow: hidden;
		visibility: hidden;
		transition: max-height 0.8s ease, visibility 0s linear 0.8s;
	}

	.fr-cart.is-expanded .fr-cart__panel {
		max-height: 70vh;
		overflow-y: auto;
		visibility: visible;
		transition: max-height 0.8s ease, visibility 0s;
	}

	/* Inside the rounded card the panel children's own side borders would double up
	   with the card border — drop them; the card frames the content. */
	.fr-cart__panel > * {
		border-left: none;
		border-right: none;
	}

	/* The sheet's own 1px border frames the top, and the bar's border-top divides it
	   from the contents — so drop the header's top border and the footer's bottom
	   border here, which would otherwise stack into a 2px line. */
	.fr-cart__header {
		border-top: none;
	}

	.fr-cart__footer {
		border-bottom: none;
	}

	/* Bar = the sheet's always-visible footer (summary + CTA). */
	.fr-cart__bar {
		display: flex;
		flex-direction: column;
		gap: 0.5rem;
		padding: 0.75rem;
		background-color: var( --wp--preset--color--white, #fff );
	}

	/* Divider between the contents and the footer bar — only when expanded (when
	   collapsed there is nothing above the bar). */
	.fr-cart.is-expanded .fr-cart__bar {
		border-top: 1px solid var( --fr-line-major );
	}

	/* The cart's own footer action buttons are redundant on mobile — the bar owns
	   the CTA. */
	.fr-cart__actions {
		display: none;
	}

	/* Reserve space so page content clears the collapsed floating sheet. The class
	   is added on <body> once by the cart (cart.php init) and never removed — it
	   marks "this page renders the cart sheet", not a state: the sheet is on screen
	   from load (it shows '0 items' while empty), so the clearance is permanent on
	   these screens rather than following the reveal. */
	body.fr-has-cart-sheet {
		padding-bottom: 6rem;
	}
}

@media ( prefers-reduced-motion: reduce ) {
	.fr-cart__panel,
	.fr-cart--ready .fr-cart__overlay,
	.fr-cart--ready .fr-cart__bar-empty,
	.fr-cart--ready .fr-cart__bar-row {
		transition: none;
	}
}

/* Bar states: the empty '0 items' and the summary + CTA row are stacked in one
   grid cell so they cross-fade in place and the bar height never jumps between
   them. Only one is interactive at a time (:inert in cart.php); the hidden one is
   also pointer-events:none, since an opacity:0 element still takes clicks.
   (No effect from lg up, where the bar itself is display:none.) */
.fr-cart__bar-states {
	display: grid;
}

.fr-cart__bar-empty,
.fr-cart__bar-row {
	grid-area: 1 / 1;
	opacity: 0;
	pointer-events: none;
}

.fr-cart--ready .fr-cart__bar-empty,
.fr-cart--ready .fr-cart__bar-row {
	transition: opacity 0.4s ease;
}

.fr-cart__bar-empty.is-shown,
.fr-cart__bar-row.is-shown {
	opacity: 1;
	pointer-events: auto;
}

/* Empty state: centred, non-interactive — there is nothing to expand and nothing
   to continue to. Min-height matches the CTA's tap target so the bar keeps the
   same height across the swap. */
.fr-cart__bar-empty {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 2.75rem;
}

/* Summary (tap target for expand) sits left, the CTA right. */
.fr-cart__bar-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
}

/* Summary = a plain clickable region (chrome stripped) that toggles the sheet;
   the price and item count sit inline on one row. */
button.fr-cart__bar-summary {
	display: flex;
	align-items: baseline;
	gap: 0.5rem;
	flex: 1;
	min-width: 0;
	padding: 0;
	background: transparent;
	border: none;
	color: inherit;
	cursor: pointer;
	text-align: left;
}

button.fr-cart__bar-summary:focus-visible {
	outline: 2px solid var( --wp--preset--color--foreground, #000 );
	outline-offset: 2px;
}

/* CTA reuses the cart button visual (black, inverts on hover); sits at its natural
   width with extra side padding (a touch wider than the footer button) and a
   comfortable (>=44px) tap target. */
button.fr-cart__bar-btn {
	width: auto;
	flex-shrink: 0;
	min-height: 2.75rem;
	padding-left: 1.75rem;
	padding-right: 1.75rem;
}
