/*
 * Screen layer — page-level composition for Hjem, Kategori, Produkt, Handlekurv,
 * Kasse, Min konto, Ønskeliste and the blog.
 *
 * What lives here is what belongs to one screen. Anything a second screen reuses
 * — the section header, the category tile, the blog teaser — is a component and
 * lives in components.css instead.
 *
 * Rules are the component layer's: tokens only, no media queries. Everything
 * that changes with the viewport is a custom property resolved in section 1 of
 * base.css.
 *
 * Contents
 *   1. Page rhythm
 *   2. Hjem — hero, promo row, the sections
 *   3. Kategori — the catalogue archives
 *   4. Søk — the product search results
 *   5. Produkt — gallery, buy box, tabs/accordion, sticky buy bar
 *   6. Handlekurv — the lines card, and the OrderSummary rail beside it
 *   7. Kasse — the four numbered blocks, the rail, and the confirmation
 *   8. Min konto — the sidebar, the dashboard and the endpoints
 *   9. Blogg — the article and its rail, and the post archive
 */

/* ══ 1. Page rhythm ══════════════════════════════════════════════════════════
 *
 * Sections carry their rhythm on the top edge only — `.dl-section--flush-bottom`
 * — so two neighbours never add their padding together and a section removed in
 * the Site Editor takes its whole gap with it. The last gap, before the footer,
 * is the one no section owns: `.dl-footer`'s own `margin-top` closes it, for
 * every screen at once (chrome.css). No screen adds a closing padding here, or
 * the footer would sit that gap plus its own away from the last row.
 */

/* ══ 2. Hjem ═════════════════════════════════════════════════════════════════ */

/* ── Hero ──
 * The wash is the only gradient in the theme. There is no gradient scale in the
 * design system and no second use for one: it marks the top of the site, and
 * that is the whole of its job — see the design system guide, "Color".
 */

.dl-hero__band {
	background: linear-gradient(180deg, var(--blue-50), var(--surface-page));
}

.dl-hero__inner {
	padding-block: var(--dl-hero-pad-block-start) var(--dl-hero-pad-block-end);
}

.dl-hero__grid {
	gap: var(--dl-hero-gap);
	align-items: center;
}

.dl-hero__text {
	display: flex;
	flex-direction: column;
	gap: var(--dl-hero-text-gap);
}

.dl-hero__title {
	max-inline-size: var(--dl-hero-title-max);
	font: var(--dl-hero-title-font);
	letter-spacing: var(--tracking-tight);
}

.dl-hero__lead {
	max-inline-size: var(--dl-hero-lead-max);
	font: var(--type-body);
	font-size: var(--dl-hero-lead-size);
	color: var(--text-muted);
}

.dl-hero__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--dl-hero-actions-gap);
}

/*
 * Stacked and full-width on mobile, side by side from 768px up. MHome draws one
 * full-width call to action; the secondary used to be display:none to match,
 * which left an editor who filled it in with nothing on phones. It stacks under
 * the primary instead, at the same width (#52).
 */
.dl-hero__primary,
.dl-hero__secondary {
	width: var(--dl-hero-action-width);
}

/*
 * The plate reserves its 4:3 whether or not there is a photograph in it, so the
 * page does not move when one arrives — and does not move as one loads.
 */
.dl-hero__media {
	position: relative;
	aspect-ratio: 4 / 3;
	display: grid;
	place-items: center;
	overflow: hidden;
	background: var(--neutral-100);
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--dl-hero-media-radius);
	box-shadow: var(--dl-hero-media-shadow);
}

/*
 * Taken out of the grid flow on purpose. As a grid item the photograph sized
 * the auto row to its own intrinsic height — the plate kept its 4:3, the image
 * did not, and `overflow: hidden` cropped the difference. Positioned against
 * the plate it is exactly the plate, and `object-fit` decides what happens to
 * the part of the photograph that does not fit that 4:3.
 */
.dl-hero__image {
	position: absolute;
	inset: 0;
	display: block;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

/*
 * `contain` for a photograph whose subject the 4:3 crop would cut into — a
 * whole product shot, a logo plate. What is left over is the plate's own
 * --neutral-100, which is the same surface the placeholder uses.
 */
.dl-hero__media--contain .dl-hero__image {
	object-fit: contain;
}

/* The stand-in until photography is supplied. Deliberate, not broken. */
.dl-hero__placeholder {
	font: var(--type-caption);
	color: var(--text-faint);
	letter-spacing: var(--tracking-caps);
	text-transform: uppercase;
}

/*
 * The finder card, which the vehicle-catalogue issue fills — until then
 * inc/hero.php renders nothing here and none of these rules match.
 *
 * On desktop it is the last thing inside the wash, 40px under the columns. On
 * mobile it straddles the wash's bottom edge: its negative bottom margin pulls
 * the band — and the hairline that closes it — up past the card, so the card
 * overlaps the boundary by 20px. What the card then hangs below the section is
 * given back as padding on the hero itself, so the USP strip below it does not
 * move up under the card.
 */
.dl-hero__finder {
	position: relative;
	margin-block-start: var(--dl-hero-finder-gap);
	margin-block-end: var(--dl-hero-finder-pull);
}

.dl-hero:has(.dl-hero__finder) .dl-hero__inner {
	padding-block-end: var(--dl-hero-band-pad-finder);
}

.dl-hero:has(.dl-hero__finder) {
	padding-block-end: var(--dl-hero-overhang);
}

/* ── Hero scene ──
 * The mosaic that opens the front page: the vehicle search laid flat along the
 * top edge, and five clickable category plates under it. Edge to edge, with its
 * own outer cap rather than the container's — the plates are the picture and the
 * picture is the width of the screen; the search bar inside it is the one thing
 * that still lines up with the rest of the page.
 *
 * The three shapes it takes are the --dl-hero-scene-* properties in section 1 of
 * base.css. There is one markup tree — see inc/hero-scene.php.
 */

.dl-hero-scene__inner {
	max-inline-size: 1920px; /* no token — the mosaic's outer cap, as drawn */
	margin-inline: auto;
	background: var(--surface-card);
	border-block-end: var(--border-width) solid var(--border-subtle);
}

.dl-hero-scene__search {
	padding-block: var(--space-5);
	border-block-end: var(--border-width) solid var(--border-subtle);
}

/*
 * The picker without its card — `dl-vehicle--flush`, the same variant the
 * catalogue sidebar uses — laid on one line. The row keeps the component's own
 * grid: only its tracks are swapped, so there is no second set of controls here,
 * just the picker's under a different width.
 */
.dl-hero-scene__vehicle {
	--dl-vehicle-head-space: 0px;
	--dl-vehicle-cols: var(--dl-hero-scene-search-cols);

	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--dl-hero-scene-search-gap);
}

.dl-hero-scene__vehicle .dl-vehicle__head {
	flex: 0 0 auto;
}

.dl-hero-scene__vehicle .dl-vehicle__row {
	flex: var(--dl-hero-scene-search-row);
}

/* Mobile puts the button under the two selects, where the card has room for it
   on neither side; from tablet up it is the row's third track. */
.dl-hero-scene__vehicle .dl-vehicle__submit {
	grid-column: var(--dl-hero-scene-search-submit-col);
}

/* Its own line under the bar, and no gap of its own — the flex gap is above it
   already, and the bar must not grow taller the moment it has something to say. */
.dl-hero-scene__vehicle .dl-vehicle__status {
	flex: 1 0 100%;
	margin-block-start: 0;
}

/* "Velg bilmodell" is the card's second line. On a bar that is one line, the two
   selects say it themselves. */
.dl-hero-scene__vehicle .dl-vehicle__hint {
	display: none;
}

/*
 * The plates. The hairline between them is the grid's own background showing
 * through a one-pixel gap rather than a border on each plate — a border would
 * double at every seam and draw one down the outer edge, which the prototype
 * does not have.
 */
.dl-hero-scene__grid {
	display: grid;
	grid-template-columns: var(--dl-hero-scene-cols);
	gap: var(--border-width);
	background: var(--border-subtle);
}

.dl-hero-scene__tile {
	position: relative;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	min-block-size: var(--dl-hero-scene-tile-h);
	overflow: hidden;
	padding: var(--dl-hero-scene-tile-pad);
	/*
	 * Dark, not --neutral-100 as the prototype's placeholder rectangle is. The
	 * caption on a plate is white in every state, and a category with no picture
	 * yet would otherwise be white words on a light grey box. The photograph
	 * covers this entirely when there is one, so what it really governs is the
	 * plate before its image has loaded and the plate that has none.
	 */
	background: var(--surface-inverse);
	color: var(--text-inverse);
	text-decoration: none;
}

/*
 * The lead plate. Two columns wide on mobile and tablet, two rows tall from
 * 1024 up — the same plate, told where to sit by two custom properties.
 */
.dl-hero-scene__tile--lead {
	grid-column: var(--dl-hero-scene-lead-col);
	grid-row: var(--dl-hero-scene-lead-row);
	min-block-size: var(--dl-hero-scene-lead-h);
	padding: var(--dl-hero-scene-lead-pad);
}

.dl-hero-scene__image {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform var(--dur-slow) var(--ease-out);
}

/* The category tile's zoom, on the category tile's timing — this is the same
   gesture on a bigger plate. Only where there is somewhere to go: a plate with
   no link is a <div>, and moving under the cursor would promise one. */
a.dl-hero-scene__tile:hover .dl-hero-scene__image {
	transform: scale(1.04); /* no token — the tile's zoom as drawn */
}

/*
 * The caption is white, and what is under it is a photograph, so the bottom of
 * the plate is washed dark. Unlike the promo tile's scrim this one is drawn
 * whether or not there is a photograph: the words are white in both cases, and
 * --neutral-100 on its own would not hold them.
 */
.dl-hero-scene__scrim {
	position: absolute;
	inset-inline: 0;
	inset-block-end: 0;
	block-size: 62%; /* no token — the wash over the small plate, as drawn */
	/* no token — the theme has no scrim scale; the handoff's own wash */
	background: linear-gradient(180deg, rgba(15, 23, 32, 0), rgba(15, 23, 32, 0.72));
}

/* The same wash over a taller box covers less of it. */
.dl-hero-scene__tile--lead .dl-hero-scene__scrim {
	block-size: 55%; /* no token — as drawn */
}

.dl-hero-scene__body {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 2px; /* no token — optical, under the 14px caption */
}

.dl-hero-scene__tile--lead .dl-hero-scene__body {
	gap: 4px; /* no token — optical */
}

.dl-hero-scene__label {
	font: var(--dl-hero-scene-label-font);
	color: var(--text-inverse);
}

.dl-hero-scene__tile--lead .dl-hero-scene__label {
	font: var(--dl-hero-scene-lead-label-font);
}

/*
 * The subcategories the plate names. Off on mobile, where the small plates are
 * 110px tall and the caption alone fills them — see section 1 of base.css.
 */
.dl-hero-scene__sub {
	display: var(--dl-hero-scene-sub-display);
	font: var(--type-caption);
	color: rgba(255, 255, 255, 0.82); /* no token — the caption's own wash, as drawn */
}

.dl-hero-scene__tile--lead .dl-hero-scene__sub {
	font: var(--type-small);
}

/* ── Promo row ── */

.dl-promo__tile {
	position: relative;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	aspect-ratio: 600 / 370;
	overflow: hidden;
	padding: var(--dl-promo-pad);
	background: var(--neutral-100);
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--radius-lg);
	color: inherit;
	transition: var(--transition-elevate);
}

a.dl-promo__tile:hover {
	box-shadow: var(--shadow-2);
}

