/* ============================================================================
   Site chrome: header, catalogue panel, footer, shortlist drawer.

   Kept in one file because these are the only components that appear on every
   page, and because the previous split let three separate `.dg-logo` rules
   fight each other across main.css.

   The header is two tiers. Tier one is context a buyer wants before they start
   (phone, policies, language); tier two is the working bar (identity, catalogue,
   search, shortlist). Scrolling retires tier one and shortens tier two, so the
   chrome costs 112px when you arrive and 60px once you are reading. Sticky
   offsets elsewhere read --header-h, which is why it is declared here as the
   sum of the two and re-declared on .is-scrolled rather than measured in JS.
   ========================================================================= */

/* Each state gets its OWN token rather than one token that changes value.
   A transitioned property reading var() of an inherited custom property that
   changes does not update at all in Chromium — transitionstart never fires and
   the height simply stays put. Giving the collapsed state its own rule makes the
   state change a cascade change, which transitions normally.

   Breakpoints below override only these four values; both states keep working
   because each reads its own token. */
:root{
  --util-h:44px;   --util-h-sm:0px;
  --bar-h:92px;    --bar-h-sm:68px;
  --header-h:calc(var(--util-h) + var(--bar-h));
}
/* Two classes beat one pseudo-class, so a later :root rule inside a media query
   cannot take the scrolled height back. */
:root.is-scrolled{
  --header-h:calc(var(--util-h-sm) + var(--bar-h-sm));
}

/* --------------------------------------------------------------- the shell */
.dg-header{
  position:sticky; top:0; z-index:50;
  background:var(--white);
  /* The rule is drawn by the shadow, not by a border. A 1px border is layout:
     it made the rendered header 63px while --header-h said 62, so every sticky
     child and every scroll-padding target sat one pixel underneath it. */
  transition:box-shadow var(--dur-base) var(--ease-standard);
}
.is-scrolled .dg-header{
  box-shadow:0 1px 0 var(--grey-200),0 1px 14px rgba(20,20,22,.06);
  /* The shell gets out of the way once it is scrolled. backdrop-filter samples
     everything painted behind the element, and this element's own opaque white
     was the first thing behind .dg-bar — so the blur below was sampling white on
     white and rendering nothing. .dg-bar keeps its own opaque white as the
     Firefox fallback, so dropping this never leaves the bar transparent. */
  background:transparent;
}
/* Once it is a layer over the page it behaves like one — the same translucent
   blur the section-anchor bar uses, so the two stacked bars read as one surface
   instead of two opaque slabs. The opaque colour above stays as the fallback:
   backdrop-filter is off by default in Firefox, and a transparent header there
   would be unreadable. */
@supports (backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px)){
  .is-scrolled .dg-bar{
    /* .72 let a near-black hero photo through as dirty grey, which turned the
       shortlist button's amber tint brown and made the whole bar read as
       disabled. At .9 it is still a layer — the blur and the content sliding
       under it do that — without borrowing the page's colour. */
    background:rgba(246,246,247,.88);
    -webkit-backdrop-filter:saturate(140%) blur(16px);
    backdrop-filter:saturate(140%) blur(16px);
  }
}

/* ------------------------------------------------------- tier one: utility */
.dg-util{
  height:var(--util-h);
  background:var(--ink);
  color:var(--grey-400);
  /* Deliberately NOT overflow:hidden. The language menu opens downward out of a
     44px bar, and clipping made it invisible AND unhittable — a bug no geometry
     check catches, because getBoundingClientRect reports the full box whether or
     not an ancestor clips it. The collapsed state hides the content with
     opacity + visibility instead, so there is nothing left to clip.
     visibility is delayed to the end of the fade on the way out and applied
     immediately on the way back in, which is what the two transition lists do. */
  transition:height var(--dur-base) var(--ease-exit),
             opacity var(--dur-instant) linear,
             visibility 0s linear 0s;
}
:root.is-scrolled .dg-util{
  height:var(--util-h-sm); opacity:0; visibility:hidden;
  transition:height var(--dur-base) var(--ease-exit),
             opacity var(--dur-instant) linear,
             visibility 0s linear var(--dur-base);
}
.dg-util__in{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--sp-5); height:var(--util-h); white-space:nowrap;
}
/* Three children now, and space-between would have parked the nav in the middle
   of the strip with the switcher out on its own. The auto margin collects the
   free space before the nav instead, so the links and the switcher stay one
   trailing cluster and the phone number keeps the start. */
.dg-util__nav{ margin-inline-start:auto; }
.dg-util a{ color:var(--grey-400); text-decoration:none; }
.dg-util a:hover{ color:var(--white); }
.dg-util__tel{
  display:inline-flex; align-items:center; gap:var(--sp-2);
  font-size:var(--fs-meta); font-weight:500; color:var(--white)!important;
  transition:color var(--dur-instant) linear;
}
.dg-util__tel svg{ color:var(--accent-text); }
.dg-util__nav ul{
  display:flex; gap:var(--sp-5); list-style:none; margin:0; padding:0;
  font-size:var(--fs-meta);
}
.dg-util__nav a{ transition:color var(--dur-instant) linear; }

/* language ------------------------------------------------------------------ */
.dg-lang{ position:relative; }
/* Two are rendered. The strip's is the desktop one; the bar's exists only for a
   phone, where there is no strip. Hiding rather than choosing at render time is
   what makes this work with a full-page cache, which serves one HTML to both. */
.dg-bar .dg-lang{ display:none; }
/* The flag identifies the language faster than a three-letter code does, so it
   leads; the code stays for anyone who cannot read a 20px flag, and because
   several of these tricolours are easy to confuse at that size. */
.dg-flag{ display:block; border-radius:50%; overflow:hidden; flex:none; }
.dg-lang__flag{ display:inline-flex; flex:none; }
.dg-lang__btn{
  display:inline-flex; align-items:center; gap:var(--sp-2);
  background:transparent; border:1px solid var(--grey-700); border-radius:var(--radius);
  color:var(--grey-300); font:inherit; font-size:var(--fs-micro); font-weight:600;
  letter-spacing:.04em; padding:6px var(--sp-2); cursor:pointer;
  transition:border-color var(--dur-instant) linear, color var(--dur-instant) linear,
             background-color var(--dur-instant) linear;
}
.dg-lang__btn:hover{ border-color:var(--grey-500); color:var(--white); background:rgba(255,255,255,.06); }
.dg-lang__code{ line-height:1; }
.dg-lang__chev{ transition:transform var(--dur-base) var(--ease-standard); }
.dg-lang__btn[aria-expanded="true"] .dg-lang__chev{ transform:rotate(180deg); }
.dg-lang__list{
  /* z-index must clear .dg-bar (the white bar below) as well as the page. */
  position:absolute; right:0; top:calc(100% + 6px); z-index:70;
  list-style:none; margin:0; padding:var(--sp-1);
  background:var(--white); border:1px solid var(--grey-200); border-radius:var(--radius-lg);
  box-shadow:var(--shadow-2); min-width:186px;
  /* It used to appear and vanish on [hidden] alone, while the chevron beside it
     took 200ms to turn — two halves of one control moving at different speeds.
     transform-origin at the top right, because that is the corner it hangs from. */
  transform-origin:100% 0; opacity:0; transform:translateY(-6px) scale(.97);
  transition:opacity var(--dur-exit) linear,
             transform var(--dur-exit) var(--ease-exit);
}
.dg-lang__list.is-open{
  opacity:1; transform:none;
  transition:opacity var(--dur-base) linear,
             transform var(--dur-base) var(--ease-enter);
}
/* .dg-util re-declares --accent-text as the raw amber and --focus as white for
   the dark bar. This dropdown is a WHITE panel inside it, so the current
   language rendered at 1.55:1 and every focus ring in here was white on white. */
.dg-lang__list{ --accent-text:var(--amber-800); --focus:var(--ink); }
.dg-lang__list[hidden]{ display:none; }
.dg-lang__list a{
  display:flex; align-items:center; gap:var(--sp-3); white-space:nowrap;
  min-height:44px; padding:0 var(--sp-3); border-radius:var(--radius-sm);
  font-size:var(--fs-meta); font-weight:600; color:var(--text-2)!important;
  transition:background-color var(--dur-instant) linear, color var(--dur-instant) linear;
}
.dg-lang__list a:hover{ background:var(--panel); color:var(--text)!important; }
.dg-lang__list a[aria-current="true"]{ color:var(--accent-text)!important; }

