/* ==========================================================================
   ui.css — UI v2 responsive design-system layer
   ==========================================================================

   Loads after site.css and foodmart.css, so it is the last word on anything it
   touches. It adds no new page markup of its own beyond what app/lib/ui2.php
   renders, and every rule here is either a token definition or a correction to
   a specific, reproducible layout defect. Rules are grouped by the defect they
   fix and each group says what the defect was, because several of them look
   like arbitrary overrides otherwise.

   Breakpoints. Chosen from where this content actually stops fitting, not from
   device names:

     560px  a product card's own minimum (200px logo well + padding) stops
            leaving room for a second column
     720px  the portal sidebar (236px) plus a readable main column (min ~430px
            for a licence key on one line) no longer both fit
     900px  a three-column product grid drops to two: below this a card is
            narrower than its image well and the logo starts letterboxing
     1080px the storefront header's search field is squeezed under 260px
     1240px --maxw; above this the page is centred and nothing changes

   ========================================================================== */

/* ============================================================== 1. TOKENS ==
   site.css already carries a fluid type scale and a spacing scale. What it
   lacked was a *shared* set of surface, control and layout tokens: cards,
   panels and tables each declared their own padding, radius and border, which
   is why the same information looks different on three pages. These name the
   values once. Existing tokens (--acc, --line, --fs-*, --sp-*) are reused, not
   replaced.
   ------------------------------------------------------------------------ */
:root{
  /* Spacing — a 4px base, so nothing lands on a half pixel */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:20px;
  --s6:24px; --s8:32px; --s10:40px; --s12:48px;

  /* Surfaces. One radius family, three depths. The previous mix of 10/12/14/16
     and four different shadows is what read as "excessive borders and
     shadows" — a page with five elevations has no elevation. */
  --r-sm:8px; --r-md:12px; --r-lg:16px; --r-pill:999px;
  --sf-border:1px solid var(--line);
  --sf-rest:0 1px 2px rgba(16,24,40,.04);
  --sf-raise:0 4px 16px -6px rgba(16,24,40,.12);

  /* Controls */
  --ctl-h:44px;              /* WCAG 2.5.5 target, and the height of every
                                button, input and select in the system */
  --ctl-h-sm:38px;
  --ctl-pad-x:18px;
  --ctl-radius:var(--r-pill);
  --field-fs:16px;           /* never smaller: iOS zooms the page below this */

  /* Layout */
  --pt-side:236px;           /* portal sidebar */
  --hdr-h:56px;              /* compact portal header */
  /* The TOTAL height of whatever is sticky at the top of this page. The
     storefront header is three stacked rows on a phone (brand bar, search
     row, and the nav panel anchor) and four on desktop, so --hdr-h was only
     ever the first of them: every scroll-margin and sticky offset computed
     from it landed content ~50px underneath the header, which is why an
     anchor link or a "Start here" heading arrived hidden behind the search
     field. site.js measures the real header into this on load, on resize and
     whenever the header itself changes height; the values below are the
     fallbacks used before that first measurement and if JS never runs. */
  --hdr-total:118px;
  --gutter-phone:16px;       /* the brief's ~16px small-phone gutter */

  /* Floating controls. Everything pinned to the bottom of the viewport adds to
     this, and the page reserves exactly this much bottom space. See §9. */
  --fab-size:56px;
  --fab-top-size:44px;       /* back-to-top: smaller than the FAB, still a
                                full 44px target (foodmart.css declares 44 and
                                was losing it to a 40px rule elsewhere) */
  --fab-gap:16px;
  --dock-h:0px;              /* height of any bottom-docked bar, set by :has() */
}

/* Page gutters: 16px on a small phone, growing with the viewport. site.css's
   --sp-gutter floors at 18px, which is close but leaves a 360px screen with
   324px of content; the brief asks for ~16px. */
@media(max-width:480px){
  :root{--sp-gutter:var(--gutter-phone)}
}

/* ==================================================== 2. EQUAL GRID TRACKS ==
   THE fix for "unequal product columns, including an oversized middle card".

   Every grid in site.css is written `repeat(3,1fr)`. `1fr` is shorthand for
   `minmax(auto,1fr)`, and that `auto` minimum resolves to the track's
   *min-content contribution* — so any item whose content cannot shrink below
   some width drags its whole column wider than its siblings, and the remaining
   free space is then split between the others. One card with a wider logo, a
   longer unbroken word, or a wider button row makes exactly the reported
   symptom: three columns, the middle one oversized.

   `minmax(0,1fr)` removes the floor, so tracks are always equal and content
   shrinks or wraps inside them instead of pushing the track out. This is also
   why the page could scroll sideways: an over-wide track overflows its
   container, which is why site.css needed `overflow-x:clip` on body. That is
   removed in §10 now the cause is gone.

   Listed explicitly rather than with a wildcard so nothing unrelated changes.
   ------------------------------------------------------------------------ */
.pgrid,
.market-grid,
.rel-grid,
.grid.c2,.grid.c3,.grid.c4,
.steps,.stats,.proof-bar,.trust-cues,
.cat-tiles,.partners-grid,.f-stats,.vid-grid,
.buysteps,.mega-cols,
.pdp-bottom,.two-col{
  grid-template-columns:repeat(var(--cols,1),minmax(0,1fr));
}
.pgrid,.market-grid,.rel-grid,.grid.c3,.steps,.proof-bar,
.vid-grid,.buysteps,.mega-cols,.f-stats{--cols:3}
.grid.c2,.partners-grid,.pdp-bottom,.two-col{--cols:2}
.grid.c4,.stats,.trust-cues,.cat-tiles{--cols:4}

/* Asymmetric two-track layouts have the same problem in their content column:
   a wide table or a long licence key forced the `1fr` track past the viewport
   and took the sidebar with it.

   These MUST carry their own collapse breakpoints. site.css already collapses
   each of them on a phone, but ui.css loads after it at equal specificity, so
   an unconditional rule here silently reinstates the desktop two-track layout
   on a 360px screen — and because the sidebar in each case is `display:none`
   by then, the fixed track stays reserved and the content gets what is left.
   On /products that is a 230px empty column against 130px of products.
   (Caught by the cascade resolver in the scratchpad, not by reading the file.) */
.mkt-layout{grid-template-columns:230px minmax(0,1fr)}
.pdp-top   {grid-template-columns:minmax(0,1fr) 380px}
.f-layout  {grid-template-columns:minmax(0,1fr) 340px}
.kv        {grid-template-columns:170px minmax(0,1fr)}

/* /products: collapses at 900px, the same point at which the facet sidebar
   becomes the Filter & sort sheet — the two must move together or the grid
   reserves a column for a panel that is no longer in flow. */
@media(max-width:900px){.mkt-layout{grid-template-columns:minmax(0,1fr)}}
/* Product page: the buy box goes under the gallery. */
@media(max-width:900px){.pdp-top{grid-template-columns:minmax(0,1fr)}}
/* Forum/blog aside. */
@media(max-width:1180px){.f-layout{grid-template-columns:minmax(0,1fr)}}
/* Definition lists (licence details, invoices, compatibility) stack their term
   above their value rather than squeezing both into a phone width. */
@media(max-width:560px){.kv{grid-template-columns:minmax(0,1fr);gap:2px 0}
                        .kv dt{margin-top:var(--s2)}}

/* Any grid child that is text can shrink. Without this a long product name or
   a filename is itself an unshrinkable box inside an already-correct track. */
.pcard,.pcard-body,.pcard-foot,.pcard-price,.pcard-cta{min-width:0}

/* ------- the storefront grid's own step-down -------
   Three across down to 900px, two to 560px, one below. 560 is where a card
   drops under the 200px logo well plus its padding, at which point a second
   column is letterboxed white space rather than a second product. */
@media(max-width:900px){.pgrid,.market-grid{--cols:2}}
/* 600, not 560: §20 restructures the card into a list row below 600px, and a
   list row needs the whole width. Two of them at 580px are 274px each, which
   is narrower than the thumbnail plus a price. */
@media(max-width:600px){.pgrid,.market-grid{--cols:1}}
@media(max-width:900px){.rel-grid,.vid-grid,.grid.c3,.grid.c4,.stats,.trust-cues,.cat-tiles{--cols:2}}
@media(max-width:600px){.rel-grid,.vid-grid,.grid.c2,.grid.c3,.grid.c4,.stats,.trust-cues,.cat-tiles,
                        .steps,.proof-bar,.partners-grid,.f-stats,.buysteps{--cols:1}}

/* Every grid in the list above MUST also appear in a step-down, because the
   repeat(var(--cols)) rule restates grid-template-columns after site.css and so
   silently cancels whatever phone breakpoint site.css gave it. Two had been
   missed: .pdp-bottom stayed two columns on a 390px phone (the product page's
   "Ask a question" form was 170px wide beside an empty track) and .two-col
   stayed two columns from 600 to 900px. Breakpoints are site.css's own. */
@media(max-width:900px){.two-col{--cols:1}}
@media(max-width:820px){.pdp-bottom{--cols:1}}

/* Single-column stacks. A grid with no column template gets one implicit
   `auto` column, and `auto` cannot shrink below its widest child's min-content
   — the /community category list measured 308px inside a 288px column on a
   320px phone and scrolled the page. Same mechanism as the equal-track fix
   above, for grids that never declared a template. Zero specificity via
   :where(), so any component that declares its own columns still wins. */
:where(.grid,.dl-cards,.f-cats,.f-msgs,.f-recent,.f-threads,.tier-pick,.mega-col,
       .nav-dd-group,.pcard-cta,.fm-assure,.state-skeleton){grid-template-columns:minmax(0,1fr)}

/* The footer is asymmetric (brand column + four link columns), so it cannot use
   the equal-track rule. It had been in the list above with no --cols, which
   made it ONE column at every width, desktop included. */
.foot-grid{grid-template-columns:minmax(0,2.2fr) repeat(4,minmax(0,1fr))}
@media(max-width:900px){.foot-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:520px){.foot-grid{grid-template-columns:minmax(0,1fr)}}

