/* ── Stepper ─────────────────────────────────────────────────────────
   Rendered by <app-stepper> (TagHelpers/AppStepperTagHelper.cs), driven by
   app-stepper.js. Everything paints from attributes the script maintains on
   each <li>: data-stepper-state is one of upcoming | current | complete |
   error, data-stepper-has-icon says the marker shows a glyph instead of its
   number, and the tab is [disabled] when the linear lock holds it.

   Colours are Bootstrap's own variables so the component follows the theme
   (dark by default here) without a second palette of its own.

   MOTION. Four things move, each for a reason a reader can feel:
     - the connector FILLS toward the step you just finished (progress);
     - the marker POPS when it turns complete (acknowledgement);
     - the current marker breathes a soft ring (where you are);
     - the panel slides in from the side you came from (direction).
   All of it is CSS keyed off classes the script adds for one beat, and all
   of it is switched off under prefers-reduced-motion. Nothing here is needed
   for the stepper to work. */

.app-stepper {
  --stepper-marker-size: 2rem;
  --stepper-line: var(--bs-border-color);
  --stepper-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --stepper-fill-duration: 0.45s;
  --stepper-pop-duration: 0.5s;
  --stepper-panel-duration: 0.32s;
}

/* ── The indicator ─────────────────────────────────────────────────── */

.app-stepper-nav {
  display: flex;
  align-items: flex-start;
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
  gap: 0;
}

.app-stepper-item {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
}

/* THE CONNECTOR is two layers on the item AFTER the first, so the row has no
   dangling stub at its start: ::before is the grey track, ::after the
   primary fill scaled from 0 to 1 along the direction of travel. The fill
   belongs to the step the line leads INTO and is drawn once the step BEFORE
   it is complete - so pressing Save on step 2 sweeps the line to step 3. */
.app-stepper-horizontal .app-stepper-item + .app-stepper-item::before,
.app-stepper-horizontal .app-stepper-item + .app-stepper-item::after {
  content: "";
  position: absolute;
  top: calc(var(--stepper-marker-size) / 2);
  right: calc(50% + var(--stepper-marker-size) / 2 + 0.25rem);
  left: calc(-50% + var(--stepper-marker-size) / 2 + 0.25rem);
  height: 2px;
  transform: translateY(-1px);
}

.app-stepper-horizontal .app-stepper-item + .app-stepper-item::before {
  background-color: var(--stepper-line);
}

.app-stepper-horizontal .app-stepper-item + .app-stepper-item::after {
  background-color: var(--bs-primary);
  transform: translateY(-1px) scaleX(0);
  transform-origin: left center;
  transition: transform var(--stepper-fill-duration) var(--stepper-ease);
}

/* DONE, not the painted state: a step you have come back to is painted
   "current" whatever else is true of it, and keying the fill off that left
   a grey line out of a finished step you were standing on while the line
   beyond it was blue - a break in the middle of the row. Seen on Account
   Setup 2026-09-11 after clicking back to Billing. */
.app-stepper-horizontal .app-stepper-item[data-stepper-done="true"] + .app-stepper-item::after {
  transform: translateY(-1px) scaleX(1);
}

/* The tab is a real <button>, reset to look like a label under a circle. */
.app-stepper-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  padding: 0 0.25rem;
  border: 0;
  background: transparent;
  color: var(--bs-secondary-color);
  text-align: center;
  cursor: pointer;
}

.app-stepper-tab:disabled {
  cursor: default;
  color: var(--bs-tertiary-color);
}

.app-stepper-tab:not(:disabled):hover .app-stepper-label {
  color: var(--bs-body-color);
}

/* A reachable marker lifts a touch under the pointer; a locked one does not
   respond at all, which is the affordance. */
.app-stepper-tab:not(:disabled):hover .app-stepper-marker {
  transform: translateY(-2px);
  border-color: var(--bs-primary);
}

.app-stepper-tab:focus-visible {
  outline: none;
}

.app-stepper-tab:focus-visible .app-stepper-marker {
  box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.35);
}

/* ── The marker ────────────────────────────────────────────────────── */