.dl-promo__image {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Above the image, and the reason the copy stays legible over one. */
.dl-promo__body {
	position: relative;
	isolation: isolate; /* so the scrim below can sit behind the copy, over the photo */
	display: flex;
	flex-direction: column;
	gap: 6px; /* no token — optical, between the eyebrow and its line */
}

/*
 * The copy is dark, and a photo underneath it is not. The scrim is hung off the
 * body box rather than given a height of its own, so it covers exactly the two
 * lines that are there: out to the tile's padding on three sides, and one pad
 * above the text, which is the distance it has to fade out in. No photo, no
 * scrim — the fallback tile is a flat surface the copy already reads against.
 */
.dl-promo__tile:has(.dl-promo__image) .dl-promo__body::before {
	content: '';
	position: absolute;
	z-index: -1;
	inset: calc(var(--dl-promo-pad) * -2) calc(var(--dl-promo-pad) * -1) calc(var(--dl-promo-pad) * -1);
	/* no token — the theme has no scrim scale; white to match the light copy surface */
	background: linear-gradient(to top, rgba(255, 255, 255, 0.94), rgba(255, 255, 255, 0.88) 45%, rgba(255, 255, 255, 0));
}

/*
 * The tile's heading. It is an <h3> so the tile is part of the outline, and it
 * keeps the font shorthand it was drawn with: --type-h3 is the same 21px but at
 * regular weight and 1.3, and the tile's line is light at 1.25. tokens/base.css
 * has already zeroed the heading margin, so the shorthand is the whole
 * difference between the element default and the design.
 */
.dl-promo__title {
	font: var(--weight-light) var(--text-xl) / 1.25 var(--font-display);
	color: var(--text-heading);
}

/* ══ 3. Kategori ═════════════════════════════════════════════════════════════
 *
 * The product category, tag, attribute and shop archives — one screen, because
 * WooCommerce falls back to the same template for all four.
 *
 * The frame is assembled on the loop hooks in inc/catalog.php, in the order the
 * ClassicTemplate block fires them: breadcrumb · title · description, then a
 * `sidebar` grid whose left column is the CategoryNav and the filter card, and
 * whose right column is the control head, the product grid and the pagination.
 *
 * The two shapes the handoff draws are one markup tree. Below 1024px the
 * sidebar is hidden — the drawer holds a second copy — and the control bar is a
 * sticky row with the chips below it. From 1024px up the bar is
 * `display: contents`, its children become cells of the head's own grid, and the
 * chips move up onto that line between the count and the sort. Every value that
 * changes is a --dl-catalog-* property resolved in section 1 of base.css.
 */

/* ── Header ── */

.dl-catalog .dl-breadcrumb {
	margin-block-end: var(--space-5);
}

.dl-catalog .woocommerce-products-header {
	margin-block-end: var(--dl-catalog-header-space);
}

.dl-catalog .woocommerce-products-header__title {
	font: var(--dl-catalog-title-font);
	letter-spacing: var(--tracking-tight);
}

/*
 * The archive description, which #30 moved out of the header and under the loop
 * — a category with three paragraphs of SEO copy used to push the products off
 * the first screen. It sits inside the content column, so it is already indented
 * past the sidebar; the rule separates it from the pagination above it.
 */
.dl-catalog__description {
	margin-block-start: var(--space-9);
	padding-block-start: var(--space-7);
	border-block-start: var(--border-width) solid var(--border-subtle);
}

/*
 * Capped at the measure the handoff draws rather than run to the column: this is
 * running copy, and 1000px of it is unreadable. The selector covers both of
 * WooCommerce's description callbacks — the taxonomy's and the shop page's.
 */
.dl-catalog .term-description,
.dl-catalog .woocommerce-product-description,
.dl-catalog .page-description {
	max-inline-size: 720px; /* no token — the description measure as drawn */
	font: var(--type-body);
	color: var(--text-muted);
}

/*
 * WooCommerce wraps the copy in a div of its own, so `.dl-prose > * + *` on the
 * wrapper lands on that one div and never on the paragraphs inside it. The
 * rhythm is handed through it.
 */
.dl-catalog__description .term-description > * + *,
.dl-catalog__description .woocommerce-product-description > * + *,
.dl-catalog__description .page-description > * + * {
	margin-block-start: var(--space-5);
}

/*
 * The category FAQ (inc/category-faq.php), drawn under whichever paragraph the
 * screen opened with rather than in place of it. Same measure and same rhythm as
 * that paragraph — it is the same body of editor copy — but a wider gap above it
 * than `.dl-prose` hands between siblings, because it starts a second subject and
 * not the next paragraph of the first. Its headings keep the global role scale.
 */
.dl-catalog__faq {
	max-inline-size: 720px; /* no token — the description measure as drawn */
	font: var(--type-body);
	color: var(--text-muted);
}

.dl-catalog__description > * + .dl-catalog__faq {
	margin-block-start: var(--space-7);
}

.dl-catalog__faq > * + * {
	margin-block-start: var(--space-5);
}

.dl-catalog__faq :is(h2, h3, h4) {
	color: var(--text-heading);
}

/* ── The two columns ── */

.dl-catalog__body {
	gap: var(--dl-catalog-gap);
	align-items: start;
}

/*
 * A grid item is `min-width: auto` by default, which lets a long product title
 * or a wide table push the column past its track instead of wrapping inside it.
 */
.dl-catalog__main {
	min-inline-size: 0;
}

/*
 * The column is an ordinary element on the page: no height cap, no scrollbar of
 * its own, and not sticky either (#27). There is exactly one scroll container on
 * this screen from 1024px up — the document — and that is the whole point. It
 * used to be three: the page, this column, and a long filter group inside it,
 * with a trackpad gesture landing in whichever one the pointer happened to be
 * over. Every department is now reachable by scrolling the page, which is what
 * navigation out of a dead end has to be; a group longer than eight options
 * closes with "Vis alle" instead of a scrollbox (see FilterBlock).
 *
 * Sticky went with them: a 280px column carrying a department tree and up to
 * seven filter groups is taller than the viewport, and a sticky element taller
 * than the viewport pins its own bottom permanently out of reach.
 */
.dl-catalog__sidebar {
	display: var(--dl-catalog-sidebar-display);
	flex-direction: column;
	gap: var(--space-5);
}

/* The filter card's own heading. The card is padding-free so the filter blocks
   inside it can carry their own hairlines edge to edge. */
.dl-catalog__filters-title {
	padding: var(--space-5);
	font: var(--weight-medium) var(--text-base) / 1 var(--font-display);
	color: var(--text-heading);
}

/* ── The VehiclePicker ── */

/*
 * At the top of the sidebar, above the CategoryNav — and below 1024px at the top
 * of the drawer, which is the same stack in the same order (#31). The vehicle is
 * a narrowing like the departments and the filters under it, and putting it here
 * gives the products their column back: #30 drew it as a wide card above the
 * control head, where on a category it pushed the first row of products down for
 * a control the customer has usually already used.
 *
 * Neither copy sets its own spacing: the sidebar and the drawer body are both
 * flex columns with a --space-5 gap, which is the gap between the picker and the
 * card under it.
 *
 * What both do set is the track. The component's row is `1fr 1fr auto` from 768px
 * up, which is drawn for a card the width of the page; 280px of sidebar and
 * 300px of drawer body are not that, and two selects and a button on one line
 * there would each be too narrow to read a model name in. The override is here,
 * on the two copies, rather than on --dl-vehicle-cols in base.css — the hero's
 * picker reads the same variable and is exactly the wide card the desktop track
 * was drawn for.
 */
.dl-catalog__vehicle,
.dl-catalog__drawer-vehicle {
	--dl-vehicle-cols: 1fr;
}

/* ── The control head ── */

.dl-catalog__head {
	display: var(--dl-catalog-head-display);
	grid-template-columns: var(--dl-catalog-head-cols);
	align-items: center;
	gap: var(--dl-catalog-head-gap);
	margin-block-end: var(--dl-catalog-head-space);
}

/*
 * Below 1024px this is the sticky bar: it runs to the screen edge on both sides
 * — the negative margin, given back as padding — and closes with a hairline, so
 * the products scroll under a solid row rather than under a transparent one.
 * Its containing block is the content column, because the head above it is
 * `display: contents` there; from 1024px up the two swap and none of this
 * applies.
 */
.dl-catalog__bar {
	display: var(--dl-catalog-bar-display);
	align-items: center;
	gap: var(--space-4);
	position: var(--dl-catalog-bar-position);
	top: calc(var(--ds-sticky-offset) + var(--dl-header-h));
	z-index: 10; /* no token — under the header (20), over the cards */
	margin-inline: var(--dl-catalog-bar-bleed);
	padding: var(--dl-catalog-bar-pad);
	background: var(--dl-catalog-bar-bg);
	border-block-end: var(--dl-catalog-bar-border);
	margin-block-end: var(--dl-catalog-bar-space);
}

.dl-catalog__filter-btn {
	display: var(--dl-catalog-filter-btn-display);
	flex: 0 0 auto;
}

.dl-catalog__count {
	grid-column: var(--dl-catalog-count-col);
	grid-row: var(--dl-catalog-head-row);
	margin: 0;
	font: var(--type-small);
	color: var(--text-muted);
}

.dl-catalog__tags {
	grid-column: var(--dl-catalog-tags-col);
	grid-row: var(--dl-catalog-head-row);
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-3);
	margin-block-end: var(--dl-catalog-tags-space);
}

/* The chip's remove affordance is a link — the server decides the URL that is
   left once the filter goes — so it needs the button's box, not the anchor's. */
.dl-catalog__tags .dl-tag__remove {
	text-decoration: none;
}

.dl-catalog__sort {
	grid-column: var(--dl-catalog-sort-col);
	grid-row: var(--dl-catalog-head-row);
	justify-self: end;
	flex: var(--dl-catalog-sort-flex);
	inline-size: var(--dl-catalog-sort-width);
	min-inline-size: 0;
	max-inline-size: 100%;
}

.dl-catalog__sort .dl-control {
	display: block;
	inline-size: 100%;
}

.dl-catalog__sort .dl-select {
	inline-size: 100%;
}

/* ── Below the grid ── */

.dl-catalog .dl-pagination {
	justify-content: center;
	flex-wrap: wrap;
	margin-block-start: var(--dl-catalog-pagination-space);
}

/* The empty catalogue takes the width of the column it replaces the grid in. */
.dl-catalog .woocommerce-no-products-found {
	padding: 0;
}

/* ── The drawer ── */

/*
 * The prototype's insets: the drawer's body is padded --space-5 / --space-6, the
 * same gutter as its header and footer, and the filter blocks are bled back out
 * to the panel edge so their hairlines run the full width as they do in the
 * card. The blocks carry their own bottom padding, so the body's is 0.
 *
 * Two scroll containers below 1024px, and no more: the page, and this body while
 * the drawer is open — which is a modal over a locked page, so they are never
 * both scrollable at once. The eight-option cap is what keeps this one honest;
 * it is 1 200px tall on Felger, where an uncapped Merke group made it 6 629.
 */
.dl-catalog__drawer-body {
	display: flex;
	flex-direction: column;
	gap: var(--space-5);
	padding: var(--space-5) var(--space-6) 0;
}

/* The form is the blocks and nothing else: no gap between them, because each one
   already carries the hairline that separates it. */
.dl-catalog__drawer-body .dl-filters {
	display: flex;
	flex-direction: column;
	margin-inline: calc(-1 * var(--space-6));
}

/* The apply button is the drawer's footer; the count inside it is one word of
   the label, so it must not wrap away from "Vis". */
.dl-catalog__drawer [data-dl-filters-count] {
	white-space: nowrap;
}

/* ══ 4. Søk ══════════════════════════════════════════════════════════════════
 *
 * The search results are the catalogue screen without its sidebar — the wrapper
 * carries `dl-catalog` as well as `dl-search-results`, so everything in section 3
 * applies and only what the search draws on its own is here: the query inside the
 * title, the count line under it, the fitment alert and the row of matching
 * categories.
 *
 * That row is the one place the screen has two shapes. Below 768px it is a
 * horizontally scrolling row of chips bleeding into the gutter; from 768px up it
 * is the g4 grid of CategoryTiles the handoff draws. The chip is the same tile
 * with its plate and its arrow taken away — one markup tree, the difference is
 * the --dl-search-cat-* properties in section 1 of base.css.
 */

/* The query, inside the h1 — guillemets included, as the handoff draws it: the
   marks belong to the quotation, not to the sentence around it. */
.dl-search-results__query {
	color: var(--text-brand);
}

.dl-search-results__count {
	margin-block-start: var(--dl-search-count-space);
	font: var(--type-body);
	color: var(--text-muted);
}

.dl-search-results__alert {
	margin-block-end: var(--dl-search-alert-space);
}

/* The eyebrows on this screen are the faint variety the handoff draws, not the
   brand one .dl-eyebrow carries over section headings. */
.dl-search-results__eyebrow {
	color: var(--text-faint);
}

.dl-search-results__cats {
	display: block;
	margin-block-end: var(--dl-search-cats-space);
}

.dl-search-results__cats .dl-search-results__eyebrow {
	margin-block-end: var(--space-4);
}

/*
 * `grid-auto-flow: column` with max-content tracks is the chip row: the tiles
 * keep their own width and the container scrolls past the gutter. From 768px up
 * the flow goes back to rows and the tracks to g4, and the negative margin
 * resolves to zero, so nothing bleeds.
 */
.dl-search-results__cat-row {
	grid-template-columns: var(--dl-search-cat-cols);
	grid-auto-flow: var(--dl-search-cat-flow);
	grid-auto-columns: var(--dl-search-cat-auto);
	overflow-x: var(--dl-search-cat-overflow);
	margin-inline: var(--dl-search-cat-bleed);
	padding-inline: calc(var(--dl-search-cat-bleed) * -1);
	scrollbar-width: none;
}

.dl-search-results__cat-row::-webkit-scrollbar {
	display: none;
}

.dl-search-results__cat-row .dl-cat-tile {
	border-radius: var(--dl-search-cat-radius);
}

.dl-search-results__cat-row .dl-cat-tile__plate {
	display: var(--dl-search-cat-plate);
}

.dl-search-results__cat-row .dl-cat-tile__meta {
	display: var(--dl-search-cat-meta-display);
}

.dl-search-results__cat-row .dl-cat-tile__foot {
	padding: var(--dl-search-cat-foot-pad);
}

.dl-search-results__cat-row .dl-cat-tile__label {
	font: var(--dl-search-cat-label-font);
	white-space: var(--dl-search-cat-label-wrap);
}

/* The sort is 210px on this screen rather than the catalogue's 200 — it holds
   "Mest relevant" beside the chips instead of "Standard sortering" alone. */
.dl-search-results .dl-catalog__sort {
	inline-size: var(--dl-search-sort-width);
}

/* ══ 5. Produkt ══════════════════════════════════════════════════════════════
 *
 * The screen is assembled on the hooks WooCommerce's own content-single-product.php
 * fires; inc/product.php lists them in firing order at the top of the file.
 *
 * Three pieces the handoff draws twice — the gallery, the information panels and
 * the buy bar — are one markup tree here, and every difference between the two
 * drawings is a --dl-product-* / --dl-gallery-* / --dl-prodinfo-* / --dl-buybar-*
 * property resolved in section 1 of base.css.
 */

/* ── The frame ── */

.dl-product .dl-breadcrumb {
	margin-block-end: var(--dl-product-head-space);
}

.dl-product__grid {
	gap: var(--dl-product-gap);
	align-items: start;
}

/*
 * A grid item is `min-width: auto` by default, which lets a long fitment title
 * or a wide specification table push the column past its track.
 */
.dl-product__grid > * {
	min-inline-size: 0;
}

/*
 * The buy box. `.summary.entry-summary` is WooCommerce's own wrapper — the one
 * element on this screen the theme cannot name, because it is written into
 * content-single-product.php between two hooks.
 */
.dl-product .summary {
	display: flex;
	flex-direction: column;
	gap: var(--dl-buybox-gap);
	padding: var(--dl-buybox-pad);
}

.dl-product .product_title {
	font: var(--dl-product-title-font);
	letter-spacing: var(--tracking-tight);
}

/*
 * The meta row under the title. The buy box's own rhythm is 16px and the design
 * sets this row 10px under the <h1>, so the negative margin closes the
 * difference — the same optical pull the SKU used to make from above, in the
 * direction the row now sits. Wrapping, not the design's single line, because
 * at 390px a brand name plus a labelled SKU is wider than the column.
 */
.dl-product__meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 10px; /* no token — optical, between --space-3 and --space-4 */
	margin-block-start: calc(var(--dl-buybox-gap) * -1 + 10px);
}

.dl-product__brand {
	font: var(--weight-regular) var(--text-sm) / 1 var(--font-display);
	color: var(--text-muted);
}

/* tokens/base.css paints every <a> --text-brand; this one is the row's colour
   and takes the brand blue on hover, as the product card's title link does. */
.dl-product__brand a {
	color: inherit;
}

.dl-product__brand a:hover {
	color: var(--text-brand);
}

.dl-product__meta-sep {
	color: var(--neutral-300);
}

