/* ===========================================================================
   PROJECT SLIDER + PROJECT POPUP
   The block shape is the client's sketch: supporting shots stacked down the
   left, the finished photograph large on the right.
   =========================================================================== */

.pslider{padding:82px 0 90px;background:#E9ECF4;overflow:hidden}

.pslider-head{
  display:flex;align-items:flex-end;justify-content:space-between;gap:36px;
  margin-bottom:32px;
}
.pslider-head h2{
  font-family:var(--font-head);font-weight:900;
  font-size:clamp(30px,3vw,42px);line-height:1.14;letter-spacing:-.028em;
  color:var(--ink);margin:15px 0 12px;
}
.pslider-head p{font-size:16px;line-height:1.7;color:var(--ink-body);max-width:56ch}

.pslider-note{
  display:flex;align-items:center;gap:9px;flex:none;padding-bottom:6px;
  font-family:var(--font-head);font-weight:600;
  font-size:13.5px;letter-spacing:.02em;color:var(--ink-muted);white-space:nowrap;
}
.pslider-dot{
  width:7px;height:7px;border-radius:50%;background:var(--live);
  animation:pslide-blink 2.4s ease-in-out infinite;
}
@keyframes pslide-blink{0%,100%{opacity:1}50%{opacity:.25}}
@media (prefers-reduced-motion:reduce){.pslider-dot{animation:none}}

/* ---- the track ----
   A transform marquee, not a scroller. Driving scrollLeft from requestAnimation-
   Frame meant a layout and paint on the main thread every frame with 60+ blocks
   in the row, and it visibly stuttered. A single translateX animation is handed
   to the compositor and runs off the main thread, so it stays smooth no matter
   what else the page is doing — and pausing is one property, not a state flag.

   The set is rendered twice and the animation runs to exactly -50%, which lands
   on the start of the second copy. Gutters are item margins rather than flex
   gap: with gap, the track is wider than twice the set and -50% misses the seam. */
.pslider-rail{
  margin-right:calc(var(--pad) * -1);
  overflow:hidden;
  /* the row is clipped mid-card at both ends; a hard cut reads as a broken
     block, a fade reads as more of the row continuing past the edge */
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 54px,#000 calc(100% - 54px),transparent 100%);
          mask-image:linear-gradient(90deg,transparent 0,#000 54px,#000 calc(100% - 54px),transparent 100%);
}
.ptrack{
  display:flex;width:max-content;
  padding:4px 0 22px;
  animation:pslide-marquee 420s linear infinite;
  will-change:transform;
}
/* The whole point of the request: it stops under the cursor.

   It used to pause on :focus-within too, which looked equivalent and was not.
   Closing the project popup restores focus to whatever opened it — a slide
   inside this rail — so :focus-within stayed true after the popup closed and
   the marquee never started again. :has(:focus-visible) keeps the pause for
   someone tabbing through the slides, where it is genuinely needed, and
   ignores focus that was restored after a mouse click. */
.pslider-rail:hover .ptrack{animation-play-state:paused}
.pslider-rail:has(:focus-visible) .ptrack{animation-play-state:paused}
@keyframes pslide-marquee{
  from{transform:translate3d(0,0,0)}
  to  {transform:translate3d(-50%,0,0)}
}
@media (prefers-reduced-motion:reduce){
  .ptrack{animation:none}
  .pslider-rail{overflow-x:auto}
}

/* Three across, sized off the shell rather than off a percentage of the track —
   the track is now twice the content width, so a percentage would halve them.
   The gutter is an item margin, not a flex gap: with gap the row is wider than
   twice the set and the -50% seam lands in the wrong place. */
.pslide{
  --pslide-gap:26px;
  position:relative;
  flex:0 0 calc((min(100vw, 1400px) - (var(--pad) * 2) - (var(--pslide-gap) * 2)) / 3);
  margin-right:var(--pslide-gap);
  display:grid;grid-template-columns:118px 1fr;gap:12px;
  height:330px;
  padding:12px;border-radius:20px;background:#fff;
  /* Each block needs a visible edge of its own. It had none: white on #F4F5F8
     is a two percent step, and a 40px blur pulled in by a -26px spread leaves
     nothing behind, so three blocks in a row merged into one white strip with
     photographs apparently floating loose in it.

     Three layers, in the order the eye reads them: a hairline that draws the
     boundary, a short contact shadow that lifts the block off the ground, and
     a wide ambient one that gives it depth. */
  box-shadow:
    inset 0 0 0 1px rgba(14,30,99,.10),
    0 1px 2px rgba(14,30,99,.06),
    0 12px 26px -14px rgba(14,30,99,.28);
  transition:box-shadow .35s ease,transform .35s cubic-bezier(.22,1,.36,1);
  cursor:pointer;
}
/* under the cursor the hairline turns brand red, so the block you are about to
   click identifies itself rather than merely rising slightly */
.pslide:hover{
  transform:translateY(-4px);
  box-shadow:
    inset 0 0 0 1px rgba(237,28,36,.42),
    0 2px 4px rgba(14,30,99,.07),
    0 26px 48px -22px rgba(14,30,99,.40);
}
/* the whole block is the target; this is the focusable, screen-reader-visible
   control that makes that true for the keyboard as well as the mouse */
.pslide-hit{position:absolute;inset:0;z-index:3;border-radius:20px;background:transparent}
.pslide-hit:focus-visible{outline:2px solid var(--red);outline-offset:2px}

.pslide-side{display:flex;flex-direction:column;gap:12px;min-height:0}
.pshot{
  position:relative;flex:1 1 0;min-height:0;
  border-radius:13px;overflow:hidden;background:var(--tint);
}
.pshot img{display:block;width:100%;height:100%;object-fit:cover;transition:transform .6s cubic-bezier(.22,1,.36,1)}
.pslide:hover .pshot img{transform:scale(1.05)}
.pshot-tag{
  position:absolute;left:7px;bottom:7px;
  padding:3px 8px;border-radius:999px;
  background:rgba(8,16,48,.82);
  font-family:var(--font-head);font-weight:700;
  font-size:9.5px;letter-spacing:.07em;text-transform:uppercase;color:#fff;
}
/* A job with only one photograph has nothing to stack, so it drops the side
   column and the photograph runs the full width of the block. */
.pslide--solo{grid-template-columns:1fr}

.pslide-main{
  position:relative;min-width:0;
  border-radius:14px;overflow:hidden;background:var(--navy);
}
/* A pale photograph bleeds straight into the white card around it, so each
   tile gets its own hairline. It has to be a pseudo-element: an inset shadow
   on the tile paints underneath its content, and the image fills the box
   completely, so the ring would never be seen. */
.pshot::after,
.pslide-main::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  border-radius:inherit;
  box-shadow:inset 0 0 0 1px rgba(14,30,99,.09);
}
.pslide-main img{display:block;width:100%;height:100%;object-fit:cover;transition:transform .7s cubic-bezier(.22,1,.36,1)}
.pslide:hover .pslide-main img{transform:scale(1.05)}

/* scrim only where the type sits, so the top of the photograph stays clean */
.pslide-cap{
  position:absolute;left:0;right:0;bottom:0;
  padding:46px 16px 15px;
  background:linear-gradient(180deg,transparent,rgba(8,16,48,.8) 48%,rgba(8,16,48,.94));
  color:#fff;
}
.pslide-cat{
  display:inline-block;margin-bottom:8px;
  padding:4px 11px;border-radius:999px;background:var(--red);
  font-family:var(--font-head);font-weight:700;
  font-size:10px;letter-spacing:.07em;text-transform:uppercase;color:#fff;
}
.pslide-cap b{
  display:block;
  font-family:var(--font-head);font-weight:800;
  font-size:16.5px;line-height:1.25;letter-spacing:-.015em;color:#fff;
}
.pslide-more{
  display:block;margin-top:5px;
  font-size:12.5px;color:rgba(255,255,255,.7);
}
.pslide-more i{font-style:normal;margin:0 3px;color:var(--red)}

.pslide-open{
  position:absolute;top:12px;right:12px;z-index:2;
  display:grid;place-items:center;
  width:36px;height:36px;border-radius:50%;
  background:rgba(255,255,255,.92);color:var(--navy);
  opacity:0;transform:scale(.8);
  transition:opacity .25s ease,transform .3s cubic-bezier(.22,1,.36,1),background .2s ease,color .2s ease;
}
.pslide:hover .pslide-open{opacity:1;transform:none}
.pslide:hover .pslide-open{background:var(--red);color:#fff}

/* ---- footer bar --------------------------------------------------------- */
.pslider-foot{display:flex;justify-content:center;margin-top:10px}

@media (max-width:1100px){
  .pslide{flex-basis:calc((min(100vw,1400px) - (var(--pad) * 2) - var(--pslide-gap)) / 2);grid-template-columns:104px 1fr}
  .pslide--solo{grid-template-columns:1fr}
}
@media (max-width:760px){
  .pslider{padding:56px 0 62px}
  .pslider-head{flex-direction:column;align-items:flex-start;gap:20px}
  .pslider-note{order:2}
  .pslide{flex-basis:calc(100vw - (var(--pad) * 2) - 34px);height:290px;grid-template-columns:92px 1fr}
  .pslide--solo{grid-template-columns:1fr}
  .pslide-cap b{font-size:15px}
}

/* =========================================================
   THE POPUP
   ========================================================= */
.pmodal{position:fixed;inset:0;z-index:240;display:grid;place-items:center;padding:26px}
.pmodal[hidden]{display:none}
.pmodal-veil{
  position:absolute;inset:0;
  background:rgba(6,12,36,.9);backdrop-filter:blur(5px);
  opacity:0;transition:opacity .24s ease;
}
.pmodal.is-open .pmodal-veil{opacity:1}

.pmodal-card{
  position:relative;z-index:1;
  display:grid;grid-template-columns:1.35fr 1fr;
  width:min(1180px,100%);max-height:calc(100vh - 52px);
  border-radius:22px;overflow:hidden;background:#fff;
  box-shadow:0 60px 120px -46px rgba(0,0,0,.92);
  opacity:0;transform:scale(.975) translateY(8px);
  transition:opacity .24s ease,transform .34s cubic-bezier(.22,1,.36,1);
}
.pmodal.is-open .pmodal-card{opacity:1;transform:none}

.pmodal-stage{position:relative;background:#0A1748;display:grid;place-items:center;min-height:0;overflow:hidden}
.pmodal-stage img{
  display:block;width:100%;height:100%;object-fit:contain;max-height:calc(100vh - 52px);
  /* The stage animates between photographs rather than swapping src outright.
     projects.js adds .is-out-* to slide the current one away, waits for the
     next file to decode, then adds .is-in-* so it arrives from the side it
     came from. Opacity and transform only, so it runs on the compositor. */
  transition:opacity .22s ease, transform .42s cubic-bezier(.22,1,.36,1);
  will-change:opacity, transform;
}
/* leaving: the current photograph gets out of the way while the next one loads */
.pmodal-stage img.is-out-l{opacity:0;transform:translateX(-4%) scale(.985)}
.pmodal-stage img.is-out-r{opacity:0;transform:translateX(4%)  scale(.985)}

/* Arriving is a keyframe rather than a class the script removes a frame later.
   A keyframe finishes at the element's own resting style, so the photograph is
   visible and centered even if the class is never taken off — the earlier
   version left it stuck at opacity 0 when the frame callback did not run. */
.pmodal-stage img.is-swap-r{animation:pmSwapR .46s cubic-bezier(.22,1,.36,1)}
.pmodal-stage img.is-swap-l{animation:pmSwapL .46s cubic-bezier(.22,1,.36,1)}
.pmodal-stage img.is-swap  {animation:pmSwapIn .5s cubic-bezier(.22,1,.36,1)}
@keyframes pmSwapR{from{opacity:0;transform:translateX(6%)  scale(.985)} to{opacity:1;transform:none}}
@keyframes pmSwapL{from{opacity:0;transform:translateX(-6%) scale(.985)} to{opacity:1;transform:none}}
@keyframes pmSwapIn{from{opacity:0;transform:scale(1.03)}   to{opacity:1;transform:none}}
.pmodal-tag{
  position:absolute;left:16px;top:16px;
  padding:6px 13px;border-radius:999px;background:var(--red);
  font-family:var(--font-head);font-weight:700;
  font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#fff;
}
.pmodal-nav{
  position:absolute;top:50%;margin-top:-23px;
  display:grid;place-items:center;
  width:46px;height:46px;border-radius:50%;
  background:rgba(255,255,255,.12);color:#fff;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.18);
  transition:background .2s ease,transform .25s cubic-bezier(.22,1,.36,1);
}
.pmodal-nav:hover{background:var(--red);box-shadow:inset 0 0 0 1px var(--red)}
.pmodal-prev{left:16px}
.pmodal-next{right:16px}
.pmodal-prev:hover{transform:translateX(-3px)}
.pmodal-next:hover{transform:translateX(3px)}

.pmodal-body{
  display:flex;flex-direction:column;
  padding:34px 34px 30px;min-height:0;overflow-y:auto;overscroll-behavior:contain;
}
.pmodal-body h2{
  font-family:var(--font-head);font-weight:900;
  font-size:clamp(24px,2.2vw,31px);line-height:1.16;letter-spacing:-.028em;
  color:var(--ink);margin:14px 0 14px;
}
.pmodal-body > p{font-size:15.5px;line-height:1.72;color:var(--ink-body)}
/* .pmodal-body is a flex column, so the chip stretched to the full width of
   the panel instead of sitting to its own text. */
.pmodal-body .pslide-cat{align-self:flex-start;background:var(--tint-red-sm);color:var(--red)}

/* The whole set, including the shots the block on screen could not carry.
   Sits below a rule with its own label, so it reads as a filmstrip belonging to
   the job rather than three loose squares floating under the paragraph. */
/* flex:none on all three. .pmodal-body is a flex column, so these were
   shrinkable items — when the panel ran long the browser squeezed the rail to
   70px against 77px of content and every thumbnail hung out of the bottom of
   its own container. The panel scrolls; these do not compress. */
.pmodal-strip-h{
  flex:none;
  display:flex;align-items:baseline;gap:10px;
  margin:26px 0 9px;padding-top:22px;
  border-top:1px solid var(--line);
}
.pmodal-strip-h b{
  font-family:var(--font-head);font-weight:800;
  font-size:12px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink);
}
.pmodal-strip-h em{font-style:normal;font-size:12.5px;color:var(--ink-muted)}

/* One row, always — a set of ten photographs stacked into three rows pushed
   everything else down the panel.

   The original version was also a single row, but with `overflow-x:auto` and
   the scrollbar hidden, so the last frame was sliced at the edge with nothing
   to say it could be scrolled. That is the part that was actually broken, and
   it is fixed by the arrows either side rather than by wrapping: they only
   appear when the row really does overflow, and each switches off at its end. */
/* The padding is doing real work: the current frame lifts 3px and carries a
   2px ring drawn outside its box, and at 3px of padding both were being
   sheared off by the scroll container. */
.pmodal-strip{
  flex:none;
  display:flex;gap:9px;
  overflow-x:auto;overscroll-behavior-x:contain;
  /* deliberately NOT scroll-behavior:smooth — where the smooth scroll does not
     advance, the arrows silently do nothing, and an arrow that does nothing is
     worse than one that jumps */
  /* no scroll snapping: it left the rail resting at 7px instead of 0, which is
     what several of the earlier off-by-a-few-pixels problems came down to, and
     it fights a scrollbar being dragged by hand */
  padding:4px 4px 12px;
  margin:0 -4px 4px;

  /* The scrollbar is the point, not an accident. It is the thing that tells a
     visitor there are more photographs than the rail is showing, and unlike a
     pair of arrow buttons it needs no script, drags, and already works with a
     trackpad, a wheel and a touch screen. Firefox gets the two-property form;
     the ::-webkit- rules below cover Chrome, Edge and Safari. */
  scrollbar-width:thin;
  scrollbar-color:var(--navy) rgba(14,30,99,.12);
}
.pmodal-strip::-webkit-scrollbar{height:7px}
.pmodal-strip::-webkit-scrollbar-track{
  background:rgba(14,30,99,.10);border-radius:99px;
}
.pmodal-strip::-webkit-scrollbar-thumb{
  background:var(--navy);border-radius:99px;
}
.pmodal-strip::-webkit-scrollbar-thumb:hover{background:var(--red)}



.pmodal-thumb{
  position:relative;display:block;
  flex:0 0 84px;height:63px;          /* 4:3, fixed so the row cannot reflow */
  border-radius:12px;overflow:hidden;background:var(--tint);
  /* the ring is drawn outside the box so it never eats into the photograph */
  box-shadow:0 0 0 1px var(--line), 0 6px 14px -10px rgba(14,30,99,.7);
  transition:box-shadow .24s ease,transform .3s cubic-bezier(.22,1,.36,1);

  /* each frame arrives a beat after the one before it, so the set assembles
     rather than appearing all at once. --n is set per button in projects.js. */
  animation:pmThumbIn .5s cubic-bezier(.22,1,.36,1) backwards;
  animation-delay:calc(var(--n, 0) * 45ms);
}
@keyframes pmThumbIn{
  from{opacity:0;transform:translateY(10px) scale(.94)}
  to  {opacity:1;transform:none}
}
/* contain, not cover. These are index frames — you are choosing between them, so
   each one has to show the whole photograph. Cropping to fill made a portrait
   flue shot unrecognisable from its own thumbnail. The tint behind is what the
   letterboxing lands on, so it reads as a frame rather than as a mistake. */
.pmodal-thumb img{
  display:block;width:100%;height:100%;object-fit:contain;
  /* unselected frames step back so the current one is obvious at a glance */
  filter:saturate(.72) brightness(.9);
  transition:filter .3s ease;
}
/* hover is navy, not red — red is reserved for the frame you are looking at,
   and two red rings on screen at once read as two selections */
.pmodal-thumb:hover{box-shadow:0 0 0 1.5px var(--navy),0 10px 20px -12px rgba(14,30,99,.85)}
.pmodal-thumb:hover img{filter:none}

/* The current frame used to be marked with a ring drawn outside its box plus a
   3px lift. Inside a scroll container both get sheared off at the edges — the
   red ring was visibly cut. The ring is now drawn inside the frame, so there is
   nothing outside the element for the container to clip, and the lift is gone
   with it. */
.pmodal-thumb.is-on{
  box-shadow:0 0 0 1px var(--line), 0 8px 18px -12px rgba(237,28,36,.9);
}
.pmodal-thumb.is-on::before{
  content:"";position:absolute;inset:0;z-index:1;
  border:2.5px solid var(--red);border-radius:12px;
  pointer-events:none;
}
.pmodal-thumb.is-on img{filter:none}
/* a red bar along the foot of the current frame, so the active state still
   reads if the photograph behind it happens to be red */
.pmodal-thumb.is-on::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:3px;
  background:var(--red);
}
.pmodal-thumb:focus-visible{outline:2px solid var(--navy);outline-offset:3px}

/* the badge sits on the foot of the frame, sized to the frame rather than to
   the word — at the old size it covered a third of the photograph */
.pmodal-thumb span{
  position:absolute;left:0;right:0;bottom:0;
  padding:2px 0;text-align:center;
  background:rgba(8,16,48,.82);
  font-family:var(--font-head);font-weight:700;
  font-size:8px;letter-spacing:.06em;text-transform:uppercase;color:#fff;
}
.pmodal-thumb.is-on span{bottom:0}

.pmodal-strip-h[hidden],.pmodal-strip[hidden]{display:none}
/* Pinned to the foot of the panel rather than sitting after the photographs.
   The strip is two rows now, which pushed these below the fold of a panel that
   scrolls with no visible scrollbar — so the two things the visitor is meant to
   do next were off screen with nothing to say so. Sticky keeps them in view and
   the panel still scrolls behind them. */
.pmodal-act{
  flex:none;
  position:sticky;bottom:0;z-index:2;
  margin-top:auto;display:flex;gap:12px;flex-wrap:wrap;
  padding:14px 0 0;background:#fff;
}
/* a short fade so a photograph scrolling under the bar does not butt into it */
.pmodal-act::before{
  content:"";position:absolute;left:0;right:0;bottom:100%;height:20px;
  background:linear-gradient(to top,#fff,rgba(255,255,255,0));
  pointer-events:none;
}
.pmodal-act .btn{flex:1 1 auto;justify-content:center}
.pmodal-act .btn--white{box-shadow:inset 0 0 0 1.5px var(--line)}
/* The call button is the site's own, unchanged.

   It was stretched to full width, centered, and given a red disc — which made
   it a fourth variant of a button that already exists in three places. It now
   carries the exact contract a service card sets: navy pill, light blue disc,
   navy glyph, hugging its own content and sitting left. The only thing
   overridden is .scard-btn's own `margin:auto 0 24px`, which is there to push
   it to the foot of a card and has no job inside this panel. */
.pmodal-act .pmodal-call{
  margin:0;
  --card-ink:var(--navy);
  --card-ico-bg:var(--tint);
  --card-ico:var(--navy);
}
/* Both actions hug their own content and sit left, the way every other button
   pair on the site does. Stretching the red one to the full panel made it the
   loudest thing in the popup and left the call button looking like an
   afterthought parked underneath it. */
.pmodal-act{flex-direction:column;align-items:flex-start}
.pmodal-act .btn{width:auto;flex:0 0 auto;padding:0 28px}
/* one height for both, set in one place so they cannot drift apart */
.pmodal-act .btn,
.pmodal-act .pmodal-call{height:58px}

.pmodal-x{
  position:absolute;top:14px;right:14px;z-index:3;
  display:grid;place-items:center;
  width:42px;height:42px;border-radius:50%;
  background:rgba(255,255,255,.94);color:var(--navy);
  box-shadow:0 8px 20px -10px rgba(8,16,48,.7);
  transition:background .2s ease,color .2s ease;
}
.pmodal-x:hover{background:var(--red);color:#fff}

@media (max-width:940px){
  .pmodal{padding:0}
  .pmodal-card{
    grid-template-columns:1fr;grid-template-rows:auto 1fr;
    width:100%;max-height:100vh;border-radius:0;
  }
  .pmodal-stage{max-height:44vh}
  .pmodal-stage img{max-height:44vh}
  .pmodal-body{padding:22px 20px 26px}
  .pmodal-nav{width:40px;height:40px;margin-top:-20px}
}

@media (prefers-reduced-motion:reduce){
  .pslide,.pslide img,.pmodal-card,.pmodal-veil{transition:none}
  .pslide:hover{transform:none}
  .pslide:hover .pshot img,.pslide:hover .pslide-main img{transform:none}
  .pmodal-card{transform:none}
}

/* Anyone who has asked their system for less motion gets the same popup with
   the movement taken out — the photographs still change, they just do not
   travel or stagger in. */
@media (prefers-reduced-motion: reduce){
  .pmodal-thumb{animation:none}
  .pmodal-stage img{transition:opacity .15s linear}
  .pmodal-stage img.is-out-l,
  .pmodal-stage img.is-out-r{transform:none}
  .pmodal-stage img.is-swap,
  .pmodal-stage img.is-swap-l,
  .pmodal-stage img.is-swap-r{animation-duration:.18s;animation-name:pmSwapIn}
}

/* On a short window the popup has to give height back from somewhere, and
   padding is the honest place to take it from — shrinking the photographs or
   hiding thumbnails would cost the visitor the thing they opened it for. */
@media (max-height:820px){
  .pmodal{padding:16px}
  .pmodal-card{max-height:calc(100dvh - 32px)}
  .pmodal-stage img{max-height:calc(100dvh - 32px)}
  .pmodal-body{padding:24px 28px 22px}
  .pmodal-body h2{font-size:clamp(21px,2vw,26px);margin:11px 0 11px}
  .pmodal-body > p{font-size:14.8px;line-height:1.65}
  .pmodal-strip-h{margin:18px 0 10px;padding-top:16px}
}
@media (max-height:680px){
  .pmodal-body{padding:20px 24px 18px}
  .pmodal-body > p{display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}
}

/* ---- marquee arrows -------------------------------------------------------
   Placed beside the "N jobs" line rather than over the rail. The rail pauses
   on hover, so a control sitting on top of it would stop the motion it exists
   to advance — and it would cover a card while doing it. */
.pslider-meta{display:flex;align-items:center;gap:18px;flex:none}
.pslider-nav{display:flex;gap:8px}
.pslider-nav[hidden]{display:none}
.pslider-arrow{
  display:grid;place-items:center;
  width:38px;height:38px;border-radius:50%;
  background:var(--navy);color:#fff;
  box-shadow:0 2px 14px -6px rgba(14,30,99,.9);
  transition:background .2s ease,transform .25s cubic-bezier(.22,1,.36,1);
}
.pslider-arrow:hover{background:var(--red)}
.pslider-arrow:first-child:hover{transform:translateX(-2px)}
.pslider-arrow:last-child:hover{transform:translateX(2px)}
.pslider-arrow:active{transform:scale(.94)}
.pslider-arrow:focus-visible{outline:2px solid var(--red);outline-offset:3px}

/* The cue. The jump itself is done by seeking the animation in projects.js;
   this only makes it read as movement rather than as a cut. It runs on the
   wrapper because the track is already animating, and it ends on its own — so
   if it never runs, nothing is broken and the rail has still moved. */
.ptrack-wrap{will-change:transform}
@keyframes pnudge-next{
  0%  {transform:translate3d(34px,0,0)}
  100%{transform:none}
}
@keyframes pnudge-prev{
  0%  {transform:translate3d(-34px,0,0)}
  100%{transform:none}
}
.ptrack-wrap.is-pnext{animation:pnudge-next .44s cubic-bezier(.22,1,.36,1)}
.ptrack-wrap.is-pprev{animation:pnudge-prev .44s cubic-bezier(.22,1,.36,1)}

@media (prefers-reduced-motion:reduce){
  .ptrack-wrap.is-pnext,.ptrack-wrap.is-pprev{animation:none}
}
@media (max-width:760px){
  .pslider-meta{gap:12px}
  .pslider-arrow{width:34px;height:34px}
}

/* ---- one or two jobs: a row, not a marquee -------------------------------
   A service with fewer than three jobs has nothing to scroll to. The rail was
   still a marquee, so it slid a duplicate of the single card across a screen
   of empty space. Below three it lays out as a plain grid: no drift, no edge
   fade, no negative margin pulling it off the right of the shell. */
.pslider--still .pslider-rail{
  margin-right:0;
  overflow:visible;
  -webkit-mask-image:none;
          mask-image:none;
}
.pslider--still .ptrack{
  display:grid;
  gap:26px;
  width:auto;
  padding:4px 0 8px;
  animation:none;
}
/* grid items ignore flex sizing, but the margin is an item margin and would
   still add a phantom gutter on the right of each card */
.pslider--still .pslide{margin-right:0}

/* Only a single job is ever still now — two or more repeat into a continuous
   marquee. A lone card filling the whole shell would be very wide and only
   330px tall, so it takes a comfortable reading width and sits under the
   heading rather than floating in the middle of the row. */
.pslider--still .ptrack{grid-template-columns:minmax(0,760px)}

@media (max-width:760px){
  .pslider--still .ptrack{grid-template-columns:minmax(0,1fr)}
}