.app-stepper-marker {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--stepper-marker-size);
  height: var(--stepper-marker-size);
  border-radius: 50%;
  border: 2px solid var(--stepper-line);
  background-color: var(--bs-body-bg);
  color: var(--bs-secondary-color);
  font-size: 0.875rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  transition:
    background-color 0.25s ease,
    border-color 0.25s ease,
    color 0.25s ease,
    box-shadow 0.25s ease,
    transform 0.2s var(--stepper-ease);
}

/* All the glyphs are always in the markup; the state picks one. */
.app-stepper-marker .app-stepper-glyph-icon,
.app-stepper-marker .app-stepper-glyph-complete,
.app-stepper-marker .app-stepper-glyph-error {
  display: none;
  line-height: 1;
}

.app-stepper-marker .app-stepper-glyph-icon {
  font-size: 0.9375rem;
}

.app-stepper-marker .app-stepper-glyph-complete,
.app-stepper-marker .app-stepper-glyph-error {
  font-size: 1.125rem;
}

/* An icon stands in for the number while the step is ahead or open. */
.app-stepper-item[data-stepper-has-icon] .app-stepper-number {
  display: none;
}

.app-stepper-item[data-stepper-has-icon][data-stepper-state="upcoming"] .app-stepper-glyph-icon,
.app-stepper-item[data-stepper-has-icon][data-stepper-state="current"] .app-stepper-glyph-icon {
  display: inline;
}

.app-stepper-item[data-stepper-state="current"] .app-stepper-marker {
  border-color: var(--bs-primary);
  color: var(--bs-primary);
  background-color: var(--bs-body-bg);
  animation: app-stepper-breathe 2.4s ease-in-out infinite;
}

/* DONE AND CURRENT: filled, with the check, AND the current ring. Coming
   back to a finished step used to swap its check for the number or icon,
   so the tick you had earned vanished the moment you revisited the step -
   and with the line fix above, a grey marker sat between two blue lines.
   Being on a step does not undo it; only complete(id, false) does. */
.app-stepper-item[data-stepper-state="current"][data-stepper-done="true"] .app-stepper-marker {
  background-color: var(--bs-primary);
  color: #fff;
}

.app-stepper-item[data-stepper-state="current"][data-stepper-done="true"] .app-stepper-number,
.app-stepper-item[data-stepper-state="current"][data-stepper-done="true"] .app-stepper-glyph-icon {
  display: none;
}

.app-stepper-item[data-stepper-state="current"][data-stepper-done="true"] .app-stepper-glyph-complete {
  display: inline;
}

.app-stepper-item[data-stepper-state="complete"] .app-stepper-marker {
  border-color: var(--bs-primary);
  background-color: var(--bs-primary);
  color: #fff;
}

.app-stepper-item[data-stepper-state="complete"] .app-stepper-number,
.app-stepper-item[data-stepper-state="error"] .app-stepper-number {
  display: none;
}

.app-stepper-item[data-stepper-state="complete"] .app-stepper-glyph-complete {
  display: inline;
}

.app-stepper-item[data-stepper-state="error"] .app-stepper-marker {
  border-color: var(--bs-danger);
  background-color: var(--bs-danger);
  color: #fff;
}

.app-stepper-item[data-stepper-state="error"] .app-stepper-glyph-error {
  display: inline;
  animation: app-stepper-shake 0.4s var(--stepper-ease);
}

/* The pop when a step turns complete: the script adds .is-just-completed
   for one animation and removes it on animationend. The check rides in
   with a small spin so it reads as "stamped" rather than "swapped". */
.app-stepper-marker.is-just-completed {
  animation: app-stepper-pop var(--stepper-pop-duration) var(--stepper-ease);
}

.app-stepper-marker.is-just-completed .app-stepper-glyph-complete {
  animation: app-stepper-stamp var(--stepper-pop-duration) var(--stepper-ease);
}

@keyframes app-stepper-pop {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.28); box-shadow: 0 0 0 0.5rem rgba(var(--bs-primary-rgb), 0.28); }
  70%  { transform: scale(0.94); }
  100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(var(--bs-primary-rgb), 0); }
}

@keyframes app-stepper-stamp {
  0%   { transform: scale(0.4) rotate(-30deg); opacity: 0; }
  60%  { transform: scale(1.15) rotate(6deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}

@keyframes app-stepper-breathe {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--bs-primary-rgb), 0.45); }
  50%      { box-shadow: 0 0 0 0.375rem rgba(var(--bs-primary-rgb), 0); }
}

