/* THE GENESYS redesign - loading, busy and progress feedback.
   ==========================================================================

   WHY THIS FILE EXISTS

   The site had no visible waiting state anywhere. Not because nothing was
   instrumented - WooCommerce instruments itself thoroughly - but because the
   parent theme turns the instrumentation off:

       genesys/style.css:6295   .blockUI { display: none !important; }

   jQuery blockUI is what WooCommerce uses for every one of its own waits: the
   cart while it recalculates, the totals card, the checkout review column on
   `update_checkout`, and the whole checkout form while the order is being
   placed. One rule hides all of it, so "Place order" looked like it did
   nothing for as long as the gateway took to answer, and pressing it twice was
   possible because the overlay that would have swallowed the second click was
   `display: none`.

   Rather than un-hide blockUI - its default look is a white box with a black
   border, wrong on this palette in both themes - this file drives the same
   states off `.processing`, which cart.js and checkout.js set on whatever they
   are about to block. That class is part of WooCommerce's public behaviour and
   is set by core, not by us.

   (Its sibling `.loading`, which add-to-cart.js puts on an AJAX add-to-cart
   button, is deliberately NOT styled here: no such button is rendered anywhere
   on this site. Every add to cart is either a form POST from the product page
   or a link to it - the redesigned card in woocommerce/content-product.php
   never fires `woocommerce_after_shop_loop_item`. A rule for it would be a
   rule nothing can reach.)

   On top of that, assets/js/loading.js adds two affordances of our own - a top
   progress bar and a `.gns-busy` state for a control that has been pressed -
   and wires them to navigation, form submission, jQuery AJAX and fetch, so the
   whole site reports back rather than only the WooCommerce screens.

   Loaded globally next to polish.css. Both are cross-cutting guarantees, and
   for the same reason: `gns-base.css` is written `:where(.gns-scope) ...` and
   only about a quarter of the site carries that wrapper.
   ====================================================================== */

/* ========================================================================
   1. Top progress bar
   ---------------------------------------------------------------------
   One bar for the whole document, created once by loading.js. It reports
   "something is happening" for navigations and for AJAX that has no better
   home; anything with a specific place on screen gets a local treatment
   further down instead.
   ===================================================================== */

.gns-progress {
  position: fixed;
  inset: 0 0 auto 0;
  height: 3px;
  /* Above the parent's fixed .site-header (style.css:228, z-index 20) and
     above anything else on the page: the highest z-index in either theme is
     the parent's 9999. */
  z-index: 100000;
  pointer-events: none;
  opacity: 0;
  transition: opacity .2s ease;
}

.gns-progress.is-on { opacity: 1; }

.gns-progress__bar {
  height: 100%;
  width: 100%;
  transform-origin: 0 50%;
  background: var(--gns-gradient);
  box-shadow: 0 0 12px rgba(0, 191, 255, .55);
  /*
   * Indeterminate: we cannot know how far a navigation has got, so loading.js
   * eases the bar towards 90% and holds it there. The progress is driven by
   * scaleX from script rather than by a keyframe animation, so
   * `prefers-reduced-motion` - which polish.css flattens to .001ms - still
   * leaves a bar that grows. It simply arrives instantly instead of easing.
   */
  transform: scaleX(0);
  transition: transform .45s var(--gns-ease-slice);
}

.gns-progress.is-done .gns-progress__bar {
  transform: scaleX(1);
  transition-duration: .18s;
}

/* ========================================================================
   2. A control that has been pressed
   ---------------------------------------------------------------------
   `.gns-busy` goes on the button or link the visitor actually clicked, so the
   feedback lands where their eye already is. loading.js swaps the label where
   a better one exists ("Placing your order...") and always adds the spinner.
   ===================================================================== */

.gns-busy {
  position: relative;
  pointer-events: none;
  cursor: progress;
}

/*
 * The spinner rides in ::before so the label stays where it was - nothing
 * reflows, the control does not change width, and a gradient CTA keeps its
 * background. Buttons on this site are flex or inline-flex almost everywhere,
 * which lays the pseudo-element out as a leading item; where they are not it
 * falls back to an inline box before the text, which reads the same.
 */
.gns-busy::before {
  content: '';
  display: inline-block;
  flex: none;
  width: 1em;
  height: 1em;
  margin-right: .55em;
  vertical-align: -.15em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: gns-spin .7s linear infinite;
  opacity: .9;
}

/*
 * `<input type="submit">` cannot hold a pseudo-element - it is a replaced
 * element with no content box to generate one. It gets the same ring painted
 * as a background instead, with room made on the left for it.
 */
input.gns-busy::before { content: none; }

input.gns-busy {
  padding-left: 2.6em;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='9' fill='none' stroke='currentColor' stroke-opacity='.3' stroke-width='3'/%3E%3Cpath d='M12 3a9 9 0 0 1 9 9' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='.7s' repeatCount='indefinite'/%3E%3C/path%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: 1em 50%;
  background-size: 1.1em 1.1em;
}

/* A busy control must not still look interactive. */
.gns-busy:hover,
.gns-busy:focus { transform: none; }