/* site.css line 804 forces several of these to 1fr with !important at 600px.
   That is the same intent, kept, but it must not outrank the two-column step
   above it for the product grid — .pgrid was never in that list, so no
   conflict; this comment exists so nobody adds it. */

/* ================================================== 3. THE PRODUCT CARD ====
   Defects: cards much taller than their content with large empty gaps,
   inconsistent image sizes, and three purchase-shaped controls competing on
   one card ("Add to cart", "Quick buy", plus the whole card being a link).
   ------------------------------------------------------------------------ */

.pcard{
  display:flex; flex-direction:column;
  padding:var(--s4);
  border-radius:var(--r-lg);
  border:var(--sf-border);
  box-shadow:var(--sf-rest);
  /* One shadow change on hover. No lift: a card that moves in a grid of
     fourteen makes the whole grid feel unstable on a trackpad. */
  transform:none;
  transition:box-shadow .2s ease,border-color .2s ease;
}
.pcard:hover{transform:none;box-shadow:var(--sf-raise);border-color:var(--line2)}

/* ------- image: one treatment for every product -------
   Cards mixed a 200px brand mark, a wide screenshot and an icon placeholder,
   at three different scales. The well is now a fixed 4:3 box with a uniform
   ground; whatever goes in it is contained and centred, so a pale logo, a dark
   logo and the placeholder all occupy the same rectangle. */
.pcard-thumb{
  position:relative;
  aspect-ratio:4/3;
  display:flex; align-items:center; justify-content:center;
  padding:var(--s3);
  margin:0 0 var(--s3);
  border-radius:var(--r-md);
  background:rgba(16,24,40,.045);
  overflow:hidden;
}
.pcard-thumb img{
  width:100%; height:100%;
  object-fit:contain;
  /* The mark is published at 200x200 and there is no larger source, so it is
     never scaled up past its native size — an upscaled logo reads as blurry.
     It is still centred in the full well, so the well stays uniform. */
  max-width:min(100%,200px); max-height:min(100%,200px);
  border-radius:var(--r-md);
}
.pcard:hover .pcard-thumb img{transform:none}   /* the zoom fought the shadow */
.pcard-ph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  color:var(--dim);background:transparent}
.pcard-ph .ico{width:34px;height:34px}

/* ------- body: fixed rhythm so a row of cards lines up -------
   The old card let each element size itself, so a one-line tagline and a
   three-line tagline produced cards of different heights inside a stretched
   grid row — the extra height became the empty gap above the price. Title and
   description now occupy a reserved number of lines, so every card in a row
   reaches its price at the same y. */
.pcard-body{padding:0;display:flex;flex-direction:column;flex:1;min-width:0;gap:0}
.pcard-eye{
  font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  color:var(--dim);margin:0 0 var(--s1);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.pcard-name{
  font-size:16.5px;line-height:1.32;font-weight:640;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  min-height:calc(2 * 1.32em);          /* two lines, reserved */
  overflow-wrap:anywhere;
}
.pcard-meta{margin:var(--s2) 0 0;min-height:0;gap:var(--s2)}
.pcard-desc{
  margin-top:var(--s2);font-size:13.5px;line-height:1.5;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  min-height:calc(2 * 1.5em);           /* two lines, reserved — was three,
                                           and the third was blank on 9 of 14 */
}

/* ------- the spec block: the same four facts, in the same order -------
   Product name, platform, symbols, strategy and licence now appear on every
   card in one place. Rendered by ui2_card_specs(); a fact the product row does
   not carry prints nothing rather than an em-dash. */
.pcard-specs{
  display:grid;grid-template-columns:auto minmax(0,1fr);
  gap:2px var(--s3);
  margin-top:var(--s3);padding-top:var(--s3);
  border-top:1px solid var(--line);
  font-size:12.5px;line-height:1.45;
}
.pcard-specs dt{color:var(--dim)}
.pcard-specs dd{color:var(--txt);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ------- price + action -------
   `margin-top:auto` on the foot pushed the whole remainder of the card's
   height into one gap above the price. With the body's own heights now
   reserved, the foot only takes the space it needs. */
.pcard-foot{margin-top:auto;padding-top:var(--s3)}
.pcard-price{display:flex;align-items:baseline;flex-wrap:wrap;gap:var(--s2);margin-bottom:var(--s3)}
.pcard-now{font-size:20px;font-weight:700;letter-spacing:-.02em}
.pcard-was{font-size:13px}
.pcard-off{align-self:center;border-radius:var(--r-pill);padding:2px 8px;font-size:11.5px}

/* One primary action. "View EA" is the discovery action the brief asks for and
   the card's job on a listing page; buying happens on the product page, where
   the licence tier, the payment method and the assurances are. The cart form
   is kept — it is the same POST, same CSRF, same handler — as the quiet
   secondary. Nothing about the purchase path changed, only which of the two
   controls is loud. */
.pcard-cta{display:grid;gap:var(--s2);margin-top:0}
.pcard-cta .btn{width:100%;min-height:var(--ctl-h);border-radius:var(--ctl-radius)}
.pcard-cta form{width:100%;display:block}
.pcard-cta form .btn{width:100%}

/* Rails reuse the card, so they inherit all of the above. They only drop the
   spec block, which is a browsing aid and not a shelf one. */
.pcard-compact .pcard-specs{display:none}
.pcard-compact .pcard-name{font-size:15.5px}
.pcard-compact .pcard-now{font-size:18px}

/* ================================================ 4. STOREFRONT HEADER =====
   Defect: five stacked rows (utility bar, announcement, brand bar, a second
   search row, category bar) before any content, on a 640px-tall phone.

   Below 1080px the header becomes two rows: brand + tools, then search. The
   utility bar, the announcement and the category bar move into the existing
   burger menu rather than being deleted — every link in them is still
   reachable, just not all at once above the fold.
   ------------------------------------------------------------------------ */
@media(max-width:1080px){
  .util-bar{display:none}
  .annc{display:none}
  /* The category bar ROW is what has to go, not its contents: `.nav` is a
     child of `.cat-bar > .wrap`, so `display:none` here took the mobile menu
     with it — the burger flipped every class and aria attribute correctly and
     then opened a panel with a display:none ancestor, i.e. nothing, while
     still locking body scroll. `display:contents` drops the two wrapper
     boxes and promotes `.nav` to a child of the sticky header, which is
     where §11 positions it. */
  .cat-bar{display:contents}
  .cat-bar > .wrap{display:contents}
  .cat-browse{display:none}

  .brand-bar .wrap{height:var(--hdr-h);gap:var(--s3)}
  /* The wide search collapses; the dedicated narrow-screen row carries it, so
     search is visible without opening anything. */
  .brand-bar .hdr-search{display:none}
  .hdr-search-row{display:block;padding-bottom:var(--s2)}
  .hdr-search-row .hdr-search{width:100%}
  .hdr-search-row .hdr-search-in{font-size:var(--field-fs);min-height:var(--ctl-h-sm)}
}
/* The burger has to pick up what the hidden bars carried. These are rendered
   in .nav already; they were simply invisible because .cat-bar was the only
   thing that showed them.

   The burger itself used to appear only below 941px while .cat-bar was hidden
   from 1080px down, so between 941 and 1080 the page carried no navigation at
   all — neither a bar nor a button. The burger now shares the 1080px
   breakpoint (§11), so exactly one of the two is always present. */

/* Sticky header must never sit on top of the thing an anchor scrolled to.
   `scroll-margin-top` on every plausible target is the fix; `scroll-padding`
   on the scroll container covers the rest, including in-page tab links. */
html{scroll-padding-top:calc(var(--hdr-total) + var(--s4))}
:target,h2[id],h3[id],section[id],.pdp-tab-panel[id],[id].panel{
  scroll-margin-top:calc(var(--hdr-total) + var(--s4));
}
/* Sticky offsets were hardcoded to 80px, which was the old header height. */
.pdp-gallery,.mkt-filters{top:calc(var(--hdr-total) + var(--s5))}

/* ============================================ 5. MOBILE FILTER & SORT ======
   Defect: on a phone the marketplace sidebar rendered its full facet list —
   three groups, sixteen links — above the products, so the grid started well
   below the fold. The same facets now live in a bottom sheet opened by one
   button, with Apply, Clear and a count of what is active.
   ------------------------------------------------------------------------ */
.mkt-fbar{display:none}
@media(max-width:900px){
  .mkt-filters{display:none}
  .mkt-filters.open{display:block}

  .mkt-fbar{
    display:flex;align-items:center;gap:var(--s2);
    margin-bottom:var(--s4);
  }
  .mkt-fbar-btn{
    display:inline-flex;align-items:center;gap:var(--s2);
    min-height:var(--ctl-h);padding:0 var(--ctl-pad-x);
    border:var(--sf-border);border-radius:var(--ctl-radius);
    background:var(--card);color:var(--txt);font:inherit;font-size:14px;font-weight:600;
    cursor:pointer;
  }
  .mkt-fbar-btn .ico{width:17px;height:17px}
  .mkt-fbar-count{
    display:inline-flex;align-items:center;justify-content:center;
    min-width:20px;height:20px;padding:0 6px;border-radius:var(--r-pill);
    background:var(--acc);color:#fff;font-size:11.5px;font-weight:700;
  }
  .mkt-fbar-clear{margin-left:auto;font-size:13.5px;min-height:var(--ctl-h);
    display:inline-flex;align-items:center;padding:0 var(--s2)}

  /* The sheet itself. Bottom-anchored because that is where a thumb is. */
  .mkt-filters.open{
    position:fixed;inset:auto 0 0 0;z-index:210;
    max-height:min(84dvh,640px);overflow-y:auto;overscroll-behavior:contain;
    background:var(--card);
    border:var(--sf-border);border-bottom:none;
    border-radius:var(--r-lg) var(--r-lg) 0 0;
    padding:var(--s5) var(--s5) calc(var(--s5) + 76px + env(safe-area-inset-bottom,0px));
    box-shadow:0 -12px 40px -12px rgba(16,24,40,.3);
  }
  .mkt-filters.open .mkt-sheet-head{
    display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
    margin:0 0 var(--s4);
  }
  .mkt-filters.open .mkt-sheet-head h2{font-size:17px;margin:0}
  /* Apply is docked so it is reachable without scrolling the facet list back. */
  .mkt-filters.open .mkt-sheet-foot{
    position:fixed;left:0;right:0;bottom:0;z-index:1;
    display:flex;gap:var(--s3);
    padding:var(--s3) var(--s5) calc(var(--s3) + env(safe-area-inset-bottom,0px));
    background:var(--card);border-top:1px solid var(--line);
  }
  .mkt-filters.open .mkt-sheet-foot .btn{flex:1;min-height:var(--ctl-h)}
  .mkt-opt{min-height:var(--ctl-h);display:flex;align-items:center}
}
/* The sheet's own furniture — its header, its docked footer and the sort group
   it borrows from the bar above the grid — exists only inside the sheet. On a
   desktop the sidebar must not sprout a "Filter & sort" heading, a second Clear
   button or a duplicate sort list. */
.mkt-sheet-head,.mkt-sheet-foot,.mkt-sheet-sort{display:none}
@media(max-width:900px){
  .mkt-filters.open .mkt-sheet-head,
  .mkt-filters.open .mkt-sheet-foot{display:flex}
  .mkt-filters.open .mkt-sheet-sort{display:block}
}

/* Sort pills were a wrapping row of five tiny links. */
@media(max-width:900px){
  .mkt-bar{flex-wrap:wrap;gap:var(--s3)}
  .mkt-sort{display:flex;gap:var(--s2);overflow-x:auto;padding-bottom:var(--s1);
    scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .mkt-sort::-webkit-scrollbar{display:none}
  .mkt-sort a{white-space:nowrap;min-height:var(--ctl-h-sm);display:inline-flex;
    align-items:center;padding:0 var(--s3);border-radius:var(--r-pill);border:1px solid var(--line)}
}

/* =============================================== 6. THE PORTAL SHELL =======
   Defect: shell_top() called layout_top(), so the entire storefront header —
   utility bar, announcement, brand bar, search row, category bar — rendered
   above the portal's own navigation, which then wrapped into several rows of
   large pills below 900px. On a phone the customer's licence key started
   roughly 400px down a 640px screen.

   UI v2 gives the portal its own 56px bar, a real sidebar on desktop, and a
   focus-managed drawer on mobile. Rendered by ui2_portal_bar() / _nav().
   ------------------------------------------------------------------------ */
.pt-bar{
  position:sticky;top:0;z-index:60;
  background:var(--card);border-bottom:1px solid var(--line);
}
.pt-bar-in{
  display:flex;align-items:center;gap:var(--s3);
  height:var(--hdr-h);
  max-width:var(--maxw);margin:0 auto;
  padding-inline:max(var(--sp-gutter),env(safe-area-inset-left,0px));
}
.pt-logo{display:inline-flex;align-items:center;flex:0 0 auto}
.pt-logo svg{height:26px;width:auto}
.pt-area{
  font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--dim);padding-left:var(--s3);border-left:1px solid var(--line);
  white-space:nowrap;
}
.pt-bar-right{display:flex;align-items:center;gap:var(--s2);margin-left:auto}
.pt-shop,.pt-inbox{
  display:inline-flex;align-items:center;gap:var(--s2);
  min-height:var(--ctl-h);min-width:var(--ctl-h);justify-content:center;
  padding:0 var(--s3);border-radius:var(--ctl-radius);
  color:var(--muted);font-size:13.5px;font-weight:600;
}
.pt-shop:hover,.pt-inbox:hover{color:var(--txt);background:var(--bg2);text-decoration:none}
.pt-inbox{position:relative;padding:0}
.pt-inbox.has-new{color:var(--acc)}
.pt-menu-btn{
  display:none;
  align-items:center;justify-content:center;
  width:var(--ctl-h);height:var(--ctl-h);margin-left:calc(var(--s3) * -1);
  border:none;border-radius:var(--r-md);background:transparent;color:var(--txt);cursor:pointer;
}

/* ------- the shell ------- */
.pt-shell{
  display:grid;grid-template-columns:var(--pt-side) minmax(0,1fr);
  max-width:var(--maxw);margin:0 auto;
  min-height:calc(100dvh - var(--hdr-h));
  align-items:start;
}
.pt-side{
  position:sticky;top:var(--hdr-h);
  align-self:start;
  max-height:calc(100dvh - var(--hdr-h));overflow-y:auto;
  padding:var(--s5) var(--s3) var(--s8);
  border-right:1px solid var(--line);
}
.pt-main{padding:var(--s8) var(--sp-gutter) var(--s12);min-width:0}
.pt-main > h1{font-size:var(--fs-xl);margin:0 0 var(--s1)}
.pt-main > .sub{color:var(--muted);font-size:var(--fs-sm);margin:0 0 var(--s6);max-width:66ch}

.pt-grp{
  font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);
  font-weight:700;padding:var(--s4) var(--s3) var(--s2);
}
.pt-grp:first-child{padding-top:0}
.pt-list{list-style:none;margin:0;padding:0}
.pt-link{
  display:flex;align-items:center;gap:var(--s2);
  min-height:var(--ctl-h);padding:0 var(--s3);
  border-radius:var(--r-md);
  color:var(--muted);font-size:14.5px;font-weight:500;
}
.pt-link:hover{color:var(--txt);background:var(--bg2);text-decoration:none}
.pt-link-t{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* The active section: a filled chip and a bar, not just a colour. A colour
   alone is the only cue the old sidebar gave, and it failed for anyone who
   cannot separate the accent from the muted grey. */
.pt-link.active{
  color:var(--acc);background:var(--acc-dim);font-weight:650;
  box-shadow:inset 3px 0 0 var(--acc);
}
.pt-count{
  margin-left:auto;flex:0 0 auto;
  min-width:20px;height:20px;padding:0 6px;border-radius:var(--r-pill);
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--acc);color:#fff;font-size:11px;font-weight:700;
}
/* Log out, kept away from everyday navigation. */
.pt-exit{margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--line)}
.pt-logout{color:var(--dim)}
.pt-logout:hover{color:var(--red);background:rgba(240,101,95,.08)}
.pt-logout .ico{width:17px;height:17px}