@keyframes app-stepper-shake {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-2px); }
  75%      { transform: translateX(2px); }
}

/* ── The text under the marker ─────────────────────────────────────── */

.app-stepper-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 0.5rem;
  min-width: 0;
  max-width: 100%;
}

.app-stepper-label {
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.25;
  /* break-word, not anywhere: anywhere lets a narrow column split a word
     at any letter, and at 768px with nine steps it showed "Organizatio /
     n". break-word only splits a word wider than the whole column, and
     hyphens marks the split when it must. */
  overflow-wrap: break-word;
  hyphens: auto;
  transition: color 0.25s ease;
}

.app-stepper-item[data-stepper-state="current"] .app-stepper-label {
  color: var(--bs-body-color);
  font-weight: 600;
}

.app-stepper-item[data-stepper-state="complete"] .app-stepper-label {
  color: var(--bs-body-color);
}

.app-stepper-item[data-stepper-state="error"] .app-stepper-label {
  color: var(--bs-danger);
}

.app-stepper-desc {
  font-size: 0.75rem;
  color: var(--bs-secondary-color);
  line-height: 1.25;
}

.app-stepper-desc:empty {
  display: none;
}

/* ── The panels ────────────────────────────────────────────────────── */

/* The entrance: a short slide from the side the reader came from, set by
   data-stepper-direction on the root, and a fade. Applied for one beat by
   .app-stepper-panel-enter; a panel that is simply shown by the server (the
   first paint) does not move. */
.app-stepper-panel.app-stepper-panel-enter {
  animation: app-stepper-panel-forward var(--stepper-panel-duration) var(--stepper-ease) both;
}

.app-stepper[data-stepper-direction="back"] .app-stepper-panel.app-stepper-panel-enter {
  animation-name: app-stepper-panel-back;
}

@keyframes app-stepper-panel-forward {
  from { opacity: 0; transform: translateX(1.25rem); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes app-stepper-panel-back {
  from { opacity: 0; transform: translateX(-1.25rem); }
  to   { opacity: 1; transform: translateX(0); }
}

/* ── The heading for the open step ─────────────────────────────────── */

/* "Step 1 of 5" as an overline, the step's name as the panel's title with
   its icon beside it. Rendered by the tag helper unless heading="false";
   the script rewrites both lines on every change. The block is the live
   region. On a phone, where the tab labels are hidden, this is the only
   place the name appears - which is why it is a heading and not the
   footnote it used to be. */
.app-stepper-heading {
  margin: 0 0 1rem;
}

.app-stepper-heading .app-stepper-progress {
  /* Undo the visually-hidden recipe below: inside the heading the count is
     meant to be seen. */
  position: static !important;
  width: auto !important;
  height: auto !important;
  margin: 0 0 0.125rem !important;
  overflow: visible !important;
  clip: auto !important;
  white-space: normal !important;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bs-secondary-color);
}

.app-stepper-title {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin: 0;
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.2;
}

.app-stepper-title-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.625rem;
  background-color: rgba(var(--bs-primary-rgb), 0.14);
  color: var(--bs-primary);
  font-size: 1.125rem;
}

.app-stepper-heading.app-stepper-heading-enter {
  animation: app-stepper-heading-in var(--stepper-panel-duration) var(--stepper-ease) both;
}