/*
 * The rules bracketing price and facts. The buy box is a flex column with a 16px
 * gap and the design sets 20px on both sides of a rule, so each side makes up the
 * 4px difference — the same optical correction .dl-product__meta makes above.
 * base.css gives every <hr> a --space-8 margin; this one is inside a gapped
 * column and states its own.
 */
.dl-product__rule {
	block-size: var(--border-width);
	margin-block: calc(var(--space-6) - var(--dl-buybox-gap));
	border: 0;
	background: var(--border-subtle);
}

/*
 * The facts list under the price — a named fact per row, the name in its own
 * column. The column is `max-content`, not the design's fixed 104px: two labels
 * as short as Lager and Frakt left 69px of nothing between name and value. It
 * still aligns every row on the widest label, and it is the one width that
 * survives a longer word — a translation, or a third row the shop adds — without
 * the number being pushed across the column.
 */
.dl-product__facts {
	display: grid;
	grid-template-columns: max-content 1fr;
	gap: var(--space-4) var(--space-5);
	margin: calc(var(--space-6) - var(--dl-buybox-gap)) 0 0;
}

.dl-product__fact-label {
	font: var(--type-small);
	color: var(--text-faint);
}

.dl-product__fact-value {
	display: flex;
	margin: 0;
}

/* Badge and note side by side — wrapping, because at 390px a badge plus a
   lead-time sentence is wider than the value column. */
.dl-product__fact-value--inline {
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-3);
}

/* The amount over its caption. */
.dl-product__fact-value--stack {
	flex-direction: column;
	gap: 3px; /* no token — tighter than --space-3, the design's own pairing */
}

.dl-product__dispatch {
	font: var(--type-small);
	color: var(--text-muted);
}

.dl-product__shipping-from {
	font: var(--type-small);
	color: var(--text-muted);
}

/* The number carries the weight, not the "Fra" around it. */
.dl-product__shipping-amount {
	font: var(--weight-medium) var(--text-sm) / 1.4 var(--font-display);
	color: var(--text-heading);
}

.dl-product__shipping-note {
	font: var(--type-caption);
	color: var(--text-faint);
}

/* ── Gallery ── */

.dl-gallery {
	display: var(--dl-gallery-display);
	gap: var(--dl-gallery-gap);
	/* Capped and centred at tablet, where it is alone in a 794px column and a
	   square photograph that wide is a wall. No cap at the other three widths,
	   where the auto margins have nothing to centre. */
	max-inline-size: var(--dl-gallery-max);
	/* Stated, not inherited from the stretch the auto margins below cancel:
	   inline-size containment measures a box's own contents as empty, so a
	   shrink-to-fit gallery under `container-type` would be 0 wide. */
	inline-size: 100%;
	margin-inline: auto;
	/* The rail's cap is a share of this box, not of the viewport — see
	   --dl-gallery-rail-max. A container only at desktop, where there is a rail. */
	container-type: var(--dl-gallery-container);
}

/*
 * The plate. It reserves its 1:1 whether or not the photograph has arrived, so
 * nothing below it moves as the images load — the one thing a gallery must not
 * do on a page whose next element is a price.
 */
.dl-gallery__frame {
	position: relative;
	flex: 1;
	min-inline-size: 0;
	/* The comment above is a claim the frame did not actually make: the 1:1 was
	   the slide's, so anything that stopped the stage producing height — a
	   missing image file, an attachment whose metadata never resolved — would
	   take the plate's height with it, and the price below it would move (#26). */
	aspect-ratio: 1 / 1;
	/* The 1:1 above is what the plate *reserves*; this is where it stops. Capped
	   on the block axis rather than the inline one, so the plate still fills the
	   column — an inline cap left a stripe of dead space between the photograph
	   and the price. Past the cap the box is landscape and the image, `contain`
	   as ever, is letterboxed rather than cropped. The reserved height stays
	   arithmetic either way, so nothing below it moves as the images land (#26). */
	max-block-size: var(--dl-gallery-plate-max);
	margin-inline: var(--dl-gallery-bleed);
	overflow: hidden;
	background: var(--dl-gallery-frame-bg);
	border: var(--dl-gallery-frame-border);
	border-radius: var(--dl-gallery-frame-radius);
}

/*
 * The stage is a scroll-snapping track holding every image at full width. That
 * is what makes it swipeable with no script at all, and it is the reason
 * assets/js/product.js reads the current image off the scroll position rather
 * than keeping a state of its own.
 */
.dl-gallery__stage {
	display: flex;
	/* Fill the plate now that the plate has a height of its own, rather than
	   sitting in the top of it. */
	block-size: 100%;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
}

.dl-gallery__stage::-webkit-scrollbar {
	display: none;
}

/*
 * Flex, not grid, for the same reason the product card's plate is — a grid
 * item's automatic minimum size is its content size in both axes, so a
 * photograph taller than the slide grew the row past it and the frame's
 * `overflow: hidden` was the only thing hiding the spill.
 */
.dl-gallery__slide {
	flex: 0 0 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 1 / 1;
	scroll-snap-align: center;
}

/*
 * `contain`, like every other product plate in the theme. The catalogue feed
 * carries every aspect ratio there is and a part cut off at the edge reads as a
 * different part — see the note in inc/setup.php.
 */
.dl-gallery__image {
	inline-size: 100%;
	block-size: 100%;
	min-inline-size: 0;
	min-block-size: 0;
	object-fit: contain;
	transition: transform var(--dur-slow) var(--ease-standard);
}

/* No lens, no lightbox — the whole of the design's zoom. Resolved under a
   pointer query, not a breakpoint; see --dl-gallery-zoom in base.css. */
.dl-gallery__slide:hover .dl-gallery__image {
	transform: scale(var(--dl-gallery-zoom));
}

.dl-gallery__placeholder {
	font: var(--type-caption);
	color: var(--text-faint);
	letter-spacing: var(--tracking-caps);
	text-transform: uppercase;
}

.dl-gallery .dl-product-card__badges {
	position: absolute;
	inset-block-start: var(--dl-gallery-inset);
	inset-inline-start: var(--dl-gallery-inset);
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-2);
	z-index: 1; /* no token — over the image, under nothing */
}

.dl-gallery__wishlist {
	display: var(--dl-gallery-wishlist-display);
	position: absolute;
	inset-block-start: 10px; /* no token — the heart's inset as drawn */
	inset-inline-end: 10px;
	z-index: 1;
	background: var(--surface-card);
}

/* ── The thumbnail rail ── */

/*
 * Never taller than the plate it belongs to: past that the chips scroll. The
 * scrollbar is left visible — thin, not hidden the way the stage's is — because
 * a clipped list of photographs with no sign that it is clipped is a list the
 * reader believes is complete. The fade below says the same thing again.
 */
.dl-gallery__rail {
	display: var(--dl-gallery-rail-display);
	flex-direction: column;
	gap: var(--space-3);
	max-block-size: var(--dl-gallery-rail-max);
	/* --focus-ring's 3px spread, which overflow-y would otherwise clip on both
	   axes, given back on the inline one. */
	padding: 3px;
	margin-inline: -3px;
	overflow-y: auto;
	overscroll-behavior: contain;
	scrollbar-width: thin;
	scrollbar-color: var(--neutral-300) transparent;
}

.dl-gallery__rail::-webkit-scrollbar {
	inline-size: 4px;
}

.dl-gallery__rail::-webkit-scrollbar-thumb {
	background: var(--neutral-300);
	border-radius: var(--radius-pill);
}

/* Which edge the list runs past, from assets/js/product.js. */
.dl-gallery__rail[data-dl-overflow='end'] {
	-webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
	mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
}

