/* =============================================================================
   LONDON SPOT: product.css                                    [product unit]
   The product page: gallery and lightbox, title, buy box, assurances, the
   detail disclosure, the related rail and the phone call bar.
   -----------------------------------------------------------------------------
   Load order:  base.css -> layout.css -> components.css -> product.css
   Reuses .btn .iconbtn .badge .accordion .sechead .rail .empty .link .price
   .num .nb and the missing-photo device from the shared sheets and restyles
   none of them. Mobile first: every wider rule is a min-width query on the
   ladder (480, 768, 1024). Every value is a base.css token or a dimension
   design.md names (the 400px side column of §14.3, the 56 to 72px
   thumbnails and the 600px well of §15.1, the 8% inset of §15.2).
   Layers: the call bar is --z-bar; the lightbox is a native <dialog> in the
   top layer and takes no rung.

      1  Page grid
      2  Gallery
      3  Lightbox
      4  Title block
      5  Buy box
      6  Assurances
      7  Details
      8  Related rail
      9  Call bar
     10  Loading
     11  Not found and load error
     12  Reduced motion
   ========================================================================== */


/* -- 1. PAGE GRID ----------------------------------------------------------
   The phone's stacking order is the DOM order and the order a shopper asks
   the questions in: the photo, the name, what it costs and in what
   condition, how to get it, then the details and the neighbours.

   From 1024px the side column takes the title, the buy box and the
   assurances together (design.md §14.3: minmax(0,1fr) 400px, gap 40 by 48).
   Its h1 stands directly under the crumb row, as every page's h1 does
   (design.md §14.6), and the gallery starts on the same line: the 24px the
   grid carried put the call below 768px under a long title (verify-r1 V7).
   Below 1024px the page is one column, and the buy box stops at that same
   400px, so the call button is a button and not a 670px bar (audit P11);
   from 768px the assurances stand beside it rather than under it, on the
   row it used to leave empty (verify-r1 V6).

   The grid has no padding of its own at any width: its first block, the
   gallery below 1024px, stands directly under the crumb row as a page's
   head does (design.md §14.6), 21px under the crumb's words like the 404's
   mark; it stood 16px lower on phones and 24px lower at 768 (review P3).
   Nor at the end: the related rail's own margin and the footer's
   --gap-page-end are the rhythm under it (design.md §14.5).
   ---------------------------------------------------------------------- */
.pdp{
  --pdp-side: 400px;
  display:grid;
  grid-template-columns:minmax(0,1fr);
  gap:var(--s-6);
}

.pdp__side{
  display:flex; flex-direction:column; gap:var(--s-5);
  min-width:0;
}
.pdp__buy,
.assure{ width:100%; max-width:var(--pdp-side) }

@media (min-width:768px) and (max-width:1023.98px){
  .pdp__side{
    display:grid;
    grid-template-columns:var(--pdp-side) minmax(0,1fr);
    gap:var(--s-5) var(--s-6);
    align-items:start;
  }
  .pdp__head{ grid-column:1 / -1 }
}

/* The side column spans both rows. The second row is the flexible one, so
   whatever the side column needs beyond the gallery goes under the details
   and never into the gallery's row: the details start --s-10 under the
   gallery whatever the side column holds, and while the skeleton stands
   they stood about 200px lower and jumped up into the first screen on
   render (CLS 0.052 at 1024x768 in English). */
@media (min-width:1024px){
  .pdp{
    grid-template-columns:minmax(0,1fr) var(--pdp-side);
    grid-template-rows:auto 1fr;
    column-gap:var(--s-12);
    row-gap:var(--s-10);
    align-items:start;
  }
  .pdp__gallery{ grid-column:1; grid-row:1 }
  .pdp__detail { grid-column:1; grid-row:2 }
  .pdp__side   { grid-column:2; grid-row:1 / span 2 }
}

.pdp__gallery,
.pdp__detail{ min-width:0 }


/* -- 2. GALLERY --------------------------------------------------------------
   design.md §15.1 to §15.3. The photograph sits on the white well, contained
   and never cropped, inset by 8% of the well (at least --s-2; the wells
   from 768px cap it at --s-10, which is 8% of their height).

     below 768   a square well, the thumbnail strip under it (56px), a swipe
                 and a "3 / 23" counter on the photo
     768-1023    a square well up to half the screen's height (design.md
                 §15.1: 512px at 768x1024, it was a 720x512 slab), the
                 thumbnails in a column beside it (72px), capped at the
                 well's height, the last one a "+N" tile (product.js
                 fitColumn), and no column for one photo
     from 1024   the same column beside a well min(62svh, 600px) tall

   product.js mainSizes() writes the photograph's `sizes` from these
   dimensions (design.md §15.4); a change here changes it there.

   The well's photo states are data-photo, set by core.js's image listeners
   exactly as on a card: wait (the plain --page-2 well, busy), ok (the
   photograph on --paper), none (the missing-photo device). The zoom disc
   only shows once there is a photograph (audits P15, P16).
   ---------------------------------------------------------------------- */
