/* ============================================================
   Our Brands — German Power Solutions

   A continuously scrolling strip of partner marks, with an arrow on
   each side that steps it one mark at a time and wraps forever in
   both directions.

   COLOUR — computed from tokens.css with the WCAG 2.x formula.
   The strip sits on the page's white ground:

     --grey-600 #5a5f66 on white   6.43:1   the marks at rest, and both
                                   the arrow's ring and its glyph.
                                   WCAG 1.4.11 asks 3:1 of a control's
                                   visual boundary, and on an icon-only
                                   button the boundary IS the control —
                                   which is why the arrows do not reach
                                   for --grey-300 (#cccccc, 1.61:1) the
                                   way a bordered text button could get
                                   away with.
     --gps-black #111111 on white 18.88:1   the marks on hover.
     --gps-red   #dd0000 on white  5.15:1   the arrows on hover.

   The marks are greyed at rest and resolve on hover. That is the
   conventional treatment for a partner wall, and it is also what
   keeps twelve competing logo colours from out-shouting the section
   heading above them.
   ============================================================ */

.brands{overflow:hidden}   /* the belt to the viewport's braces */

/* ---------- The rail ----------
   Holds the clipped strip and the two arrows, and owns every knob the
   rest of the file reads. Wider than the page container on purpose: a
   marquee that stops at the container gutter reads as a broken
   carousel rather than a continuous band.

     --brand-gap     space between two marks. Also the distance the
                     loop adds on top of one copy's width.
     --brand-speed   seconds for one full cycle. Derived from a _base_
                     so that the phone rung can scale it AND the
                     Customizer can set the base from an inline style
                     without the media query silently losing to it.
     --brand-nav     arrow diameter.
     --brand-gutter  the lane each arrow sits in, which is also what
                     the strip is inset by — see below.
     --brand-fade    how far the strip's edge fade reaches in.
     --brand-step    how long one arrow press takes to travel.

   The arrows get a gutter of their own rather than floating over the
   strip. Overlaying them was the first cut and it looked wrong on a
   phone: at 375px the edge mask has only about 26px to work with, so a
   mark passing under a button stayed half legible around its rim, and
   the band read as a logo with a button dropped on it rather than as a
   control beside a strip. Insetting the clipped viewport by exactly
   the arrow's lane costs about 12% of the width at 375px and almost
   nothing at 1440, and then nothing ever passes underneath a button.
   ---------- */
.brands__rail{
  position:relative;

  --brand-gap:clamp(2rem,4vw,3.5rem);
  --brand-speed-base:48s;
  --brand-speed:var(--brand-speed-base);
  --brand-nav:2.75rem;
  --brand-gutter:clamp(3.75rem,6vw,5.5rem);
  --brand-fade:clamp(1.25rem,3vw,2.5rem);
  --brand-step:450ms;

  padding-inline:var(--brand-gutter);
}

/* overflow:hidden here is not cosmetic. Without it the second copy
   extends a full copy-width past the right edge and the document gains
   a horizontal scrollbar at every viewport size. */
