/* ==========================================================
   ht-acc / effects.css
   ht-kit: two effects.

     FX/THRESHOLD  PAGE · Considered · LOAD
                   First page of a session, once.
     FX/WIPE       ACTIONS · Quiet · HOVER
                   Every button, on hover and on focus.

   No Signature effect ships on this site and no page
   transition, so the Signature slot is open and unspent.
   FX/THRESHOLD is Considered and FX/WIPE is Quiet, so
   between them they take neither the slot nor more than one
   of the two-or-three Considered places.
   ========================================================== */

/* ─ FX/THRESHOLD ────────────────────────────────────────────
   The arming direction is the whole point, and it is the one
   thing that changed from the source.

   The source shipped the panel painted, covering the page,
   and relied on JavaScript running to take it away. Script
   off, file missing, an exception on line one, or a slow
   network and the visitor sat behind an opaque panel with the
   entire site underneath it and no way through.

   Here nothing is painted until js/modules/loader.js adds
   .is-preloading to the root. Every rule below is behind that
   class. There is no failure mode that hides content: the
   worst case is that the panel never appears, which is the
   site working normally.

   Reduced motion never arms it at all. The module returns
   before adding the class, so there is nothing to clear.
   ────────────────────────────────────────────────────────── */

.loader { display: none; }

html.is-preloading .loader {
  position: fixed;
  inset: 0;
  z-index: var(--z-panel);
  display: grid;
  place-items: center;
  background: var(--bg-panel);
  color: var(--text-inverse);
  transition: opacity var(--dur-preload-out) var(--ease),
              visibility var(--dur-preload-out) var(--ease);
}

html.is-preloading.is-clearing .loader {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* The page underneath must not scroll while the panel is up. */
html.is-preloading body { overflow: hidden; }

.loader__inner { display: grid; justify-items: center; gap: var(--sp-24); }

.loader__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--brand-mark);
  block-size: var(--brand-mark);
  border-radius: var(--radius-sm);
  background: var(--fill-accent-text);
  color: var(--text-inverse);
  font-size: var(--fs-h3);
  font-weight: var(--weight-bold);
}

.loader__word {
  font-size: var(--fs-h3);
  font-weight: var(--weight-bold);
  letter-spacing: var(--track-tight);
  color: var(--text-inverse);
}

html.is-preloading .loader__mark {
  animation: loader-pop var(--dur-preload-pop) var(--ease) both;
}

html.is-preloading .loader__word {
  animation: loader-pop var(--dur-preload-pop) var(--ease) var(--delay-preload-pop) both;
}

.loader__bar {
  position: relative;
  overflow: hidden;
  inline-size: var(--loader-bar-w);
  block-size: var(--loader-bar-h);
  border-radius: var(--radius-pill);
  background: var(--border-hairline);
}

.loader__bar i {
  position: absolute;
  inset-block: 0;
  inline-size: var(--loader-bar-run);
  border-radius: var(--radius-pill);
  background: var(--fill-accent);
}

html.is-preloading .loader__bar i {
  animation: loader-sweep var(--dur-preload-sweep) var(--ease) infinite;
}

@keyframes loader-pop {
  from { opacity: 0; translate: 0 var(--loader-pop-y); }
  to   { opacity: 1; translate: 0 0; }
}

@keyframes loader-sweep {
  0%   { inset-inline-start: calc(var(--loader-bar-run) * -1); }
  100% { inset-inline-start: 100%; }
}

/* Belt as well as braces. A browser that honours reduced
   motion but somehow reaches the armed state still gets no
   panel. */
@media (prefers-reduced-motion: reduce) {
  html.is-preloading .loader { display: none; }
  html.is-preloading body    { overflow: visible; }
}

/* ─ FX/WIPE ─────────────────────────────────────────────────
   ACTIONS family · Quiet tier · HOVER input.

   A solid block rises from the bottom edge of the button and
   the label inverts over it.

   Two things make it work and both are load bearing.

   The label sits in its own positive layer. A positioned
   pseudo-element paints above an element's own text, so the
   block would cover the label if the label were left as bare
   inline content. That is why every button carries a span:
   it is the only way to get the text above the block without
   a negative z-index, which kit effects rule 7 forbids
   because it would drop the block behind the card instead of
   behind the text.

   The hover opacity is retired while the wipe is live. The
   button's own :hover fades it to --hover-opacity, and a
   block rising behind a fading button reads as two things
   happening at once. Under reduced motion the wipe is removed
   and the opacity fade comes back, so a hover still says
   something.

   Transform only, so it stays on the compositor.
   ────────────────────────────────────────────────────────── */

.btn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--wipe-fill);
  transform: translate3d(0, 100%, 0);
  transition: transform var(--dur-wipe) var(--ease);
}

.btn > span {
  position: relative;
  z-index: 1;
  transition: color var(--dur-invert) linear var(--delay-invert-out);
}

.btn:hover > span,
.btn:focus-visible > span { transition-delay: var(--delay-invert-in); }

.btn:hover::before,
.btn:focus-visible::before { transform: translate3d(0, 0, 0); }