.dl-gallery__rail[data-dl-overflow='start'] {
	-webkit-mask-image: linear-gradient(to top, #000 calc(100% - 28px), transparent);
	mask-image: linear-gradient(to top, #000 calc(100% - 28px), transparent);
}

.dl-gallery__rail[data-dl-overflow='both'] {
	-webkit-mask-image: linear-gradient(to bottom, transparent, #000 28px, #000 calc(100% - 28px), transparent);
	mask-image: linear-gradient(to bottom, transparent, #000 28px, #000 calc(100% - 28px), transparent);
}

.dl-gallery__thumb {
	flex: 0 0 auto; /* the chip keeps its height as the rail scrolls */
	inline-size: var(--dl-gallery-thumb-size);
	block-size: var(--dl-gallery-thumb-size);
	padding: 0;
	overflow: hidden;
	background: var(--neutral-50);
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--radius-md);
	cursor: pointer;
	transition: var(--transition-control);
}

/*
 * The selected chip takes the brand border *and* the focus ring, which is what
 * the design draws. It is `aria-current`, not a class: a chip that looks current
 * is one a screen reader announces as current.
 */
.dl-gallery__thumb[aria-current='true'] {
	border-color: var(--border-brand);
	box-shadow: var(--focus-ring);
}

/* A chip is a miniature of the photograph it switches to, so it does not crop
   what the large image shows whole. */
.dl-gallery__thumb-image {
	inline-size: 100%;
	block-size: 100%;
	object-fit: contain;
}

/* ── The dot pager ── */

.dl-gallery__dots {
	display: var(--dl-gallery-dots-display);
	justify-content: center;
	align-items: center;
	gap: 6px; /* no token — the pager's own gap as drawn */
	padding-block-start: var(--space-4);
}

/*
 * The active dot stretches rather than swapping colour alone — 6px to 18px over
 * --dur-fast, which tokens/motion.css already zeroes under prefers-reduced-motion.
 * The hit target is the padding, so the 6px dot is still 24px of tappable area.
 */
.dl-gallery__dot {
	inline-size: var(--dl-gallery-dot-size);
	block-size: var(--dl-gallery-dot-size);
	padding: 0;
	border: 0;
	border-radius: var(--radius-full);
	background: var(--neutral-300);
	cursor: pointer;
	transition: inline-size var(--dur-fast) var(--ease-standard), background-color var(--dur-fast) var(--ease-standard);
}

.dl-gallery__dot[aria-current='true'] {
	inline-size: var(--dl-gallery-dot-active);
	background: var(--brand);
}

/* ── The cart row ── */

.dl-buybox__row {
	display: flex;
	align-items: center;
	gap: var(--space-4);
}

/* The stepper is sized by its own three parts. Without this the button's `flex: 1`
   wins the whole row and squeezes it to the width of one glyph. */
.dl-buybox__row .dl-qty {
	flex: 0 0 auto;
}

/* The button takes whatever the stepper and the heart leave, at every width. */
.dl-buybox__submit {
	flex: 1;
	min-inline-size: 0;
}

.dl-buybox__wishlist {
	display: var(--dl-buybox-wishlist-display);
	flex: 0 0 auto;
}

.dl-buybox__variations {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
}

.dl-buybox__reset {
	justify-self: start;
	font: var(--type-small);
	color: var(--text-muted);
}

.dl-buybox__variation-wrap {
	display: flex;
	flex-direction: column;
	gap: var(--dl-buybox-gap);
}

/*
 * WooCommerce leaves `price_html` empty when every variation costs the same —
 * the range in the summary above is already right — so the Price is hidden along
 * with its value rather than leaving a stranded "Prisen er inkl. mva." line.
 */
.woocommerce-variation-price:has(.dl-price__value:empty) {
	display: none;
}

/* ── The reassurance lines ── */

/*
 * The "Passer din bil?" alert and the picker it carried are gone with #28 — the
 * reason is in inc/product.php, where the callback used to be. The
 * `dl-vehicle--flush` rules went with them; they are back in components.css with
 * #31, which is where the catalogue sidebar's picker uses the variant.
 */

.dl-product__assurances {
	display: flex;
	flex-direction: column;
	gap: 10px; /* no token — optical, between --space-3 and --space-4 */
	padding-block-start: var(--space-3);
}

.dl-product__assurance {
	display: flex;
	align-items: center;
	gap: 10px; /* no token — pairs with the row gap above */
	font: var(--type-small);
	color: var(--text-muted);
}

.dl-product__assurance-icon {
	flex: 0 0 auto;
	color: var(--brand);
}

/* ── Tabs / Accordion ── */

.dl-prodinfo {
	display: var(--dl-prodinfo-display);
	flex-direction: column;
	/* From 1024px up this is the reading measure — the strip's rule and the panel
	   below it end on the same line. Full width in the accordion, which is the
	   card. */
	max-inline-size: var(--dl-prodinfo-max);
	margin-block-start: var(--dl-prodinfo-space);
	background: var(--surface-card);
	border: var(--dl-prodinfo-border);
	border-radius: var(--dl-prodinfo-radius);
	/* The accordion card clips its rows to the radius; the tab strip must not
	   clip the focus ring on a tab. */
	overflow: var(--dl-prodinfo-overflow);
}

/*
 * Below 1024px this is `display: contents`, so the headers become children of
 * the column above and `order` interleaves them with the panels; from 1024px up
 * it is the tab strip and `order` no longer applies, because the container is a
 * plain block again. --dl-i is the tab's index, printed by the template — the
 * one number the markup carries, because the tab count is whatever the shop's
 * plugins make it.
 */
.dl-prodinfo__list {
	display: var(--dl-prodinfo-list-display);
	gap: var(--dl-prodinfo-tab-gap);
	border-block-end: var(--dl-prodinfo-list-rule);
}

.dl-prodinfo__tab {
	order: calc(var(--dl-i) * 2);
	position: relative;
	display: flex;
	align-items: center;
	gap: 14px; /* no token — the accordion header's gap */
	padding: var(--dl-prodinfo-tab-pad);
	border: 0;
	background: transparent;
	color: var(--text-muted);
	font: var(--dl-prodinfo-tab-font);
	text-align: start;
	cursor: pointer;
	transition: var(--transition-control);
}

.dl-prodinfo__tab-label {
	flex: 1;
}

/*
 * State is colour and the indicator, never weight: --dl-prodinfo-tab-font is the
 * same in every state, because a bolder selected tab is a wider selected tab and
 * every tab after it moves when the customer switches. The treatment that makes
 * the strip read as controls rather than as three words of body copy is written
 * out beside that token in base.css.
 */
.dl-prodinfo__tab[aria-selected='true'],
.dl-prodinfo__tab[aria-expanded='true'] {
	color: var(--text-heading);
}

@media (hover: hover) and (pointer: fine) {
	.dl-prodinfo__tab:hover {
		background: var(--dl-prodinfo-tab-hover-bg);
		color: var(--text-heading);
	}
}

/*
 * The tab indicator is an element rather than a border: the design system draws
 * it 2px and every border in this system is exactly 1px. It only paints where
 * there is a rule to sit on, which is the tab strip.
 */
.dl-prodinfo__tab::after {
	content: '';
	position: absolute;
	inset-inline: 0;
	inset-block-end: -1px;
	block-size: 2px; /* no token — indicator weight, not a border */
	background: transparent;
	transition: background-color var(--dur-fast) var(--ease-standard);
}

.dl-prodinfo__tab[aria-selected='true']::after {
	background: var(--brand);
}

.dl-prodinfo__chevron {
	display: var(--dl-prodinfo-chevron-display);
	flex: 0 0 auto;
	color: var(--text-faint);
	transition: transform var(--dur-fast) var(--ease-standard);
}

.dl-prodinfo__tab[aria-expanded='true'] .dl-prodinfo__chevron {
	transform: rotate(180deg);
}

/*
 * The hairline between accordion rows. It has to be selected in the DOM, not in
 * the visual order — `display: contents` does not make a header a sibling of the
 * panels it is painted between — so it is every header but the first, which is
 * the same set. From 1024px up --dl-prodinfo-row-rule is 0 and the strip's own
 * --dl-prodinfo-list-rule is the only hairline there is — the one the selected
 * tab's indicator is painted onto.
 */
.dl-prodinfo__list > .dl-prodinfo__tab:not(:first-child) {
	border-block-start: var(--dl-prodinfo-row-rule);
}

.dl-prodinfo__panel {
	order: calc(var(--dl-i) * 2 + 1);
	padding: var(--dl-prodinfo-panel-pad);
}

.dl-prodinfo__panel[hidden] {
	display: none;
}

.dl-prodinfo__body {
	max-inline-size: var(--dl-prodinfo-panel-max);
}

/* ── Spesifikasjoner ── */

.dl-spec {
	inline-size: 100%;
	border-collapse: collapse;
	font: var(--type-body);
}

.dl-spec__label,
.dl-spec__value {
	padding: 10px 0; /* no token — the row's own rhythm as drawn */
	border-block-end: var(--border-width) solid var(--border-subtle);
	text-align: start;
	font-weight: var(--weight-regular);
	vertical-align: top;
}

.dl-spec__label {
	inline-size: var(--dl-prodinfo-spec-label-width);
	padding-inline-end: var(--space-5);
	font-weight: var(--dl-spec-label-weight);
	color: var(--text-muted);
}

.dl-spec__value {
	color: var(--text-heading);
}

/*
 * A row lit under the pointer, so one of thirty is followed across the measure
 * without counting hairlines. Hover only — there is no such thing to fall back on
 * for touch, and the label column's own weight is what carries the scan there.
 */
@media (hover: hover) and (pointer: fine) {
	.dl-spec__row:hover .dl-spec__label,
	.dl-spec__row:hover .dl-spec__value {
		background: var(--surface-page);
	}
}

/* ── Anmeldelser ──
 *
 * The shop ships reviews, so the panel WooCommerce's registry contributes is
 * drawn rather than left to its default comment markup over .dl-prose. The
 * hierarchy is the other two panels': --text-muted for what frames a review —
 * author, date, count — and --text-heading for the review itself.
 *
 * The stars are the vendored Lucide `star`, masked, the same outline every other
 * glyph in the theme is. State is colour, exactly as the wishlist heart's is:
 * --status-warning for the rating given, --border-default for the rest. There is
 * no solid star in an outline icon set and inventing one for five glyphs would be
 * the design system's only exception.
 */

.dl-reviews {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
}

/* The average is a row of stars and the first review opens with another one;
   without this they stack straight onto each other and read as one control. */
.dl-reviews__comments {
	display: flex;
	flex-direction: column;
	gap: var(--space-6);
}

/*
 * The rating track. WooCommerce prints
 * `.star-rating > span[style="width:60%"]`, its text carried for older readers
 * while `role="img"` + `aria-label` say the rating — so the text is taken out of
 * the box and the two mask layers paint over it.
 */
.star-rating {
	--dl-star-size: 16px; /* no token — the rating glyph, one of the icon sizes */
	position: relative;
	display: block;
	flex: 0 0 auto;
	inline-size: calc(var(--dl-star-size) * 5);
	block-size: var(--dl-star-size);
	overflow: hidden;
	color: var(--border-default);
	font-size: 0;
	line-height: 0;
}

.star-rating > span {
	position: absolute;
	inset-block: 0;
	inset-inline-start: 0;
	overflow: hidden;
	color: var(--status-warning);
	font-size: 0;
	line-height: 0;
}

.star-rating::before,
.star-rating > span::before {
	content: '';
	position: absolute;
	inset-block: 0;
	inset-inline-start: 0;
	inline-size: calc(var(--dl-star-size) * 5);
	background: currentColor;
	-webkit-mask: var(--ds-icon-star) left center / var(--dl-star-size) var(--dl-star-size) repeat-x;
	mask: var(--ds-icon-star) left center / var(--dl-star-size) var(--dl-star-size) repeat-x;
}

/* ── The average, in place of the heading the tab already carries ── */

.dl-reviews__summary {
	display: flex;
	align-items: center;
	gap: var(--space-4);
}

.dl-reviews__score {
	font: var(--type-h4);
	color: var(--text-heading);
}

.dl-reviews__count {
	font: var(--type-small);
	color: var(--text-muted);
}

/* ── The list ── */

/*
 * Two classes, because the panel body is `.dl-prose` and `.entry-content` and
 * both give a real `ol` its markers back — running copy wants them, a list of
 * reviews does not. This is the same list the accordion draws; it is not prose.
 */
.dl-reviews .dl-reviews__list,
.dl-reviews .children {
	margin: 0;
	padding: 0;
	list-style: none;
}

.dl-reviews__list .review,
.dl-reviews__list .comment {
	padding-block: var(--space-6);
	border-block-start: var(--border-width) solid var(--border-subtle);
}

.dl-reviews__list > li:first-child {
	padding-block-start: 0;
	border-block-start: 0;
}

/* A reply sits under its review, indented by the avatar's own column. */
.dl-reviews .dl-reviews__list .children {
	padding-inline-start: var(--space-9);
}

.dl-reviews .comment_container {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: var(--space-5);
	align-items: start;
}

.dl-reviews .comment_container img.avatar {
	inline-size: var(--space-9);
	block-size: var(--space-9);
	border-radius: var(--radius-pill);
	background: var(--surface-sunken);
}

.dl-reviews .comment-text {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
	min-inline-size: 0;
}

.dl-reviews .comment-text p.meta {
	margin: 0;
	font: var(--type-small);
	color: var(--text-muted);
}

.dl-reviews .woocommerce-review__author {
	font-weight: var(--weight-medium);
	color: var(--text-heading);
}

.dl-reviews .woocommerce-review__verified {
	color: var(--status-success);
	font-style: normal;
}

.dl-reviews .description {
	color: var(--text-body);
}

.dl-reviews .description > :last-child {
	margin-block-end: 0;
}

.dl-reviews__empty {
	margin: 0;
	color: var(--text-muted);
}

.dl-reviews__pagination ul {
	display: flex;
	gap: var(--space-3);
	margin: 0;
	padding: 0;
	list-style: none;
}

/* ── The form ── */

.dl-reviews__form-wrapper {
	padding-block-start: var(--space-7);
	border-block-start: var(--border-width) solid var(--border-subtle);
}

.dl-reviews__form-title {
	display: block;
	margin-block-end: var(--space-5);
	font: var(--type-h4);
	color: var(--text-heading);
}

.dl-reviews__form {
	display: flex;
	flex-flow: var(--dl-review-form-flow);
	gap: var(--space-5);
}

.dl-reviews__form p {
	margin: 0;
}

/* Name and e-mail share a row where there is room for two; the rating and the
   review itself never do — one is a short control, the other is the point.
   Which it is, is resolved in base.css §1 — see --dl-review-form-flow. */
.dl-reviews__form .comment-form-author,
.dl-reviews__form .comment-form-email {
	inline-size: var(--dl-review-form-half);
}

.dl-reviews__form > :not(.comment-form-author):not(.comment-form-email) {
	inline-size: 100%;
}

/* The rating is six short options; a full-width select over them reads as a
   bigger decision than it is. */
.dl-reviews__rating .dl-select {
	max-inline-size: 280px; /* no token — the rating select's own measure */
}

.dl-reviews__consent {
	padding-block: 0;
}

.dl-reviews__must-log-in,
.dl-reviews__gate {
	margin: 0;
	color: var(--text-muted);
}

/* ── The related and up-sell rows ── */

.dl-product__row {
	margin-block-start: var(--ds-section-rhythm);
}

/* ── StickyBuyBar ── */

/*
 * Pinned above the BottomNav, which is why it starts from --dl-bottom-nav-space
 * rather than from 0: at mobile that is the bar's height, at tablet it is zero
 * because the bar is gone, and from 1024px up this whole element is
 * `display: none`.
 *
 * The room it takes at the foot of the page is theme.json's root bottom padding,
 * which adds --dl-buybar-space — set here, on the screen that has a bar — so the
 * space exists before the bar is revealed and revealing it moves nothing. It is
 * set on :root through the body class rather than on the body itself, because
 * base.css § 4's scroll-padding reads it from the scrollport, which is the root
 * element and does not inherit from <body>.
 */
:root:has(body.single-product) {
	--dl-buybar-space: calc(var(--dl-buybar-h) + env(safe-area-inset-bottom));
}

.dl-buybar {
	display: var(--dl-buybar-display);
	align-items: center;
	position: fixed;
	inset-inline: 0;
	inset-block-end: var(--dl-bottom-nav-space);
	z-index: 40; /* no token — above the page, below the bottom bar (50) */
	block-size: var(--dl-buybar-h);
	background: var(--surface-card);
	border-block-start: var(--border-width) solid var(--border-subtle);
	/* no token — the design system's shadows all fall downwards; this one lifts */
	box-shadow: 0 -4px 16px rgba(25, 30, 37, 0.06);
}

/* `hidden` has to keep winning: the rule above is more specific than the UA's
   `[hidden] { display: none }`, so the bar would show while the script has it
   hidden. */
.dl-buybar[hidden] {
	display: none;
}

.dl-buybar__inner {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	inline-size: 100%;
	padding-inline: var(--ds-gutter);
}

.dl-buybar__price {
	min-inline-size: 0;
}

.dl-buybar__price .dl-price__value {
	white-space: nowrap;
}

/* The VAT note is a footnote to a price that has a whole buy box above it; in a
   72px bar it is a second line the price cannot spare. */
.dl-buybar__price .dl-price__vat {
	display: none;
}

.dl-buybar__qty {
	flex: 0 0 auto;
}

.dl-buybar__submit {
	flex: 1;
	min-inline-size: 0;
}

/* ══ 6. Handlekurv ═══════════════════════════════════════════════════════════
 *
 * The cart. A card of CartLines on the left, the OrderSummary on the right —
 * `rail` from bp.js, which is `1fr 380px` from 1024px up and one column below
 * it, where the summary becomes a card under the lines.
 *
 * The summary sticks from md up, at the measured header height rather than at
 * the handoff's literal 160px: assets/js/sticky.js writes --dl-header-h and a
 * sticky element only travels inside its parent's box, which is why the rail is
 * a direct child of the grid and carries nothing else. #14's rule.
 *
 * **This screen has no scroll container of its own.** Both columns grow with
 * their content and the page is the only thing that scrolls — the count #27
 * settled for every screen with a rail. A cart of forty lines is a long page,
 * which is correct; a 600px box with its own scrollbar beside a sticky summary
 * is two scrolls in one viewport.
 */

.dl-cart__steps {
	margin-block-end: var(--dl-cart-steps-space);
}

.dl-cart__title {
	margin-block-end: var(--dl-cart-title-space);
	font: var(--dl-cart-title-font);
	letter-spacing: var(--tracking-tight);
}

.dl-cart__grid {
	display: grid;
	grid-template-columns: var(--dl-track-rail);
	align-items: start;
	gap: var(--dl-cart-gap);
}

/* Nothing is beside an empty cart: the card takes the whole column rather than
   leaving a 380px hole where a summary of nothing would be. */
.dl-cart__grid--empty {
	grid-template-columns: 1fr;
}

/* The form is the grid's first cell, not a box of its own. */
.dl-cart__main {
	min-width: 0;
}

/* Padding is on the sides only, so the hairline between two lines runs the full
   width of the card — the handoff's `Card padding="0 var(--space-7)"`. */
.dl-cart__card {
	padding-inline: var(--dl-cart-card-pad-inline);
}

.dl-cart__list {
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * The two ghost actions under the lines. On mobile they wrap rather than
 * shrink — «Fortsett å handle» and «Oppdater kurv» do not both fit a 390px row
 * — and they sit above the totals card rather than below it, which is where the
 * handoff's MCart draws the first of them. One markup tree; the deviation is
 * recorded in the theme README.
 */
.dl-cart__actions {
	display: flex;
	flex-wrap: var(--dl-cart-actions-wrap);
	justify-content: space-between;
	gap: var(--space-4);
	padding: var(--dl-cart-actions-pad);
	border-block-start: var(--border-width) solid var(--border-subtle);
}

.dl-cart__empty {
	padding-block: var(--space-9);
}

/* `align-items: start` on the grid keeps the two columns from stretching each
   other's cards — but a sticky element only travels inside its own parent's
   box, and a rail as tall as the summary has nowhere to travel. So the rail
   alone stretches back to the row. #14's rule, the same as the checkout's.
 *
 * The checkout needs nothing else; the cart does, because WooCommerce's own
 * `.cart_totals` wrapper sits between the rail and the summary and is exactly
 * as tall as it. That wrapper stays — cart/cart-totals.php explains that plugins
 * select on it — so the rail becomes a flex column and the wrapper grows to fill
 * it, which hands the summary the travel the rail already has.
 *
 * `:only-child` because `woocommerce_cart_collaterals` is a public hook: a
 * plugin that adds a box beside the totals would otherwise have it pushed to
 * the foot of a full-height column. Where that happens the summary simply stops
 * sticking, which is where it stood before this rule. */
.dl-cart__rail {
	display: flex;
	flex-direction: column;
	align-self: stretch;
	min-width: 0;
}

.dl-cart__rail > .cart_totals:only-child {
	flex: 1 1 auto;
}

/* ── OrderSummary ──
 * Rendered by inc/order-summary.php, and rendered again by the checkout — the
 * class names are the partial's, not this screen's.
 */

.dl-summary {
	position: var(--dl-summary-position);
	top: var(--dl-summary-top);
	padding: var(--space-7);
}

.dl-summary__title {
	margin-block-end: var(--space-6);
	font: var(--type-h3);
}

.dl-summary__rows {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	font: var(--type-body);
}

.dl-summary__row {
	display: flex;
	justify-content: space-between;
	gap: var(--space-4);
}

.dl-summary__label {
	color: var(--text-muted);
}

.dl-summary__value {
	color: var(--text-heading);
	text-align: end;
}

/* Free shipping is stated, not implied by a zero. */
.dl-summary__value--free {
	color: var(--status-success);
}

/* Shipping this cart cannot know yet — there is no address until the checkout. */
.dl-summary__value--muted {
	color: var(--text-muted);
}

.dl-summary__row--discount .dl-summary__value {
	color: var(--status-success);
}

/* WooCommerce's own remove-coupon link, which rides inside the discount row. */
.dl-summary__row--discount a {
	color: var(--text-faint);
}

.dl-summary__coupon {
	display: flex;
	gap: var(--space-3);
	padding-block-start: var(--space-3);
}

.dl-summary__coupon-input {
	flex: 1;
	min-width: 0;
}

/* A coupon's answer sits under the field that asked the question — see
   delsenter_cart_is_coupon_request(). */
.dl-summary__notices .dl-alert {
	padding: var(--space-3) var(--space-4);
}

.dl-summary__divider {
	height: var(--border-width);
	margin-block: var(--space-3);
	border: 0;
	background: var(--border-subtle);
}

.dl-summary__total {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-4);
}

.dl-summary__total-label {
	font: var(--type-body-strong);
	color: var(--text-heading);
}

/* The Price component is a column of value + VAT note; in the total row both
   halves are set against the right edge. */
.dl-summary__total-value {
	align-items: flex-end;
	text-align: end;
}

.dl-summary__cta {
	margin-block-start: var(--space-3);
}

.dl-summary__chips {
	display: flex;
	justify-content: center;
	gap: 10px; /* no token — the chip spacing as drawn */
	margin-block-start: 4px; /* no token — optical, under a 48px button */
}

.dl-summary__chip {
	padding: 5px var(--space-4); /* no token — the chip's own inset as drawn */
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--radius-sm);
	font: var(--type-caption);
	color: var(--text-muted);
}

/* WooCommerce's notices are Alerts everywhere (inc/notices.php); on this screen
   they sit between the title and the grid.

   Not `.dl-cart > .woocommerce-notices-wrapper`: the shortcode prints its own
   `div.woocommerce` around everything it renders, so the wrapper is a
   grandchild. With the child combinator this rule matched nothing and the
   notice sat flush against the grid under it. */
.dl-cart > .dl-notice,
.dl-cart .woocommerce-notices-wrapper > .dl-alert {
	margin-block-end: var(--space-6);
}

.dl-alert__list {
	margin: 0;
	padding: 0;
	list-style: none;
}

.dl-alert__list li + li {
	margin-block-start: var(--space-2);
}

/* ══ 7. Kasse ════════════════════════════════════════════════════════════════
 *
 * «Ett skjema — alt synlig». Four flat sections in the first column of the same
 * `rail` grid the cart uses — `1fr 380px` from 1024px up, one column below it —
 * with the OrderSummary in the second, sticky at the measured header height
 * rather than at the handoff's literal `top: 160`.
 *
 * There are no numbered cards *inside* the form. The customer sees the whole of
 * what is asked at once and fills it in in whatever order they like, which is
 * the direction the design settled on. CheckoutSteps above the form is a
 * different thing — it places the screen in the flow, and all three screens of
 * that flow carry it.
 *
 * The grid *is* the form. `form.checkout` has to wrap the whole screen — the
 * sections carry the fields, the sticky bar carries the button below 1024px —
 * so it cannot be a box inside one column. Its three children are the left
 * column, the rail and the bar; the heading row and the express row are drawn
 * *outside* it, from delsenter_the_checkout_screen(), because a third grid
 * column is not what they are.
 *
 * The fields are WooCommerce's own markup. `p.form-row` is the design's Field,
 * `.woocommerce-input-wrapper` the box a leading icon is laid over, and
 * `.checkout-inline-error-message` the error line — all three are what
 * `woocommerce_form_field()` prints, so a field a plugin adds is drawn without
 * this theme knowing it exists. inc/checkout.php explains why nothing is
 * rebuilt.
 */

/* CheckoutSteps — step 1 above the form, step 2 on the confirmation. */
.dl-checkout__steps {
	margin-block-end: var(--dl-cart-steps-space);
}

/* ── The heading row ── */

.dl-checkout__head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-5);
	margin-block-end: var(--space-7);
}