/* ========================================================================
   3. WooCommerce's own waits
   ---------------------------------------------------------------------
   `.processing` is set by cart.js and checkout.js on whatever they are about
   to block. These are the containers core actually blocks; naming them rather
   than styling `.processing` globally keeps the overlay off anything else that
   happens to reuse the word.

   `.gns-busy-host.is-busy` is the same treatment, opted into by our own code -
   see loading.js.
   ===================================================================== */

form.woocommerce-cart-form.processing,
div.cart_totals.processing,
form.checkout.processing,
#order_review.processing,
.woocommerce-checkout-review-order.processing,
.gns-busy-host.is-busy {
  /*
   * `position: relative` is safe on all of these: none of them is the sticky
   * element. In the cart the sticky box is `.gns-cart__side` (cart.css:202)
   * and `.cart_totals` is the card inside it; on the checkout it is the aside,
   * not `#order_review`.
   */
  position: relative;
  /*
   * Restores the interaction blocking that `.blockUI { display: none }` threw
   * away. Without it "Place order" stays clickable while the gateway is being
   * called, and a second press starts a second attempt.
   */
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
}

form.woocommerce-cart-form.processing::after,
div.cart_totals.processing::after,
form.checkout.processing::after,
#order_review.processing::after,
.woocommerce-checkout-review-order.processing::after,
.gns-busy-host.is-busy::after {
  /*
   * WARNING: `position: absolute` here is load-bearing, not decoration. A
   * ::before/::after inside a `display: grid` container becomes a GRID ITEM
   * and takes the first cell - the trap documented in shop.css and hit again
   * on `.col2-set` during the checkout rebuild. An absolutely positioned
   * pseudo-element is out of flow, so it is never a grid or flex item,
   * whatever the host turns out to be.
   */
  content: '';
  position: absolute;
  inset: 0;
  z-index: 4;
  border-radius: inherit;
  /* --gns-veil, not --gns-scrim: the veil takes the page's own ground colour
     and inverts with the palette, so it recedes in both themes. See the note
     on the token in tokens.css. */
  background: var(--gns-veil);
}

form.woocommerce-cart-form.processing::before,
div.cart_totals.processing::before,
#order_review.processing::before,
.woocommerce-checkout-review-order.processing::before,
.gns-busy-host.is-busy::before {
  content: '';
  position: absolute;          /* see the note on ::after above */
  z-index: 5;
  top: 50%;
  left: 50%;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border: 3px solid var(--gns-border-strong);
  border-top-color: var(--gns-cyan);
  border-radius: 50%;
  animation: gns-spin .8s linear infinite;
}

/*
 * The checkout form is taller than the viewport on every screen size, so a
 * spinner centred on the ELEMENT lands far below the button that was pressed.
 * This one is pinned near the top of the viewport instead. `position: fixed`
 * keeps it out of flow for the same reason `absolute` does above.
 */
form.checkout.processing::before {
  content: '';
  position: fixed;
  z-index: 5;
  top: 50%;
  left: 50%;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border: 3px solid var(--gns-border-strong);
  border-top-color: var(--gns-cyan);
  border-radius: 50%;
  animation: gns-spin .8s linear infinite;
}

/* ========================================================================
   4. Shared placeholder shimmer
   ---------------------------------------------------------------------
   A light band travelling across a placeholder. It moves a background
   position rather than a child element, so it needs no clipping and works on
   a bare pseudo-element.

   ⚠️ MOVED HERE from shop.css, which declared it for the product-grid
   skeleton (`.gns-skeleton__*`, driven by load-more.js) before this file
   existed. The cart's line-subtotal placeholder wants the same treatment, and
   two identical @keyframes under one name is worse than one global
   declaration. shop.css still uses it; nothing there changed but the deletion.
   ===================================================================== */

@keyframes gns-shimmer {
  from { background-position: 100% 0; }
  to   { background-position: 0 0; }
}

/* ========================================================================
   5. Live region
   ---------------------------------------------------------------------
   Every visual state above has a spoken counterpart. The node is in the
   accessibility tree but not on screen: `display: none` and
   `visibility: hidden` both remove it from that tree, so the clip technique is
   the only one a screen reader still reads.
   ===================================================================== */

.gns-sr-status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ========================================================================
   6. Reduced motion
   ---------------------------------------------------------------------
   polish.css flattens every animation to .001ms for `prefers-reduced-motion`,
   which would leave the spinners above as motionless arcs. Each is replaced by
   something static that still reads as busy, and the text label - loading.js
   always sets one - carries the meaning regardless. The sweep in section 5 is
   switched off by its own consumer, cart.css.
   ===================================================================== */

@media (prefers-reduced-motion: reduce) {
  .gns-busy::before,
  .add_to_cart_button.loading::after,
  .ajax_add_to_cart.loading::after {
    /* A complete ring, rather than an arc frozen at an arbitrary angle. */
    border-top-color: currentColor !important;
    opacity: .5;
  }

  form.woocommerce-cart-form.processing::before,
  div.cart_totals.processing::before,
  form.checkout.processing::before,
  #order_review.processing::before,
  .woocommerce-checkout-review-order.processing::before,
  .gns-busy-host.is-busy::before {
    border-color: var(--gns-cyan) !important;
    opacity: .6;
  }

  .gns-progress__bar { box-shadow: none; }
}