.pgal{
  --pgal-inset: clamp(var(--s-2), 8%, var(--s-10));
  display:flex; flex-direction:column; gap:var(--s-3);
  min-width:0;
}

.pgal__main{
  position:relative;
  /* its layers (the crossfade 1, the badges, count and zoom disc 2) stay
     inside the well (design.md §4 "Layers") */
  isolation:isolate;
  display:grid;
  grid-template-rows:minmax(0,1fr);
  grid-template-columns:minmax(0,1fr);
  place-items:center;
  width:100%;
  aspect-ratio:1;
  padding:var(--pgal-inset);
  background:var(--paper);
  border:var(--bw-1) solid var(--bd);
  border-radius:var(--r);
  overflow:hidden;
  /* a sideways swipe steps the photograph; the page still scrolls and
     pinches */
  touch-action:pan-y pinch-zoom;
  transition:border-color var(--d-hover) var(--ease), background-color var(--d-hover) var(--ease);
}
.pgal__main[data-photo="wait"],
.pgal__main[data-photo="none"]{ background:var(--page-2) }
button.pgal__main{ cursor:zoom-in }
button.pgal__main:disabled{ cursor:default }
@media (hover:hover){
  button.pgal__main:not(:disabled):hover{ border-color:var(--bd-2) }
}

@media (min-width:768px){
  /* a square well up to half the screen's height (design.md §15.1): the
     width gives way, so the ratio holds */
  .pgal__main{ width:min(100%, 50svh) }
}
@media (min-width:1024px){
  .pgal__main{ width:100%; aspect-ratio:auto; height:min(62svh, 600px) }
}

.pgal__img{
  grid-area:1 / 1;
  width:100%; height:100%;
  object-fit:contain; object-position:center;
}
.pgal__main[data-photo="none"] .pgal__img{ display:none }
.pgal__main[data-photo="ok"] .nophoto{ display:none }
/* a product with no photograph: the device fills the well */
.pgal__none{
  grid-area:1 / 1;
  display:grid; grid-template:minmax(0,1fr) / minmax(0,1fr);
  width:100%; height:100%; min-width:0; min-height:0;
}

/* The crossfade layer. product.js parks the OUTGOING photo here as
   --pgal-prev, writes the new src underneath once it has decoded, then drops
   .is-xfade and this fades out over --d-ui. It sits exactly on the image box
   (the well's inset) and under the badges and the zoom disc. It carries no
   photo until the first swap, so on the first paint it is an inert box. */
.pgal__main::after{
  content:""; position:absolute; z-index:1;
  inset:var(--pgal-inset);
  background-color:var(--paper);
  background-image:var(--pgal-prev, none);
  background-repeat:no-repeat;
  background-position:center;
  background-size:contain;
  opacity:0; pointer-events:none;
  transition:opacity var(--d-ui) var(--ease-out);
}
.pgal__main.is-xfade::after{ opacity:1; transition:none }

/* The condition badge (design.md §6 "Badge"), top-left, the card's own
   pills; a discount badge would follow it. */
.pgal__badges{
  position:absolute; z-index:2;
  inset-block-start:var(--s-3); inset-inline-start:var(--s-3);
  display:flex; flex-direction:column; align-items:flex-start; gap:var(--s-1);
  pointer-events:none;
}

/* The zoom disc: a permanent affordance on the photograph, the float circle
   of design.md §6 "Icon button". It is part of the well's button, not a
   control of its own. */
.pgal__zoom{
  position:absolute; z-index:2;
  inset-inline-end:var(--s-3); inset-block-end:var(--s-3);
  display:grid; place-items:center;
  width:44px; height:44px;
  border-radius:var(--r-pill);
  color:var(--ink-2);
  background:var(--paper);
  box-shadow:inset 0 0 0 var(--bw-1) var(--bd), var(--sh-float);
  transition:color var(--d-hover) var(--ease), background-color var(--d-hover) var(--ease);
}
.pgal__main:not([data-photo="ok"]) .pgal__zoom{ display:none }
@media (hover:hover){
  button.pgal__main:hover .pgal__zoom{ background:var(--page-2); color:var(--ink) }
}
button.pgal__main:active .pgal__zoom{ background:var(--bd) }

/* The position on a phone: "3 / 23", bottom-left, mirroring the zoom disc.
   A figure, so --t-xs and tabular. Not a control. */
