/* ==========================================================================
   Product image gallery — shared component
   --------------------------------------------------------------------------
   Owns every product gallery on the product landing pages. The markup stays
   exactly as it was authored:

     <div class="gallery">                     (+ .solo when there is 1 image)
       <figure class="gal-main"><img id="galMain" ...></figure>
       <ul class="gal-thumbs"><li><button data-full data-alt><img></button></li></ul>
     </div>

   product-gallery.js progressively enhances that into a framed stage with
   prev/next arrows, an image counter, and a full-screen viewer that opens on
   click. Inspection deliberately happens in the viewer, not under the pointer
   on the stage: hover-tracking multiplies a small mouse movement by the zoom
   factor, so at 3x the picture lurches away from the cursor. Dragging inside
   the viewer moves the image 1:1 with the hand instead, which is what makes it
   feel steady enough to actually read a port label. Everything the JS injects is namespaced .pg-*, so this file is the
   single place any of it is styled. Do not re-declare these rules per page.

   Colours come from the page tokens (--line, --blue, --magenta, --surface,
   --ink*, --shadow-*), so light, dark and the [data-theme] override all follow
   the host page. The stage itself stays white in every theme on purpose: the
   product photography is shot on white, and a dark frame would ring it.
   ========================================================================== */

/* ---------- 1. the gallery column (baseline, no JS required) ---------- */

.gallery {
  position: sticky;
  top: 18px;
  display: flex;
  flex-direction: column;
  align-self: start;
  width: 100%;
  min-width: 0;
  min-height: 0;
  /* No max-width. The photograph fills whatever column the page's own hero
     grid hands it — capping it here shrank the 5G router pages, whose grid
     gives the gallery a much wider column than the 560px the Prime and
     Panamax pages happened to use. The picture comes first; the zoom is an
     addition to it, never a reason to make it smaller. */
}

.gal-main {
  position: relative;
  flex: none;
  margin: 0;
  aspect-ratio: 1 / 1;
  /* No inner padding either: the photograph goes edge to edge in its frame.
     The controls sit over it, the way every product gallery does. */
  border-radius: 18px;
  border: 1px solid var(--line);
  background: #fff;
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

/* No side gutter, deliberately.

   A gutter here was keeping the arrows off the product, but it costs far more
   than it buys. The frame is square and the product photographs are square, so
   padding the sides makes the content box narrower than it is tall; object-fit
   then letterboxes the picture vertically by the same amount it was inset
   horizontally, and the photograph ends up floating in the middle of its own
   card with a margin of white on all four sides. It also made the multi-image
   pages look unlike the single-image ones, which have no padding and fill their
   frames properly.

   The arrows sit over the picture instead, which is what every product gallery
   does; they already carry a solid disc, a border and a shadow to stay legible
   over a photograph. */

/* LOAD-BEARING — do not weaken this selector.
   Scoped through .gallery on purpose. Host templates carry their own rules for
   the figure they used to hold: the four router pages (rollover-switch, the two
   poe-switch pages and hbe-ax3000-router) all have
   `.sw-shot img { max-width: 460px; height: auto }` in a page-level <style>
   that comes AFTER this file. A bare `.gal-main img` is (0,1,1), ties with that
   rule, loses on source order, and the photo renders narrow and top-aligned in
   its square frame. The extra class makes this (0,2,1) — a genuine specificity
   win, not a source-order accident — and those four pages now depend on it
   rather than on per-page overrides. Dropping the class, or the max-width /
   max-height resets, regresses all four to a 460px-capped image at once. */
.gallery .gal-main img {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: contain;
  /* never crop, never stretch — the frame letterboxes instead */
}

ul.gal-thumbs {
  display: grid;
  /* auto-FIT with a stretching track, as these pages had before: empty tracks
     collapse and the remaining thumbs share the full width, so a strip of
     eight lands on one row instead of orphaning the last one onto a second. */
  /* capped rather than a bare 1fr: with eight thumbs the cap never binds and
     they fill the row as before, but a two-image product would otherwise get
     two half-width thumbnails larger than the strip should ever be. */
  grid-template-columns: repeat(auto-fit, minmax(62px, 120px));
  gap: 8px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
  flex: none;
}

ul.gal-thumbs button {
  display: block;
  width: 100%;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #fff;
  cursor: pointer;
  transition: border-color .13s, transform .13s, box-shadow .13s;
}

ul.gal-thumbs button:hover {
  transform: translateY(-2px);
}

ul.gal-thumbs button[aria-current="true"] {
  border-color: var(--blue);
  box-shadow: 0 0 0 1px var(--blue);
}

ul.gal-thumbs button:focus-visible {
  outline: 2px solid var(--magenta);
  outline-offset: 2px;
}

ul.gal-thumbs img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 9px;
}