/* ---------------------------------------------------- tier two: working bar */
/* The working bar is a light grey ground, not white, so the controls sitting on
   it — the white search field, the white category button — read as controls
   rather than as text floating on the page. The page itself stays white, so the
   bar also marks where the chrome ends and the content starts without a rule. */
.dg-bar{ position:relative; background:var(--grey-100); }
/* Three groups, not seven loose controls: the mark, the way in to the catalogue,
   and the things you have collected. The gap inside a group stays tight; the
   space BETWEEN groups is what tells them apart, so it is set on the first item
   of each rather than by wrapping the markup in divs the mobile layout would
   then have to unpick. */
.dg-bar__in{
  display:flex; align-items:center; gap:var(--sp-3);
  height:var(--bar-h);
  transition:height var(--dur-base) var(--ease-standard);
}
.dg-catbtn{ margin-inline-start:var(--sp-7); }
.dg-saved{ margin-inline-start:var(--sp-7); }
/* The mark is the one thing here that is not a control, so it gets its own
   breathing room rather than sitting in the same rhythm as the buttons. */
.dg-logo{ margin-inline-end:var(--sp-4); }
:root.is-scrolled .dg-bar__in{ height:var(--bar-h-sm); }
.dg-logo{ flex:0 0 auto; display:block; line-height:0; }
.dg-logo img{
  display:block; width:auto; height:46px;
  transition:height var(--dur-base) var(--ease-standard);
}
.is-scrolled .dg-logo img{ height:36px; }

.dg-catbtn{
  flex:0 0 auto;
  display:inline-flex; align-items:center; gap:var(--sp-2);
  height:60px; padding:0 var(--sp-5);
  /* Outlined. The filled treatment now belongs to the price CTA alone, so the
     catalogue button steps back rather than competing with it. */
  background:var(--white); color:var(--ink);
  border:1px solid var(--grey-300); border-radius:var(--radius);
  font:inherit; font-size:var(--fs-sm); font-weight:800; cursor:pointer;
  transition:background-color var(--dur-instant) linear,
             border-color var(--dur-instant) linear,
             height var(--dur-base) var(--ease-standard);
}
.dg-catbtn:hover{ background:var(--tint-saved); border-color:var(--accent-hover); }
/* Both sit on the grey bar now, so they keep an explicit white fill. */
.dg-burger, .dg-searchbtn{ background:var(--white); }
.dg-catbtn:active{ background:var(--accent-hover); }
.dg-catbtn__chev{ transition:transform var(--dur-base) var(--ease-standard); }
.dg-catbtn[aria-expanded="true"] .dg-catbtn__chev{ transform:rotate(180deg); }
.is-scrolled .dg-catbtn{ height:44px; }

/* search -------------------------------------------------------------------- */
.dg-search{
  flex:1 1 auto; min-width:0; position:relative;
  display:flex; align-items:center;
}
.dg-search__ico{
  position:absolute; left:var(--sp-5); color:var(--grey-400); pointer-events:none;
  transition:color var(--dur-instant) linear;
}
.dg-search input[type="search"]{
  width:100%; height:60px;
  padding:0 var(--sp-5) 0 52px;
  /* White on the grey bar. It was grey-on-grey, which left the one control
     people come to the header to use with no edge of its own. */
  background:var(--white);
  border:1px solid var(--grey-200); border-radius:var(--radius);
  font:inherit; font-size:var(--fs-sm); color:var(--text);
  transition:height var(--dur-base) var(--ease-standard),
             border-color var(--dur-instant) linear,
             background-color var(--dur-instant) linear;
}
/* grey-400 on grey-100 is 3.17:1 — under the 4.5:1 a placeholder needs. */
.dg-search input::placeholder{ color:var(--grey-500); }
.dg-search input:hover{ border-color:var(--grey-300); }
.dg-search input:focus{
  outline:none; background:var(--white);
  border-color:var(--accent-text); box-shadow:0 0 0 3px var(--accent-soft);
}
.dg-search input:focus + label + .dg-search__ico,
.dg-search:focus-within .dg-search__ico{ color:var(--accent-text); }
.is-scrolled .dg-search input[type="search"]{ height:44px; }

/* shortlist ----------------------------------------------------------------- */
/* Matched to .dg-callback, and both to .dg-catbtn: same 52px box, same 700
   weight, same radius. They were already identical in height — what made them
   read as different sizes was three treatments in a row (filled, outlined,
   bare). The right-hand pair is now one outlined pair against the filled
   catalogue button. */
/* A square, at every width. It used to carry a label that vanished under 820px,
   so the same control was two different shapes depending on the screen — and it
   sits next to an icon-only compare button that does the same kind of job. */
.dg-saved{
  flex:0 0 auto;
  display:inline-flex; align-items:center; justify-content:center; gap:var(--sp-2);
  width:60px; height:60px; padding:0;
  background:var(--tint-saved); color:var(--ink);
  border:1px solid transparent; border-radius:var(--radius);
  font:inherit; font-size:var(--fs-sm); font-weight:800;
  cursor:pointer; white-space:nowrap;
  transition:background-color var(--dur-instant) linear, color var(--dur-instant) linear,
             border-color var(--dur-instant) linear, height var(--dur-base) var(--ease-standard);
}
.dg-saved:hover{ border-color:var(--accent-hover); }
.dg-saved__ico{ position:relative; display:inline-flex; }
.dg-saved__n{
  position:absolute; top:-6px; right:-8px; min-width:17px; height:17px;
  display:inline-flex; align-items:center; justify-content:center;
  padding:0 4px; border-radius:9px;
  /* Ink, matching the compare badge beside it. White on #FBCA28 is 1.55:1 — the
     count was effectively unreadable, and the two badges disagreed. */
  background:var(--accent); color:var(--ink);
  font-size:10px; font-weight:700; line-height:1; font-variant-numeric:tabular-nums;
  /* Pops only when the number changes; see .is-bump in app.js. */
  transition:transform var(--dur-base) var(--ease-enter);
}
.dg-saved__n[hidden]{ display:none; }
.dg-saved__n.is-bump{ animation:dg-bump 420ms var(--ease-enter); }
@keyframes dg-bump{
  0%{ transform:scale(1) }
  35%{ transform:scale(1.45) }
  100%{ transform:scale(1) }
}

/* The compare tray's header handle. Same box and same tint as the shortlist
   beside it, because they are the same kind of thing — a list the visitor is
   building — and differ only by icon. Icon-only: a third label in this row does
   not fit on a laptop. */
.dg-cmpbtn{
  flex:0 0 auto;
  display:inline-flex; align-items:center; justify-content:center;
  width:60px; height:60px; padding:0;
  background:var(--tint-saved); color:var(--ink);
  border:1px solid transparent; border-radius:var(--radius);
  cursor:pointer;
  transition:background-color var(--dur-instant) linear, border-color var(--dur-instant) linear,
             width var(--dur-base) var(--ease-standard), height var(--dur-base) var(--ease-standard);
}
/* Only when it arrives mid-session. Left on the base rule it replayed on every
   page load for anyone with a tray already going, which reads as a glitch
   rather than as feedback. */
.dg-cmpbtn.is-pop{ animation:dg-cmpbtn-in var(--dur-panel) var(--ease-enter) both; }
.dg-cmpbtn[hidden]{ display:none; }
.dg-cmpbtn:hover{ border-color:var(--accent-hover); }
/* No state colour at all: this and the shortlist beside it sit in the same row
   doing the same kind of job, and any tone that lands on one and not the other
   reads as one of them being disabled. The tray is a full-width bar when it is
   open — the button does not also have to say so. */
.dg-cmpbtn__ico{ position:relative; display:inline-flex; }
.dg-cmpbtn__n{
  position:absolute; top:-6px; right:-8px; min-width:17px; height:17px;
  display:inline-flex; align-items:center; justify-content:center;
  padding:0 4px; border-radius:9px;
  background:var(--accent); color:var(--ink);
  font-size:10px; font-weight:700; line-height:1; font-variant-numeric:tabular-nums;
  transition:transform var(--dur-base) var(--ease-enter);
}
.dg-cmpbtn__n[hidden]{ display:none; }
.dg-cmpbtn__n.is-bump{ animation:dg-bump 420ms var(--ease-enter); }
.is-scrolled .dg-cmpbtn{ width:44px; height:44px; }
@keyframes dg-cmpbtn-in{
  from{ opacity:0; transform:scale(.7) }
  to{ opacity:1; transform:none }
}
@media (prefers-reduced-motion:reduce){
  .dg-cmpbtn.is-pop{ animation:none; }
  .dg-cmpbtn{ transition:none; }
}