.pgal__count{
  position:absolute; z-index:2;
  inset-inline-start:var(--s-3); inset-block-end:var(--s-3);
  display:inline-flex; align-items:center;
  height:24px; padding-inline:var(--s-2);
  border-radius:var(--r-pill);
  background:var(--paper);
  box-shadow:inset 0 0 0 var(--bw-1) var(--bd), var(--sh-float);
  font-size:var(--t-xs); line-height:1; font-weight:600;
  color:var(--ink-2);
}
@media (min-width:768px){ .pgal__count{ display:none } }

/* THE THUMBNAILS. A horizontal strip under the photograph below 768px: it
   scrolls on its own and runs under the screen's edge (one of the few
   things allowed to cross the gutter, design.md §14.2). The block padding
   is room for the focus ring inside the scroller and is given back by the
   negative margin, so the strip adds nothing to the rhythm. */
.pgal__thumbs{
  display:flex; gap:var(--s-2);
  overflow-x:auto; overscroll-behavior-x:contain;
  scroll-snap-type:x proximity;
  margin-inline:calc(var(--gut) * -1);
  padding-inline:var(--gut);
  scroll-padding-inline:var(--gut);
  padding-block:var(--s-1);
  margin-block:calc(var(--s-1) * -1);
  scrollbar-width:none; -ms-overflow-style:none;
}
.pgal__thumbs::-webkit-scrollbar{ display:none }

.pgal__thumb,
.pgal__more{
  position:relative; flex:none;
  display:grid; place-items:center;
  grid-template-rows:minmax(0,1fr);
  grid-template-columns:minmax(0,1fr);
  width:56px; height:56px;
  padding:var(--s-1);
  background:var(--page-2);
  border-radius:var(--r-sm);
  box-shadow:inset 0 0 0 var(--bw-1) var(--bd);
  scroll-snap-align:start;
  transition:box-shadow var(--d-hover) var(--ease), background-color var(--d-press) var(--ease-out);
}
button.pgal__thumb,
.pgal__more{ cursor:pointer }
.pgal__thumb.is-ok{ background:var(--paper) }
.pgal__thumb img{ width:100%; height:100%; object-fit:contain }
@media (hover:hover){
  button.pgal__thumb:hover,
  .pgal__more:hover{ box-shadow:inset 0 0 0 var(--bw-1) var(--bd-2) }
}
button.pgal__thumb:active,
.pgal__more:active{ background:var(--bd) }
/* the ring draws inside: the strip and the column both clip */
.pgal__thumb:focus-visible,
.pgal__more:focus-visible{ outline-offset:var(--ring-in) }
/* selected: a 2px ring, never a colour swap */
.pgal__thumb[aria-current="true"],
.pgal__more[aria-current="true"]{ box-shadow:inset 0 0 0 var(--bw-2) var(--slate-800) }
/* Focused and current at once, the focus ring stands inside the selection
   edge: drawn on it, it hid the one sign of the photograph on screen while
   the tile had focus (final accessibility check, 6). */
.pgal__thumb[aria-current="true"]:focus-visible,
.pgal__more[aria-current="true"]:focus-visible{ outline-offset:calc(var(--ring-in) - var(--bw-2)) }

/* "+N": the photos the column has no room for, in the lightbox. A figure. */
.pgal__more{
  font-size:var(--t-sm); line-height:var(--lh-ui); font-weight:600;
  color:var(--ink);
}
.pgal__more[hidden]{ display:none }

/* From 768px the thumbnails stand in a column beside the photograph, and
   only when there is more than one photograph (.pgal--strip, product.js):
   a single photograph starts on the content edge (audit P24). The column
   stretches to the grid row and, being size-contained, adds nothing to it,
   so it is exactly as tall as the well at every width; product.js shows as
   many as fit and turns the last slot into "+N" (audit P11: 23 photos made
   a 1,836px column). At 768px the strip under the well left 208px of empty
   row beside a 512px photograph and pushed the title 84px down. */
@media (min-width:768px){
  .pgal__thumb,
  .pgal__more{ width:72px; height:72px }
  .pgal--strip{
    display:grid;
    grid-template-columns:72px minmax(0,1fr);
    column-gap:var(--s-3);
    align-items:start;
  }
  .pgal--strip .pgal__main{ grid-column:2; grid-row:1 }
  .pgal--strip .pgal__thumbs{
    grid-column:1; grid-row:1;
    align-self:stretch;
    contain:size;
    flex-direction:column;
    overflow:hidden;
    margin:0; padding:0;
    scroll-padding:0;
    scroll-snap-type:none;
  }
}