.dl-checkout__title {
	margin: 0;
	font: var(--dl-checkout-title-font);
	letter-spacing: var(--tracking-tight);
}

.dl-checkout__head-aside {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-6);
}

.dl-checkout__back {
	display: inline-flex;
	align-items: center;
	gap: 6px; /* no token — the label sits against its arrow */
	font: var(--type-small);
	color: var(--text-brand);
	text-decoration: none;
}

.dl-checkout__back:hover {
	text-decoration: underline;
}

.dl-checkout__secure {
	display: inline-flex;
	align-items: center;
	gap: 7px; /* no token — optical, from the design system */
	font: var(--type-caption);
	color: var(--text-muted);
}

.dl-checkout__secure .ds-icon {
	color: var(--status-success);
}

/* ── The express row ──
 *
 * Drawn in full even though nothing fills it by default: a gateway that hooks
 * `delsenter_checkout_express_buttons` gets the design without touching CSS.
 * See delsenter_get_checkout_express() for why the default is nothing at all. */

.dl-checkout__express {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	margin-block-end: var(--space-7);
}

.dl-checkout__express-row {
	display: grid;
	grid-template-columns: var(--dl-track-form2);
	gap: 10px; /* no token — the handoff's button pair */
}

.dl-checkout__express-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 8px; /* no token */
	height: var(--control-h-lg);
	border: 0;
	border-radius: var(--radius-md);
	background: var(--surface-card);
	color: var(--text-heading);
	font: var(--weight-medium) var(--text-base) / 1 var(--font-display);
	cursor: pointer;
	transition: var(--transition-control);
}

.dl-checkout__express-btn--outline {
	border: var(--border-width) solid var(--border-default);
}

.dl-checkout__express-or {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	font: var(--type-caption);
	color: var(--text-faint);
}

.dl-checkout__express-or::before,
.dl-checkout__express-or::after {
	content: '';
	flex: 1;
	height: var(--border-width);
	background: var(--border-subtle);
}

/* ── The grid ── */

.dl-checkout__form {
	display: grid;
	grid-template-columns: var(--dl-track-rail);
	align-items: start;
	gap: var(--dl-cart-gap);
}

.dl-checkout__main {
	display: flex;
	flex-direction: column;
	gap: var(--dl-checkout-section-gap);
	min-width: 0;
}

/* The two sections of #customer_details are the column's children, not a box of
   their own: the wrapper only exists for country-select.js to scope to. */
.dl-checkout__customer {
	display: contents;
}

/* `align-items: start` on the grid keeps the two columns from stretching each
   other's cards — but a sticky element only travels inside its own parent's
   box, and a rail as tall as the summary has nowhere to travel. So the rail
   alone stretches back to the row: the box is the form's height, the summary
   inside it sticks. #14's rule, the same as the cart's. */
.dl-checkout__rail {
	align-self: stretch;
	min-width: 0;
}

/* ── A section ── */

.dl-checkout__section-head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-5);
	margin-block-end: var(--dl-checkout-head-gap);
}

.dl-checkout__section-title {
	margin: 0;
	font: var(--type-h3);
}

/* «Har du konto? Logg inn» — the affordance for the panel above the form. */
.dl-checkout__login-ask {
	display: inline-flex;
	align-items: center;
	gap: 5px; /* no token — three words and two glyphs on one line */
	font: var(--type-small);
	color: var(--text-muted);
}

.dl-checkout__login-ask .ds-icon {
	color: var(--text-faint);
}

.dl-checkout__login-link {
	display: inline-flex;
	align-items: center;
	gap: 5px; /* no token */
	color: var(--text-brand);
	font-weight: var(--weight-medium);
	text-decoration: none;
}

.dl-checkout__login-link .ds-icon {
	color: currentcolor;
}

.dl-checkout__login-link:hover {
	text-decoration: underline;
}

/* One rhythm through a section, however deep WooCommerce's own wrappers nest —
   the billing inner, the account block, the second address and the order notes
   are each a div this theme did not choose the depth of. */
.dl-checkout__section-body > * + *,
.woocommerce-billing-fields__inner > * + *,
.dl-checkout__shipping-fields > * + *,
.dl-checkout__additional > * + *,
.dl-checkout__account > * + *,
.shipping_address > * + * {
	margin-block-start: var(--dl-checkout-field-gap);
}

/* The second address unfolds under its checkbox; checkout.js slides it. */
.dl-checkout__shipping-fields,
.dl-checkout__additional {
	margin-block-start: var(--space-6);
}

/* ── The field grids ── */

.dl-checkout__fields {
	display: grid;
	grid-template-columns: var(--dl-track-form2);
	gap: var(--dl-checkout-field-gap);
}

/* Postnummer, Poststed, Gateadresse — the design's order, imposed through
   `priority` in PHP so address-i18n.js re-sorts to the same arrangement. */
.dl-checkout__fields--address {
	grid-template-columns: var(--dl-track-address);
}

/* A row spans the grid unless it is one of the short ones; a field this theme
   has never seen takes the full width, which is the safe default. */
.dl-checkout__fields > .form-row {
	grid-column: 1 / -1;
}

.dl-checkout__fields > .form-row.dl-field--col {
	grid-column: span 1;
}

/* The e-mail address and the telephone take the whole row even though they are
   short fields — the design leads with one and closes with the other. */
.dl-checkout__fields > .form-row.dl-field--wide {
	grid-column: 1 / -1;
}

/* The faktura toggle, and the panel the two rows behind it live in. The panel is
   folded away by `hidden`, set from checkout.js — not from here — so with
   scripts blocked the fields are visible rather than unreachable. `.form-row`
   sets `display: flex`, which outranks the UA's `[hidden]` rule, so it has to be
   said again. */
.dl-checkout__fields > .form-row.dl-checkout__invoice-toggle {
	margin-block-start: var(--space-2);
}

.dl-checkout__fields > .form-row[hidden] {
	display: none;
}

.dl-checkout__biz,
.dl-checkout__login .dl-login,
.dl-checkout__methods > li > .payment_box {
	padding: var(--space-6);
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--radius-md);
	background: var(--neutral-25);
	animation: ds-rise var(--dur-base) var(--ease-out);
}

.dl-checkout__biz {
	margin-block-start: 10px; /* no token — the panel sits against its checkbox */
}

.dl-checkout__biz[hidden] {
	display: none;
}

.dl-checkout__biz-fields {
	display: grid;
	grid-template-columns: var(--dl-track-form2);
	gap: var(--dl-checkout-field-gap);
}

.dl-checkout__biz-fields > .form-row {
	grid-column: 1 / -1;
	margin: 0;
}

.dl-checkout__biz-fields > .form-row.dl-field--col {
	grid-column: span 1;
}

/* ── The login panel above the form ── */

.dl-checkout__login {
	margin-block-end: var(--space-7);
}

.dl-checkout__login:empty {
	display: none;
}

.dl-checkout__login .dl-login {
	max-width: none;
	margin-block-end: 0;
}

.dl-checkout__login .dl-login__fields {
	display: grid;
	grid-template-columns: var(--dl-track-form2);
	gap: var(--dl-checkout-field-gap);
}

/* ── Field, as WooCommerce prints it ── */

.form-row.dl-field {
	display: flex;
	flex-direction: column;
	gap: 6px; /* no token — the Field's own label/control gap, as in components.css */
	margin: 0;
}

.dl-field__label .required {
	margin-inline-start: 3px; /* no token */
	color: var(--status-danger);
	text-decoration: none;
}

.dl-field__label .optional {
	color: var(--text-muted);
	font: var(--type-caption);
}

/* The wrapper WooCommerce puts round the control is the box an icon is laid
   over — the same job .dl-control does in the component layer. */
.dl-field .woocommerce-input-wrapper {
	position: relative;
	display: block;
}

.dl-field .woocommerce-input-wrapper .description {
	display: block;
	margin-block-start: 4px; /* no token — under the control, above the next field */
	font: var(--type-caption);
	color: var(--text-muted);
}

/* Leading icon. A mask rather than an element: the markup is WooCommerce's and
   has nowhere to put one. */
.dl-field--icon .woocommerce-input-wrapper::before {
	content: '';
	position: absolute;
	top: calc(var(--control-h-md) / 2);
	left: var(--space-4);
	width: 16px; /* no token — the design system's inline glyph */
	height: 16px;
	transform: translateY(-50%);
	background: currentcolor;
	color: var(--text-faint);
	pointer-events: none;
}

.dl-field--icon .dl-input {
	padding-left: var(--space-9); /* 40px — 12 left + 16 glyph + 12 gap */
}

.dl-field--icon-mail .woocommerce-input-wrapper::before {
	-webkit-mask: var(--ds-icon-mail) center / contain no-repeat;
	mask: var(--ds-icon-mail) center / contain no-repeat;
}

.dl-field--icon-phone .woocommerce-input-wrapper::before {
	-webkit-mask: var(--ds-icon-phone) center / contain no-repeat;
	mask: var(--ds-icon-phone) center / contain no-repeat;
}

.dl-field--icon-building-2 .woocommerce-input-wrapper::before {
	-webkit-mask: var(--ds-icon-building-2) center / contain no-repeat;
	mask: var(--ds-icon-building-2) center / contain no-repeat;
}

/* The select's chevron, on the same reasoning. */
.dl-field .woocommerce-input-wrapper:has(> .dl-select)::after {
	content: '';
	position: absolute;
	top: calc(var(--control-h-md) / 2);
	right: 11px; /* no token — optical, from the design system */
	width: 16px; /* no token */
	height: 16px;
	transform: translateY(-50%);
	background: currentcolor;
	color: var(--text-faint);
	-webkit-mask: var(--ds-icon-chevron-down) center / contain no-repeat;
	mask: var(--ds-icon-chevron-down) center / contain no-repeat;
	pointer-events: none;
}