/* A call-back is the low-commitment ask next to the shortlist: outlined, so it
   reads as a peer of "saved" rather than competing with the red catalogue
   button on the other side of the search field. */
.dg-callback{
  flex:0 0 auto;
  display:inline-flex; align-items:center; gap:var(--sp-2);
  height:60px; padding:0 var(--sp-4);
  background:var(--ink); color:var(--white);
  border:1px solid var(--ink); border-radius:var(--radius);
  font:inherit; font-size:var(--fs-sm); font-weight:800; cursor:pointer; white-space:nowrap;
  transition:border-color var(--dur-instant) linear, color var(--dur-instant) linear,
             background-color var(--dur-instant) linear, height var(--dur-base) var(--ease-standard);
}
/* Lime on near-black is 14:1 — the one place a colour this bright is readable. */
.dg-callback{ text-decoration:none; }
.dg-callback svg{ color:var(--lime-400); transition:transform var(--dur-base) var(--ease-enter); }
.dg-callback:hover{ background:var(--grey-900); border-color:var(--grey-900); }
.dg-callback:hover svg{ transform:rotate(-12deg) scale(1.08); }
.is-scrolled .dg-callback{ height:44px; }
.is-scrolled .dg-saved{ width:44px; height:44px; }

/* The phone's search control. A link, not a button: with scripting off it still
   reaches a search page, and search.js intercepts the click before then. It is
   only ever shown under 680px — see the breakpoint block. */
.dg-searchbtn{
  display:none; flex:0 0 auto;
  width:60px; height:60px; align-items:center; justify-content:center;
  background:transparent; border:1px solid var(--grey-200); border-radius:var(--radius);
  color:var(--text); cursor:pointer;
  transition:background-color var(--dur-instant) linear, border-color var(--dur-instant) linear,
             width var(--dur-base) var(--ease-standard), height var(--dur-base) var(--ease-standard);
}
.dg-searchbtn:hover{ background:var(--panel); border-color:var(--grey-300); }
:root.is-scrolled .dg-searchbtn{ width:44px; height:44px; }

.dg-burger{
  display:none; flex:0 0 auto;
  width:60px; height:60px; align-items:center; justify-content:center;
  background:transparent; border:1px solid var(--grey-200); border-radius:var(--radius);
  color:var(--text); cursor:pointer;
  transition:background-color var(--dur-instant) linear, border-color var(--dur-instant) linear,
             color var(--dur-instant) linear;
}
.dg-burger:hover{ background:var(--panel); border-color:var(--grey-300); }
:root.is-scrolled .dg-burger{ width:44px; height:44px; }
/* The control that opened the sheet is the control that closes it: on a phone
   the sheet covers everything else, so there is nowhere else to put the X. */
.dg-burger__i{ display:inline-flex; }
.dg-burger__i--close{ display:none; }
.dg-burger[aria-expanded="true"]{ background:var(--ink); border-color:var(--ink); color:var(--white); }
.dg-burger[aria-expanded="true"] .dg-burger__i--open{ display:none; }
.dg-burger[aria-expanded="true"] .dg-burger__i--close{ display:inline-flex; }

/* One lock for every full-screen surface — the phone menu, the search palette
   and the filter sheet. It lived in filters.css, which is enqueued on catalogue
   pages only, so on the home page the menu opened over a page that went on
   scrolling underneath it: the header collapsed mid-swipe and Back left the site
   entirely. html as well as body, because iOS Safari propagates the body's
   overflow to the viewport and ignores it there. */
html.dg-locked, html.dg-locked body{ overflow:hidden; }

/* ------------------------------------------------------ the catalogue panel */
/* The shell keeps the old open/close mechanics: grid-template-rows 0fr -> 1fr is
   the only way to transition to a height the content decides, and the inner div
   owns the overflow. What changed is what is inside it. */
/* A fade and a short slide, not a height reveal. The grid-template-rows 0fr->1fr
   trick this replaced never actually measured its content here — the panel's
   height was coming entirely from a hard-coded min-height, and the moment that
   was removed to make the panel match its rail, the whole thing collapsed to
   40px of padding. With the row animation gone the height is simply the rail's,
   which is what "same height as the categories wrapper" means. */
.dg-mega{
  position:absolute; left:0; right:0; top:100%; z-index:45;
  background:var(--white);
  border-top:1px solid var(--grey-200);
  border-bottom:1px solid var(--grey-200);
  box-shadow:0 24px 56px -28px rgba(20,20,22,.4);
  opacity:0; visibility:hidden; transform:translateY(-6px);
  transition:opacity var(--dur-exit) linear,
             transform var(--dur-exit) var(--ease-exit),
             visibility 0s linear var(--dur-exit);
}
/* A closed menu must be OUT of the page, not merely invisible.
   This used to be display:block because the old grid-template-rows animation
   needed the box laid out to measure. That animation is gone, and the override
   was leaving a 1032x347 panel parked over the hero: `visibility:hidden` on
   .dg-mega is overridden back to `visible` by .dg-mega__item.is-on
   .dg-mega__pane, opacity:0 does not stop hit-testing, and the result was that
   elementFromPoint() in the middle of the hero returned .dg-mega__grid. It ate
   the wheel — the pane sets overscroll-behavior:contain — and every hover and
   click in that band with it.
   pointer-events covers the closing transition too, when [hidden] is not back
   on yet but .is-open has already gone. */
.dg-mega:not(.is-open){ pointer-events:none; }
.dg-mega.is-open{ pointer-events:auto; }
.dg-mega.is-open{
  opacity:1; visibility:visible; transform:none;
  transition:opacity var(--dur-base) linear,
             transform var(--dur-panel) var(--ease-enter),
             visibility 0s linear 0s;
}
/* width:100% is load-bearing. .dg-wrap carries margin-inline:auto, and an auto
   margin on a grid item makes it shrink to max-content instead of filling its
   track — the whole panel collapsed to the width of the rail. */
.dg-mega__in{
  --rail-w:296px;
  --gutter:var(--sp-5);          /* .dg-wrap's own padding, which the pane clears */
  width:100%; overflow:hidden; position:relative;
}
/* The panel is exactly as tall as the rail of families beside it, top aligned.
   It used to carry a 392px floor, which left a band of empty white under a
   six-row rail and made the menu a different height from the thing it belongs
   to. A family with twenty subcategories scrolls inside the pane instead.
   padding-block, never the shorthand: .dg-wrap's inline padding is the page
   gutter and the shorthand silently zeroes it. */
.dg-mega__in{ padding-block:var(--sp-4) var(--sp-5); max-height:min(72vh,620px); }

/* --- the family rail --- */
.dg-mega__rail{ list-style:none; margin:0; padding:0; width:var(--rail-w); }
.dg-mega__item{ position:static; }
.dg-mega__row{
  position:relative; z-index:1;
  display:flex; align-items:center; gap:10px;
  padding:11px 12px; border-radius:var(--radius);
  text-decoration:none; color:var(--text-2);
  font-size:var(--fs-sm); line-height:1.3;
  transition:background-color var(--dur-instant) linear, color var(--dur-instant) linear;
}
.dg-mega__ico{ display:inline-flex; flex:none; color:var(--grey-400);
  transition:color var(--dur-instant) linear; }
.dg-mega__label{ flex:1; min-width:0; }
.dg-mega__chev{ display:inline-flex; flex:none; color:var(--grey-400);
  opacity:0; transform:translateX(-4px);
  transition:opacity var(--dur-base) var(--ease-standard),
             transform var(--dur-base) var(--ease-standard); }
.dg-mega__item.is-on > .dg-mega__row,
.dg-mega__row:hover, .dg-mega__row:focus-visible{
  background:var(--tint-saved); color:var(--ink); font-weight:700; }
.dg-mega__item.is-on > .dg-mega__row .dg-mega__ico,
.dg-mega__row:hover .dg-mega__ico{ color:var(--amber-800); }
.dg-mega__item.is-on > .dg-mega__row .dg-mega__chev,
.dg-mega__row:hover .dg-mega__chev{ opacity:1; transform:none; }

/* --- the picture pane ---
   Positioned against .dg-mega__in, not against the row, so it always fills the
   same rectangle whichever family is showing and the panel never changes height
   as the pointer runs down the rail. The containing block is that element's
   PADDING box, which includes the page gutter — hence the gutter appears in the
   start offset as well as the end one. */