/* -- 3. LIGHTBOX -------------------------------------------------------------
   design.md §6 "Lightbox". Native <dialog>.showModal(): top layer, focus
   trap, Escape and focus restore are the platform's.

   The stage is a grid of definite height, so the photograph is contained in
   both directions and the whole of it is inside the window at every width
   (from 1024px half of a portrait photograph used to sit below the window,
   audit P12). The arrows stand in cells of their own, never over the
   photograph: a row in the thumb's reach on phones, the stage's sides from
   768px (the photograph's swap animation painted over the previous arrow on
   phones, so a tap on it did nothing, audit P5). A swipe steps too.
   ---------------------------------------------------------------------- */
.lb{
  display:flex; flex-direction:column;
  width:100%; max-width:100%;
  height:100%; max-height:100%;
  margin:0; padding:0; border:0;
  background:transparent; color:var(--on-fill);
  overflow:hidden;
}
.lb::backdrop{ background:var(--scrim-media) }
.lb:not([open]){ display:none }

.lb__bar{
  flex:none;
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--s-3);
  padding-block:calc(var(--s-2) + env(safe-area-inset-top)) var(--s-2);
  padding-inline:max(var(--gut), env(safe-area-inset-left)) max(var(--gut), env(safe-area-inset-right));
}
.lb__n{ font-size:var(--t-sm); line-height:var(--lh-ui); font-weight:600; color:var(--on-dark) }

.lb__stage{
  flex:1 1 auto; min-height:0;
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  grid-template-rows:minmax(0,1fr) auto;
  grid-template-areas:"img img" "prev next";
  gap:var(--s-4) var(--s-6);
  padding-block:var(--s-2) calc(var(--s-4) + env(safe-area-inset-bottom));
  padding-inline:max(var(--gut), env(safe-area-inset-left)) max(var(--gut), env(safe-area-inset-right));
  touch-action:pan-y pinch-zoom;
}
.lb__img{
  grid-area:img;
  place-self:center;
  width:auto; height:auto;
  max-width:100%; max-height:100%;
  object-fit:contain;
  border-radius:var(--r-sm);
  background:var(--paper);
}
.lb__nav--p{ grid-area:prev; justify-self:end }
.lb__nav--n{ grid-area:next; justify-self:start }
.lb__nav[hidden]{ display:none }
.lb :focus-visible{ outline-color:var(--focus-2) }

@media (min-width:768px){
  .lb__stage{
    grid-template-columns:44px minmax(0,1fr) 44px;
    grid-template-rows:minmax(0,1fr);
    grid-template-areas:"prev img next";
    gap:var(--s-5);
    padding-block:var(--s-3) calc(var(--s-6) + env(safe-area-inset-bottom));
    padding-inline:max(var(--s-6), env(safe-area-inset-left)) max(var(--s-6), env(safe-area-inset-right));
  }
  .lb__nav{ align-self:center }
}

/* The overview row, from 768px (audit P29): every photograph as a 56px
   thumbnail, centred while they fit and scrolling sideways when they do
   not; the current one ringed in --on-fill, the one ink that reads on the
   plate. One Tab stop, the arrow keys step (product.js). */
.lb__thumbs{ display:none }
@media (min-width:768px){
  .lb__thumbs:not([hidden]){
    flex:none;
    display:flex; gap:var(--s-2);
    justify-content:safe center;
    overflow-x:auto; overscroll-behavior-x:contain;
    padding-block:var(--s-1) calc(var(--s-4) + env(safe-area-inset-bottom));
    padding-inline:max(var(--s-6), env(safe-area-inset-left)) max(var(--s-6), env(safe-area-inset-right));
    scrollbar-width:none;
  }
  .lb__thumbs::-webkit-scrollbar{ display:none }
  .lb--strip .lb__stage{ padding-block-end:var(--s-3) }
  .lb__thumb{
    flex:none;
    display:grid; grid-template-rows:minmax(0,1fr); grid-template-columns:minmax(0,1fr);
    width:56px; height:56px;
    padding:var(--s-1);
    background:var(--paper);
    border-radius:var(--r-sm);
    transition:box-shadow var(--d-hover) var(--ease);
  }
  .lb__thumb img{ width:100%; height:100%; object-fit:contain }
  .lb__thumb:focus-visible{ outline-offset:var(--ring-in) }
  .lb__thumb[aria-current="true"]{ box-shadow:inset 0 0 0 var(--bw-2) var(--slate-800), 0 0 0 var(--bw-2) var(--on-fill) }
}

/* THE LIGHTBOX'S ONE MOVEMENT. The plate and the chrome arrive over --d-ui
   and the photograph settles the last 1.5% of its size, on open and on every
   step, so a step reads as a change of subject. Animations keyed on [open]
   (a <dialog> going from display:none gives a transition nothing to start
   from). The exit is a cut. Nothing here is load-bearing: the src is set in
   JS before the class that runs the animation. */