/* WooCommerce's noscript «update country» button, which only a customer with no
   JavaScript ever sees. */
.dl-field noscript button {
	margin-block-start: var(--space-3);
}

/* ── The invalid state ──
 * The row is marked by WooCommerce, on blur and again on a server-side error.
 * The border is --status-danger, which is --red-600.
 *
 * The same border is painted from `[aria-invalid='true']` in components.css,
 * which checkout.js now writes alongside the description. The two overlap on
 * the three text controls and both stay: the class also reaches
 * `.dl-check__box`, where the attribute sits on a visually hidden input, and
 * the attribute reaches every consumer of the component outside the checkout. */

.form-row.woocommerce-invalid .dl-input,
.form-row.woocommerce-invalid .dl-select,
.form-row.woocommerce-invalid .dl-textarea {
	border-color: var(--status-danger);
}

.form-row.woocommerce-invalid .dl-check__box {
	border-color: var(--status-danger);
}

.checkout-inline-error-message {
	margin: 0;
	font: var(--type-caption);
	color: var(--status-danger);
}

/* ── Levering — the rate tiles ── */

.dl-shipping__package {
	margin-block-end: var(--space-4);
	font: var(--type-body-strong);
	color: var(--text-heading);
}

.dl-shipping__tiles {
	display: grid;
	grid-template-columns: var(--dl-checkout-tiles-ship);
	gap: 10px; /* no token — the handoff's tile gutter */
	margin: 0;
	padding: 0;
	list-style: none;
}

/* The card is the tile, not the label: the rate's note is a sibling of the
   label, and it belongs inside the same box as the name it explains. */
.dl-shipping__tile {
	position: relative;
	padding: var(--space-5);
	border: var(--border-width) solid var(--border-default);
	border-radius: var(--radius-md);
	background: var(--surface-card);
	transition: var(--transition-control);
}

.dl-shipping__tile-label {
	display: flex;
	align-items: flex-start;
	gap: 11px; /* no token — glyph to text, from the handoff */
	text-align: left;
}

/* The whole card is the target, note included — the label is stretched over the
   tile rather than the note being pulled into it, which would read the note out
   as the radio's accessible name. `--single` has no radio, so no overlay. */
.dl-shipping__tile-label:not(.dl-shipping__tile-label--single)::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
	cursor: pointer;
}

/* Selected, from the radio itself. Not from a class written at render time: the
   fragment only comes back after the round trip, and for those few hundred
   milliseconds two tiles would look chosen. `:has()` is the live state, and the
   theme already depends on it for the Select's chevron. */
.dl-shipping__tile:has(.dl-shipping__tile-input:checked) {
	border-color: var(--border-brand);
	background: var(--brand-subtle);
}

/* Visually hidden rather than `display: none`: a radio nobody can focus is a
   choice nobody can make from the keyboard. The ring is on the tile. */
.dl-shipping__tile-input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: 0;
	opacity: 0;
	pointer-events: none;
}

.dl-shipping__tile:focus-within {
	border-color: var(--border-brand);
	box-shadow: var(--focus-ring);
}

.dl-shipping__tile-icon {
	margin-block-start: 1px; /* no token — optical, against the title's cap height */
	color: var(--text-faint);
}

.dl-shipping__tile:has(.dl-shipping__tile-input:checked) .dl-shipping__tile-icon {
	color: var(--brand);
}

.dl-shipping__tile-text {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 3px; /* no token — title to estimate */
	min-width: 0;
}

.dl-shipping__tile-title {
	font: var(--weight-regular) var(--text-base) / 1.2 var(--font-body);
	color: var(--text-heading);
}

.dl-shipping__tile-desc {
	font: var(--type-caption);
	color: var(--text-muted);
}

.dl-shipping__tile-price {
	font: var(--weight-medium) var(--text-sm) / 1.2 var(--font-display);
	color: var(--text-heading);
	white-space: nowrap;
}

.dl-shipping__free {
	color: var(--status-success);
}

/* A rate's note — the pickup instructions, a courier's terms, a custom quote.
 * Secondary text inside the tile, under the rate it explains and indented to
 * the title's column, past the glyph. It stays a sibling of the <label> rather
 * than part of it, so it is announced as a description once rather than read
 * out as the radio's name. */
.dl-shipping__note {
	margin-block-start: var(--space-3);
	margin-inline-start: 29px; /* no token — glyph (18) + its gutter (11) */
	font: var(--type-caption);
	color: var(--text-muted);
}

/* The label's overlay lies over the note, so anything the note offers to click
   has to be lifted back above it. */
.dl-shipping__note a {
	position: relative;
	z-index: 1;
}

.dl-shipping__note strong {
	font-weight: var(--weight-semibold);
	color: var(--text-heading);
}

.dl-shipping__contents {
	margin-block-start: var(--space-3);
	font: var(--type-caption);
	color: var(--text-muted);
}

/* ── Betaling — the method tiles ──
 *
 * `display: contents` on the <li> is what lets the tile and the gateway's panel
 * be siblings in one grid: the tiles fill the columns and the panel spans the
 * row beneath them, which is where the design opens it. The <li> loses its box
 * and, in some browsers, its list semantics — payment-method.php writes
 * `role="listitem"` back on, and payment.php `role="list"`. */

.dl-checkout__methods {
	display: grid;
	grid-template-columns: var(--dl-checkout-tiles-pay);
	gap: 10px; /* no token — the same tile gutter as Levering */
	margin: 0;
	padding: 0;
	list-style: none;
}

.dl-checkout__methods > li {
	display: contents;
}

.dl-checkout__method-tile {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 4px; /* no token — title to description */
	padding: var(--space-5);
	border: var(--border-width) solid var(--border-default);
	border-radius: var(--radius-md);
	background: var(--surface-card);
	cursor: pointer;
	transition: var(--transition-control);
}

/* No server-rendered twin here, unlike the rate tiles: selecting a gateway does
   not always repaint `#payment`, so a class written at render time would go
   stale the moment the customer changed their mind. `:has()` is the live state
   and the only one. */
.dl-checkout__method:has(.dl-checkout__method-input:checked) .dl-checkout__method-tile {
	border-color: var(--border-brand);
	background: var(--brand-subtle);
}

.dl-checkout__method-input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: 0;
	opacity: 0;
	pointer-events: none;
}

.dl-checkout__method-tile:focus-within {
	border-color: var(--border-brand);
	box-shadow: var(--focus-ring);
}

.dl-checkout__method-title {
	font: var(--weight-medium) var(--text-base) / 1.2 var(--font-display);
	color: var(--text-heading);
}

.dl-checkout__method-desc {
	font: var(--type-caption);
	color: var(--text-muted);
}

/* A gateway's own mark, where the shop has uploaded one. */
.dl-checkout__method-icon {
	display: flex;
	align-items: center;
	gap: var(--space-3);
}

.dl-checkout__method-icon img {
	max-height: 24px; /* no token — the gateway marks are supplied at mixed sizes */
	width: auto;
}

/* The gateway's panel: its markup, in a tinted well spanning the row under the
   tiles. The box itself is shared with the faktura panel and the login panel. */
.dl-checkout__methods > li > .payment_box {
	/* After every tile, not after its own. Auto-placement follows document
	   order, which would put the open panel between the tile it belongs to and
	   the next one and break the row of three; `order` moves it past all of
	   them. Only one panel is ever laid out — WooCommerce gives the rest
	   `display: none`, which takes them out of the grid entirely. */
	order: 1;
	grid-column: 1 / -1;
	margin: 0;
	font: var(--type-small);
	color: var(--text-body);
}

.dl-checkout__methods > li > .payment_box p:last-child {
	margin-block-end: 0;
}

.dl-checkout__method-empty {
	grid-column: 1 / -1;
}

/* ── The consents and the button ── */

.dl-checkout__consents {
	display: flex;
	flex-direction: column;
	gap: 10px; /* no token — the handoff's consent stack */
	margin-block-start: var(--space-6);
}

.dl-checkout__consents .form-row {
	margin: 0;
}

.dl-checkout__terms .woocommerce-terms-and-conditions {
	max-height: 200px; /* no token — a scrollable excerpt of the terms page */
	margin-block-end: var(--space-4);
	padding: var(--space-5);
	overflow-y: auto;
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--radius-sm);
	font: var(--type-small);
}

/* The place-order row keeps the nonce, the noscript fallback and whatever a
   plugin hooks either side of the submit — the button itself is in the slot
   below, or in the sticky bar. */
.dl-checkout__place-order {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	margin: 0;
}

.dl-checkout__place-order:empty {
	display: none;
}

.dl-checkout__submit-slot {
	margin-block-start: var(--space-7);
}

/* The slot is empty exactly when checkout.js has moved the button into the
   sticky bar — so it disappears then, and not one moment earlier. With scripts
   blocked the button never leaves, and it stays visible on every width, which
   is the fallback the whole arrangement is built around. */
.dl-checkout__submit-slot:empty {
	display: none;
}

/* The amount in the button's label. Hidden below 1024px, where the design's
   label is the short «Fullfør» and the amount is stated beside it in the bar. */
.dl-checkout__submit-total {
	display: none;
}

/* The button says it is working and refuses a second press; assets/js/checkout.js
   sets both, and the ring stays the global one. */
.dl-checkout__submit[aria-disabled='true'] {
	cursor: progress;
	opacity: 0.7; /* no token — the design system's busy state */
}

/* ── The rail ── */

/*
 * The rail is compact by construction, not by capping: #27 holds here as it
 * holds everywhere else, and the card has no height limit and no scrollbar of
 * its own. What keeps it inside the viewport is that it draws two lines and
 * closes the rest in a disclosure — see delsenter_get_checkout_lines() — and
 * that the rows under them are set tighter than the cart's.
 */
.dl-summary--checkout {
	padding: var(--dl-checkout-rail-pad);
}

/* Tighter than the cart's --space-4. The rail states six or seven amounts one
   under the other and is read as a block; the cart's card states three and can
   afford the air. */
.dl-summary--checkout .dl-summary__rows {
	gap: var(--dl-checkout-rail-row-gap);
}

.dl-summary__head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-4);
	margin-block-end: var(--space-4);
}

.dl-summary__head .dl-summary__title {
	margin: 0;
}

.dl-summary__head-link {
	font: var(--type-small);
	color: var(--text-brand);
	text-decoration: none;
}

.dl-summary__head-link:hover {
	text-decoration: underline;
}

/* Progress towards free shipping. Drawn only where the shop actually has a
   threshold — see delsenter_checkout_free_shipping_threshold(). */
.dl-checkout__freeship {
	display: flex;
	flex-direction: column;
	gap: 7px; /* no token — the label sits close over its track */
	margin: var(--space-5) 0;
}

.dl-checkout__freeship-text {
	margin: 0;
	font: var(--type-caption);
	color: var(--text-muted);
}

.dl-checkout__freeship-done {
	color: var(--status-success);
}

.dl-checkout__freeship-track {
	display: block;
	height: 5px; /* no token — the handoff's progress rail */
	overflow: hidden;
	border-radius: var(--radius-pill);
	background: var(--neutral-100);
}

.dl-checkout__freeship-fill {
	display: block;
	height: 100%;
	border-radius: var(--radius-pill);
	background: var(--brand);
	transition: width var(--dur-base) var(--ease-out);
}

.dl-checkout__freeship--done .dl-checkout__freeship-fill {
	background: var(--status-success);
}

/* The rail's coupon field. An input and a button, never a form: the rail is
   inside form.checkout. checkout.js posts it. */
.dl-checkout__coupon-rail {
	display: flex;
	gap: 8px; /* no token */
	margin-block-end: var(--space-5);
}

.dl-checkout__coupon-rail .dl-input {
	flex: 1;
	min-width: 0;
}

.dl-checkout__trust {
	display: flex;
	flex-direction: column;
	gap: 9px; /* no token — the handoff's reassurance stack */
	margin: 0;
	padding: 0;
	list-style: none;
}

.dl-checkout__trust-item {
	display: flex;
	align-items: center;
	gap: 9px; /* no token */
	font: var(--type-caption);
	color: var(--text-muted);
}

.dl-checkout__trust-item .ds-icon {
	color: var(--status-success);
}

/* ── The rail's lines ── */

.dl-summary__lines {
	margin-block-end: var(--space-6);
}

/* ── «Vis 5 varer til» ──
 *
 * The disclosure that holds the lines past the second. FilterBlock's control,
 * at the rail's scale: one summary, two words in it, and the chevron turning to
 * say which way the next click goes. The rule above it is the last line's
 * bottom border, given back — the list drops it on `:last-child` so a closed
 * card does not end in one. */

.dl-checkout__lines-more {
	border-block-start: var(--border-width) solid var(--border-subtle);
}

.dl-checkout__lines-toggle {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	min-height: 40px; /* no token — the rail's own hit target, under the page's */
	font: var(--type-small);
	font-weight: var(--weight-medium);
	color: var(--text-brand);
	cursor: pointer;
	list-style: none;
}

.dl-checkout__lines-toggle::-webkit-details-marker {
	display: none;
}

.dl-checkout__lines-chevron {
	transition: transform var(--dur-fast) var(--ease-standard);
}

.dl-checkout__lines-more[open] .dl-checkout__lines-chevron {
	transform: rotate(180deg);
}

/* One control, two words: the label states what the click will do next. Both
   halves are `display: none` by default and exactly one of them is given back,
   so the open control reads «Vis færre» rather than standing as a bare chevron
   with nothing to say what it does. */
.dl-checkout__lines-hide,
.dl-checkout__lines-more[open] .dl-checkout__lines-show {
	display: none;
}

.dl-checkout__lines-more[open] .dl-checkout__lines-hide {
	display: inline;
}

/* The list inside the disclosure carries the rule the control sits under, so the
   first line it reveals is not welded to the summary. */
.dl-checkout__lines--rest {
	border-block-start: var(--border-width) solid var(--border-subtle);
}

.dl-checkout__lines {
	margin: 0;
	padding: 0;
	list-style: none;
}

.dl-checkout__line {
	display: flex;
	align-items: flex-start;
	gap: var(--space-4);
	padding: var(--space-4) 0;
	border-block-end: var(--border-width) solid var(--border-subtle);
}