/* The wipe is the hover state now, so the fade stands down. */
.btn:hover,
.btn:focus-visible { opacity: 1; }

/* The soft button sits on a pale field, so it wipes to the accent and
   inverts its label to white. The solid button is already the accent,
   so it wipes to ink and keeps the label it has. */
.btn--soft::before { background: var(--wipe-fill-soft); }

.btn--soft:hover > span,
.btn--soft:focus-visible > span { color: var(--accent-ink); }

/* A disabled control does not respond to a pointer. */
.btn[disabled]::before,
.btn[aria-disabled="true"]::before { display: none; }

@media (prefers-reduced-motion: reduce) {
  .btn::before { display: none; }
  .btn--solid:hover,
  .btn--solid:focus-visible { opacity: var(--hover-opacity); }
  .btn--soft:hover,
  .btn--soft:focus-visible  { opacity: var(--hover-opacity-soft); }
}

/* ─ FX/WIPE on the ink field ────────────────────────────────
   A solid button standing on the ink panel cannot wipe to ink:
   the block matches the panel behind it, so the button appears
   to vanish and leaves its label floating. Measured before the
   fix, the closing call to action's block and its panel were
   the same #1C1C1E.

   On that field the wipe inverts to paper and the label goes
   dark, 17.01:1 either way. Three places qualify: the closing
   call to action, whose panel is always ink, the header once it
   has scrolled and turned ink itself, and the phone band's
   sticky call bar, which is ink at every scroll position. At the
   top of the page the header is still paper, so there it keeps
   the ordinary ink wipe: a white block on a white bar would lose
   the button's shape the same way. The fill follows the field,
   not the button. */

.cta .btn--solid::before,
.sticky-call .btn--solid::before,
.site-header.is-stuck .btn--solid::before { background: var(--wipe-fill-inverse); }

.cta .btn--solid:hover > span,
.cta .btn--solid:focus-visible > span,
.sticky-call .btn--solid:hover > span,
.sticky-call .btn--solid:focus-visible > span,
.site-header.is-stuck .btn--solid:hover > span,
.site-header.is-stuck .btn--solid:focus-visible > span { color: var(--ink); }

/* ─ FX/WIPE on the round arrow buttons ──────────────────────
   The service tile arrows and the carousel's previous and next
   controls, so every control on the site answers a hover the
   same way. The block rises inside the circle: overflow and the
   circular radius clip it, and the icon takes the same positive
   layer a button label does.

   The tile arrow is driven by the tile, not by itself. The
   whole card is the link and the arrow is decorative, so
   hovering anywhere on the card moves it. */

.tile__arrow,
.serve__btn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.tile__arrow::before,
.serve__btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--wipe-fill);
  transform: translate3d(0, 100%, 0);
  transition: transform var(--dur-wipe) var(--ease);
}

.tile__arrow > .icon,
.serve__btn > .icon {
  position: relative;
  z-index: 1;
  transition: color var(--dur-invert) linear var(--delay-invert-out);
}

.tile:hover .tile__arrow::before,
.tile:focus-visible .tile__arrow::before,
.serve__btn:hover::before,
.serve__btn:focus-visible::before { transform: translate3d(0, 0, 0); }

.tile:hover .tile__arrow > .icon,
.tile:focus-visible .tile__arrow > .icon,
.serve__btn:hover > .icon,
.serve__btn:focus-visible > .icon {
  color: var(--accent-ink);
  transition-delay: var(--delay-invert-in);
}

/* A disabled control does not answer a pointer. */
.serve__btn[disabled]::before { display: none; }

@media (prefers-reduced-motion: reduce) {
  .tile__arrow::before,
  .serve__btn::before { display: none; }
  .serve__btn:hover { opacity: var(--hover-opacity); }

  /* With the wipe gone there is nothing left on the card, so the fade
     comes back here and only here. A hover should still say something
     to somebody who has asked for less motion. */
  .tile:hover { opacity: var(--hover-opacity-soft); }
}

/* ─ no script ───────────────────────────────────────────────
   The carousel's buttons and progress bar do nothing without
   the module, so they do not appear. The rail is a native
   overflow-x scroller and keeps working on its own, which is
   why the cards themselves need no fallback.

   css/noscript.css covers the same case for browsers that do
   not support the scripting media feature.
   ────────────────────────────────────────────────────────── */

@media (scripting: none) {
  .serve__nav    { display: none; }
  .nav-hamburger { display: none; }
}

/* With no script there is no hamburger anywhere below 1100, so
   the call to action returns to the header rather than leaving it
   with only a wordmark, and Contact stays reachable without
   going to the footer for it. Loaded after layout.css, so it wins
   on order. */
@media (scripting: none) and (max-width: 1100px) {
  .site-header .btn { display: inline-flex; }
}

/* The tablet band hides its links in favour of the hamburger, and
   with no script there is no hamburger. There is room for the
   links at this width, which is what the band did before the
   switch moved up, so they come back rather than leaving the
   header with no navigation at all. */
@media (scripting: none) and (min-width: 761px) and (max-width: 1100px) {
  .navcap { display: flex; }
}