/* ------- mobile: the sidebar becomes a drawer -------
   720px is where 236px of sidebar plus a main column wide enough for a licence
   key on one line stop both fitting. */
@media(max-width:720px){
  .pt-menu-btn{display:inline-flex}
  /* Both, deliberately: display:block already takes the tracks out of play, but
     stating the single column too means the shell is still correct if some
     later rule restores `display:grid`. */
  .pt-shell{display:block;grid-template-columns:minmax(0,1fr)}
  .pt-main{padding:var(--s5) var(--sp-gutter) var(--s10)}
  .pt-shop span{display:none}
  .pt-shop{padding:0;width:var(--ctl-h)}

  .pt-side{
    position:fixed;inset:0 auto 0 0;z-index:220;
    width:min(310px,86vw);max-height:none;height:100dvh;
    border-right:1px solid var(--line);background:var(--card);
    padding:var(--s5) var(--s4) calc(var(--s8) + env(safe-area-inset-bottom,0px));
    transform:translateX(-100%);
    transition:transform .22s cubic-bezier(.2,.7,.3,1);
    box-shadow:0 0 40px -8px rgba(16,24,40,.4);
  }
  .pt-side[data-open="true"]{transform:translateX(0)}
  .pt-scrim{
    position:fixed;inset:0;z-index:215;
    background:rgba(8,11,20,.5);
    opacity:0;pointer-events:none;transition:opacity .22s;
  }
  .pt-scrim[data-open="true"]{opacity:1;pointer-events:auto}
  body.pt-drawer-open{overflow:hidden}
}
@media(min-width:721px){
  .pt-scrim{display:none}
  .pt-drawer-head{display:none}
}
.pt-drawer-head{
  display:flex;align-items:center;justify-content:space-between;gap:var(--s3);
  margin-bottom:var(--s4);
}
.pt-drawer-head strong{font-size:15px}
.pt-drawer-close{
  width:var(--ctl-h);height:var(--ctl-h);margin-right:calc(var(--s2) * -1);
  display:inline-flex;align-items:center;justify-content:center;
  border:none;background:transparent;color:var(--muted);border-radius:var(--r-md);cursor:pointer;
}
.pt-drawer-close:hover{color:var(--txt);background:var(--bg2)}
@media(prefers-reduced-motion:reduce){
  .pt-side,.pt-scrim{transition:none}
}

/* ==================================================== 7. DOWNLOADS =========
   Defect: a six-column table on a phone, with the filename column clipped and
   the Download button pushed off the right edge behind a horizontal scroll.

   Desktop keeps the table — it is the right shape for scanning versions. Below
   720px each row becomes a card. Both are rendered from the same loop, so
   there is no second source of truth about what a customer may download; the
   authorisation check in portal_download_file() is untouched.
   ------------------------------------------------------------------------ */
.dl-cards{display:none}
.dl-file{
  font-family:var(--num);font-size:12.5px;color:var(--muted);
  overflow-wrap:anywhere;word-break:normal;   /* <wbr> supplies the break points */
  display:block;max-width:38ch;
}
.tbl td .btn{white-space:nowrap}
.dl-badge{
  display:inline-flex;align-items:center;
  margin-left:var(--s2);padding:2px 8px;border-radius:var(--r-pill);
  background:var(--acc-dim);color:var(--acc);
  font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
}