@keyframes lb-in   { from{ opacity:0 } to{ opacity:1 } }
@keyframes lb-photo{ from{ opacity:0; transform:scale(.985) } to{ opacity:1; transform:none } }

.lb[open]::backdrop{ animation:lb-in var(--d-ui) var(--ease-out) both }
.lb[open] .lb__bar,
.lb[open] .lb__stage{ animation:lb-in var(--d-ui) var(--ease-out) both }
.lb__img.is-swap{ animation:lb-photo var(--d-ui) var(--ease-out) both }


/* -- 4. TITLE BLOCK ------------------------------------------------------------
   The page's one h1, static in product.html. design.md §3: the long title
   step is --t-h2 and the price never outranks the title (OD5 default). A
   title breaks between words or after a slash (product.js writes <wbr>),
   never inside a model number (audit P27).

   The brand line is a link to the brand's products, in the data's own form,
   the same as the spec sheet shows (audit P28), in a 44px row.
   ---------------------------------------------------------------------- */
.pdp__head{ display:flex; flex-direction:column; gap:var(--s-1); min-width:0 }

.pdp__title{
  font-size:var(--t-h1); line-height:var(--lh-h1);
  font-weight:700; letter-spacing:var(--ls-heading); color:var(--ink);
  overflow-wrap:break-word;
  text-wrap:balance;
}
.pdp__title--long{ font-size:var(--t-h2); line-height:var(--lh-h2) }

.pdp__brand{
  display:flex; align-items:center;
  min-height:44px;
  font-size:var(--t-small-ka); line-height:var(--lh-ui); font-weight:500;
}
.pdp__brand[hidden]{ display:none }
/* a short brand („Sony", „3M") is still a 44px target; the words stay on
   the title's edge */
.pdp__brand .link{ display:inline-flex; align-items:center; min-height:44px; min-width:44px }


/* -- 5. BUY BOX ----------------------------------------------------------------
   What it costs, in what condition, whether it can be had, and how to get
   it, in that order and nothing else:

     price          --t-num below 768, --t-num-lg from 768 (design.md §3);
                    the heaviest figure in the box
     condition      directly under the price, for every grade: an icon and
                    words at body size (design.md §2 "Status colours"):
                    check, info, and alert in --warn for grade 2 (audit P1)
     the call       the page's one primary; „დარეკვა" alone below 768px, the
                    number from 768px (audit P4)
     article code   the call's note, 12px under it: the lead, the code as a
                    figure (a step under the price) and a copy control
                    (audit P3)
     stock          neutral ink with the phone icon; "confirm by phone" is
                    not a promise, so it is never --ok green (audit P7)
     the wishlist   a labelled ghost toggle (audit P18)
   With nothing between the condition and the call, and the h1 on the crumb
   row (§1), the call is on the first screen at 1024x768 and 1280x800 under
   every title on file: six lines at the most, three titles (verify-r1 V7).
   ---------------------------------------------------------------------- */
.buybox{
  display:flex; flex-direction:column; gap:var(--s-4);
  padding:var(--s-5);
  background:var(--paper);
  border:var(--bw-1) solid var(--bd);
  border-radius:var(--r);
  box-shadow:var(--sh-1);
  min-width:0;
}
@media (min-width:768px){ .buybox{ padding:var(--s-6) } }

.buybox__prices{ display:flex; align-items:baseline; flex-wrap:wrap; gap:var(--s-2) var(--s-3) }
.buybox__price{ font-size:var(--t-num); line-height:var(--lh-num) }
@media (min-width:768px){ .buybox__price{ font-size:var(--t-num-lg) } }

/* A line with a leading icon: the condition and the stock. Body copy at
   every width (design.md §3 "Text roles": a sentence a shopper reads to
   decide). The 20px icon stands on the first line's middle. */
.pline{
  display:flex; align-items:flex-start; gap:var(--s-3);
  font-size:var(--t-body); line-height:var(--lh-body);
  color:var(--ink-2);
  min-width:0;
}
.pline__ic{
  flex:none; width:20px; height:20px;
  margin-block-start:calc((1lh - 20px) / 2);
  color:var(--ink-2);
}
.pline__t{ display:block; min-width:0 }
.pline__h,
.pline__d{ display:block }
/* a line head is a short heading: when it wraps, it wraps evenly („ზარით"
   stood alone on the stock line's second line at 320, verify-r1 V2) */
.pline__h{ text-wrap:balance }

/* the condition belongs to the price: 8px under it, not the row gap */
.pcond{ margin-block-start:calc(var(--s-2) - var(--s-4)) }
/* „რას ნიშნავს?" closes the sentence and may wrap onto a line of its own
   on a phone: it stays whole, and a transparent layer gives it a 44px tall
   target without moving a line (the ring still draws round the words) */