.gallery.solo ul.gal-thumbs {
  display: none;
}

@media (max-width:1100px) {
  .gallery {
    position: static;
    display: block;
    max-width: 520px;
    margin-inline: auto;
  }
}

/* ---------- 2. the stage: swap crossfade ---------- */

/* .pg-on is set by the JS once enhancement succeeds, so every rule below is
   scoped to it: with JS off the gallery above still renders and the thumbs
   still work as plain links to nothing — nothing here can strand a user. */

.gallery.pg-on .gal-main {
  touch-action: pan-y;
  /* vertical scroll still belongs to the page; horizontal is ours for swipe */
}

/* Opacity only. The stage image DOES transform — see section 5, hover zoom —
   but that transform is eased per frame in JS, so a transition on it here
   would fight the loop rather than help it. */
.gallery.pg-on .gal-main img#galMain,
.gallery.pg-on .gal-main img.pg-img {
  transition: opacity .2s linear;
}

.gallery.pg-on .gal-main img.pg-fading {
  opacity: 0;
}

/* cursor tells the whole story before anything is clicked */
.gallery.pg-on .gal-main {
  cursor: zoom-in;
}

/* The frame answers the pointer; the picture does not. A deeper shadow and a
   solid affordance pill say "this opens" without the product shifting by a
   single pixel while the mouse crosses it. */
.gallery.pg-on .gal-main {
  transition: box-shadow .18s ease, border-color .18s ease;
}

.gallery.pg-on .gal-main:hover {
  border-color: color-mix(in srgb, var(--blue) 32%, var(--line));
  box-shadow: 0 2px 6px rgba(20, 30, 40, .07), 0 14px 34px rgba(20, 30, 40, .13);
}

.gallery.pg-on .gal-main:hover .pg-hint {
  background: #fff;
  color: var(--blue);
  border-color: color-mix(in srgb, var(--blue) 38%, var(--line));
}

/* the stage is a real control, so it takes a real focus ring */
.gallery.pg-on .gal-main:focus-visible {
  outline: 3px solid var(--magenta);
  outline-offset: 3px;
}

/* ---------- 3. prev / next arrows ---------- */

.pg-nav {
  position: absolute;
  top: 50%;
  z-index: 3;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-top: -22px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: rgba(255, 255, 255, .96);
  -webkit-backdrop-filter: saturate(1.6) blur(6px);
  backdrop-filter: saturate(1.6) blur(6px);
  /* reads as a control sitting on the photo, not a pale shape lost in it */
  box-shadow: 0 1px 2px rgba(20, 30, 40, .14), 0 8px 22px rgba(20, 30, 40, .20);
  color: #1d2328;
  cursor: pointer;
  opacity: .92;
  transition: opacity .16s, transform .16s, box-shadow .16s, background-color .16s;
}

.pg-nav svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.1;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

.pg-prev { left: 8px; }
.pg-next { right: 8px; }

.gal-main:hover .pg-nav { opacity: 1; }

.pg-nav:hover {
  background: #fff;
  transform: scale(1.07);
  box-shadow: 0 2px 4px rgba(20, 30, 40, .1), 0 10px 26px rgba(20, 30, 40, .18);
}

.pg-nav:active { transform: scale(.96); }

.pg-nav:focus-visible {
  outline: 2px solid var(--magenta);
  outline-offset: 2px;
  opacity: 1;
}

/* ---------- 4. counter + zoom affordance ---------- */

.pg-count,
.pg-hint {
  position: absolute;
  bottom: 10px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .94);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  box-shadow: 0 2px 10px rgba(20, 30, 40, .14);
  font: 600 11.5px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  letter-spacing: .02em;
  color: #4d565e;
  pointer-events: none;
  transition: opacity .18s;
}

.pg-count {
  right: 10px;
  font-variant-numeric: tabular-nums;
}

.pg-hint { left: 10px; }