.dl-checkout__line:last-child {
	border-block-end: 0;
}

/* Flex, not grid — a grid item's automatic minimum is its content size in
   both axes, so a photograph taller than the plate overflowed it. The note on
   .dl-product-card__plate has the whole of it. */
.dl-checkout__line-plate {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 52px; /* no token — the order line's plate */
	height: 52px;
	overflow: hidden;
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--radius-md);
	background: var(--neutral-50);
	color: var(--neutral-300);
}

.dl-checkout__line-img {
	width: 100%;
	height: 100%;
	min-width: 0;
	min-height: 0;
	object-fit: contain;
}

.dl-checkout__line-info {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 5px; /* no token — title to meta row */
	min-width: 0;
}

.dl-checkout__line-title {
	font: var(--weight-regular) var(--text-sm) / 1.35 var(--font-body);
	color: var(--text-heading);
}

.dl-checkout__line-meta-row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-4);
}

.dl-checkout__line-meta,
.dl-checkout__line-qty {
	font: var(--type-caption);
	color: var(--text-faint);
}

/* The quantity is stated, never edited: the card links back to the cart for
   that. There is no stepper here on purpose. */
.dl-checkout__line-total {
	font: var(--type-caption);
	color: var(--text-muted);
	white-space: nowrap;
}

.dl-summary__row--tax .dl-summary__value--muted {
	color: var(--text-muted);
}

.dl-summary__action {
	margin-block-start: var(--space-3);
}

/* ── The sticky bar, below 1024px ──
 *
 * The third child of form.checkout, spanning both columns. It holds no button of
 * its own: checkout.js moves the one #place_order node in and out of the slot
 * below. Where that has not happened — scripts blocked — the action row has
 * nothing but the amount in it, so it is not drawn at all. */

.dl-checkout__bar {
	position: sticky;
	bottom: 0;
	z-index: 20; /* no token — above the form, below the drawer */
	grid-column: 1 / -1;
	margin-block-start: var(--space-8);
	border-block-start: var(--border-width) solid var(--border-subtle);
	background: var(--surface-card);
	box-shadow: var(--shadow-3);
}

.dl-checkout__bar-panel {
	max-height: 260px; /* no token — the handoff's collapsed summary */
	padding: var(--space-5) var(--space-5) 0;
	overflow: auto;
}

.dl-checkout__bar-panel[hidden] {
	display: none;
}

.dl-checkout__bar-panel .dl-summary__rows {
	margin: var(--space-5) 0;
}

.dl-checkout__bar-toggle {
	display: flex;
	align-items: center;
	gap: 8px; /* no token */
	width: 100%;
	padding: var(--space-4) var(--space-5) 0;
	border: 0;
	background: transparent;
	font: var(--type-small);
	color: var(--text-brand);
	cursor: pointer;
}

.dl-checkout__bar-toggle .ds-icon {
	color: var(--text-faint);
}

.dl-checkout__bar-count {
	margin-inline-start: auto;
	font: var(--type-caption);
	color: var(--text-faint);
}

.dl-checkout__bar-action {
	display: flex;
	align-items: center;
	gap: var(--space-5);
	padding: var(--space-4) var(--space-5) var(--space-5);
}

.dl-checkout__bar-totals {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	min-width: 0;
}

.dl-checkout__bar-label {
	font: var(--type-caption);
	color: var(--text-faint);
}

.dl-checkout__bar-total {
	font: var(--type-price);
	font-size: var(--text-xl);
	color: var(--text-heading);
}

.dl-checkout__bar-slot {
	flex: 0 0 auto;
	display: flex;
}

.dl-checkout__bar-slot:empty {
	display: none;
}

/* Nothing to press means nothing to draw: with JavaScript blocked the button
   never leaves the slot under Betaling, and a bar that is only an amount is
   noise. */
.dl-checkout__bar:not(:has(#place_order)) .dl-checkout__bar-action {
	display: none;
}

/* ── The two things that are presence, not value ──
 *
 * Everything responsive in this theme goes through a token; these two do not,
 * because they are about which of two arrangements *exists* rather than what
 * size it is. Below 1024px the rail is hidden — hidden, not removed: it is the
 * `.woocommerce-checkout-review-order-table` fragment and has to stay in the
 * document for WooCommerce to replace it — and the sticky bar states the same
 * numbers, rendered server-side by the same helpers. */

@media (max-width: 1023px) {
	.dl-checkout__rail {
		display: none;
	}
}

@media (min-width: 1024px) {
	.dl-checkout__bar {
		display: none;
	}

	.dl-checkout__submit-total {
		display: inline;
	}
}

/* ── The coupon fallback and the notices above the form ── */

.dl-checkout__coupon {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: var(--space-3);
	margin-block-end: var(--space-6);
}

.dl-checkout__coupon-field {
	flex: 1;
	min-width: 200px; /* no token — the field stops shrinking before it is unusable */
	margin: 0;
}

.dl-checkout__coupon-action {
	margin: 0;
}

/* The same grandchild as on the cart: `[woocommerce_checkout]` wraps its output
   in `div.woocommerce`, so the notices wrapper is never the screen's own child. */
.dl-checkout .woocommerce-notices-wrapper > .dl-alert,
.dl-checkout__coupon-toggle .dl-alert,
.woocommerce-form-login-toggle .dl-alert {
	margin-block-end: var(--space-6);
}

/* WooCommerce's checkout.js does not print its errors into the notices wrapper:
   it prepends them into the form — `$form.prepend( '<div
   class="woocommerce-NoticeGroup …">' )`, both on a failed submit and on a
   failed order-review refresh. The form *is* the two-column grid, so that group
   arrives as a third, unplaced grid item: it takes the first column and pushes
   the whole checkout into the rail's narrow track. It is a notice, not a
   column — so it spans the row, and the grid's own gap spaces it. */
.dl-checkout__form > .woocommerce-NoticeGroup {
	grid-column: 1 / -1;
}

/* ── The login panel ── */

.dl-login {
	max-width: 480px; /* no token — a login form does not want the full measure */
	margin-block-end: var(--space-7);
	padding: var(--dl-checkout-block-pad);
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--radius-lg);
	background: var(--surface-card);
}

.dl-login__fields {
	display: flex;
	flex-direction: column;
	gap: var(--dl-checkout-field-gap);
}

.dl-login__actions {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	margin-block-start: var(--space-5);
}

.dl-login__lost {
	margin-block-start: var(--space-4);
	margin-block-end: 0;
	font: var(--type-small);
}

/* ══ The confirmation ═════════════════════════════════════════════════════════
 *
 * One 640px card, centred, with the EmptyState in it — and the order's own
 * details in cards under it for the customer who wants to check them. The icon
 * is decided by the order's state, not by the screen: see thankyou.php.
 */

.dl-checkout__done {
	display: flex;
	flex-direction: column;
	gap: var(--space-6);
	max-width: 640px; /* no token — the handoff's confirmation measure */
	margin-inline: auto;
}

.dl-checkout__done-card {
	padding: var(--dl-checkout-done-pad);
}

.dl-checkout__done-card--danger .dl-empty__icon {
	background: var(--status-danger-bg);
	color: var(--status-danger);
}

.dl-checkout__done-card--pending .dl-empty__icon {
	background: var(--status-warning-bg);
	color: var(--status-warning);
}

.dl-checkout__done-card--success .dl-empty__icon {
	background: var(--status-success-bg);
	color: var(--status-success);
}

.dl-checkout__done .dl-empty__action {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-4);
}

.dl-checkout__overview {
	padding: var(--dl-checkout-block-pad);
}

/* ── The invalid cart ──
 *
 * WooCommerce replaces the whole checkout form with `checkout/cart-errors.php`
 * when the cart no longer holds. One card, the confirmation's measure, with the
 * EmptyState in it — and the reasons in the Alert inside it, which is the one
 * part of this screen the customer actually has to read, so it is left-aligned
 * and full width inside a card whose other copy is centred. */

.dl-checkout__errors {
	max-width: 640px; /* no token — the confirmation's measure; one card reads badly wider */
	margin-inline: auto;
}

.dl-checkout__errors-card {
	padding: var(--dl-checkout-done-pad);
}

.dl-checkout__errors-card .dl-empty__icon {
	background: var(--status-danger-bg);
	color: var(--status-danger);
}

.dl-checkout__errors-list {
	width: 100%;
	text-align: start;
}

.dl-checkout__errors-list .dl-alert {
	margin-block: 0;
}

/* ── Betal ordre — the order-pay endpoint ──
 *
 * One column, not the checkout's rail. The rail is `display: none` below 1024px
 * because the sticky bar states the same numbers there; this screen has no bar
 * and nothing to put in one, and the card *is* the statement of what is owed —
 * so it goes in the column every width can read. Narrower than the shop's
 * measure for the same reason the confirmation is: one card and one section
 * read badly across 1280px. */

.dl-checkout--pay {
	max-width: 720px; /* no token — one step wider than the confirmation's 640, which holds a card and a section */
	margin-inline: auto;
}

.dl-checkout__pay {
	display: flex;
	flex-direction: column;
	gap: var(--dl-checkout-section-gap);
	min-width: 0;
}

/* The amount, beside the heading, where the checkout draws the way back to the
   cart — the two facts a customer arriving from an e-mail has to recognise are
   which order this is and what it costs. */
.dl-checkout__pay-total {
	display: flex;
	align-items: baseline;
	gap: var(--space-3);
}

.dl-checkout__pay-total-label {
	font: var(--type-small);
	color: var(--text-muted);
}

/* The addresses card is spaced by the column's own gap here. The margin it
   carries is for the confirmation, where it is stacked under the order card
   rather than laid out by a flex parent. */
.dl-checkout__pay > .dl-order--addresses {
	margin-block-start: 0;
}

/* The receipt step's four facts are the summary's rows in a list, so the list
   itself has to stand down. */
.dl-checkout__facts {
	margin: 0;
	padding: 0;
	list-style: none;
}

/* ── Order details, here and in Min konto ── */

.dl-order {
	padding: var(--dl-checkout-block-pad);
}

.dl-order__title {
	margin-block-end: var(--space-5);
	font: var(--dl-checkout-block-title-font);
}

.dl-order__lines {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	margin: 0 0 var(--space-5);
	padding: 0 0 var(--space-5);
	border-block-end: var(--border-width) solid var(--border-subtle);
	list-style: none;
}

.dl-order__line {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-4);
}

/* Flex, not grid — a grid item's automatic minimum is its content size in
   both axes, so a photograph taller than the plate overflowed it. The note on
   .dl-product-card__plate has the whole of it. */
.dl-order__line-plate {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 48px; /* no token — the same plate the checkout rail draws */
	height: 48px;
	overflow: hidden;
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--radius-sm);
	background: var(--neutral-50);
	color: var(--text-faint);
}

.dl-order__line-img {
	width: 100%;
	height: 100%;
	min-width: 0;
	min-height: 0;
	object-fit: contain;
}

.dl-order__line-info {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 2px; /* --space-1 */
	min-width: 0;
	font: var(--type-small);
}

.dl-order__line-title {
	color: var(--text-heading);
}

.dl-order__line-qty {
	color: var(--text-muted);
	font-weight: inherit;
}

.dl-order__line-total {
	font: var(--type-body-strong);
	color: var(--text-heading);
	white-space: nowrap;
}

.dl-order__line-note {
	flex: 1 0 100%;
	font: var(--type-caption);
	color: var(--text-muted);
}

.dl-order__note .dl-summary__value {
	text-align: start;
}

.dl-order__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-4);
	margin-block-start: var(--space-5);
}

.dl-order--addresses {
	margin-block-start: var(--space-6);
}

.dl-order__addresses {
	display: grid;
	grid-template-columns: var(--dl-track-form2);
	gap: var(--space-6);
}

.dl-order__addresses--single {
	grid-template-columns: 1fr;
}

.dl-order__address-title {
	margin-block-end: var(--space-3);
	font: var(--type-body-strong);
	color: var(--text-heading);
}

.dl-order__address address {
	display: flex;
	flex-direction: column;
	gap: 2px; /* --space-1 */
	font: var(--type-small);
	font-style: normal;
	color: var(--text-body);
}

/* Org nr under the billing address and the registration number under the
   addresses. Drawn by the theme for orders from either checkout, because
   WooCommerce draws them for neither unless the order came through the Store
   API — see inc/order-fields.php. */
.dl-order__address-label {
	color: var(--text-muted);
}

.dl-order__extra {
	display: flex;
	gap: var(--space-3);
	margin-block-start: var(--space-5);
	padding-block-start: var(--space-5);
	border-block-start: var(--border-width) solid var(--border-subtle);
	font: var(--type-small);
	color: var(--text-body);
}

.dl-order__extra-label {
	color: var(--text-muted);
}

/* A gateway's own thank-you output — bank details, a QR code — in a card of the
   screen's rhythm. The markup inside it is the gateway's and is left alone. */
.dl-checkout__gateway-note > :first-child {
	margin-block-start: 0;
}

.dl-checkout__gateway-note h2 {
	margin-block-end: var(--space-4);
	font: var(--dl-checkout-block-title-font);
}

.dl-checkout__gateway-note h3 {
	margin-block: var(--space-4) var(--space-2);
	font: var(--type-body-strong);
	color: var(--text-heading);
}

.dl-checkout__gateway-note ul {
	margin: 0;
	padding: 0;
	list-style: none;
	font: var(--type-small);
}

/* ══ 8. Min konto ════════════════════════════════════════════════════════════
 *
 * One markup tree at four breakpoints (#21). Above 1024px the screen is the
 * `acct` grid — a 260px sticky sidebar card and the endpoint's content beside
 * it. Below it there is no sidebar at all: `.dl-account__side` is
 * `display: contents`, which drops the card's box and lets its two children
 * take their own places in the single column, and `order` puts the identity
 * first, the screen second and the menu last. That is MAccount's layout, drawn
 * from the same document — see § 1 of base.css for every property.
 */

.dl-account-screen__title {
	margin-block-end: var(--space-7);
	font: var(--type-h1);
	color: var(--text-heading);
}

.dl-account {
	gap: var(--dl-acct-gap);
	align-items: start;
}

.dl-account__side {
	display: var(--dl-acct-side-display);
	position: var(--dl-acct-side-position);
	top: var(--dl-acct-side-top);
	padding: var(--space-4);
}