.pcond .link{ position:relative; white-space:nowrap }
.pcond .link::after{
  content:""; position:absolute;
  inset-block:calc(var(--s-3) * -1); inset-inline:0;
}
.pcond .pline__h b{ font-weight:600; color:var(--ink) }
.pcond--repair .pline__ic{ color:var(--warn) }

.pstock .pline__h{ font-weight:600 }

/* the article code. Its lead is a sentence the shopper acts on, so it is
   body copy at every width, as the condition and stock lines beside it are
   (design.md §3 "Text roles"); from 768px it was set at 14px under a 16px
   condition line in the same column (review P8). */
.pcode{ display:flex; flex-direction:column; gap:var(--s-1); min-width:0 }
.pcode__l{
  font-size:var(--t-body); line-height:var(--lh-body);
  color:var(--ink-2);
}
.pcode__l--long{ display:none }
@media (min-width:768px){
  .pcode__l--short{ display:none }
  .pcode__l--long{ display:inline }
}
.pcode__row{
  display:flex; align-items:center; gap:var(--s-2);
  min-height:44px; min-width:0;
}
/* The code is a figure a step under the price: --t-body 600 below 768px,
   where the price is --t-num (20px); at --t-num 700 its fourteen characters
   outweighed the seven of the price (verify-r1 V3). From 768px the price
   is --t-num-lg (24px) and the code takes --t-num 700. */
.pcode__v{
  min-width:0;
  font-size:var(--t-body); line-height:var(--lh-num); font-weight:600;
  color:var(--ink);
  overflow-wrap:anywhere;
}
@media (min-width:768px){ .pcode__v{ font-size:var(--t-num); font-weight:700 } }

/* the code row is the call's note: what to say once the shop answers, so
   it hangs 12px under the button rather than a row gap away */
.buybox__call + .pcode{ margin-block-start:calc(var(--s-3) - var(--s-4)) }
.buybox__call .l-long{ display:none }
@media (min-width:768px){
  .buybox__call .l-short{ display:none }
  .buybox__call .l-long{ display:inline }
}

/* ONE CALL ON SCREEN (design.md §14.14). While the call bar is up, it is the
   call: the buy box's own button steps back, so the part of it that shows
   above the bar is an empty slot in the box, not a second red button (on a
   430 phone 34 of its 48px stood over the bar's own call, review V14). It
   comes back as the bar leaves, over the bar's own --d-ui. Opacity and not
   visibility: it stays in the Tab order, and focusing it scrolls it whole
   into view, where the bar stands down (product.js §10). The transition
   list is .btn's own with the fade added. */
.buybox__call{
  transition:background-color var(--d-hover) var(--ease),
             color            var(--d-hover) var(--ease),
             box-shadow       var(--d-hover) var(--ease),
             transform        var(--d-press) var(--ease-press),
             opacity          var(--d-ui)    var(--ease);
}
body.buybar-on .buybox__call{ opacity:0; pointer-events:none }
/* the saved heart is the one red on the toggle (design.md §2, a state) */
.buybox__wish[aria-pressed="true"] .icon{ color:var(--red-700) }


/* -- 6. ASSURANCES -------------------------------------------------------------
   Where the item can be seen and what the warranty is. The sentences are
   body copy at every width (design.md §3 "Text roles" names these two
   lines): from 768px they were --t-sm under the 16px stock note of the same
   column, two sizes for one role (review P8). The shop row is a link to the
   contact page; the warranty row is a statement and stays a paragraph.
   The row icons are --ink-2, as the buy box's line icons and home's facts
   strip draw the same pin and shield (review P11).
   ---------------------------------------------------------------------- */
.assure{
  display:flex; flex-direction:column;
  border:var(--bw-1) solid var(--bd);
  border-radius:var(--r);
  background:var(--paper);
  overflow:hidden;
}
.assure__r{
  display:flex; align-items:flex-start; gap:var(--s-3);
  min-height:44px;
  padding:var(--s-3) var(--s-4);
  color:var(--ink-2);
  font-size:var(--t-body); line-height:var(--lh-body);
  transition:background-color var(--d-hover) var(--ease);
}
.assure__r + .assure__r{ border-top:var(--bw-1) solid var(--bd) }
.assure__r[hidden]{ display:none }
/* The block clips for its corners, so the shop row's ring draws inside the
   row (design.md §2 "The ring"): drawn outside, the clip took its top and
   both sides and left a 3px line along the divider, which read as the
   divider (final accessibility check, P2). The row takes the block's inner
   radius where it meets a corner, so the ring follows the corner and is
   not cut by it: its top corners always, all four when the warranty row is
   hidden (an item that needs repair). */