.pg-hint svg {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

/* ---------- 5. hover zoom ---------- */

/* The picture scales up inside its own frame and follows the pointer, so a
   port or a label can be read without leaving the page.

   Containment is the frame's job and it already does it: .gal-main is
   overflow:hidden, so however far the picture is scaled it is cropped at the
   frame edge and cannot reach the copy beside it.

   The origin is the centre, which is where the picture already sits at rest,
   so scaling alone never moves it off centre and the JS only has to supply a
   clamped offset on top. It is pinned here rather than set alongside the
   transform because an origin that followed the cursor would repaint instantly
   on every move — origin is not a transitionable step — and no amount of
   easing on the transform would smooth that.

   There is deliberately no transition on transform. The JS eases it per frame
   instead: a transition restarts on every mousemove and leaves the picture its
   full duration behind the hand, which reads as floaty. Easing toward the
   target frame by frame damps the jitter without buying the lag. */
.gallery.pg-on .gal-main img#galMain,
.gallery.pg-on .gal-main img.pg-img {
  transform-origin: 50% 50%;
}

/* promoted only while it is actually moving, rather than parked on a layer for
   the life of every product page */
.gallery.pg-on .gal-main.pg-hz-on img#galMain,
.gallery.pg-on .gal-main.pg-hz-on img.pg-img {
  will-change: transform;
}

/* the affordance pill has said its piece once the picture is zoomed */
.gal-main.pg-hz-on .pg-hint {
  opacity: 0;
}

/* ---------- 6. thumbnail rail ---------- */

/* On phones the auto-fill grid stacks into several tall rows and pushes the
   copy far down the page, so the rail becomes one horizontally scrolling
   track instead — the same pattern the spec tables use. */
@media (max-width:640px) {
  ul.gal-thumbs {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    padding-bottom: 4px;
    scrollbar-width: thin;
  }

  ul.gal-thumbs li {
    flex: 0 0 62px;
    scroll-snap-align: start;
  }
}

/* ---------- 7. full-screen lightbox ---------- */

/* One <div class="pg-lb"> is created lazily and reused by every gallery on the
   page. It is a modal: the JS sets aria-modal, traps Tab and restores focus. */

.pg-lb {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  flex-direction: column;
  /* opaque enough to hold on its own: backdrop-filter is an enhancement, not
     a dependency, and at .93 the page underneath stayed readable through it */
  background: rgba(10, 18, 24, .975);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  opacity: 0;
  visibility: hidden;
  transition: opacity .22s ease, visibility .22s;
}

.pg-lb.pg-open {
  opacity: 1;
  visibility: visible;
}

.pg-lb-stage {
  /* The gutters are named once and reused as the picture's insets below, so
     the photograph can never reach under the edge arrows. */
  --pg-gut-y: clamp(10px, 1.6vw, 22px);
  --pg-gut-x: clamp(58px, 7vw, 104px);
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  touch-action: none;
  cursor: zoom-in;
}

.pg-lb-stage.pg-zoomed { cursor: grab; }
.pg-lb-stage.pg-dragging { cursor: grabbing; }

/* Sized by its four insets rather than by percentages. `max-height: 100%` on a
   grid item whose row is auto-sized by that same item is cyclic, and browsers
   may drop the constraint — which let the picture grow past the stage and get
   cut off at the bottom, losing exactly the fine detail this viewer exists to
   show. Four fixed edges cannot overflow, and object-fit does the letterboxing. */
.pg-lb-img {
  position: absolute;
  /* Sized explicitly, not by opposing offsets: an <img> is a replaced element,
     so `width:auto` on it resolves to the INTRINSIC width and the `right`
     offset is dropped as over-constrained — which is what let a 1500px photo
     spill past the stage. These percentages resolve against the stage's own
     definite box, so there is no cycle and no overflow. */
  top: var(--pg-gut-y);
  left: var(--pg-gut-x);
  width: calc(100% - (var(--pg-gut-x) * 2));
  height: calc(100% - (var(--pg-gut-y) * 2));
  object-fit: contain;
  border-radius: 6px;
  transform-origin: center center;
  will-change: transform;
  transition: transform .3s cubic-bezier(.22, .61, .36, 1), opacity .18s linear;
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
}

.pg-lb-img.pg-tracking { transition: opacity .18s linear; }
.pg-lb-img.pg-fading { opacity: 0; }

/* --- lightbox chrome --- */

.pg-lb-bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px clamp(14px, 3vw, 26px);
  color: #e8eef2;
}

.pg-lb-cap {
  font: 500 13px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  color: #aab8c2;
  max-width: 70ch;
  margin: 0;
}