.brands__viewport{
  overflow:hidden;
  padding-block:var(--sp-5);
  /* Fades the strip into the page at both edges so a mark is never
     chopped hard against the clip. A mask rather than two gradient
     overlays, so it works on any page background. */
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 var(--brand-fade),#000 calc(100% - var(--brand-fade)),transparent);
          mask-image:linear-gradient(90deg,transparent,#000 var(--brand-fade),#000 calc(100% - var(--brand-fade)),transparent);
}

/* One moving element, not two. Everything that shifts the strip —
   the idle animation and the arrows alike — writes to this element's
   transform, because two sources competing for one property is how a
   carousel ends up jumping a tile every time you touch it. */
.brands__lane{
  --brand-copies:2;

  display:flex;
  width:max-content;
  gap:var(--brand-gap);
  animation:brands-scroll var(--brand-speed) linear infinite;
}

/* flex:none so a copy keeps its intrinsic width instead of being
   squeezed to fit the viewport — squeezing would make the travel
   distance wrong and the loop visibly jump. */
.brands__track{
  display:flex;align-items:center;
  flex:none;
  gap:var(--brand-gap);
  margin:0;padding:0;list-style:none;
}

/* One cycle moves the lane by exactly one copy's width plus one gap,
   which lands copy 2 precisely where copy 1 began — so the restart is
   invisible.

   Written as a division rather than a hard -50% so the figure stays
   correct for any number of copies. The lane is n*W + (n-1)*gap wide;
   add one gap, divide by n, and you get W + gap whatever n is.
   assets/js/brands.js raises --brand-copies when one copy is narrower
   than the viewport — a client with four logos, or a 5K display. With
   no script it stays at the 2 the markup ships, which is exactly what
   this file did before the arrows existed. */
@keyframes brands-scroll{
  from{transform:translateX(0)}
  to{transform:translateX(calc((-100% - var(--brand-gap)) / var(--brand-copies)))}
}

/* Pause on hover and on keyboard focus anywhere on the rail, arrows
   included — reading a logo you are pointing at should not require
   chasing it, and neither should reaching for the control next to it. */
.brands__rail:hover .brands__lane,
.brands__rail:focus-within .brands__lane{animation-play-state:paused}

/* An open detail panel stops the strip for as long as it is open. The
   control that opened it is a logo on a moving lane; leaving the lane
   running means the thing the visitor just pointed at slides away while
   they read about it, and the logo they would click to close is no longer
   where they left it. */
.brands--panel-open .brands__lane{animation-play-state:paused}

/* ---------- Manual mode ----------
   The first arrow press ends the auto-scroll for the session. The
   lane's position becomes a plain inline transform that the arrows
   move, and the keyframes would only fight them for the property.

   This is also the WCAG 2.2.2 stop mechanism — see the note at the
   foot of this file. */
.brands--manual .brands__lane{
  animation:none;
  transition:transform var(--brand-step) var(--ease);
}

/* The Customizer's autoplay switch, off. Same end state, reached
   before the visitor touches anything. */
.brands--noautoplay .brands__lane{animation:none}

/* ---------- The marks ---------- */
.brand{
  display:flex;align-items:center;justify-content:center;
  flex:none;
  min-width:0;
}

.brand__mark{
  width:clamp(5.5rem,9vw,7.5rem);
  height:auto;aspect-ratio:48/48;
  fill:none;stroke:var(--grey-600);stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;
  transition:stroke var(--dur) var(--ease),opacity var(--dur) var(--ease);
}

/* A client-uploaded logo, which is an <img> rather than a sprite mark.
   Bounded by height so marks of different ratios sit on one baseline,
   and capped in width so a 2000px upload cannot stretch the track. */
.brand__img{
  width:auto;max-width:11rem;
  height:clamp(2.25rem,4vw,3rem);
  object-fit:contain;
  filter:grayscale(1);opacity:.72;
  transition:filter var(--dur) var(--ease),opacity var(--dur) var(--ease);
}

.brand a:hover .brand__mark,
.brand a:focus-visible .brand__mark{stroke:var(--gps-black)}
.brand a:hover .brand__img,
.brand a:focus-visible .brand__img{filter:grayscale(0);opacity:1}

/* Non-linked tiles still resolve on hover, so the strip does not look
   half-interactive when only some partners have a URL. */
.brands__viewport:hover .brand__mark{stroke:var(--gps-black)}
.brands__viewport:hover .brand__img{filter:grayscale(0);opacity:1}

/* ---------- Arrows ----------
   Centred in the gutter the rail reserves for them, so the arithmetic
   holds at every width: the gutter is never narrower than 60px and the
   button is 44px, which leaves at least 8px of air on either side.

   44px square is WCAG 2.5.5 (AAA), not merely the 24px of 2.5.8 (AA).
   A logo strip is a low-stakes control, but it is also one people jab
   at on a phone while scrolling past. */
.brands__nav{
  position:absolute;
  top:50%;
  z-index:2;
  translate:0 -50%;          /* leaves `transform` free for the press */

  display:flex;align-items:center;justify-content:center;
  width:var(--brand-nav);height:var(--brand-nav);
  padding:0;
  border:2px solid var(--grey-600);
  border-radius:50%;
  background:var(--gps-white);
  color:var(--grey-600);
  cursor:pointer;
  transition:color var(--dur) var(--ease),
             border-color var(--dur) var(--ease),
             transform var(--dur-fast) var(--ease);
}

/* Same specificity as .brands__nav, and it comes later — but relying
   on order for this would be a trap the first time someone reorders
   the file. The arrows ship hidden and assets/js/brands.js reveals
   them, so a page whose script never arrives shows no dead controls. */
.brands__nav[hidden]{display:none}

.brands__nav:hover{color:var(--gps-red);border-color:var(--gps-red)}
.brands__nav:active{transform:scale(.94)}

.brands__nav--prev{left:calc((var(--brand-gutter) - var(--brand-nav)) / 2)}
.brands__nav--next{right:calc((var(--brand-gutter) - var(--brand-nav)) / 2)}

.brands__nav-ico{
  width:1.25rem;height:1.25rem;
  fill:none;stroke:currentColor;stroke-width:2.5;
  stroke-linecap:round;stroke-linejoin:round;
}

/* ============================================================
   Reduced motion

   tokens.css already forces animation-duration to .01ms and
   iteration-count to 1 globally under this query. With the default
   animation-fill-mode of `none` the transform reverts to its base
   value when the animation ends, so the lane lands back at
   translateX(0) rather than stranded at the end of its travel. The
   explicit `animation:none` below says so rather than leaving it to
   be inferred. The same global rule flattens transition-duration to
   .01ms, which is what makes an arrow press a jump rather than a
   slide here — nothing in this file has to special-case it.

   What the global rule cannot decide is the clone. When script has
   run, the arrows are the way through the strip and the clone is what
   they wrap around, so it stays. When script has NOT run there are no
   arrows and nothing is moving, so a second copy of every mark would
   just print each logo twice — the clone goes and the strip becomes an
   ordinary scrollable row, which is the only way left to reach the
   marks past the edge.
   ============================================================ */
.brands--noautoplay:not(.brands--enhanced) .brands__viewport{
  overflow-x:auto;
  -webkit-mask-image:none;mask-image:none;
}
.brands--noautoplay:not(.brands--enhanced) .brands__track--clone{display:none}

@media (prefers-reduced-motion:reduce){
  .brands__lane{animation:none}

  .brands:not(.brands--enhanced) .brands__viewport{
    overflow-x:auto;
    /* A visible edge fade on a scrollable row hides the very thing
       that signals it scrolls. */
    -webkit-mask-image:none;mask-image:none;
  }
  .brands:not(.brands--enhanced) .brands__track--clone{display:none}
}

/* ============================================================
   Breakpoints — canonical ladder from base.css.
   ============================================================ */

/* Phones. A slower strip, because the same linear speed across a
   375px viewport means a given mark is on screen for a third as long
   as it is at 1440 -- the band reads as flickering rather than
   scrolling. Narrower gap for the same reason the marks shrink.

   Scaled off --brand-speed-base rather than restated, so the
   Customizer's one number still governs both rungs. */
@media (width < 48rem){
  .brands__rail{
    --brand-speed:calc(var(--brand-speed-base) * .667);
    --brand-gap:clamp(1.25rem,5vw,2rem);
  }
}

/* ============================================================
   WCAG 2.2.2 (Pause, Stop, Hide) — where this stands

   The strip starts on its own and runs past five seconds, so it owes
   the visitor a mechanism to stop it. There is no longer a Pause
   button; what carries the criterion instead is:

     1. either arrow ends the auto-scroll permanently for the session
        (.brands--manual above). It is keyboard-reachable, touch-sized
        and always visible;
     2. hover and focus-within pause it while either is true;
     3. prefers-reduced-motion never starts it at all;
     4. the Customizer's autoplay switch turns it off site-wide.

   (1) is a real stop mechanism, but its buttons are labelled Previous
   and Next rather than Stop, so a visitor looking specifically for a
   way to halt the motion has to discover it. That is the one soft spot
   left and it is recorded in README known issues, with (4) as the
   one-click remedy for a site whose accessibility policy will not
   accept it.
   ============================================================ */

/* ============================================================
   Brand detail panels

   ORDER MATTERS, for the same reason it does in reveal.css.

   The panels are rendered VISIBLE by template-parts/brands.php. With no
   JavaScript they are ordinary page content under the strip, and each
   logo above is an in-page anchor to one of them -- so the write-ups are
   readable, linkable, and crawlable without a line of script running.
   brands.js is what collapses them, not what creates them.

   That inversion is the whole safety property. A panel cannot be
   "stuck closed" by a failed script, because a failed script never
   closes it in the first place.
   ============================================================ */

.brands__details{
  margin-block-start:var(--sp-7);
  display:grid;
  gap:var(--sp-5);
}

.brands__detail{
  position:relative;
  display:grid;
  gap:var(--sp-2) var(--sp-7);
  grid-template-columns:1fr;
  padding:var(--sp-6);
  background:var(--grey-100);
  border:1px solid var(--grey-300);
  border-radius:var(--radius-panel);
}

/* THE rule that makes the whole disclosure work.

   [hidden] is a UA style of display:none, and a UA style loses to any
   author declaration -- so .brands__detail{display:grid} above would keep
   every panel on screen the instant JavaScript set the attribute, and the
   band would render as a wall of every brand's write-up at once.

   Scoped to .brands--details-enhanced so it can only take effect once
   brands.js is actually running. footer.css documents the same trap for
   the same reason. */
.brands--details-enhanced .brands__detail[hidden]{display:none}

/* A panel addressed by fragment opens even in the enhanced state, so a
   copied link, a bookmark or a search result lands on readable content
   whether or not the script's own hash handling ran. */
.brands--details-enhanced .brands__detail:target{display:grid}

@media (width >= 48rem){
  .brands__detail{grid-template-columns:minmax(0,1fr) minmax(12rem,18rem)}
  .brands__detail-title,
  .brands__detail-tagline,
  .brands__detail-actions{grid-column:1/-1}
}

.brands__detail-title{
  margin:0;
  font-family:var(--font-head);
  font-size:var(--fs-2xl);
  font-weight:var(--fw-bold);
  letter-spacing:var(--tr-snug);
  line-height:var(--lh-head);
  color:var(--gps-black);
  /* Room for the close button, which is absolutely positioned into the
     panel's top-right corner. Without this a long brand name runs under it. */
  padding-inline-end:var(--sp-7);
}

.brands__detail-tagline{
  margin:0;
  color:var(--grey-600);
  font-size:var(--fs-sm);
  letter-spacing:var(--tr-caps);
  text-transform:uppercase;
}

.brands__detail-lede{
  margin:0 0 var(--sp-4);
  font-size:var(--fs-lg);
  line-height:var(--lh-body);
  color:var(--gps-black);
}

.brands__detail-body{font-size:var(--fs-base);line-height:var(--lh-body)}
.brands__detail-body > :first-child{margin-block-start:0}
.brands__detail-body > :last-child{margin-block-end:0}

.brands__detail-subhead{
  margin:0 0 var(--sp-3);
  font-size:var(--fs-xs);
  font-weight:var(--fw-bold);
  letter-spacing:var(--tr-caps);
  text-transform:uppercase;
  color:var(--grey-600);
}

.brands__detail-lines{margin:0;padding:0;display:grid;gap:var(--sp-2)}
.brands__detail-lines li{
  padding-block-end:var(--sp-2);
  border-block-end:1px solid var(--grey-300);
  font-size:var(--fs-sm);
}
.brands__detail-lines li:last-child{border-block-end:0;padding-block-end:0}

.brands__detail-actions{
  margin:var(--sp-4) 0 0;
  display:flex;
  flex-wrap:wrap;
  gap:var(--sp-4);
  align-items:center;
}

.brands__detail-more{
  font-size:var(--fs-sm);
  font-weight:var(--fw-med);
  color:var(--gps-red);
}

/* Ships with the `hidden` attribute and is revealed by brands.js. Without
   script the panel is permanently open, and a Close button that cannot
   close anything is worse than no button at all. Declared explicitly
   because [hidden] would otherwise lose to the display:grid below it. */
.brands__detail-close[hidden]{display:none}

.brands__detail-close{
  position:absolute;
  inset-block-start:var(--sp-3);
  inset-inline-end:var(--sp-3);
  display:grid;
  place-items:center;
  /* 2.75rem = 44px: WCAG 2.5.5 AAA, not merely the 24px AA floor. */
  inline-size:2.75rem;
  block-size:2.75rem;
  padding:0;
  background:transparent;
  border:1px solid transparent;
  border-radius:50%;
  color:var(--grey-600);
  cursor:pointer;
}
.brands__detail-close:hover,
.brands__detail-close:focus-visible{
  color:var(--gps-black);
  border-color:var(--grey-300);
  background:var(--gps-white);
}
.brands__detail-close-ico{inline-size:1.25rem;block-size:1.25rem}

/* The logo, once it is a disclosure control rather than an outbound link. */
.brand__toggle{
  display:block;
  border:0;
  background:none;
  cursor:pointer;
}
.brands--details-enhanced .brand__toggle[aria-expanded="true"] .brand__mark,
.brands--details-enhanced .brand__toggle[aria-expanded="true"] .brand__img{
  opacity:1;
}