@keyframes app-stepper-heading-in {
  from { opacity: 0; transform: translateY(-0.25rem); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (max-width: 767.98px) {
  .app-stepper-title {
    font-size: 1.25rem;
  }

  .app-stepper-title-icon {
    width: 2rem;
    height: 2rem;
    font-size: 1rem;
  }
}

/* In the vertical rail the heading belongs to the panel column. */
@media (min-width: 992px) {
  .app-stepper-vertical .app-stepper-heading,
  .app-stepper-vertical .app-stepper-panels {
    grid-column: 2;
  }
}

/* ── The progress line ─────────────────────────────────────────────── */

/* Visually hidden on a desktop, where the rail already says where you are;
   shown on a phone, where the labels are hidden (below) and this is the
   only text saying so. The live region reads out on every change either
   way. Bootstrap's own .visually-hidden recipe, inlined so the media query
   below can undo it. */
.app-stepper-progress {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* ── Vertical: rail beside the panel on lg and up ──────────────────── */

@media (min-width: 992px) {
  .app-stepper-vertical {
    display: grid;
    grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr);
    column-gap: 2rem;
    align-items: start;
  }

  /* The rail spans every row of the grid; heading, panels and bar share the
     second column. Without the span the heading (placed in column 2) would
     push the panels under the rail. */
  .app-stepper-vertical .app-stepper-nav {
    grid-column: 1;
    grid-row: 1 / span 3;
    flex-direction: column;
    position: sticky;
    top: 1rem;
    margin-bottom: 0;
  }

  .app-stepper-vertical .app-stepper-item {
    flex: 0 0 auto;
  }

  .app-stepper-vertical .app-stepper-tab {
    flex-direction: row;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.5rem 0.25rem;
    text-align: left;
  }

  .app-stepper-vertical .app-stepper-text {
    align-items: flex-start;
    margin-top: 0.25rem;
  }

  /* The connector runs down from one marker to the next; the fill grows
     downward. It starts 0.25rem INSIDE the marker above (hidden behind its
     opaque background) and ends flush with the marker below - measured
     2026-09-11, an equal 0.25rem at both ends left a visible break under
     each line. */
  .app-stepper-vertical .app-stepper-item + .app-stepper-item::before,
  .app-stepper-vertical .app-stepper-item + .app-stepper-item::after {
    content: "";
    position: absolute;
    left: calc(0.25rem + var(--stepper-marker-size) / 2);
    top: calc(-0.5rem - 0.25rem);
    bottom: calc(100% - 0.5rem);
    width: 2px;
    height: auto;
    transform: translateX(-1px);
  }

  .app-stepper-vertical .app-stepper-item + .app-stepper-item::before {
    background-color: var(--stepper-line);
  }

  .app-stepper-vertical .app-stepper-item + .app-stepper-item::after {
    background-color: var(--bs-primary);
    transform: translateX(-1px) scaleY(0);
    transform-origin: center top;
    transition: transform var(--stepper-fill-duration) var(--stepper-ease);
  }

  .app-stepper-vertical .app-stepper-item[data-stepper-done="true"] + .app-stepper-item::after {
    transform: translateX(-1px) scaleY(1);
  }

  /* In a rail the panel arrives from below rather than from the side. */
  .app-stepper-vertical .app-stepper-panel.app-stepper-panel-enter,
  .app-stepper-vertical[data-stepper-direction="back"] .app-stepper-panel.app-stepper-panel-enter {
    animation-name: app-stepper-panel-rise;
  }

  @keyframes app-stepper-panel-rise {
    from { opacity: 0; transform: translateY(0.75rem); }
    to   { opacity: 1; transform: translateY(0); }
  }
}

/* ── The phone treatment ───────────────────────────────────────────── */

/* Nine markers at 2rem is 18rem before a single connector; a 320px phone
   has 296px inside its padding. A notch smaller below sm keeps the row one
   row. */
@media (max-width: 575.98px) {
  .app-stepper {
    --stepper-marker-size: 1.75rem;
  }
}

/* Below md five labels do not fit in a row. The markers stay - they are
   the position indicator - and the labels go, replaced by the progress
   line, which is the current step's number and name. */
@media (max-width: 767.98px) {
  .app-stepper-nav {
    margin-bottom: 0.75rem;
  }

  .app-stepper-text {
    display: none;
  }

  .app-stepper-progress {
    position: static !important;
    width: auto !important;
    height: auto !important;
    margin: 0 0 1.25rem !important;
    overflow: visible !important;
    clip: auto !important;
    white-space: normal !important;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--bs-body-color);
  }
}

/* ── The bar: Back / message / Next ────────────────────────────────── */

/* Rendered by the tag helper after the panels; app-stepper.js fills the
   text and hides Back on the first step (or no-back) and Next on no-next.
   Floating by default - see the class comment on the tag helper - and the
   same recipe as the event form's .event-form-actions so the two bars read
   as one thing: sticky to the viewport's bottom edge while the stepper
   extends below it, settling into the flow at the end. */
.app-stepper-controls {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.25rem;
  padding: 0.75rem 1rem;
  background-color: var(--bs-body-bg);
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
}

.app-stepper-controls-floating {
  position: sticky;
  bottom: 0;
  z-index: 3;
  margin-bottom: 1.5rem;
  box-shadow: 0 -0.25rem 0.75rem rgba(0, 0, 0, 0.08);
}

/* On a near-black ground a shadow is invisible, so the bar needs an edge
   instead - otherwise it floats over the content with nothing separating
   it. Same fix as the event form's bar. */
[data-bs-theme="dark"] .app-stepper-controls-floating {
  box-shadow: 0 -0.25rem 0.75rem rgba(0, 0, 0, 0.5);
  border-color: var(--bs-border-color-translucent);
}

/* The bar keeps its shape when a button is hidden: Next stays on the right
   with Back gone, because the message takes the slack. */
.app-stepper-message {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.875rem;
  color: var(--bs-secondary-color);
}

.app-stepper-message:empty {
  /* Still takes the row's slack so Next stays right-aligned. */
  min-height: 1px;
}

.app-stepper-message[data-stepper-message-kind="danger"] {
  color: var(--bs-danger);
}

.app-stepper-message[data-stepper-message-kind="success"] {
  color: var(--bs-success);
}

.app-stepper-message.is-fresh {
  animation: app-stepper-message-in 0.3s var(--stepper-ease);
}

@keyframes app-stepper-message-in {
  from { opacity: 0; transform: translateY(0.25rem); }
  to   { opacity: 1; transform: translateY(0); }
}

.app-stepper-next,
.app-stepper-back {
  flex: 0 0 auto;
  white-space: nowrap;
}

.app-stepper-next .app-stepper-spinner {
  margin-right: 0.375rem;
  vertical-align: -0.125em;
}

/* Next lifts a hair on hover and settles on press, so the way forward
   feels like a thing you can push. */
.app-stepper-next:not(:disabled) {
  transition: transform 0.15s var(--stepper-ease), box-shadow 0.15s ease;
}

.app-stepper-next:not(:disabled):hover {
  transform: translateY(-1px);
  box-shadow: 0 0.25rem 0.75rem rgba(var(--bs-primary-rgb), 0.35);
}

.app-stepper-next:not(:disabled):active {
  transform: translateY(0);
  box-shadow: none;
}

/* A refused Next: the open step's marker shakes so the eye goes from the
   button that did nothing to the step that still needs something. Added
   for one beat by the script. */
.app-stepper-marker.is-refused {
  animation: app-stepper-refuse 0.45s var(--stepper-ease);
  border-color: var(--bs-danger);
  color: var(--bs-danger);
}

@keyframes app-stepper-refuse {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-4px); }
  40%      { transform: translateX(4px); }
  60%      { transform: translateX(-3px); }
  80%      { transform: translateX(3px); }
}

/* Busy: the whole bar dims a touch besides the spinner, so a slow save
   reads as "working" and not "stuck". */
.app-stepper[data-stepper-busy] .app-stepper-controls {
  opacity: 0.85;
}

@media (max-width: 575.98px) {
  /* Below sm the message goes under the buttons rather than squeezing
     between them. */
  .app-stepper-controls {
    flex-wrap: wrap;
    padding: 0.625rem 0.75rem;
  }

  .app-stepper-message {
    order: 3;
    flex-basis: 100%;
  }

  .app-stepper-message:empty {
    display: none;
  }

  .app-stepper-back {
    margin-right: auto;
  }

  .app-stepper-next {
    margin-left: auto;
  }
}

/* In the vertical rail the bar belongs to the panel column, not under the
   rail. */
@media (min-width: 992px) {
  .app-stepper-vertical .app-stepper-controls {
    grid-column: 2;
  }
}

/* ── Reduced motion ────────────────────────────────────────────────── */

/* Every animation and transition above, off. State still changes - the
   colours and glyphs still swap - it just does so at once. */
@media (prefers-reduced-motion: reduce) {
  .app-stepper *,
  .app-stepper *::before,
  .app-stepper *::after {
    animation: none !important;
    transition: none !important;
  }
}