.dl-account__id {
	order: var(--dl-acct-id-order);
	display: flex;
	align-items: center;
	gap: var(--dl-acct-id-gap);
	padding: var(--dl-acct-id-pad);
	margin-block-end: var(--space-3);
}

.dl-account__avatar {
	flex: 0 0 auto;
	display: grid;
	place-items: center;
	width: var(--dl-acct-avatar-size);
	height: var(--dl-acct-avatar-size);
	border-radius: var(--radius-pill);
	background: var(--brand-subtle);
	color: var(--text-brand);
	font: var(--dl-acct-avatar-font);
}

.dl-account__id-text {
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 2px; /* no token — two lines of one identity */
}

.dl-account__name {
	font: var(--dl-acct-name-font);
	color: var(--text-heading);
}

.dl-account__email {
	font: var(--type-caption);
	color: var(--text-faint);
	overflow-wrap: anywhere;
}

/* ── The menu ── */

.dl-account__menu {
	order: var(--dl-acct-menu-order);
	display: flex;
	flex-direction: column;
}

.dl-account__menu-list {
	display: flex;
	flex-direction: column;
	gap: var(--dl-acct-menu-gap);
	margin: 0;
	padding: 0;
	list-style: none;
}

.dl-account__menu-link {
	display: flex;
	align-items: center;
	gap: 11px; /* no token — as drawn */
	padding: var(--dl-acct-menu-row-pad);
	border-radius: var(--dl-acct-menu-row-radius);
	background: var(--dl-acct-menu-row-bg);
	color: var(--text-body);
	font: var(--weight-light) var(--text-base)/1.3 var(--font-body);
	text-decoration: none;
	transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.dl-account__menu-link:hover {
	background: var(--brand-subtle);
	color: var(--text-brand);
}

.dl-account__menu-icon {
	flex: 0 0 auto;
	color: var(--text-faint);
}

.dl-account__menu-label {
	flex: 1;
}

.dl-account__menu-chevron {
	display: var(--dl-acct-menu-chevron);
	flex: 0 0 auto;
	color: var(--text-faint);
}

/* State is the ARIA attribute, never a parallel class: a row that looks current
   is the one a screen reader announces as current. */
.dl-account__menu-link[aria-current='page'] {
	background: var(--brand-subtle);
	color: var(--text-brand);
}

.dl-account__menu-link[aria-current='page'] .dl-account__menu-icon {
	color: var(--brand);
}

.dl-account__menu-rule {
	display: var(--dl-acct-menu-rule-display);
	height: 1px;
	margin-block: var(--space-3);
	background: var(--border-subtle);
}

.dl-account__menu-list--out .dl-account__menu-link {
	color: var(--text-muted);
}

/* ── The screen beside it ── */

.dl-account__main {
	order: var(--dl-acct-main-order);
	min-width: 0;
}

.dl-account__panel {
	display: flex;
	flex-direction: column;
	gap: var(--space-5);
}

.dl-account__card {
	padding: var(--dl-checkout-block-pad);
}

.dl-account__card-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	margin-block-end: var(--space-5);
}

.dl-account__card-title {
	margin: 0;
	font: var(--type-h3);
	color: var(--text-heading);
}

.dl-account__card-link {
	font: var(--type-small);
}

/* ── Oversikt ── */

.dl-account__stats {
	gap: var(--space-4);
}

/*
 * The row is three-up in the design. While the wishlist card is not built —
 * see delsenter_wishlist_enabled() — there are two, and two cards in a
 * three-column track would sit against an empty third. The two share the width
 * instead, which is what the row already does below 768px.
 */

.dl-account__stats--two {
	--dl-track-stats: repeat(2, 1fr);
}

.dl-account__stat {
	display: flex;
	flex-direction: var(--dl-acct-stat-direction);
	align-items: var(--dl-acct-stat-align);
	gap: var(--dl-acct-stat-gap);
	padding: var(--dl-acct-stat-pad);
	text-align: center;
	text-decoration: none;
}

.dl-account__stat-icon {
	flex: 0 0 auto;
	display: grid;
	place-items: center;
	width: var(--dl-acct-stat-icon-size);
	height: var(--dl-acct-stat-icon-size);
	border-radius: var(--radius-pill);
	background: var(--dl-acct-stat-icon-bg);
	color: var(--dl-acct-stat-icon-color);
}

.dl-account__stat-text {
	display: flex;
	flex-direction: column;
	gap: 2px; /* no token — a number over its caption */
	text-align: start;
}

.dl-account__stat-value {
	font: var(--dl-acct-stat-value-font);
	color: var(--text-heading);
}

.dl-account__stat-label {
	font: var(--type-caption);
	color: var(--text-muted);
}

.dl-account__alert {
	margin: 0;
}

/* ── One order ── */

.dl-account__order-date {
	margin: 0;
	font: var(--type-small);
	color: var(--text-muted);
}

.dl-account__tracking {
	display: flex;
	gap: var(--space-4);
	margin-block-start: var(--space-5);
	padding: var(--space-5);
	border-radius: var(--radius-md);
	background: var(--surface-page);
}

.dl-account__tracking-icon {
	flex: 0 0 auto;
	color: var(--brand);
}

.dl-account__tracking-body {
	display: flex;
	flex-direction: column;
	gap: 4px; /* no token — a title over its numbers */
	min-width: 0;
}

.dl-account__tracking-title {
	font: var(--type-body-strong);
	color: var(--text-heading);
}

.dl-account__tracking-list {
	margin: 0;
	padding: 0;
	list-style: none;
	font: var(--type-mono);
	letter-spacing: var(--tracking-wide);
	overflow-wrap: anywhere;
}

.dl-account__notes {
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: var(--space-5);
}

.dl-account__note-date {
	display: block;
	margin-block-end: 4px; /* no token — a timestamp over its note */
	font: var(--type-caption);
	color: var(--text-faint);
}

/* ── Adresser ── */

.dl-account__address {
	margin: 0;
	font: var(--type-body);
	font-style: normal;
	color: var(--text-body);
}

/* ── The forms ── */

.dl-account__fields > .form-row {
	grid-column: 1 / -1;
}

.dl-account__fields > .form-row.dl-field--col {
	grid-column: span 1;
}

.dl-account__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-4);
	margin-block-start: var(--space-6);
}

/* ── Logg inn / Ny kunde ── */

.dl-account-login {
	align-items: start;
}

.dl-account-login .dl-login {
	display: flex;
	flex-direction: column;
	gap: var(--space-5);
}

.dl-login__note {
	margin: 0;
	font: var(--type-small);
	color: var(--text-muted);
}

/* ══ 9. Blogg ════════════════════════════════════════════════════════════════
 *
 * The one screen in the theme that is not WooCommerce and not a wrapper. The
 * body of an article is the author's own blocks, so nothing here styles running
 * copy by class: everything an author can write gets its look from theme.json's
 * `styles.blocks`, and what is left over — the pseudo-elements theme.json cannot
 * express, the sibling borders and the overflow rules — hangs off
 * `.entry-content`, which is the class `core/post-content` emits and which
 * `add_editor_style()` therefore carries into the editor canvas as well.
 *
 * Nothing here has a media query. The article's two shapes — a rail beside the
 * copy from 1024px, one column below it — are the --dl-article-* properties in
 * § 1 of base.css.
 *
 * This section is the reading column, and it draws two screens: the article and
 * the static page. `templates/page.html` reuses `.dl-article`,
 * `.dl-article__head`, `.dl-article__body` and `.entry-content` unchanged and
 * adds no selector of its own; what it leaves out is `.dl-article__grid`,
 * `.dl-article__rail`, `.dl-article__toc` and `.dl-article__share`. One
 * behavioural difference is worth writing down: delsenter_article_anchor_ids()
 * is gated on `is_singular( 'post' )`, so headings in page copy get no injected
 * ids — deliberate, because a page has no table of contents to point at them.
 * Nothing here jumps, and the scroll-padding that would clear the header if it
 * did is `html`'s (base.css § 4), so a page pays for none of it.
 */

/*
 * The reading column — the article and the static page — opens at 16px, the
 * same gap `.dl-section` takes on the screens that begin with a document, so
 * the head sits straight under the header instead of floating a section's
 * rhythm below it. It closes at nothing: the footer's own `margin-top` owns the
 * last gap on every screen (§ 1), and a closing padding here would stack on it.
 */
.dl-article > .dl-container {
	padding-block: var(--space-5) 0; /* 16px, then the footer */
}

/* ── The head ── */

/*
 * The head is the first thing in the reading column, not a band above it: the
 * eyebrow, the title, the lead and the image share the copy's left edge and its
 * measure, so the article reads as one column from the breadcrumb down and the
 * rail runs beside the whole of it rather than beside the body alone. It
 * carries no measure of its own for that reason — `.dl-article__body` caps it.
 *
 * The head is outside `.entry-content`, so it needs the wrap rule of its own: a
 * page titled with a 40-character OEM number, or a lead carrying a fitment code,
 * is the same overflow the copy is already protected from further down.
 * `anywhere` rather than `break-word` for the reason given there — only
 * `anywhere` shrinks the min-content contribution, which is what keeps the head
 * from widening the column it sits in.
 */
.dl-article__head {
	margin-block: var(--space-6) var(--dl-article-head-space);
	overflow-wrap: anywhere;
}

.dl-article__eyebrow {
	color: var(--text-brand);
}

.dl-article__title {
	margin-block: 10px 14px; /* no token — optical, between the 11px eyebrow and the lead */
	font: var(--type-h1);
}

.dl-article__lead {
	font-size: var(--text-lg);
	color: var(--text-muted);
}

/*
 * 21:9, and the ratio is on the wrapper rather than on the image, so the space
 * is reserved before the photograph lands and the article below it does not
 * jump. The empty variant is the same box with the caption in it.
 */
.dl-article__media {
	aspect-ratio: 21 / 9;
	margin: 0 0 var(--space-10);
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--radius-2xl);
	overflow: hidden;
}

.dl-article__media--empty {
	display: grid;
	place-items: center;
	background: var(--neutral-100);
}

.dl-article__media-caption {
	font: var(--type-caption);
	color: var(--text-faint);
	letter-spacing: var(--tracking-caps);
	text-transform: uppercase;
}

.dl-article__image {
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

/* ── The two columns ── */

.dl-article__grid {
	gap: var(--dl-article-gap);
	align-items: start;
}

/*
 * The reading measure, and `min-inline-size: 0` because a grid item's default
 * `auto` minimum is its content's min-content width — one unbroken fitment code
 * would otherwise widen the column and push the rail off the screen. The
 * `overflow-wrap` rules further down are the other half of that.
 */
.dl-article__body {
	max-inline-size: 720px; /* no token — the reading measure as drawn */
	min-inline-size: 0;
}

.dl-article__rail {
	position: var(--dl-article-rail-position);
	top: var(--dl-article-rail-top);
	display: flex;
	flex-direction: column;
	gap: var(--space-5);
}

/* ── The table of contents ── */

.dl-article__toc {
	display: var(--dl-article-toc-display);
}

.dl-article__toc-list {
	margin-block-start: var(--space-3);
}

.dl-article__toc-link {
	display: block;
	padding: var(--space-2) 0;
	font: var(--type-small);
	color: var(--text-muted);
	text-decoration: none;
}

.dl-article__toc-link:hover {
	color: var(--text-brand);
}

/*
 * The current section, set by assets/js/article.js. The attribute is the state
 * and the style reads it — there is no parallel `is-current` class to keep in
 * sync, and a reader on a screen reader is told which entry it is.
 */
.dl-article__toc-link[aria-current='true'] {
	color: var(--text-brand);
}

/* ── The mention card ── */

.dl-article__mention-title {
	margin-block: var(--space-3) var(--space-5);
	font: var(--type-h4);
	color: var(--text-heading);
}

/* ── The share row ── */

.dl-article__share {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	margin-block-start: var(--space-6);
	padding-block-start: var(--space-5);
	border-top: var(--border-width) solid var(--border-subtle);
}

.dl-article__share-label {
	margin-inline-end: auto;
	font: var(--type-small);
	color: var(--text-muted);
}

/*
 * A 34px bordered square, as drawn. No :focus-visible rule of its own — the one
 * global focus rule in § 4 of base.css covers every control in the theme.
 */
.dl-article__share-btn {
	display: grid;
	place-items: center;
	inline-size: 34px; /* no token — the icon square as drawn */
	block-size: 34px; /* no token — pairs with the width above */
	border: var(--border-width) solid var(--border-subtle);
	border-radius: var(--radius-sm);
	color: var(--text-muted);
	transition: var(--transition-control);
}

.dl-article__share-btn:hover {
	border-color: var(--border-default);
	color: var(--text-brand);
}

/* ── The author's own copy ──
 *
 * Everything below hangs off `.entry-content` rather than off
 * `.dl-article__body`, so it reaches the editor canvas too — an author sees the
 * spacing and the wrapping they will get. Backgrounds, radii, padding and type
 * are theme.json's and are deliberately not repeated here.
 */

/*
 * A table of contents link lands on the heading rather than behind the sticky
 * header — but that is `html`'s scroll-padding now (base.css § 4), not a
 * scroll-margin here. One rule answers every jump in the theme, so this one is
 * back to the spacing it was always also about.
 */
.entry-content > :is(h2, h3) {
	margin-block-start: var(--space-8);
}

/*
 * `anywhere`, not `break-word`. Only `anywhere` also shrinks the element's
 * min-content contribution, which is what stops a 40-character OEM part number
 * from widening the grid column it sits in — `break-word` breaks the line but
 * still reserves the unbroken width.
 */
.entry-content {
	overflow-wrap: anywhere;
}

.entry-content :is(code, kbd, samp, .dl-code) {
	word-break: break-word;
}

.entry-content :is(table, pre) {
	max-inline-size: 100%;
	overflow-x: auto;
}

/* ── The archive ── */

.dl-blog__head {
	margin-block-end: var(--space-8);
}

.dl-blog__title {
	font: var(--type-h1);
	color: var(--text-heading);
}

/* The query inside the h1, as on the shop's own search results. */
.dl-blog__query {
	color: var(--text-brand);
}

.dl-blog__count {
	margin-block-start: var(--space-4);
	font: var(--type-body);
	color: var(--text-muted);
}

.dl-blog .dl-pagination {
	justify-content: center;
	flex-wrap: wrap;
	margin-block-start: var(--dl-catalog-pagination-space);
}