@media(max-width:720px){
  .dl-table{display:none}
  .dl-cards{display:grid;gap:var(--s3)}
  .dl-card{
    border:var(--sf-border);border-radius:var(--r-lg);
    padding:var(--s4);background:var(--card);box-shadow:var(--sf-rest);
  }
  .dl-card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s3)}
  .dl-card-name{font-size:15px;font-weight:640;color:var(--txt);min-width:0;overflow-wrap:anywhere}
  .dl-card-ver{font-size:13px;color:var(--muted);margin-top:2px}
  .dl-card .dl-file{margin-top:var(--s3);max-width:none;font-size:12px}
  .dl-card-meta{
    display:flex;flex-wrap:wrap;gap:var(--s1) var(--s3);
    margin-top:var(--s2);font-size:12.5px;color:var(--dim);
  }
  .dl-card .btn{width:100%;margin-top:var(--s4);min-height:var(--ctl-h)}
}

/* Update notes: the changelog was a <pre>, which is the one element that
   cannot wrap, so a long line scrolled the whole page sideways. */
.chglog pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px;line-height:1.6}

/* ================================================ 8. NOTIFICATIONS =========
   Defect: each notification was a tall block with a type badge, full body text
   and a bare "Open" link, so an inbox of ten filled three screens.
   ------------------------------------------------------------------------ */
.notif{
  display:grid;
  grid-template-columns:auto minmax(0,1fr) auto;
  align-items:start;gap:var(--s3);
  padding:var(--s3) 0;
}
.notif .dot{width:8px;height:8px;margin-top:7px}
.notif-body{min-width:0}
.notif h4{
  font-size:14.5px;line-height:1.4;margin:0;
  display:flex;align-items:baseline;gap:var(--s2);flex-wrap:wrap;
}
.notif p{
  font-size:13px;line-height:1.5;margin-top:2px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  white-space:normal;                       /* was pre-line — a stored newline
                                               made a two-word body three lines */
  overflow-wrap:anywhere;
}
.notif .t{font-size:12px;margin-top:var(--s1)}
.notif-act{
  flex:0 0 auto;align-self:center;
  min-height:var(--ctl-h-sm);display:inline-flex;align-items:center;
  padding:0 var(--s3);border-radius:var(--ctl-radius);
  border:1px solid var(--line);color:var(--acc);font-size:13px;font-weight:600;white-space:nowrap;
}
.notif-act:hover{text-decoration:none;background:var(--acc-dim)}
@media(max-width:560px){
  .notif{grid-template-columns:auto minmax(0,1fr)}
  .notif-act{grid-column:2;justify-self:start;margin-top:var(--s2)}
}

/* Loading / empty / error states, shared by every portal list. */
.state{
  display:flex;flex-direction:column;align-items:center;gap:var(--s3);
  padding:var(--s10) var(--s5);text-align:center;
  border:1px dashed var(--line2);border-radius:var(--r-lg);
  color:var(--muted);
}
.state .ico{width:28px;height:28px;color:var(--dim)}
.state h3{font-size:15.5px;color:var(--txt);margin:0}
.state p{font-size:13.5px;margin:0;max-width:44ch}
.state.error{border-style:solid;border-color:rgba(240,101,95,.4);color:var(--red)}
.state-skeleton{display:grid;gap:var(--s3)}
.state-skeleton span{
  display:block;height:52px;border-radius:var(--r-md);
  background:linear-gradient(90deg,var(--bg2) 25%,rgba(148,170,214,.10) 37%,var(--bg2) 63%);
  background-size:400% 100%;animation:sk 1.3s ease infinite;
}
@keyframes sk{0%{background-position:100% 0}100%{background-position:0 0}}
@media(prefers-reduced-motion:reduce){.state-skeleton span{animation:none}}

/* A confirmation that belongs to a flow, shown in that flow and dismissible. */
.inline-note{
  display:flex;align-items:flex-start;gap:var(--s3);
  padding:var(--s3) var(--s4);margin-bottom:var(--s4);
  border:1px solid rgba(62,207,142,.35);border-radius:var(--r-md);
  background:rgba(62,207,142,.07);color:var(--green);font-size:13.5px;line-height:1.5;
}
.inline-note .ico{width:18px;height:18px;flex:0 0 18px;margin-top:2px}
.inline-note button{
  margin-left:auto;flex:0 0 auto;
  width:32px;height:32px;border:none;background:transparent;color:inherit;
  font-size:18px;line-height:1;cursor:pointer;border-radius:var(--r-sm);
}
.inline-note button:hover{background:rgba(62,207,142,.14)}

/* ============================================ 9. FLOATING CONTROLS =========
   One system for everything pinned to the bottom of the viewport: the docked
   bars (mobile buy bar, unfinished-cart bar, preview bar) and the floating
   buttons (WhatsApp, back to top).

   --dock-h     total height of the bars actually docked right now. site.js
                MEASURES it (ResizeObserver) and sets it on <body>; the :has()
                values below are only the no-JS fallback, and each is scoped
                to the widths where that bar is displayed — the buy bar is
                display:none above 820px, so it must not lift anything there.
   --dock-safe  the iPhone home-indicator inset, counted ONCE. A docked bar
                already pads itself with env(safe-area-inset-bottom), so when
                one is docked the buttons above it must not add it again.
   --cartbar-h  the cart bar alone; the buy bar stacks on top of it.

   Replaced 2026-09-19: three layers each carried their own constants
   (site.css 84/108/118px, foodmart.css 72/84/136px, this file 72/76/148px),
   this file positioned `.fm-totop` — a class that does not exist, the button
   is `.fm-top` — so back-to-top followed foodmart's numbers while WhatsApp
   followed these, aligned to different right edges; and the page padding
   here lost to site.css's `body.has-buybar{padding-bottom:74px}` on every
   product page, so the buy bar could cover the end of the page.
   ------------------------------------------------------------------------ */
:root{--dock-safe:env(safe-area-inset-bottom,0px);--cartbar-h:0px}
body:has(.cart-bar){--dock-h:calc(70px + env(safe-area-inset-bottom,0px));--dock-safe:0px;
  --cartbar-h:calc(70px + env(safe-area-inset-bottom,0px))}
body:has(.fm-bar){--dock-h:44px}
@media(max-width:820px){
  body:has(.cart-bar){--dock-h:calc(118px + env(safe-area-inset-bottom,0px));
    --cartbar-h:calc(118px + env(safe-area-inset-bottom,0px))}
  body:has(.buybar){--dock-h:calc(64px + env(safe-area-inset-bottom,0px));--dock-safe:0px}
  body:has(.buybar):has(.cart-bar){--dock-h:calc(182px + env(safe-area-inset-bottom,0px))}
}

/* The buy bar sits on the cart bar rather than under it. */
body.has-buybar:has(.cart-bar) .buybar{bottom:var(--cartbar-h)}

/* Docked bars above page content, floating buttons above the bars' edge, and
   everything below the sheets/drawers/lightbox (200+). */
.buybar{z-index:90}
.cart-bar{z-index:95}

.wa-fab{
  right:calc(var(--fab-gap) + env(safe-area-inset-right,0px));
  width:var(--fab-size);height:var(--fab-size);
  z-index:100;
  transition:bottom .2s ease,transform .15s ease;
}
/* Back to top shares the WhatsApp button's vertical axis (centred on it, since
   it is smaller) and stacks directly above it. The offset was computed from a
   hardcoded 44px while the button is 40px, so the two were 2px out of line at
   every width; --fab-top-size is the button's real size. */
.fm-top{
  right:calc(var(--fab-gap) + env(safe-area-inset-right,0px)
             + (var(--fab-size) - var(--fab-top-size)) / 2);
  width:var(--fab-top-size);height:var(--fab-top-size);
  z-index:100;
}
/* On a phone the pair is a 56 + 40 + gaps column down the right edge of a
   360px screen, i.e. a sixth of the width standing on the content. Both step
   down; both stay above the 44px target in their tappable box. */
@media(max-width:600px){
  :root{--fab-size:50px;--fab-gap:14px}
  .wa-fab .ico{width:24px;height:24px}
}
/* Specificity matches site.css `body.has-buybar .wa-fab` (0,2,1), which would
   otherwise put the button back on a hardcoded 84px. */
body .wa-fab,
body.has-buybar .wa-fab,
body:has(.cart-bar) .wa-fab{bottom:calc(var(--dock-h) + var(--fab-gap) + var(--dock-safe))}
body .fm-top,
body.has-buybar .fm-top{bottom:calc(var(--dock-h) + var(--fab-gap) + var(--dock-safe) + var(--fab-size) + var(--s3))}

/* Reserve the docked bars plus the button stack at the end of the page, so the
   last thing on it is never permanently covered. Padding, not margin, so a
   full-height background still reaches the bottom. Listed at site.css's and
   foodmart.css's specificity so their fixed paddings cannot undercut it. */
body,
body.has-buybar,
body:has(.cart-bar),
body:has(.fm-bar){
  padding-bottom:calc(var(--dock-h) + var(--dock-safe) + var(--fab-gap) + var(--fab-size) + var(--s3));
}

/* On checkout the FAB steps aside: a support button next to a "Pay now"
   button is a mis-tap on a payment. */
body.is-checkout .wa-fab{width:var(--ctl-h);height:var(--ctl-h);opacity:.85}
body.is-checkout .fm-top{display:none}

/* The mobile menu is a panel under the header (z 50). Nothing floating may sit
   on top of it, or cover its last links. */
body.nav-open .wa-fab,
body.nav-open .fm-top,
body.nav-open .buybar,
body.nav-open .cart-bar{visibility:hidden}

/* ============================================== 10. OVERFLOW & WRAPPING ====
   site.css sets `body{overflow-x:clip}`. That hides sideways overflow rather
   than preventing it — the content is still there, just unreachable, and it
   masks exactly the grid bug fixed in §2. Removed. If the page scrolls
   sideways after this, something is genuinely too wide and should be found,
   not clipped.
   ------------------------------------------------------------------------ */
body{overflow-x:visible}

/* Unbreakable strings from real data — a product tagline that is a bare MQL5
   URL, a licence key, an email — widened the Grizzly product page to 546px on
   a 390px phone. break-word only splits a word that would otherwise overflow
   its line, so ordinary prose is unaffected; it is the base, and the
   components below that need `anywhere` (for min-content sizing) keep it. */