.dg-mega__pane{
  position:absolute;
  inset-block:var(--sp-4) var(--sp-5);
  inset-inline-start:calc(var(--gutter) + var(--rail-w) + var(--sp-5));
  inset-inline-end:var(--gutter);
  overflow-y:auto; overscroll-behavior:contain;
  padding-inline-start:var(--sp-5);
  border-inline-start:1px solid var(--grey-200);
  opacity:0; visibility:hidden; transform:translateX(6px);
  transition:opacity var(--dur-exit) linear,
             transform var(--dur-exit) var(--ease-exit),
             visibility 0s linear var(--dur-exit);
}
.dg-mega__item.is-on .dg-mega__pane{
  opacity:1; visibility:visible; transform:none;
  transition:opacity var(--dur-base) linear,
             transform var(--dur-panel) var(--ease-enter),
             visibility 0s linear 0s;
}
.dg-mega__all{
  display:inline-flex; align-items:center; gap:7px; margin-bottom:var(--sp-4);
  font-size:var(--fs-sm); font-weight:700; color:var(--ink); text-decoration:none; }
.dg-mega__all svg{ color:var(--amber-800);
  transition:transform var(--dur-base) var(--ease-standard); }
.dg-mega__all:hover svg{ transform:translateX(3px); }

.dg-mega__grid{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr));
  gap:var(--sp-4) var(--sp-3);
}
.dg-mega__grid a{
  display:flex; flex-direction:column; gap:6px;
  padding:8px; border-radius:var(--radius); text-decoration:none;
  transition:background-color var(--dur-instant) linear;
}
.dg-mega__grid a:hover{ background:var(--grey-100); }
/* position, not grid. With `display:grid; place-items:center` the image is a
   grid item that is NOT stretched, so its `height:100%` had no definite height
   to resolve against and fell back to the file's own ratio: a 300x289 photo
   became a 149x143 element inside a 149x111 box, and overflow:hidden cut the
   machine off top and bottom. Absolutely positioned, the element IS the box, and
   object-fit:contain then letterboxes the whole machine inside it. */
.dg-mega__fig{
  position:relative;
  aspect-ratio:4/3; overflow:hidden; border-radius:var(--radius-sm);
  background:var(--white);
  transition:background-color var(--dur-instant) linear;
}
.dg-mega__fig img{
  position:absolute; inset:0;
  width:100%; height:100%; object-fit:contain; padding:4px;
}
/* display:none, not visibility — a lazy image with no box never intersects the
   viewport, so it is never fetched. The panel sits at the top of every page in
   the site, so without this the browser would pull thirty-four photographs on
   every single page load for a menu nobody has opened. */
.dg-mega:not(.is-open) .dg-mega__fig img,
.dg-mega__item:not(.is-on) .dg-mega__fig img{ display:none; }
/* No hover zoom. These tiles exist so someone can recognise a machine, and
   scaling inside a clipped box crops the thing they are recognising — on a long
   bed saw the ends go first. The tile's ground lifts instead. */
.dg-mega__grid a:hover .dg-mega__fig{ background:var(--grey-100); }
.dg-mega__noimg{ color:var(--grey-300); }
.dg-mega__kid{
  font-size:var(--fs-meta); line-height:1.3; color:var(--text-2);
  transition:color var(--dur-instant) linear; }
.dg-mega__grid a:hover .dg-mega__kid{ color:var(--ink); }

/* The pane is tall; stagger the tiles so it arrives as one object. */
.dg-mega__item.is-on .dg-mega__grid li{
  animation:dg-mega-in var(--dur-base) var(--ease-enter) both;
  animation-delay:calc(var(--i,0) * 14ms);
}
@keyframes dg-mega-in{ from{ opacity:0; transform:translateY(5px); } to{ opacity:1; transform:none; } }

/* The accordion's disclosure button belongs to the phone sheet only. It is
   styled inside the 900px block and nowhere else, so above that it fell back to
   a raw <button> — a native bordered box with a chevron in it, sitting in the
   gaps of the desktop family rail. Hidden here, shown there. */
.dg-mega__tog{ display:none; }

.dg-catpanel__more{ display:none; }

@media (prefers-reduced-motion:reduce){
  .dg-mega,.dg-mega__pane,.dg-mega__chev,.dg-mega__fig img{ transition-duration:1ms; }
  .dg-mega__item.is-on .dg-mega__grid li{ animation:none; }
}

/* ============================================================ subscribe band */
.dg-sub{ background:var(--grey-900); color:var(--grey-300); }
.dg-sub__in{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--sp-6); flex-wrap:wrap; padding-block:var(--sp-6);
}
.dg-sub__h{
  font-family:var(--font-display); font-weight:700;
  font-size:var(--fs-h2); line-height:var(--lh-h2); color:var(--white); margin:0 0 6px;
}
.dg-sub__copy p{ margin:0; font-size:var(--fs-sm); line-height:var(--lh-sm); }
.dg-sub__copy em{ font-style:normal; color:var(--accent-text); font-weight:600; }
.dg-sub__form{ display:flex; gap:var(--sp-2); flex:0 1 460px; min-width:280px; }
.dg-sub__form input{
  /* 16px, not --fs-sm. Any input under 16px zooms iOS Safari on focus and the
     zoom does not come back on blur; this band ships on every page. */
  flex:1 1 auto; min-width:0; height:48px; padding:0 var(--sp-4);
  background:rgba(255,255,255,.06);
  border:1px solid var(--grey-700); border-radius:var(--radius);
  font:inherit; font-size:16px; color:var(--white);
  transition:border-color var(--dur-instant) linear, background-color var(--dur-instant) linear;
}
.dg-sub__form input::placeholder{ color:var(--grey-500); }
.dg-sub__form input:hover{ border-color:var(--grey-600); }
.dg-sub__form input:focus{
  outline:none; border-color:var(--accent-text); background:rgba(255,255,255,.1);
}
.dg-sub__form .dg-btn{ flex:0 0 auto; height:48px; }

/* The answer to the subscribe form. Every outcome used to land the visitor back
   on the same dark band with an empty field and no word either way — including
   "that address is not valid" and "you are already on the list". */
.dg-sub__note{
  display:flex; align-items:flex-start; gap:var(--sp-2);
  flex:1 1 100%; order:-1; margin:0 0 var(--sp-3);
  padding:10px var(--sp-4); border-radius:var(--radius);
  font-size:var(--fs-sm); line-height:var(--lh-sm);
  animation:dg-blank-in var(--dur-base) var(--ease-enter) both;
}
.dg-sub__note:focus{ outline:none; }
.dg-sub__note:focus-visible{ outline:3px solid var(--white); outline-offset:2px; }
.dg-sub__note-i{ display:inline-flex; flex:none; margin-top:2px; }
/* On the near-black band, so both of these are fills read against ink. */
.dg-sub__note.is-ok{ background:rgba(29,203,122,.14); color:var(--green-500); }
.dg-sub__note.is-err{ background:rgba(255,180,60,.14); color:var(--amber-400); }
@media (prefers-reduced-motion:reduce){ .dg-sub__note{ animation:none; } }

/* ==================================================================== footer */
/* The ONE clearance between a page's content and the dark band under it. Page
   templates used to add their own on top of this — the product page stacked
   .dg-pdp__top's 48, .dg-pdp's 96 and this 96 into 240px of white before the
   subscribe band. Nothing below owns bottom space now; this does. */
.dg-footer{ background:var(--ink); color:var(--grey-300); margin-top:var(--sp-8); }
.dg-footer a{
  color:var(--grey-300); text-decoration:none;
  font-size:var(--fs-sm); line-height:var(--lh-sm);
  transition:color var(--dur-instant) var(--ease-standard);
}
.dg-footer a:hover{ color:var(--white); }
/* On the near-black footer the accent fill is already the readable one
   (7.84:1); it is --accent-text that would fail there, not this. */
.dg-footer .accent{ color:var(--accent-text); }