.pg-lb-tools {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

/* the close button leaves the viewer while the others act inside it — a gap
   and a brighter rim keep the exit from reading as a third zoom control */
.pg-lb-tools .pg-btn:last-child {
  margin-left: 10px;
  border-color: rgba(255, 255, 255, .34);
  background: rgba(255, 255, 255, .13);
}

.pg-lb-tools .pg-btn:last-child:hover {
  background: rgba(255, 255, 255, .24);
  border-color: rgba(255, 255, 255, .55);
}

.pg-btn {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 50%;
  background: rgba(255, 255, 255, .08);
  color: #e8eef2;
  cursor: pointer;
  transition: background-color .15s, transform .15s, border-color .15s, opacity .15s;
}

.pg-btn svg {
  width: 19px;
  height: 19px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.1;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

.pg-btn:hover {
  background: rgba(255, 255, 255, .17);
  border-color: rgba(255, 255, 255, .3);
  transform: scale(1.06);
}

.pg-btn:active { transform: scale(.95); }

.pg-btn:focus-visible {
  outline: 2px solid var(--magenta, #eb008b);
  outline-offset: 2px;
}

.pg-btn[disabled] {
  opacity: .35;
  cursor: default;
  transform: none;
}

.pg-lb-count {
  font: 600 12.5px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  color: #aab8c2;
  padding: 0 4px;
  min-width: 5ch;
  text-align: center;
}

/* edge arrows inside the lightbox */
.pg-lb-nav {
  position: absolute;
  top: 50%;
  margin-top: -26px;
  width: 52px;
  height: 52px;
  z-index: 2;
}

.pg-lb-nav.pg-prev { left: clamp(8px, 2vw, 26px); }
.pg-lb-nav.pg-next { right: clamp(8px, 2vw, 26px); }

.pg-lb-nav svg { width: 24px; height: 24px; }

/* while zoomed the drag gesture owns the stage, so the arrows retire.
   Keyed off the root, not the stage: the arrows are children of the stage, so
   a sibling combinator would never match them. */
.pg-lb.pg-is-zoomed .pg-lb-nav {
  opacity: 0;
  pointer-events: none;
}

/* --- lightbox thumbnail strip --- */

.pg-lb-strip {
  flex: none;
  display: flex;
  justify-content: center;
  gap: 8px;
  padding: 12px clamp(10px, 3vw, 26px) 18px;
  overflow-x: auto;
  scrollbar-width: thin;
  overscroll-behavior-x: contain;
}

.pg-lb-strip button {
  flex: 0 0 auto;
  width: 58px;
  height: 58px;
  padding: 2px;
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: 10px;
  background: rgba(255, 255, 255, .06);
  cursor: pointer;
  transition: border-color .15s, transform .15s, background-color .15s;
}

.pg-lb-strip button img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 8px;
  display: block;
}

.pg-lb-strip button:hover { transform: translateY(-2px); }

.pg-lb-strip button[aria-current="true"] {
  border-color: var(--blue, #4fb0e0);
  background: rgba(255, 255, 255, .16);
  box-shadow: 0 0 0 1px var(--blue, #4fb0e0);
}

.pg-lb-strip button:focus-visible {
  outline: 2px solid var(--magenta, #eb008b);
  outline-offset: 2px;
}

.pg-lb.pg-solo .pg-lb-strip,
.pg-lb.pg-solo .pg-lb-nav { display: none; }

/* the page must not scroll behind an open lightbox */
html.pg-lock,
body.pg-lock {
  overflow: hidden;
  touch-action: none;
}

/* screen-reader-only live region announcing "Image 3 of 8" */
.pg-live {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- 8. touch, and reduced motion ---------- */

/* Hover-zoom is a pointer idiom. On touch it either never fires or fires on
   the tap that was meant to open the lightbox, so it is switched off and the
   hint copy changes to match (the JS writes the wording). */
@media (hover: none), (pointer: coarse) {
  .gallery.pg-on .gal-main { cursor: default; }
  .pg-hint { opacity: .95; }
  .pg-nav { opacity: 1; }
}

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

  .gallery.pg-on .gal-main img,
  .pg-lb-img,
  .pg-lb,
  .pg-nav,
  .pg-btn,
  ul.gal-thumbs button,
  .pg-lb-strip button {
    transition-duration: .01ms !important;
  }

  .pg-nav:hover,
  .pg-btn:hover,
  ul.gal-thumbs button:hover,
  .pg-lb-strip button:hover {
    transform: none;
  }
}

/* ---------- 9. small screens ---------- */

@media (max-width:640px) {
  .pg-nav {
    width: 40px;
    height: 40px;
    margin-top: -20px;
  }

  .pg-nav svg { width: 18px; height: 18px; }

  .pg-lb-nav {
    width: 44px;
    height: 44px;
    margin-top: -22px;
  }

  .pg-lb-cap { display: none; }

  .pg-lb-strip button {
    width: 46px;
    height: 46px;
  }
}