body{overflow-wrap:break-word}

/* site.css caps an image's width but not its height, so an <img> carrying
   width/height attributes kept its attribute height when it shrank and
   stretched vertically. Element-level, so any class that sizes an image
   deliberately (object-fit wells, thumbnails) still wins. */
img{max-width:100%;height:auto}

/* Anything that can legitimately be wider than a phone scrolls inside its own
   box instead. */
.tbl-wrap,.dl-table,.mkt-sort,.rail-track{overflow-x:auto;-webkit-overflow-scrolling:touch}

/* Long unbroken strings: filenames, email addresses, licence keys, URLs.
   These are the three things a customer is most likely to meet in the portal
   and the three most likely to be a single unbreakable token. */
.license-key,.mono,.dl-file,.notif p,.pcard-name,
td[data-wrap],.kv dd,.article-body a{
  overflow-wrap:anywhere;
}
.license-key{
  display:block;max-width:100%;
  font-size:15px;letter-spacing:.04em;line-height:1.5;
  overflow-wrap:anywhere;
}
/* An email address breaks at @ and . rather than mid-domain. */
.email-wrap{word-break:break-word;overflow-wrap:anywhere}

/* Fixed heights on text containers are a guaranteed clip as soon as the text
   is longer than the designer's sample. min-height is the safe form. */
.pcard-name,.pcard-desc,.notif h4,.state p{height:auto}

/* ==================================================== 11. CONTROLS =========
   One button system, one field system. The store previously had buttons at
   four heights and fields at three, which is most of what read as
   "inconsistent padding".
   ------------------------------------------------------------------------ */
.btn{
  min-height:var(--ctl-h);
  padding-inline:var(--ctl-pad-x);
  border-radius:var(--ctl-radius);
  font-size:14.5px;font-weight:600;
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
}
.btn-sm{min-height:var(--ctl-h-sm);padding-inline:var(--s4);font-size:13.5px}
.btn-lg{min-height:52px;padding-inline:var(--s6);font-size:16px}
.btn-block{width:100%}

/* The element selectors sit inside :where() so they carry zero specificity.
   Written plainly, `input[type=search]` scored (0,1,1) and beat every
   component class at (0,1,0) — it reset the header search's 40px icon gutter
   to 14px, so the magnifier sat on top of the placeholder on every page. A
   field system is a default; a component that styles its own field must win. */
.in,:where(input[type=text],input[type=email],input[type=password],input[type=search],
input[type=tel],input[type=url],input[type=number],select,textarea){
  font-size:var(--field-fs);
  min-height:var(--ctl-h);
  padding:11px 14px;
  border-radius:var(--r-md);
  width:100%;
}
textarea,textarea.in{min-height:120px;line-height:1.6}
select{min-height:var(--ctl-h)}

/* Visible labels, and a field's error beside the field it belongs to. */
.field{display:block;margin-bottom:var(--s4);min-width:0}
.field > label{
  display:block;margin-bottom:6px;
  font-size:13.5px;font-weight:600;color:var(--txt);
}
.field-hint{display:block;margin-top:5px;font-size:12.5px;color:var(--dim)}
.field-err{display:block;margin-top:6px;font-size:13px;color:var(--red);font-weight:500}
.field.has-err .in,.field.has-err input,.field.has-err select,.field.has-err textarea{
  border-color:var(--red);
}
.field.has-err .in:focus{box-shadow:0 0 0 3px rgba(240,101,95,.16)}

/* Forms are one column on a phone regardless of what the page asked for. */
@media(max-width:640px){
  .form-row,.form-2col,.grid.c2.form{grid-template-columns:minmax(0,1fr)!important}
}

/* A submit that has been pressed cannot be pressed again, but a *failed*
   payment must stay retryable — site.js clears this on the pageshow event and
   on any server-rendered error, so a back-button return to a failed checkout
   finds a live button, not a dead one. */
.btn[aria-disabled="true"],.btn[data-submitting]{
  opacity:.65;pointer-events:none;
}

/* ================================================ 12. ACCESSIBILITY ========
   ------------------------------------------------------------------------ */
:focus-visible{outline:2px solid var(--acc);outline-offset:2px;border-radius:6px}
/* A focused control inside a scrolling drawer must not have its ring clipped */
.pt-side :focus-visible,.mkt-filters :focus-visible{outline-offset:-2px}

/* Every icon-only control gets a real target and, in the markup, a label. */
.pt-menu-btn,.pt-drawer-close,.pt-inbox,.nav-toggle,.rail-btn,.cart-bar-x,.wa-fab{
  min-width:var(--ctl-h);min-height:var(--ctl-h);
}
.rail-btn{width:var(--ctl-h);height:var(--ctl-h)}
.cart-bar-x{width:var(--ctl-h);height:var(--ctl-h);display:inline-flex;
  align-items:center;justify-content:center}

/* Skip link — there was none, so a keyboard user traversed the whole header on
   every page. */
.skip-link{
  position:absolute;left:var(--s4);top:-100px;z-index:400;
  padding:10px 16px;border-radius:var(--r-md);
  background:var(--acc);color:#fff;font-weight:600;
  transition:top .15s;
}
.skip-link:focus{top:var(--s3);text-decoration:none}

/* Images: reserve the box so nothing below them jumps when they arrive. */
.pcard-thumb,.market-thumb,.vid-card img,.pdp-main img{background-color:rgba(16,24,40,.045)}
img[width][height]{height:auto}

/* ==================================================== 13. DENSITY ==========
   "Excessive borders, shadows and padding": a panel inside a panel drew two
   borders and two shadows around the same content, and the card grid drew a
   third around each item.
   ------------------------------------------------------------------------ */
.panel{
  border-radius:var(--r-lg);border:var(--sf-border);box-shadow:var(--sf-rest);
  padding:var(--s5);margin-bottom:var(--s5);
}
.panel > .panel,.panel .card,.panel .tbl-wrap{
  border:none;box-shadow:none;padding-inline:0;background:transparent;
}
.panel > .panel:last-child,.panel .tbl-wrap:last-child{margin-bottom:0}
.panel h2{font-size:15.5px;margin-bottom:var(--s4)}
@media(max-width:600px){
  .panel{padding:var(--s4);border-radius:var(--r-md)}
}
.card{border-radius:var(--r-lg);border:var(--sf-border);box-shadow:var(--sf-rest);padding:var(--s5)}
.tbl th{padding:10px var(--s4)}
.tbl td{padding:12px var(--s4)}

/* ==================================================== 14. CHECKOUT =========
   Two columns on a desktop, one on a phone, with the order summary FIRST on
   the phone: a buyer needs to see what they are paying for, and the total,
   before the fields — not after them.

   Nothing in this section changes a price, a payment method or an order. It is
   the same form; checkout_start_v2() renders it, and the branch that creates
   the order is untouched.
   ------------------------------------------------------------------------ */
.co-h1{font-size:clamp(24px,4vw,30px);letter-spacing:-.02em;margin:0 0 var(--s5)}
.co-layout{
  display:grid;grid-template-columns:minmax(0,1fr) 360px;
  gap:var(--s8);align-items:start;margin-top:var(--s6);
}
.co-main{min-width:0}

/* ------- the summary ------- */
/* --hdr-h is the PORTAL bar (56px). Checkout renders the storefront header,
   which is 124px on a desktop and 118px on a phone, so the order summary was
   sticking 48px too high and its total slid under the header on every scroll.
   Above 900px only — below that the summary is static and sits above the
   fields (see the step down). */
.co-summary{position:sticky;top:calc(var(--hdr-total) + var(--s5));min-width:0;margin:0}
.co-summary h2{font-size:14px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--muted);margin-bottom:var(--s4)}
.co-item{
  display:grid;grid-template-columns:minmax(0,1fr) auto;
  gap:2px var(--s3);padding:var(--s3) 0;border-bottom:1px solid var(--line);
}
.co-item:first-of-type{padding-top:0}
.co-item-t{grid-column:1;display:flex;align-items:baseline;gap:var(--s2);flex-wrap:wrap;min-width:0}
.co-item-t strong{font-size:14.5px;overflow-wrap:anywhere}
.co-item-m{grid-column:1;font-size:12.5px;color:var(--dim);line-height:1.5}
.co-item-p{grid-column:2;grid-row:1/3;align-self:center;
  font-weight:600;white-space:nowrap;font-variant-numeric:tabular-nums}

.co-line{display:flex;justify-content:space-between;gap:var(--s3);
  font-size:13.5px;padding:var(--s2) 0;color:var(--muted)}
.co-line > span:last-child{white-space:nowrap;font-variant-numeric:tabular-nums}
.co-line-save,.co-line-save > span:last-child{color:var(--green)}

.co-total{
  display:flex;align-items:baseline;justify-content:space-between;gap:var(--s3);
  padding-top:var(--s3);margin-top:var(--s2);border-top:1px solid var(--line);
  font-size:19px;font-weight:700;
}
.co-total > span:last-child{white-space:nowrap;font-variant-numeric:tabular-nums}
/* The currency code, so "$849" is never ambiguous to an international buyer. */
.co-total small{font-size:12px;font-weight:600;color:var(--muted);margin-left:2px}

.co-alt{
  margin-top:var(--s4);padding:var(--s3) var(--s4);
  border:1px dashed var(--line2);border-radius:var(--r-md);
}
.co-alt .co-line{padding:0;color:var(--green);font-weight:600;font-size:14px}
.co-alt p{margin:var(--s2) 0 0;font-size:12.5px;color:var(--muted);line-height:1.5}
.co-note{margin:var(--s4) 0 0;font-size:12.5px;color:var(--dim);line-height:1.55}
.co-note-c{text-align:center;margin-top:var(--s3)}

/* ------- the form ------- */
.co-block{border:0;padding:0;margin:0 0 var(--s6);min-width:0}
.co-legend,.co-block > h2{
  display:block;padding:0;margin:0 0 var(--s3);
  font-size:16px;font-weight:700;letter-spacing:-.01em;color:var(--txt);
}
.co-hint{margin-bottom:var(--s4)}
.co-billed{font-size:14px;color:var(--muted);margin:0;line-height:1.6}