.dg-footer__in{
  display:grid;
  grid-template-columns:minmax(0,1.5fr) repeat(3,minmax(0,1fr));
  gap:var(--sp-7); padding-block:var(--sp-8) var(--sp-7);
}
.dg-footer__logo{ display:block; line-height:0; margin-bottom:var(--sp-4); }
.dg-footer__logo img{ display:block; width:auto; height:42px; }
.dg-footer__blurb{
  font-size:var(--fs-sm); line-height:var(--lh-sm); margin:0; max-width:36em;
  color:var(--grey-400);
}
.dg-footer__h{
  font-family:var(--font-display); font-weight:700;
  font-size:var(--fs-card); line-height:1.2; letter-spacing:.02em;
  color:var(--white); margin:0 0 var(--sp-4);
}
.dg-footer__links,.dg-footer__contact,.dg-footer__legal{ list-style:none; margin:0; padding:0; }
.dg-footer__links li + li{ margin-top:6px; }
.dg-footer__links a{
  display:inline-flex; align-items:baseline; gap:var(--sp-2);
  transition:color var(--dur-instant) linear, transform var(--dur-instant) var(--ease-standard);
}
.dg-footer__links a:hover{ transform:translateX(3px); }
.dg-footer__n{
  font-size:var(--fs-micro); color:var(--grey-600); font-variant-numeric:tabular-nums;
}
.dg-footer__contact{ margin-top:var(--sp-5); display:grid; gap:var(--sp-3); }
.dg-footer__contact li{
  display:flex; gap:var(--sp-3); align-items:flex-start;
  font-size:var(--fs-sm); line-height:var(--lh-sm);
}
.dg-footer__contact svg{ flex:0 0 auto; margin-top:3px; color:var(--grey-600); }
.dg-footer__contact em{
  display:block; font-style:normal; font-size:var(--fs-micro); color:var(--grey-600);
}