.assure :focus-visible{ outline-offset:var(--ring-in) }
a.assure__r{
  border-start-start-radius:calc(var(--r) - var(--bw-1));
  border-start-end-radius:calc(var(--r) - var(--bw-1));
}
a.assure__r:not(:has(~ .assure__r:not([hidden]))){
  border-end-start-radius:calc(var(--r) - var(--bw-1));
  border-end-end-radius:calc(var(--r) - var(--bw-1));
}
@media (hover:hover){
  a.assure__r:hover{ background:var(--page-2) }
  a.assure__r:hover .assure__go{ color:var(--ink-strong) }
}
a.assure__r:active{ background:var(--bd) }
.assure__r > .icon{ flex:none; color:var(--ink-2); margin-block-start:calc((1lh - 20px) / 2) }
.assure__c{ display:block; min-width:0; flex:1 1 auto }
.assure__t{ display:block; color:var(--ink); font-weight:600 }
.assure__d{ display:block; color:var(--ink-2) }
.assure__r > .assure__go{ color:var(--ink-2); margin-block-start:calc((1lh - 18px) / 2) }


/* -- 7. DETAILS: the disclosure bodies and the spec sheet ----------------------
   As wide as the gallery column; the description at --measure, about 60
   characters a line (design.md §3 "Measure", audit P15). */
.pdp__detail .acc__c p{ max-width:var(--measure) }

/* The panel's height is the shared disclosure's. This only keeps a body from
   being legible before it has room to be: keyed on the very .is-open the
   height is keyed on, so the failure mode is a closed panel and nothing
   else. */
.pdp__detail .acc__c{
  opacity:0;
  transition:opacity var(--d-ui) var(--ease-out);
}
.pdp__detail .acc__item.is-open .acc__c{ opacity:1 }

/* THE SPEC SHEET is the site's one label and value list, the contact page's
   rows (page.css §6): the label role (14px 500 --ink-2, design.md §3), the
   value start-aligned, 16px of air a side, a hairline between two rows.
   Below 480px each label stands over its value; from 480px one column of
   rows, the labels in a column as wide as the longest of them and the
   values beside it (design.md §14.1: spec rows side by side from 480).

   It used to be a second build (review P10): labels at weight 400, rows
   12px apart, values end-aligned, and from 768px two columns of rows. There
   a value kept what its label left of a 248px column at 1024px and a
   Georgian name broke inside its word, „საფრანგე" over „თი" (verify-r3
   V16, 599 products); the full width of the detail column gives the longest
   name room to wrap between its words (break-word stays the fallback for
   data nobody has seen). And every row drew its own rule, so the sheet
   ended on two parallel rules, the last row's and the disclosure's, and on a
   staircase of three in two columns (V15): here the rules stand between
   rows only, and the disclosure's closing hairline closes the sheet. The
   first row starts and the last one ends where a description paragraph
   would, on the panel's own padding. */
.specs__r{
  display:grid; row-gap:var(--s-1);
  padding-block:var(--s-4);
  min-width:0;
}
.specs__r:first-child{ padding-block-start:0 }
.specs__r:last-child{ padding-block-end:0 }
.specs__r + .specs__r{ border-top:var(--bw-1) solid var(--bd) }
.specs__r dt{
  font-size:var(--t-small-ka); line-height:var(--lh-small-ka);
  font-weight:500; color:var(--ink-2);
}
.specs__r dd{ color:var(--ink); overflow-wrap:break-word; min-width:0 }

@media (min-width:480px){
  .specs{
    display:grid;
    grid-template-columns:max-content minmax(0,1fr);
    column-gap:var(--s-8);
  }
  .specs__r{
    grid-column:1 / -1;
    grid-template-columns:subgrid;
    align-items:baseline;
  }
}

/* the supplier's own designation: long, Latin, never our own copy. Its
   label stays over it at every width, and it alone may break anywhere
   (design.md §3, the `.break` setting for an unbroken supplier string). */
@media (min-width:480px){
  .specs__r--wide{ grid-template-columns:minmax(0,1fr) }
}
.specs__r--wide dd{ color:var(--ink-2); overflow-wrap:anywhere }


/* -- 8. RELATED RAIL -----------------------------------------------------------
   A section after the details: --gap-section from the disclosure's closing
   hairline, and nothing under it (the footer owns the page end). No rule of
   its own: a section heading has no rule in front of it (design.md §6
   "Section heading"), and a full-width rule 32 or 48px under the
   disclosure's hairline drew two parallel lines of different lengths
   (verify-r1 V4). */
.rel{
  margin-top:calc(var(--gap-section) / 2);
  padding-bottom:0;
}