/* Payment methods. One radio group; the whole card is the target, so the
   native radio is hidden rather than merely small — a 20px radio next to a
   tappable card gives a phone two targets for one choice. The card itself is
   well over 44px, and :focus-within keeps the keyboard ring. */
.pm-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s3)}
.pm{
  display:block;position:relative;cursor:pointer;min-width:0;
  min-height:var(--ctl-h);
  padding:var(--s4);border:var(--sf-border);border-radius:var(--r-md);
  background:var(--bg);transition:border-color .15s,background .15s;
}
.pm input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
.pm-body{display:block;min-width:0}
.pm-head{display:flex;align-items:center;gap:var(--s3);margin-bottom:var(--s2)}
.pm-head .icon{width:26px;height:26px;flex:0 0 26px;margin:0;color:var(--acc)}
.pm-head strong{font-size:15px}
.pm-amt{margin-left:auto;font-weight:700;white-space:nowrap;font-variant-numeric:tabular-nums}
.pm-desc{display:block;font-size:13px;color:var(--muted);line-height:1.55}
.pm-speed{display:block;margin-top:var(--s2);font-size:12px;color:var(--dim)}
.pm:hover{border-color:var(--line2)}
.pm:has(input:checked){border-color:var(--acc);background:var(--acc-dim);
  box-shadow:0 0 0 1px var(--acc) inset}
.pm:focus-within{outline:2px solid var(--acc);outline-offset:2px}

.co-terms{margin:var(--s5) 0 var(--s5)}
.co-submit{margin-top:var(--s2)}
.co-assure{margin-top:var(--s6)}

/* ------- the step down ------- */
@media(max-width:900px){
  .co-layout{grid-template-columns:minmax(0,1fr);gap:var(--s6)}
  /* Summary above the fields, and no longer sticky: a sticky panel in a
     single-column flow pins itself over the form the buyer is filling in. */
  .co-summary{order:-1;position:static}
}

/* Payment status. Pending, failed and paid must not look alike — the classic
   pages said all three in body text of the same colour. */
.pay-state{
  display:flex;gap:var(--s3);align-items:flex-start;
  padding:var(--s4);border-radius:var(--r-md);margin-bottom:var(--s5);
  font-size:14px;line-height:1.55;
}
.pay-state .ico{width:20px;height:20px;flex:0 0 20px;margin-top:1px}
.pay-state.pending{background:rgba(232,178,62,.09);border:1px solid rgba(232,178,62,.35);color:var(--amber)}
.pay-state.failed {background:rgba(240,101,95,.09);border:1px solid rgba(240,101,95,.38);color:var(--red)}
.pay-state.paid   {background:rgba(62,207,142,.09);border:1px solid rgba(62,207,142,.35);color:var(--green)}
.pay-state strong{display:block;color:inherit;font-size:14.5px;margin-bottom:2px}
.pay-state p{margin:0;color:var(--muted)}

/* ==================================================== 15. PREVIEW BAR ======
   ------------------------------------------------------------------------ */