.dg-social{ display:flex; gap:var(--sp-2); list-style:none; margin:var(--sp-5) 0 0; padding:0; }
.dg-social a{
  display:inline-flex; align-items:center; justify-content:center;
  width:38px; height:38px; border-radius:var(--radius);
  background:rgba(255,255,255,.05); color:var(--grey-300);
  transition:background-color var(--dur-instant) linear,
             color var(--dur-instant) linear,
             transform var(--dur-instant) var(--ease-standard);
}
.dg-social a:hover{ background:var(--accent); color:var(--on-accent); transform:translateY(-2px); }
.dg-social .is-wa:hover{ background:#25D366; color:var(--ink); }

.dg-footer__tags{
  display:flex; align-items:baseline; gap:var(--sp-3) var(--sp-4); flex-wrap:wrap;
  padding-block:var(--sp-5); border-top:1px solid var(--grey-700);
}
.dg-footer__tagsh{ font-size:var(--fs-meta); color:var(--grey-500); }
.dg-footer__tags ul{ display:flex; flex-wrap:wrap; gap:var(--sp-2); list-style:none; margin:0; padding:0; }
.dg-footer__tags a{
  display:inline-block; padding:6px var(--sp-3);
  background:rgba(255,255,255,.05); border-radius:var(--radius);
  font-size:var(--fs-meta); color:var(--grey-400);
  transition:background-color var(--dur-instant) linear, color var(--dur-instant) linear;
}
.dg-footer__tags a:hover{ background:rgba(255,255,255,.12); color:var(--white); }

.dg-footer__bar{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:var(--sp-3) var(--sp-5); padding-block:var(--sp-4);
  border-top:1px solid var(--grey-700);
}
.dg-footer__copy{ margin:0; font-size:var(--fs-meta); color:var(--grey-500); }
.dg-footer__legal{ display:flex; flex-wrap:wrap; gap:var(--sp-4); }
.dg-footer__legal a{ font-size:var(--fs-meta); }

/* ========================================================= shortlist drawer */
/* Both drawers are called .dg-drawer. The mobile FILTER sheet in filters.css
   declares the same block at the same specificity and is enqueued after this
   file, so on every catalogue page it was winning: the shortlist opened as a
   bottom sheet pinned to the left edge instead of sliding in from the right.
   Each drawer is now namespaced by the data hook its own markup already carries,
   so neither can reach the other and load order stops mattering. */
.dg-drawer[data-saved-drawer][hidden]{ display:none; }
.dg-drawer[data-saved-drawer]{ position:fixed; inset:0; z-index:80; }
[data-saved-drawer] .dg-drawer__scrim{
  position:absolute; inset:0; background:rgba(20,20,22,.5);
  opacity:0; transition:opacity var(--dur-backdrop) var(--ease-standard);
}
[data-saved-drawer] .dg-drawer__panel{
  position:absolute; top:0; right:0; bottom:0; width:min(420px,100%);
  display:flex; flex-direction:column;
  background:var(--white); box-shadow:var(--shadow-panel);
  transform:translateX(100%);
  transition:transform var(--dur-panel) var(--ease-exit);
}
.dg-drawer[data-saved-drawer].is-open .dg-drawer__scrim{ opacity:1; }
.dg-drawer[data-saved-drawer].is-open .dg-drawer__panel{
  transform:translateX(0); transition:transform var(--dur-panel) var(--ease-enter);
}
[data-saved-drawer] .dg-drawer__head{
  display:flex; align-items:center; justify-content:space-between; gap:var(--sp-4);
  padding:var(--sp-5); border-bottom:1px solid var(--grey-200);
}
[data-saved-drawer] .dg-drawer__head h2{ margin:0; font-size:var(--fs-h3); line-height:var(--lh-h3); }
[data-saved-drawer] .dg-drawer__x{
  width:38px; height:38px; display:inline-flex; align-items:center; justify-content:center;
  background:transparent; border:0; border-radius:var(--radius); color:var(--text-2); cursor:pointer;
  transition:background-color var(--dur-instant) linear, color var(--dur-instant) linear;
}
[data-saved-drawer] .dg-drawer__x:hover{ background:var(--panel); color:var(--text); }
/* A flex column so the blank state inside it can take `margin:auto` and sit in
   the middle of the panel. The empty message used to be a SIBLING of this box —
   and this box is flex:1 — so it was pushed to the floor of a full-height
   drawer: the reader opened the shortlist to a screen of white with one grey
   sentence at the very bottom. */
[data-saved-drawer] .dg-drawer__body{
  flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain;
  padding:var(--sp-3) var(--sp-5);
  display:flex; flex-direction:column;
}
[data-saved-drawer] [data-saved-empty]{ margin:auto 0; }
[data-saved-drawer] [data-saved-empty][hidden]{ display:none; }
[data-saved-drawer] .dg-drawer__foot{
  padding:var(--sp-5); border-top:1px solid var(--grey-200);
  display:grid; gap:var(--sp-2);
}
[data-saved-drawer] .dg-drawer__foot[hidden]{ display:none; }
.dg-sv{
  display:flex; gap:var(--sp-3); align-items:center;
  padding:var(--sp-3) 0; border-bottom:1px solid var(--grey-200);
}
.dg-sv:last-child{ border-bottom:0; }
.dg-sv img{
  flex:0 0 auto; width:56px; height:56px; object-fit:contain;
  background:var(--grey-100); border-radius:var(--radius);
}
.dg-sv__t{
  flex:1 1 auto; min-width:0;
  font-size:var(--fs-meta); line-height:var(--lh-meta); color:var(--text);
  text-decoration:none;
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
  transition:color var(--dur-instant) linear;
}
.dg-sv__t:hover{ color:var(--accent-text); }
.dg-sv__x{
  flex:0 0 auto; width:30px; height:30px;
  display:inline-flex; align-items:center; justify-content:center;
  background:transparent; border:0; border-radius:var(--radius);
  color:var(--grey-400); cursor:pointer;
  transition:background-color var(--dur-instant) linear, color var(--dur-instant) linear;
}
.dg-sv__x:hover{ background:var(--accent-soft); color:var(--accent-text); }

/* ============================================================== breakpoints */
@media (max-width:1100px){
  /* The label goes first: the icon and the placeholder already say what it is. */
  .dg-callback span{ display:none; }
  .dg-callback{ padding:0 var(--sp-3); }
}

/* ------------------------------------------------------------- the phone menu
   900px, not 680, is where the two-pane menu stops making sense: between the two
   the catalogue button opened a panel whose panes only switched on mouseenter,
   so on an iPad every family but the first was unreachable — you could only ever
   see pane one, because tapping a family row navigates.

   Below this the menu is the whole navigation: a sheet that owns the viewport
   under the header, with the families COLLAPSED. The previous build expanded all
   six at once, which made the menu 4244px — six screens, one family heading per
   screen, and no way to see what families exist. Collapsed, the whole tree plus
   the utility links fit in one screen and any subcategory is two taps away.

   The second arm is a phone in landscape. 844x390 clears every width breakpoint
   here, so a turned phone was served the full desktop header — 137px of chrome
   on a 390px-tall screen, with a categories button, a call-back button and a
   ⌘K keycap advertising a keyboard it does not have. Guarded on any-pointer so
   a short desktop window never gets the phone layout.

   app.js derives BOTH its breakpoints from the same string; if these two ever
   disagree, every pane is painted open while all six are still absolutely
   positioned on top of one another. */
@media (max-width:900px), (max-height:520px) and (any-pointer:coarse){
  .dg-util__nav{ display:none; }
  .dg-footer__in{ grid-template-columns:1fr 1fr; gap:var(--sp-6); }
  .dg-footer__brand{ grid-column:1 / -1; }

  /* One control for the menu, not two. */
  .dg-catbtn{ display:none; }
  .dg-burger{ display:inline-flex; }
  /* Not built, and the tel: link in the bar above is one tap away. */
  .dg-callback{ display:none; }
  /* 15px zooms iOS Safari on focus, and the zoom does not come back on blur. */
  .dg-search input[type="search"]{ font-size:16px; }
  /* Both 40px, both things a thumb aims at. */
  .dg-lang__btn{ min-height:44px; padding-inline:var(--sp-3); }
  .dg-util__tel{ min-height:44px; }

  /* ---------------------------------------------------- one header, one size
     The collapse was transitioning SIX layout heights at once — the utility bar,
     the bar row, the logo, the search field, the shortlist and the burger —
     inside a position:sticky header that sits in normal flow. Every frame of
     those 200ms changed the document height by up to 54px, which is a full
     layout of the page plus a scroll-anchoring correction, landing exactly while
     a finger is dragging. That is the stutter you feel at the top of every page.

     So on a phone there is no collapse. The utility bar stops being chrome and
     becomes what it looks like — a strip at the top of the page that scrolls
     away on its own — and the working bar alone is sticky, at one height, with
     nothing animating. Same end state, no per-frame layout, and no JS involved
     in the geometry at all. */
  :root, :root.is-scrolled{
    --util-h:44px;  --util-h-sm:44px;
    --bar-h:64px;   --bar-h-sm:64px;
    --header-h:var(--bar-h);
  }
  /* The header stays the sticky element and pins with its own utility bar pulled
     off the top of the screen — position:sticky is bounded by its PARENT's box,
     so moving the sticky down to .dg-bar gave it 44px of travel and then it left
     with the page. A negative top is the whole mechanism: scroll 44px and the
     working bar is at 0 with the dark strip gone, with no class, no JS and no
     height to animate. */
  .dg-header{ top:calc(-1 * var(--util-h)); }
  .dg-util, :root.is-scrolled .dg-util{
    height:var(--util-h); opacity:1; visibility:visible; transition:none;
  }
  .dg-bar__in, :root.is-scrolled .dg-bar__in{ height:var(--bar-h); }
  .dg-logo img, .is-scrolled .dg-logo img{ height:30px; }
  .dg-search input[type="search"], .is-scrolled .dg-search input[type="search"]{ height:48px; }
  /* .dg-saved belongs in the square group, not on a height-only rule: it was
     getting 48px of height and keeping its 60px desktop width, so the one
     control whose own comment promises "a square, at every width" was the only
     rectangle in a row of squares — and 44x48 once scrolled. The 12px it was
     carrying was also the difference between fitting and not at 320. */
  .dg-saved, .is-scrolled .dg-saved,
  .dg-cmpbtn, .is-scrolled .dg-cmpbtn,
  .dg-burger, :root.is-scrolled .dg-burger,
  .dg-searchbtn, :root.is-scrolled .dg-searchbtn{ width:48px; height:48px; }
  /* Nothing here changes size any more, so nothing here may transition a size.
     Left in, these fire once on the class flip and cost a layout for nothing. */
  .dg-bar__in, .dg-logo img, .dg-catbtn, .dg-callback, .dg-saved, .dg-cmpbtn,
  .dg-burger, .dg-searchbtn, .dg-search input[type="search"]{
    transition-property:background-color,border-color,color;
  }

  /* No blur on a phone. It buys nothing over a 62px white bar, it costs a
     composited layer on every scroll frame — and, load-bearing here,
     backdrop-filter makes an element the containing block for every FIXED
     descendant. With it on, the sheet below resolved its inset against a 62px
     bar instead of the viewport and collapsed to 56px for anyone who had
     scrolled before opening the menu. */
  .is-scrolled .dg-bar{
    background:var(--grey-100);
    -webkit-backdrop-filter:none; backdrop-filter:none;
  }

  /* --- the sheet ---
     fixed, not absolute: absolute hangs off .dg-bar, and .dg-bar is inside a
     position:sticky header, which stops sticking the moment anything locks the
     document's overflow — the header, and the button that closes this, went 482px
     off the top of the screen. dvh rather than vh so a phone's collapsing URL bar
     does not leave the last row under the fold. */
  .dg-mega{
    position:fixed; left:0; right:0; top:var(--header-h); bottom:0;
    height:auto;
    display:flex; flex-direction:column;
    border:0; box-shadow:none;
    transform:translateY(10px);
    /* --header-h changes when the bar collapses mid-open, so the sheet follows
       it rather than leaving a white gap under the header. */
    transition:opacity var(--dur-exit) linear,
               transform var(--dur-exit) var(--ease-exit),
               visibility 0s linear var(--dur-exit),
               top var(--dur-base) var(--ease-standard);
  }
  @supports (height:100dvh){
    .dg-mega{ bottom:auto; height:calc(100dvh - var(--header-h)); }
  }
  .dg-mega.is-open{
    transition:opacity var(--dur-base) linear,
               transform var(--dur-panel) var(--ease-enter),
               visibility 0s linear 0s,
               top var(--dur-base) var(--ease-standard);
  }
  .dg-mega__in{
    flex:1 1 auto; min-height:0; max-height:none;
    overflow-y:auto; -webkit-overflow-scrolling:touch; overscroll-behavior:contain;
    /* The gutter comes from .dg-wrap on the same element, so padding-block only —
       the shorthand here is what made the old menu run edge to edge. */
    padding-block:var(--sp-2) calc(var(--sp-7) + env(safe-area-inset-bottom));
  }

  /* --- a family row: link on the left, disclosure on the right --- */
  .dg-mega__rail{ width:auto; }
  .dg-mega__item{
    display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center;
    border-bottom:1px solid var(--grey-200);
  }
  .dg-mega__item:last-child{ border-bottom:0; }
  /* A family with no subcategories has no disclosure, so its label takes the
     whole width instead of leaving a 56px hole where the button would be. */
  .dg-mega__item:not(:has(.dg-mega__tog)) .dg-mega__row{ grid-column:1 / -1; }
  .dg-mega__row{
    min-height:56px; padding:10px 2px; border-radius:0;
    font-size:var(--fs-card); line-height:var(--lh-card); font-weight:600; color:var(--text);
  }
  /* The pill treatment belongs to the desktop rail, where it marks which pane is
     showing. Here the open family is marked by the chevron and by its own list. */
  .dg-mega__item.is-on > .dg-mega__row{ background:none; }
  .dg-mega__item.is-on > .dg-mega__row .dg-mega__ico{ color:var(--amber-800); }
  .dg-mega__chev{ display:none; }
  .dg-mega__tog{
    display:inline-flex !important; align-items:center; justify-content:center;
    width:56px; height:56px; margin-inline-end:-10px; padding:0;
    background:none; border:0; color:var(--grey-500); cursor:pointer;
    -webkit-tap-highlight-color:transparent;
  }
  .dg-mega__togi{ transition:transform var(--dur-panel) var(--ease-standard); }
  .dg-mega__item.is-on > .dg-mega__tog{ color:var(--amber-800); }
  .dg-mega__item.is-on > .dg-mega__tog .dg-mega__togi{ transform:rotate(180deg); }

  /* --- the disclosure ---
     grid-template-rows 0fr -> 1fr is the only way to transition to a height the
     content decides; the inner wrapper owns the overflow, and both boxes need
     min-height:0 or the row refuses to go below its content. */
  .dg-mega__pane{
    grid-column:1 / -1;
    position:static; inset:auto; transform:none;
    opacity:1; visibility:visible;
    border-inline-start:0; padding:0;
    display:grid; grid-template-rows:0fr;
    overflow:hidden; overscroll-behavior:auto;
    transition:grid-template-rows var(--dur-panel) var(--ease-standard);
  }
  .dg-mega__item.is-on > .dg-mega__pane{ grid-template-rows:1fr; }
  /* No padding here, ever. min-height:0 lets the CONTENT box go to zero, but
     under border-box the padding is still part of the row's minimum, so a 12px
     padding-bottom left every collapsed family 12px tall — six of them, plus a
     stripe of white under each closed row. The breathing room goes on the list
     inside instead, where it collapses with everything else. */
  .dg-mega__paneIn{
    min-height:0; overflow:hidden; padding:0;
    opacity:0; transition:opacity var(--dur-base) linear;
  }
  /* Slightly behind the height, so the two read as one movement rather than as
     content appearing and then a box growing around it. */
  .dg-mega__item.is-on > .dg-mega__pane > .dg-mega__paneIn{ opacity:1; transition-delay:70ms; }

  /* --- subcategories as a list, not a shop window ---
     A 2-up photo grid put twelve subcategories over 900px. Inside a menu the
     picture is recognition, not merchandising, so it shrinks to a 40px mark and
     the row becomes the tap target. */
  .dg-mega__all{
    display:flex; align-items:center; min-height:48px; margin:0;
    font-size:var(--fs-sm); color:var(--amber-800);
  }
  .dg-mega__grid{ display:block; padding-bottom:var(--sp-3); }
  .dg-mega__grid a{
    flex-direction:row; align-items:center; gap:var(--sp-3);
    min-height:48px; padding:5px var(--sp-2) 5px 0;
  }
  .dg-mega__fig{
    width:40px; height:40px; flex:none; aspect-ratio:auto;
    background:var(--grey-100);
  }
  .dg-mega__kid{ font-size:var(--fs-sm); line-height:var(--lh-sm); }
  .dg-mega__item.is-on .dg-mega__grid li{ animation:none; }

  .dg-catpanel__more{
    display:block; margin-top:var(--sp-4);
    padding-top:var(--sp-3); border-top:1px solid var(--grey-200);
  }
  .dg-catpanel__more ul{ list-style:none; margin:0; padding:0; display:grid; }
  .dg-catpanel__more a{
    display:flex; align-items:center; min-height:48px;
    font-size:var(--fs-sm); color:var(--text-2); text-decoration:none;
  }
  .dg-catpanel__more a:hover{ color:var(--accent-text); }

  /* The compare tray is fixed at z-index 55 and the header — the menu's own
     stacking context — sits at 50, so the tray painted over the bottom of the
     sheet and ate the last utility link. It is also not a thing anyone needs
     while they are choosing where to go, so it gets out of the way. */
  body.dg-navopen .dg-header{ z-index:70; }
  body.dg-navopen .dg-cmp{ transform:translateY(120%); pointer-events:none; }

  /* The menu's lock, and deliberately NOT the document-overflow one the search
     palette and the filter sheet use. Those cover the whole viewport, so it does
     not matter that overflow:hidden un-sticks the header and zeroes the scroll
     position behind them. This sheet starts BELOW the header and the header has
     to stay exactly where it is, because it holds the only way out.

     So: the sheet is fixed and covers everything under the header, and
     .dg-mega__in has overscroll-behavior:contain, which means a swipe that
     starts inside it never chains to the page even when the menu itself has
     nothing left to scroll. That leaves only the header strip itself, and a pan
     starting there is not a scroll request while a menu is open. */
  body.dg-navopen .dg-util,
  body.dg-navopen .dg-bar__in{ touch-action:none; }
}
@media (max-width:820px){
  .dg-saved__lbl{ display:none; }
  .dg-lang__code{ display:none; }
  /* No shrink here: .dg-saved, .dg-callback and .dg-burger are all still 60px at
     this width, and --bar-h is 76px, so 52 only made this one button short. */
  .is-scrolled .dg-cmpbtn{ width:44px; height:44px; }
}
@media (max-width:680px){
  .dg-logo{ margin-inline-end:0; }
  .dg-bar__in{ gap:var(--sp-3); }
  /* The tray's own pill still carries the count and still reopens it. */
  .dg-cmpbtn{ display:none; }
  /* .dg-search is the only flex:1 min-width:0 item in the row, so every fixed
     button is taken out of the input — but #dg-q carries 76px of its own padding
     under border-box, a floor it cannot shrink past. At 320 the field was 118px
     inside a 28px box, painting over the whole shortlist button and the burger's
     left edge. A phone gets the button instead. */
  .dg-search{ display:none; }
  .dg-searchbtn{ display:inline-flex; margin-inline-start:auto; }
  /* --sp-7 separates the controls from the search FIELD on the desktop bar. The
     field is display:none here and .dg-searchbtn's auto margin already pushes the
     whole cluster to the trailing edge, so the 48px was a second separator with
     nothing left to separate — and the row is only just wide enough at 320: with
     it, logo + button + saved + burger came to 336 inside a 288 content box. */
  .dg-saved{ margin-inline-start:0; }
  .dg-sub__in{ padding-block:var(--sp-5); }
  .dg-sub__form{ flex:1 1 100%; }
}
@media (max-width:560px){
  .dg-footer__in{ grid-template-columns:1fr; }
  .dg-util__in{ justify-content:space-between; }
}

/* The bar collapses on scroll, which would otherwise animate on first paint for
   anyone landing mid-page via an anchor. */
@media (prefers-reduced-motion:reduce){
  .dg-util,.dg-bar__in,.dg-logo img,.dg-catbtn,.dg-callback,.dg-saved,
  .dg-search input,.dg-mega,
  [data-saved-drawer] .dg-drawer__panel,[data-saved-drawer] .dg-drawer__scrim,
  [data-drawer] .dg-drawer__panel,[data-drawer] .dg-drawer__scrim{ transition:none!important; }
}

/* ============================================================ phone chrome
   A bottom bar and a WhatsApp button, both fixed to the viewport.

   The bar at the top of a phone is the hardest part of the screen to reach
   one-handed, and it was carrying the shortlist and the comparison list — the
   two things a visitor builds up while browsing and wants back constantly. So
   those move to the thumb, along with home, the catalogue tree and service.

   --tabbar-h is the single source of truth: the page's own bottom padding and
   every other bottom-fixed thing on the site offset against it. */
:root{ --tabbar-h:0px; }
.dg-tabbar{ display:none; }
.dg-wafab{ display:none; }

@media (max-width:900px), (max-height:520px) and (any-pointer:coarse){
  :root{ --tabbar-h:calc(60px + env(safe-area-inset-bottom)); }

  /* The strip is gone on a phone. Its phone number lives on the WhatsApp button
     and the contact page, its four policy links are in the menu sheet, and its
     language switcher moved into the bar. Zeroing --util-h with it keeps the
     header's negative-top trick arithmetic honest — that value is what the
     header pulls itself up by. */
  .dg-util{ display:none; }
  :root, :root.is-scrolled{ --util-h:0px; --util-h-sm:0px; }
  .dg-header{ top:0; }

  /* --- the bar: menu and mark on the start side, search and language on the
     end side. Nothing else: the shortlist and compare are downstairs now. --- */
  /* Bare. On a phone this is the first thing on the bar and it sits next to the
     mark, not in a row of controls — a bordered white square there read as a
     card with a hamburger in it. The tap target stays 48px; only the box goes.
     The negative start margin pulls the 22px glyph back onto the page gutter,
     which the button's own centring had pushed it 13px off. */
  .dg-burger{
    order:-2;
    background:none; border-color:transparent; box-shadow:none;
    margin-inline-start:-13px; margin-inline-end:var(--sp-2);
  }
  .dg-burger:hover{ background:none; border-color:transparent; }
  /* The glyph already swaps to an X, so the open state needs no fill of its own
     — and an ink square would put the wrapper straight back. */
  .dg-burger[aria-expanded="true"]{
    background:none; border-color:transparent; color:var(--ink);
  }
  .dg-logo{ order:-1; margin-inline-end:auto; }
  .dg-searchbtn{ order:1; }
  .dg-bar .dg-lang{ display:block; order:2; }
  .dg-saved, .dg-cmpbtn{ display:none; }
  .dg-lang__btn{ padding-inline:var(--sp-2); gap:6px; }
  .dg-lang__code{ display:none; }
  /* The switcher was drawn for the dark strip: a grey-700 border and grey-300
     type. The bar is grey-100, so on a phone it was a near-invisible outline
     around a flag. Same control, ground-appropriate contrast — and matching the
     white square the search button next to it already is. */
  .dg-bar .dg-lang__btn{
    min-height:48px; background:var(--white); border-color:var(--grey-200);
    color:var(--text);
  }
  .dg-bar .dg-lang__btn:hover{ background:var(--white); border-color:var(--grey-300); color:var(--ink); }

  /* --- the bar itself --- */
  .dg-tabbar{
    position:fixed; inset-inline:0; bottom:0; z-index:60;
    display:grid; grid-auto-flow:column; grid-auto-columns:1fr;
    height:var(--tabbar-h);
    padding-bottom:env(safe-area-inset-bottom);
    background:var(--white);
    border-top:1px solid var(--grey-200);
    box-shadow:0 -2px 16px rgba(20,20,22,.07);
  }
  .dg-tabbar__b{
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
    min-width:0; padding:0 2px; border:0; background:none; cursor:pointer;
    font:inherit; color:var(--text-3); text-decoration:none;
    -webkit-tap-highlight-color:transparent;
    transition:color var(--dur-instant) linear;
  }
  .dg-tabbar__b:hover{ color:var(--text-2); }
  .dg-tabbar__b[aria-current="page"], .dg-tabbar__b[aria-expanded="true"]{ color:var(--ink); }
  .dg-tabbar__b:focus-visible{ outline:3px solid var(--ink); outline-offset:-4px; border-radius:var(--radius-sm); }
  .dg-tabbar__i{ position:relative; display:inline-flex; }
  /* 10px and no tracking: five Georgian labels across 390px is the tightest
     type on the site, and letter-spacing at this size is what turns it into a
     row of ellipses. */
  .dg-tabbar__t{
    max-width:100%; font-size:10px; line-height:1.2; font-weight:600;
    letter-spacing:normal; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  }
  .dg-tabbar__n{
    position:absolute; top:-5px; right:-9px; min-width:16px; height:16px;
    display:inline-flex; align-items:center; justify-content:center;
    padding:0 4px; border-radius:8px;
    background:var(--accent); color:var(--ink);
    font-size:10px; font-weight:700; line-height:1; font-variant-numeric:tabular-nums;
  }
  .dg-tabbar__n[hidden]{ display:none; }

  /* --- WhatsApp, floating clear of the bar --- */
  .dg-wafab{
    position:fixed; z-index:59;
    inset-inline-end:var(--sp-4);
    bottom:calc(var(--tabbar-h) + var(--sp-4));
    width:52px; height:52px;
    display:inline-flex; align-items:center; justify-content:center;
    border-radius:50%;
    background:#25D366; color:var(--white);
    box-shadow:0 6px 20px rgba(37,211,102,.38), 0 2px 6px rgba(20,20,22,.18);
    transition:transform var(--dur-base) var(--ease-standard);
  }
  .dg-wafab:active{ transform:scale(.94); }
  .dg-wafab:focus-visible{ outline:3px solid var(--ink); outline-offset:3px; }

  /* --- everything else that lives at the bottom of the viewport --------------
     The bar is 60px of opaque chrome over the foot of the page. Anything that
     was pinned to bottom:0 is now pinned behind it. */
  body{ padding-bottom:var(--tabbar-h); }
  /* The floating tray is gone on a phone. It is a second bar pinned to the same
     edge as the one that now carries a compare button with a live count, and
     two stacked bars over the foot of a 390px screen is most of the thumb's
     reach. The button goes straight to the comparison page instead — see the
     display check in app.js. */
  .dg-cmp{ display:none; }
  /* .dg-sticky's own offset lives in single.css: that file is enqueued after
     this one and declares bottom:0 at the same specificity, so an override
     here lost. The rule belongs with the component anyway. */
  /* The WhatsApp button steps up over the buy bar rather than sitting under it. */
  :root.has-sticky .dg-wafab{ bottom:calc(var(--tabbar-h) + var(--sticky-h) + var(--sp-3)); }
  /* The menu sheet and the search overlay cover the bar; nothing should show
     through the bottom of them. */
  .dg-mega, .dg-srch{ z-index:70; }
}
@media (prefers-reduced-motion:reduce){
  .dg-tabbar__b, .dg-wafab{ transition:none; }
  .dg-wafab:active{ transform:none; }
}

/* ======================================================= call-back dialog
   A small centred card, which is what every reference for this does: the ask
   is one phone number, and a page load to a four-field form was the wrong
   weight for it. */
.dg-modal{ position:fixed; inset:0; z-index:80; display:grid; place-items:center;
  padding:var(--sp-4); }
.dg-modal[hidden]{ display:none; }
.dg-modal__scrim{
  position:absolute; inset:0; background:rgba(20,20,22,.55);
  -webkit-backdrop-filter:blur(2px); backdrop-filter:blur(2px);
  opacity:0; transition:opacity var(--dur-base) linear;
}
.dg-modal.is-open .dg-modal__scrim{ opacity:1; }
/* Rises and settles rather than appearing. transform and opacity only, so the
   whole thing is compositor work and nothing reflows behind it. */
.dg-modal__panel{
  position:relative; z-index:1;
  width:min(440px,100%); max-height:calc(100dvh - var(--sp-6));
  display:flex; flex-direction:column;
  background:var(--white); border-radius:calc(var(--radius-lg) * 1.3);
  box-shadow:0 24px 60px -12px rgba(20,20,22,.34), 0 2px 8px rgba(20,20,22,.10);
  opacity:0; transform:translateY(14px) scale(.97);
  transition:opacity var(--dur-base) linear,
             transform var(--dur-panel) var(--ease-enter);
}
.dg-modal.is-open .dg-modal__panel{ opacity:1; transform:none; }

.dg-modal__head{
  flex:none; display:flex; align-items:center; justify-content:space-between;
  gap:var(--sp-4); padding:var(--sp-5) var(--sp-5) 0;
}
.dg-modal__h{ margin:0; font-size:var(--fs-h3); line-height:var(--lh-h3); font-weight:700; }
.dg-modal__x{
  flex:none; width:36px; height:36px; display:inline-flex; align-items:center;
  justify-content:center; padding:0; cursor:pointer;
  background:var(--grey-100); border:0; border-radius:50%; color:var(--text-2);
  transition:background-color var(--dur-instant) linear, color var(--dur-instant) linear;
}
.dg-modal__x:hover{ background:var(--grey-200); color:var(--ink); }
.dg-modal__x:focus-visible{ outline:3px solid var(--ink); outline-offset:2px; }

.dg-modal__body{ flex:1 1 auto; min-height:0; overflow-y:auto;
  padding:var(--sp-4) var(--sp-5) var(--sp-5); overscroll-behavior:contain; }
/* The form ships its own panel, border and 560px cap for standalone use; inside
   this card that is a second frame around the first. */
.dg-modal .dg-quote-sec{ padding:0; background:none; border:0; }
.dg-modal .dg-quote--cb{ background:none; border:0; padding:0; max-width:none; }

/* The immediate channels, under a rule rather than beside the button — the
   form is the primary and these are the way past it. */
.dg-modal__alt{ margin-top:var(--sp-5); padding-top:var(--sp-4);
  border-top:1px solid var(--grey-200); }
.dg-modal__or{ display:block; margin-bottom:var(--sp-3);
  font-size:var(--fs-meta); line-height:var(--lh-meta); color:var(--text-3); }
.dg-modal__reach{ display:grid; grid-template-columns:1fr 1fr; gap:var(--sp-2); }
.dg-modal__reach:has(> a:only-child){ grid-template-columns:1fr; }
.dg-modal__btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  min-height:46px; padding:0 var(--sp-3); border-radius:var(--radius);
  background:var(--grey-100); color:var(--ink);
  font-size:var(--fs-meta); font-weight:700; text-decoration:none;
  transition:background-color var(--dur-instant) linear;
}
.dg-modal__btn:hover{ background:var(--grey-200); }
.dg-modal__btn svg{ flex:none; color:var(--text-3); }
.dg-modal__btn.is-wa svg{ color:#25D366; }
.dg-modal__btn span{ font-variant-numeric:tabular-nums; white-space:nowrap; }
.dg-modal .dg-agent{ margin-top:var(--sp-4); }

@media (max-width:480px){
  /* Bottom sheet on a phone: a centred card with the keyboard up is a card
     pushed half off the top of the screen. */
  .dg-modal{ place-items:end center; padding:0; }
  .dg-modal__panel{
    width:100%; max-height:calc(100dvh - var(--sp-7));
    border-radius:calc(var(--radius-lg) * 1.3) calc(var(--radius-lg) * 1.3) 0 0;
    transform:translateY(100%);
  }
  .dg-modal__body{ padding-bottom:calc(var(--sp-5) + env(safe-area-inset-bottom)); }
}
@media (prefers-reduced-motion:reduce){
  .dg-modal__scrim,.dg-modal__panel{ transition-duration:1ms; }
  .dg-modal__panel{ transform:none; }
}