/* -- 9. CALL BAR ---------------------------------------------------------------
   Below 1024px: the price, the article code with its copy control, and the
   call, in the bottom bar's place (design.md §7, §14.13; owner default
   OD16). --bar-call tall plus the safe area. It registers itself as the
   bottom band, so html's scroll padding, the body's bottom padding and the
   toast host all pay for it; the page adds nothing of its own (the 80px
   band under the footer was that, audit P20).
   ---------------------------------------------------------------------- */
:root:has(body.buybar-on){ --pin-bottom:var(--bar-call) }

.buybar{
  position:fixed; z-index:var(--z-bar);
  inset-inline:0; bottom:0;
  height:calc(var(--bar-call) + env(safe-area-inset-bottom));
  padding-bottom:env(safe-area-inset-bottom);
  background:var(--paper);
  box-shadow:inset 0 var(--bw-1) 0 var(--bd), var(--sh-up);
  transform:translateY(100%); visibility:hidden;
  transition:transform var(--d-ui) var(--ease), visibility 0s linear var(--d-ui);
}
.buybar.is-on{
  transform:none; visibility:visible;
  transition:transform var(--d-ui) var(--ease), visibility 0s;
}
.buybar__in{
  display:flex; align-items:center; gap:var(--s-2);
  height:var(--bar-call);
}
/* the photograph from 480px, where the row has the room (a slot of 44px:
   the missing-photo glyph alone, design.md §6) */
.buybar__img{
  display:none;
  width:44px; height:44px; flex:none;
  object-fit:contain;
  padding:var(--s-1);
  background:var(--paper);
  border:var(--bw-1) solid var(--bd); border-radius:var(--r-xs);
}
@media (min-width:480px){ .buybar__img{ display:block } }
/* price and code, the copy control beside the code, the call at the far
   end */
.buybar__txt{ display:flex; flex-direction:column; min-width:0; flex:0 1 auto }
.buybar__p{ font-size:var(--t-num); line-height:var(--lh-num) }
.buybar__code{
  font-size:var(--t-xs); line-height:var(--lh-xs);
  color:var(--ink-2);
  white-space:nowrap;
}
@media (min-width:480px){
  .buybar__code{ font-size:var(--t-sm); line-height:var(--lh-ui) }
}
/* the one action keeps its 44px and its words; the tighter inline padding
   is what lets price, code, copy and call share a 288px row */
.buybar__b{ flex:none; margin-inline-start:auto; padding-inline:var(--s-4) }

@media (min-width:1024px){ .buybar{ display:none } }


/* -- 10. LOADING -----------------------------------------------------------------
   The skeleton is the page's own structure with bars where the words go
   (product.html), so every block is reserved at the size of what replaces
   it: the well and the strip by the gallery's own rules, a title bar per
   line box, the buy box's rows at their line heights. Flat --page-2, no
   shimmer (design.md §10). */
.sk-line{ display:block; height:1lh }
.sk-line::before{
  content:""; display:block;
  height:.7em; margin-block-start:calc((1lh - .7em) / 2);
  border-radius:var(--r-xs);
  background:var(--page-2);
}
.sk-line--short::before{ width:62% }
.sk-line--brand::before{ width:88px }
.sk-line--price{ width:40% }
.sk-line--code{ width:70% }
.sk-dot{ border-radius:var(--r-pill); background:var(--page-2) }
.sk-btn{ display:block; height:44px; border-radius:var(--r-sm); background:var(--page-2) }
.sk-btn--lg{ height:48px }
span.pgal__thumb{ background:var(--page-2); box-shadow:none }


/* -- 11. NOT FOUND AND LOAD ERROR ----------------------------------------------
   A status page (design.md §14.12): the shared empty state as the page's
   h1, one sentence, the actions and the ways on. #pdpMsg carries the
   shell's .status, one screen tall, so the footer never rises into view
   when the skeleton gives way to it (CLS 0.13 to 0.24, shell hand-over).
   It has no padding of its own: the state starts on the crumb row's foot,
   where the 404's does (§14.6); it stood 16px lower on phones and 24px
   lower from 768px (reviews P3, S4). The similar rail under it is a section
   like the related one, --gap-section from the chips. */
.pdp__msg .rel{ padding-top:calc(var(--gap-section) / 2) }


/* -- 12. REDUCED MOTION ----------------------------------------------------------
   base.css collapses every duration to 0s. What is left is the travel: the
   crossfade becomes a cut (the preload that shields it stays), the
   lightbox's plate and photograph simply appear, the bar's slide is gone
   (its translate is its hidden state, so it stays). */
@media (prefers-reduced-motion: reduce){
  .pgal__main::after,
  .pgal__main.is-xfade::after{ transition:none }
  .lb[open]::backdrop,
  .lb[open] .lb__bar,
  .lb[open] .lb__stage,
  .lb__img.is-swap{ animation:none }
  .pdp__detail .acc__c{ transition:none }
}