.ui2-bar{background:#1d2739;color:#eaf0fb}
.ui2-bar a{color:#a8c7ff}

/* ==================================================== 16. FLASH MESSAGES ===
   Dismissible, with the close control on its own 44px target rather than as a
   character glued to the end of the sentence.
   ------------------------------------------------------------------------ */
.flash-x{display:flex;align-items:flex-start;gap:var(--s3);padding-right:var(--s2)}
.flash-x > span{flex:1;min-width:0}
.flash-close{
  flex:0 0 auto;
  width:var(--ctl-h);height:var(--ctl-h);
  display:inline-flex;align-items:center;justify-content:center;
  margin:-8px -4px -8px 0;
  border:none;background:transparent;color:inherit;
  font-size:22px;line-height:1;cursor:pointer;border-radius:var(--r-sm);opacity:.7;
}
.flash-close:hover{opacity:1;background:rgba(16,24,40,.06)}

/* ==================================================== 17. PORTAL FOOTER ====
   The full five-column marketing footer under a licence page is noise. The
   legal text and the support address stay — they are the two things a customer
   in the portal might actually need — and the shop columns collapse.
   ------------------------------------------------------------------------ */
@media(max-width:900px){
  body.is-portal .foot-grid > div:not(:first-child):not(:last-child){display:none}
}
body.is-portal .site-foot{margin-top:var(--s8)}

/* ==================================================== 18. CASCADE FIXES ====
   Rules that lost to a more specific selector in an earlier sheet. Each was
   found by auditing every ui.css declaration against site.css and foodmart.css
   for the same subject element and property, rather than by looking at a page
   — this codebase has three recorded cases of a correct-looking rule being
   silently outranked (a white button on a white card, dark-navy text on a dark
   scrim, a nav CTA taking --muted). The audit script is in the scratchpad and
   is worth re-running after any further CSS work.
   ------------------------------------------------------------------------ */

/* foodmart.css `:root[data-theme="light"] .pcard-thumb` (0,3,0) outranks a bare
   `.pcard-thumb` (0,1,0), so the design system's well never applied. Matched. */
:root[data-theme="light"] .pcard-thumb,
.pcard-thumb{background:#f4f6fb}

/* `.mkt-search .in{font-size:13.5px}` (0,2,0) outranked the 16px field floor.
   Below 900px site.css restates 16px, so this only bit between 901 and 1080px —
   which is a small tablet in portrait, where iOS still zooms. */
.mkt-search .in,
.hdr-search .hdr-search-in{font-size:var(--field-fs)}

/* `.hero .two-col` (0,2,0) keeps its deliberate 1.08/0.92 ratio, but it needs
   the same zero floor as every other track or a long headline widens it. */
.hero .two-col{grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr)}
@media(max-width:860px){.hero .two-col{grid-template-columns:minmax(0,1fr)}}

/* `.chglog .rel pre` (0,2,1) already wraps; this adds the break opportunities a
   long unbroken token needs, at matching specificity so it cannot be lost. */
.chglog .rel pre{overflow-wrap:anywhere;word-break:normal}

/* `.btn{white-space:nowrap}` is right for a pill in a row and wrong for a
   full-width button in a 1fr grid track: a label that cannot wrap is an
   unshrinkable min-content contribution, which is the same mechanism as §2.
   Only block buttons are released. */
.btn.btn-block,.pcard-cta .btn,.dl-card .btn,.mkt-sheet-foot .btn{
  white-space:normal;
}

/* ============================================ 12. HERO EA ROTATOR ==========
   The logo badge (z-index 2) is centred over the screenshot and, on a phone,
   reaches up to the platform chip, which had no z-index — so the badge
   painted over "MT5 · BREAKOUT". The chip is a label on the artwork and goes
   on top of it. */
.ea-slide-type{z-index:3}

/* The dots were absolutely positioned 12px from the rotator's bottom with no
   room reserved, so they sat on the last line of the description. The slide
   body now keeps a strip for them. The dots were also 8x8px targets: the
   button becomes 24px and the visible dot is drawn inside it. */
.ea-slide-body{padding-bottom:calc(18px + 24px)}
.ea-dots{bottom:10px;right:12px;gap:0}
.ea-dot,.ea-dot.on{
  position:relative;width:24px;height:24px;
  background:transparent;border-radius:var(--r-pill);
}
.ea-dot::before{
  content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:8px;height:8px;border-radius:99px;
  background:rgba(16,24,40,.22);transition:background .2s,width .2s;
}
.ea-dot.on{width:32px}
.ea-dot.on::before{width:20px;background:var(--acc)}
.ea-dot:focus-visible{outline:2px solid var(--acc);outline-offset:1px}

/* ============================================ 13. FOOTER LINKS =============
   site.css's tap-target pass made `.site-foot a` inline-block below 940px to
   add height. That also let the links flow side by side, so every footer
   column read as one unseparated sentence ("About Contact Support Client
   Login"). And the same broad selector turned the "risk disclosure" link in
   the middle of the risk-warning sentence into a block on desktop.
   Column links: one per line, tall enough to tap, but only as wide as their
   text so the empty space beside a link is not a tap target.
   Links inside running text: inline. */
.foot-grid a{display:block;width:fit-content;max-width:100%}
@media(max-width:940px){.foot-grid a{padding-block:7px}}
.site-foot p a{display:inline;padding:0}

/* ============================================ 14. TABLE LABELS =============
   site.css drops `table.tbl{min-width:600px}` to 0 on phones so tables shrink
   rather than force 600px — right, but with no floor at all the browser
   squeezed short label cells to ~35px: on /vps/* "Forex VPS Pro" and "You are
   here" wrapped one word per line. Labels stay whole; prose cells still wrap.
   If that makes a table wider than the phone, .tbl-wrap scrolls it inside its
   own box, so the page itself never scrolls sideways. */
.tbl td strong,.tbl .badge,.tbl td > a:only-child{white-space:nowrap}

/* ============================================ 15. LONG TITLES ==============
   Stress-tested with a 40-character unbroken token in a product name: the
   product page title measured 516px on a 390px phone. .pdp-head is a column
   flexbox with align-items:flex-start, so the title is sized to fit-content,
   and `break-word` (§10) does not lower an element's min-content width —
   the token stayed one line. `anywhere` does. It still breaks only a word
   that would otherwise overflow, so ordinary headings render identically. */
h1,h2,h3,h4,.pdp-title,.ea-slide-name,.market-name,.rel-name,.buybar-name{overflow-wrap:anywhere}
.pdp-head > *{max-width:100%;min-width:0}

/* ==========================================================================
   MOBILE PASS — 2026-09-20
   ==========================================================================
   Everything below fixes a defect that was reproduced in a browser at
   320/360/375/390/393/414/430/768px before it was written, and re-measured
   after. Each block says what was broken and why the fix is where it is.
   ========================================================================== */

/* ============================================ 19. THE MOBILE NAV PANEL ====
   Defect (P0): tapping the burger did nothing. The JS was never at fault —
   it set `.nav.open`, `aria-expanded="true"` and `body.nav-open` exactly as
   written. §4 hid `.cat-bar`, the panel's grandparent, so the panel resolved
   to a 0x0 box that no amount of z-index could rescue, and `body{overflow:
   hidden}` still fired: the only observable effect of the burger was that the
   page stopped scrolling. §4 now uses `display:contents`, which leaves `.nav`
   in the flow as a child of the sticky `.site-head`.

   The panel is `position:fixed` rather than absolutely positioned inside the
   header: a sticky ancestor that later gains a transform (a theme animation,
   a modal) would turn an absolute panel into a clipped one, and fixed
   positioning also lets the panel own its own scrollbar under a short
   viewport without stretching the header.
   ------------------------------------------------------------------------ */
@media(max-width:1080px){
  .nav-toggle{display:block}

  .nav{
    display:none;
    position:fixed;
    top:var(--hdr-total);
    left:0; right:0;
    max-height:calc(100vh - var(--hdr-total));
    max-height:calc(100dvh - var(--hdr-total));
    overflow-y:auto; -webkit-overflow-scrolling:touch; overscroll-behavior:contain;
    flex-direction:column; align-items:stretch;
    gap:2px;
    margin:0;
    padding:var(--s3) var(--sp-gutter)
            calc(var(--s5) + env(safe-area-inset-bottom,0px));
    padding-left:calc(var(--sp-gutter) + env(safe-area-inset-left,0px));
    padding-right:calc(var(--sp-gutter) + env(safe-area-inset-right,0px));
    background:var(--bg2);
    border-top:1px solid var(--line);
    border-bottom:1px solid var(--line);
    box-shadow:0 24px 44px -18px rgba(0,0,0,.55);
    /* Above the floating controls (100) and the docked bars (90/95); below
       the sheets and the lightbox (200+), which are modal over everything. */
    z-index:130;
  }
  .nav.open{display:flex}

  /* Large, obviously tappable rows rather than a squeezed desktop bar. */
  .nav > a,
  .nav-dd-trig{
    min-height:var(--ctl-h);
    display:flex; align-items:center;
    padding:var(--s3) var(--s2);
    font-size:15.5px;
    border-radius:var(--r-sm);
  }
  .nav > a + a,
  .nav-dd + .nav-dd,
  .nav-dd + a,
  .nav > a + .nav-dd{border-top:1px solid var(--line)}
  .nav-dd{display:flex;flex-direction:column;min-width:0}
  .nav-dd-trig{justify-content:space-between;font-weight:600}
  .nav-dd-trig .ico{margin-left:auto}
  .nav-dd-panel{
    position:static;transform:none;opacity:1;visibility:visible;
    min-width:0;background:none;border:none;box-shadow:none;
    padding:0 0 var(--s2) var(--s3);gap:var(--s2);
  }
  .nav-dd-panel a{min-height:var(--ctl-h-sm);display:flex;align-items:center}
  .nav .btn,.nav > a.btn{margin:var(--s3) 0 var(--s1);min-height:var(--ctl-h);justify-content:center}

  /* Tapping the page behind the panel closes it (site.js). The backdrop makes
     that discoverable and stops a stray tap reaching a link underneath. It
     starts BELOW the header so the × stays reachable — a full-viewport
     backdrop over a z-50 header would have swallowed the close button. */
  body.nav-open::before{
    content:"";
    position:fixed;
    top:var(--hdr-total); left:0; right:0; bottom:0;
    background:rgba(4,7,14,.55);
    z-index:120;
  }
  /* .site-head is position:sticky with z-index:50, so it OPENS A STACKING
     CONTEXT: the panel z-index:130 is scored against its siblings inside the
     header, not against the page, and the whole header — panel included —
     painted at 50, i.e. underneath the z-120 backdrop. Measured: the first
     menu link was the visible element but elementFromPoint returned the
     backdrop, so every tap would have closed the menu instead of following
     the link. The header itself is raised while the panel is open. */
  body.nav-open .site-head{z-index:130}
}

/* Above 1080 the bar is back and the panel must not linger in any state. */
@media(min-width:1081px){
  .nav{display:flex;position:static;max-height:none;overflow:visible;
       background:none;border:0;box-shadow:none;padding:0;z-index:auto}
  body.nav-open::before{display:none}
  body.nav-open{overflow:visible}
}

/* The burger's two glyphs swap on state; the button is a real 44px target. */
.nav-toggle{min-width:var(--ctl-h);min-height:var(--ctl-h);
  display:none;align-items:center;justify-content:center}
@media(max-width:1080px){.nav-toggle{display:flex}}

/* ======================================== 20. THE PRODUCT CARD ON A PHONE ==
   Defect (P1): measured on /products at 390px, one card was 666px tall — 83%
   of the viewport for a single product, 10,004px for the shelf of fifteen.
   The card is a desktop card (4:3 image well, then everything under it in one
   column) narrowed to one column, which is exactly what the brief calls
   squeezing rather than restructuring.

   On a phone it becomes a list row: a square thumbnail beside the eyeline,
   name and seller/risk line, then the description, price and the two actions
   across the full width. That is the shape every mobile marketplace uses,
   and it is the one shape in which the thumbnail is not competing with the
   price for the fold.

   `display:contents` on .pcard-body promotes its children to grid items of
   .pcard so the thumbnail can sit beside the first three of them and the rest
   can span — without adding a wrapper element to a component that eight
   surfaces render.

   The spec list is the one thing dropped. It is a browsing aid that repeats
   the platform already in the eyeline; the rail's compact card has always
   dropped it, and every item the brief lists as required (image, category,
   title, seller, risk, price, was-price, discount, View EA, Add to cart) is
   still here.
   ------------------------------------------------------------------------ */
@media(max-width:600px){
  .pcard:not(.pcard-compact){
    display:grid;
    grid-template-columns:clamp(84px,26vw,112px) minmax(0,1fr);
    /* The slack between a square thumbnail and three short lines of text
       lands in the meta row, where the badge centres against it, instead of
       being split into three arbitrary gaps. */
    grid-template-rows:auto auto 1fr auto auto;
    grid-template-areas:
      "thumb eye"
      "thumb name"
      "thumb meta"
      "desc  desc"
      "foot  foot";
    column-gap:var(--s3);
    padding:var(--s3);
    align-content:start;
  }
  .pcard:not(.pcard-compact) .pcard-body{display:contents}
  .pcard:not(.pcard-compact) .pcard-thumb{
    grid-area:thumb;
    aspect-ratio:1;
    width:100%;
    margin:0;
    padding:var(--s2);
    align-self:start;
  }
  .pcard:not(.pcard-compact) .pcard-eye {grid-area:eye;align-self:end}
  .pcard:not(.pcard-compact) .pcard-name{grid-area:name;font-size:15.5px;min-height:0}
  .pcard:not(.pcard-compact) .pcard-meta{grid-area:meta;align-self:center;margin-top:var(--s1);
                                         display:flex;flex-wrap:wrap;align-items:center}
  /* A grid item blockifies, and Chrome resolves the base display:-webkit-box
     to flow-root there, silently dropping -webkit-line-clamp with it. Two
     lines are re-imposed by height, which does not depend on the legacy box. */
  .pcard:not(.pcard-compact) .pcard-desc{grid-area:desc;margin-top:var(--s2);
    font-size:13px;line-height:1.5;min-height:0;max-height:3em;overflow:hidden}
  .pcard:not(.pcard-compact) .pcard-specs{display:none}
  .pcard:not(.pcard-compact) .pcard-foot{grid-area:foot;margin-top:0;padding-top:var(--s3)}
  .pcard:not(.pcard-compact) .pcard-price{margin-bottom:var(--s2)}
  .pcard:not(.pcard-compact) .pcard-now{font-size:19px}

  /* Two actions side by side rather than two full-width stacked bars: the
     same two controls, 46px of card height cheaper, and both still above the
     44px target at 320px. */
  .pcard:not(.pcard-compact) .pcard-cta{grid-template-columns:1fr 1fr;align-items:stretch}
  .pcard:not(.pcard-compact) .pcard-cta > *{min-width:0}
  .pcard:not(.pcard-compact) .pcard-cta .btn{
    min-height:var(--ctl-h);padding-left:var(--s2);padding-right:var(--s2);
    font-size:13.5px;white-space:nowrap;
  }
  .pcard:not(.pcard-compact) .pcard-cta form .btn{min-height:var(--ctl-h)}
}

/* ============================================ 21. THE RAILS ARE CAROUSELS ==
   Defect (P1): a rail track is `grid-auto-columns:minmax(230px,78vw)` inside
   an overflow-x box. With eight cards there is never free space, so every
   track resolves to its 230px MINIMUM — on a 390px phone that is 1.6 cards,
   i.e. the reported "second product cut in half", and it reads as a broken
   grid rather than as a shelf you can push.

   These are deliberate carousels (they have a heading, a "Shop all" link and
   arrow buttons on desktop), so they stay horizontal — but one card at a
   time with an unambiguous peek, and they snap, so the half card is now
   obviously the next one rather than a clipped one.
   ------------------------------------------------------------------------ */
@media(max-width:900px){
  .rail-track{
    grid-auto-columns:min(78vw,290px);
    scroll-snap-type:x proximity;
    scroll-padding-left:4px;
    gap:var(--s3);
  }
  .rail-track > *{scroll-snap-align:start}
  /* A shelf card is a shelf card: no description, no spec list, one action. */
  .pcard-compact{padding:var(--s3)}
  .pcard-compact .pcard-thumb{aspect-ratio:16/11;margin-bottom:var(--s2)}
  .pcard-compact .pcard-body{padding:0}
  .pcard-compact .pcard-name{font-size:14.5px}
  .pcard-compact .pcard-foot{padding-top:var(--s2)}
  .pcard-compact .pcard-price{margin-bottom:var(--s2)}
  .pcard-compact .pcard-now{font-size:17px}
  .pcard-compact .pcard-cta .btn{min-height:var(--ctl-h)}
}

/* ------- /products renders the MQL5-style tile, not the full card -------
   site.css scopes a separate design to `.pgrid`: a centred square tile with
   the eyeline, description, risk badge and seller line all `display:none`,
   because MQL5's own grid shows name, rating and price only. That is a
   reasonable desktop grid of 150-190px tiles — but below 600px §20 turns the
   card into a full-width list row, where the same suppression leaves a 358px
   row carrying a name and a price and two-thirds empty, and it removes four
   of the ten things this card is required to show.

   So the tile's suppressions are lifted for the phone layout only. Equal
   specificity to site.css's rules and later in the cascade; the desktop and
   tablet tile grid is untouched. */
@media(max-width:600px){
  .pgrid .pcard{text-align:left}
  .pgrid .pcard-eye{display:block}
  .pgrid .pcard-desc{display:-webkit-box}
  .pgrid .pcard-meta{justify-content:flex-start;margin-top:var(--s1)}
  .pgrid .pcard-meta .badge{display:inline-flex}
  .pgrid .pcard-meta > small:first-child{display:block}
  .pgrid .pcard-name{font-size:15.5px;-webkit-line-clamp:2}
  .pgrid .pcard-thumb{border-radius:var(--r-md)}
  .pgrid .pcard-thumb img.pcard-logo{border-radius:var(--r-sm)}
  .pgrid .pcard-price{justify-content:flex-start;margin-bottom:var(--s2)}
  .pgrid .pcard-now{font-size:19px}
  .pgrid .pcard-was{font-size:13px}
  .pgrid .pcard-off{font-size:11px;padding:2px 8px}
  .pgrid .pcard-foot{padding-top:var(--s3)}
  .pgrid .pcard-cta .btn{padding:7px var(--s2);font-size:13.5px;min-height:var(--ctl-h)}
}

/* The tile grid is designed around a 150-190px square. Two columns on a
   768px tablet makes each tile 351px, i.e. a 351px image well and a 659px
   card — the same "desktop card squeezed into fewer columns" defect the
   phone had, one breakpoint up. Three columns puts the tile back at its own
   size. Desktop (>=901px) keeps its three columns unchanged. */
@media(min-width:601px) and (max-width:900px){.pgrid{--cols:3}}

/* ======================================= 22. VERTICAL RHYTHM ON A PHONE ===
   Defect (P2): the homepage measured 16,019px at 390px — 20 viewports, of
   which roughly 1,300px was section padding alone. site.css had already
   halved the desktop rhythm for phones (`.section{padding:34px 0}` at 600px)
   but foodmart.css loads after it and restates `.section{padding:60px 0}` at
   760px, so the phone rule had been dead since the theme port. Everything
   here is the phone rhythm re-asserted from the last layer, plus the blocks
   whose own height was set for a desktop column.
   ------------------------------------------------------------------------ */
/* 900, not 760: foodmart.css steps the rhythm down at 760, which leaves a
   768px tablet on the full 96px desktop padding — the one required test
   width that sat between the two rules. */
@media(max-width:900px){
  .section{padding:44px 0}
  .section-head{margin-bottom:26px}
  .rail{margin-bottom:30px}
  .hero{padding:44px 0 40px}
}
@media(max-width:600px){
  .section{padding:34px 0}
  .section-head{margin-bottom:22px}
  .section-head p{margin-top:10px;font-size:15px}
  .cta-band{padding:24px 18px!important}
  .cta-band p{margin-bottom:18px}
}

/* ------- hero -------
   1,175px of hero on a 800px viewport: the visitor could not see the headline
   and the primary action at the same time. The type scale is untouched (the
   brief asks for hierarchy, not smaller text) — this is padding, the gap
   under the lead, and the stats block. */
@media(max-width:600px){
  .hero{padding:30px 0 28px}
  .hero h1{max-width:none}
  .hero p.lead{margin:14px 0 20px;font-size:16px}
  .hero-cta{gap:var(--s2)}
  .hero-cta .btn-lg{min-height:50px;padding:13px 20px}
  .hero-note{gap:var(--s2) var(--s4);margin-top:var(--s4)}
  .two-col{gap:var(--s6)}
}

/* ------- the five hero statistics -------
   A flex row with a 26px gap and five items on a 358px line wrapped into a
   ragged 2/2/1 with the orphan hanging under a gap. An explicit track grid
   makes it a deliberate block: three up on a normal phone, two at 320px,
   and every figure gets the same width. */
.hero-stats{gap:var(--s4) var(--s5)}
@media(max-width:600px){
  .hero-stats{
    display:grid;
    grid-template-columns:repeat(auto-fit,minmax(96px,1fr));
    gap:var(--s3) var(--s2);
    margin-top:var(--s5);
    padding-top:var(--s4);
    border-top:1px solid var(--line);
  }
  .hero-stats > div{min-width:0}
  .hero-stats b{font-size:21px}
  .hero-stats span{font-size:11.5px;line-height:1.3}
}

/* ------- category tiles -------
   Full-width 88px rows for an icon, a name and an EA count: seven of them
   filled three-quarters of the viewport to carry twenty-one words. Two up
   and shorter. The tile keeps a 60px+ tap target. */
@media(max-width:600px){
  .cat-tiles{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:var(--s2)}
  .cat-tile{padding:var(--s3) var(--s3);gap:var(--s2);min-height:62px}
  .cat-tile-ico{width:34px;height:34px;border-radius:9px}
  .cat-tile-ico .ico{width:17px;height:17px}
  .cat-tile b{font-size:13.5px;line-height:1.25}
  .cat-tile small{font-size:11.5px}
}
/* The FoodMart disc variant of the same idea. */
@media(max-width:600px){
  .fm-cats{grid-template-columns:repeat(auto-fit,minmax(84px,1fr));gap:var(--s3)}
  .fm-cat-disc{width:62px;height:62px}
  .fm-cat-disc .ico{width:24px;height:24px}
}

/* ------- trust cues -------
   Four one-line claims, one per row, 264px. Two up. */
@media(max-width:600px){
  .trust-cues{--cols:2;gap:var(--s2) var(--s3)}
  .trust-strip{padding:var(--s4) 0}
  .trust-strip .wrap{gap:var(--s4)}
}

/* ------- footer -------
   1,909px of footer on a phone: the brand column plus four link columns, all
   stacked. The link columns go two up — they are short lists of short labels
   and each one is 158px wide at 360px, which is wider than any label in
   them. The brand column keeps the full width. */
@media(max-width:520px){
  .foot-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s5) var(--s4)}
  .foot-grid > *:first-child{grid-column:1 / -1}
  .site-foot{padding-top:36px}
}

/* ==================================== 23. THE FLOATING PAIR GETS OUT OF THE
   WAY WHILE YOU SCROLL ======================================================
   Measured at 390px: the WhatsApp button and back-to-top sit at x 326-376,
   and every full-width control on a phone runs to x 374 — so the pair was
   standing on "Add to cart" on the marketplace, on "View EA" in the rails,
   on the "MQL5 Verified Seller" link on the product page and on the footer
   links. Shrinking them (§ above) narrows the overlap but cannot remove it:
   a fixed button in the corner of a single-column layout is always over
   something.

   So they leave while the page is moving down and come back when it stops or
   reverses — the standard behaviour for a FAB over a scrolling list. They are
   out of the way exactly while the reader is passing content, and present
   whenever the reader has stopped to act. Only on phones: on a desktop the
   layout is centred with empty margins and nothing is underneath them.
   ------------------------------------------------------------------------ */
@media(max-width:900px){
  .wa-fab,.fm-top{
    transition:transform .24s ease,opacity .2s ease,bottom .2s ease,
               visibility .2s,color .18s,border-color .18s,background .18s;
  }
  body.fabs-away .wa-fab,
  body.fabs-away .fm-top{
    opacity:0;
    pointer-events:none;
    transform:translateY(calc(var(--fab-size) + var(--fab-gap) * 2));
  }
}
@media(prefers-reduced-motion:reduce){
  .wa-fab,.fm-top{transition-duration:.01s}
}

/* site.css caps the open panel at `100dvh - 64px` in two places, from when
   the header was one 64px row. It is `.nav.open` (0,2,0) against §19's
   `.nav` (0,1,0), so it won on specificity and the panel could run 54px
   past the bottom of the screen. Restated at matching specificity against
   the measured header. */
@media(max-width:1080px){
  .nav.open{
    max-height:calc(100vh - var(--hdr-total));
    max-height:calc(100dvh - var(--hdr-total));
    padding-bottom:calc(var(--s5) + env(safe-area-inset-bottom,0px));
  }
}

/* A link that exists only while the burger is the navigation. The header's
   own "Free tools" entry is .util-hide-sm and disappears at 760px, so the
   downloads library had no route on a phone at all; above the burger's
   breakpoint the header link is back and this one steps aside rather than
   being listed twice. */
.nav-only-mobile{display:none}
@media(max-width:1080px){.nav-only-mobile{display:flex}}

/* The submenu inside the burger is an accordion group, not a floating card.
   site.css gives .nav-dd-panel a background and an 18px drop shadow for the
   desktop hover flyout, and foodmart.css re-asserts the background per theme
   at `:root[data-theme="light"] .nav-dd-panel` (0,2,1) — which outranks a
   plain `.nav-dd-panel` reset (0,1,0). Measured at 390px: the panel was
   correctly in flow with zero overlap, but painted as a white card casting a
   shadow over the three links below it inside an already-white menu. Both
   themes restated at (0,3,1) so the group reads as indented list items. */
@media(max-width:1080px){
  .nav .nav-dd-panel,
  :root[data-theme="light"] .nav .nav-dd-panel,
  :root[data-theme="dark"]  .nav .nav-dd-panel{
    background:none;
    border:0;
    box-shadow:none;
    border-radius:0;
  }
  /* A hairline instead, so the group still reads as nested. */
  .nav .nav-dd-panel{border-left:2px solid var(--line);margin-left:var(--s2)}
}
