/* feedback.css — the app's "это сработало" layer (ADL0075).
 *
 * Five things. The first four are chrome that no screen owns; the fifth is the
 * one component whose whole job is the same question ("что произошло?"), and it
 * is here rather than in a file of its own because it is the same hand-written,
 * server-decided, reduced-motion-aware layer:
 *   1. .toast          — the плашка naming what happened   (_feedback.html)
 *   2. .hl             — the row that was just written fades from amber
 *   3. #app-progress   — the 2px top bar while a request is in flight
 *   4. .htmx-request   — the control that issued that request
 *   5. .rail           — ход заказа: the notch the заказ reached (conv. 46,
 *                        _order_rail.html + order_rail.go)
 *
 * HAND-WRITTEN CSS on purpose, the same call dialog.css makes: `app.css` is a
 * PREBUILT Tailwind file (ADL0024, `make css`) whose scan covers *.html + *.go,
 * so (a) a class toggled by `static/feedback.js` has no styles unless we write
 * them (knowledge/06 gotcha 6) and (b) none of the five can be expressed as
 * utilities anyway — keyframes, ::after, a fixed overlay stack.
 *
 * Colours are literal rgb() for the same reason, and are the app's own:
 * emerald-600 (money/success), sky-600 (neutral), rose-600 (refusal) — the
 * per-card palette of knowledge/06 conv. 38.
 */

/* ---- 1. the плашка ------------------------------------------------------- */

/* Bottom-RIGHT on a desktop and a full-width strip on a phone: the phone is
 * held in the цех with a thumb over the bottom of the screen, and a card
 * pinned to one corner there is the one place a thumb covers. z-index sits
 * above the sticky chrome but is irrelevant against the app's <dialog>, which
 * renders in the browser's top layer and is therefore always above this. */
#toast-host {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .5rem;
  /* The host spans a corner of the viewport at all times; only the cards in it
   * may take a click, or an empty host would eat the controls under it. */
  pointer-events: none;
}

@media (max-width: 640px) {
  #toast-host {
    left: .75rem;
    right: .75rem;
    bottom: .75rem;
    align-items: stretch;
  }
}

.toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: .625rem;
  max-width: 24rem;
  padding: .625rem .75rem;
  border-radius: .625rem;
  border-left: 4px solid;
  background: #fff;
  color: rgb(15 23 42);
  font-size: .8125rem;
  line-height: 1.35;
  box-shadow: 0 10px 25px rgb(15 23 42 / .18);
  animation: aport-toast-in .18s cubic-bezier(.16, 1, .3, 1);
}

.toast-ok { border-left-color: rgb(5 150 105); }
.toast-info { border-left-color: rgb(2 132 199); }
.toast-warn { border-left-color: rgb(225 29 72); }

/* A note can carry one unbreakable token — a pasted URL, a percent-encoded
 * filename — and a flex item never shrinks below its min-content (conv. 23). */
.toast-text {
  min-width: 0;
  overflow-wrap: anywhere;
}

.toast-x {
  margin-left: auto;
  flex: none;
  color: rgb(148 163 184);
  font-size: .75rem;
  line-height: 1;
  padding: .125rem .125rem 0;
  cursor: pointer;
}

.toast-x:hover { color: rgb(71 85 105); }

.toast.is-out { opacity: 0; transition: opacity .16s linear; }

@keyframes aport-toast-in {
  from { opacity: 0; transform: translateY(6px); }
}

/* ---- 2. the row that just changed --------------------------------------- */

/* Colour only, no transform: this runs on a table row inside a card and any
 * geometry here would push the six rows below it. amber-200 -> transparent,
 * because amber is the app's "внимание сюда" and is not one of the status
 * families that could make it read as a state (conv. 38).
 *
 * 1.4s is deliberately long for an acknowledgement: the eye arrives at the row
 * AFTER the swap, often from the form it was typing in. */
.hl { animation: aport-hl 1.4s cubic-bezier(.16, 1, .3, 1); }

@keyframes aport-hl {
  from { background-color: rgb(253 230 138); }
  to { background-color: transparent; }
}

/* .hl-card - the same acknowledgement for an element that OWNS its background:
 * the status strip and a Doska card are both bg-white sitting on a slate-100
 * page, so fading their background to `transparent` shows the page through them
 * and then SNAPS back to white when the animation ends (found in review). A
 * card therefore gets a fading RING and its background is never touched.
 * shadow-sm is repeated in both keyframes because box-shadow is one property:
 * animating the ring alone would drop the card's own shadow for the duration. */
.hl-card { animation: aport-hl-ring 1.4s cubic-bezier(.16, 1, .3, 1); }

@keyframes aport-hl-ring {
  from { box-shadow: 0 0 0 3px rgb(253 230 138), 0 1px 2px 0 rgb(0 0 0 / .05); }
  to { box-shadow: 0 0 0 3px rgb(253 230 138 / 0), 0 1px 2px 0 rgb(0 0 0 / .05); }
}

/* ---- 3. the request in flight ------------------------------------------- */

#app-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 0;
  z-index: 70;
  background: rgb(5 150 105);
  opacity: 0;
  pointer-events: none;
  transition: width .2s ease-out, opacity .2s linear;
}

#app-progress.is-on { opacity: 1; }

/* ---- 4. the control that issued it -------------------------------------- */

/* htmx puts `htmx-request` on the element it issues the request FOR whenever no
 * hx-indicator is named (vendored htmx 2.0.4), which is app-wide here - so
 * every mutation gets an in-flight state with no attribute in any template.
 *
 * EVERY SELECTOR HERE NAMES A CONTROL, and that is not cosmetic: htmx falls
 * back to `document.body` as the requesting element when `htmx.ajax()` is
 * called without a `source` (vendored 2.0.4), which board.js and the накладная
 * re-pull both do. `pointer-events` is INHERITED, so a bare `.htmx-request`
 * rule froze the whole page for the length of those requests - the накладная
 * one fires on window focus, so it ate the user's first click (found in
 * review).
 *
 * pointer-events:none is the cheap half of double-submit protection: it stops a
 * second CLICK on a control already in flight. It is not the whole answer -
 * Enter in a text field would still submit - so the forms whose second POST
 * would create a DUPLICATE RECORD (платёж, строка, заказ, клиент, товар,
 * операция кассы) also carry `hx-disabled-elt="find [data-submit]"`: a
 * disabled default button makes implicit submission a no-op per the HTML spec,
 * so Enter cannot post twice either. `[data-submit]` and not `button`, because
 * htmx's `find` matches the FIRST match only and those forms hold repeater and
 * stepper buttons ahead of the real submit. */
button.htmx-request,
a.htmx-request { opacity: .65; pointer-events: none; }

/* A field is dimmed but NOT made click-through: a click aimed at the caret has
 * to land in the field, not fall through to whatever is behind it. The four
 * search boxes are exempt entirely - they fire per keystroke (hx-get on the
 * input itself), and a box that flickers while you type is noise, not a cue. */
input.htmx-request:not([name="q"]),
select.htmx-request,
textarea.htmx-request { opacity: .6; }

/* The whole form dims while its POST is out - the payment form, the "+ строка"
 * form - so the cue is where the user's hands are, not only on the button. */
form.htmx-request { opacity: .7; }

form.htmx-request button,
form.htmx-request input,
form.htmx-request select,
form.htmx-request textarea { pointer-events: none; }

/* The spinner rides the button's own text colour, so it needs no per-button
 * variant across the seven card palettes this app uses.
 *
 * [data-submit] ONLY - the primary submit of a form. An inline ::after has
 * layout, so on an icon-sized button (the ✕ of an order line, the row actions
 * in the прайс and the ledger) it grew the button mid-request and shifted the
 * row under the pointer: the flinch the Файлы card's ring comment already
 * refuses to pay (found in review). Those buttons keep the dimming. */
button[data-submit].htmx-request::after {
  content: "";
  display: inline-block;
  width: .7em;
  height: .7em;
  margin-left: .45em;
  vertical-align: -.05em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: aport-spin .6s linear infinite;
}

@keyframes aport-spin {
  to { transform: rotate(360deg); }
}

/* ---- 5. ход заказа: the progress rail ------------------------------------ */

/* One notch per workflow status on the order card (_order_rail.html,
 * order_rail.go, knowledge/06 conv. 46). Go decides which notch is which; everything below is
 * how that is drawn and how a move to the next notch is animated.
 *
 * NO HARDCODED STEP COUNT anywhere in here, on purpose: the list has been six
 * long only since ADL0053 retired "КП", and a rail whose geometry is a set of
 * percentages would go crooked the next time a status is added. Every step is
 * one equal flex cell, and each cell draws its own connector from its own dot's
 * centre to the next cell's (left:50% -> right:-50%, i.e. exactly half a cell
 * over the boundary). Add or remove a status and the arithmetic is the
 * browser's.
 *
 * The fill is the app's own progress colour, not a new one: emerald-500, the
 * same as the Платежи card's "доля оплаты" bar (ADL0074). The TRACK is NOT that
 * bar's emerald-100 — it is slate-200, the colour of the dots still ahead, so
 * the unreached half of the rail reads as one grey object. A green track under
 * grey dots reads as progress already made. Literal rgb() for the same reason
 * as the rest of this file. */

.rail {
  display: flex;
  align-items: flex-start;
  /* The rail is the first row of the статус strip and the controls follow it,
   * so the bottom margin is the gap between the two — the card's own `gap-3`
   * belongs to the control row and stops at its edge. */
  margin: 0 0 .875rem;
  padding: 0;
  list-style: none;
  width: 100%;
}

.rail-step {
  position: relative;
  /* equal cells (`1 1 0`, not `1`): the labels have very different widths and
   * `flex-basis: auto` would space the notches by how long a Russian word is. */
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .375rem;
}

/* The connector to the next notch. Its 2px centre sits on the dot's centre:
 * half the dot (.375rem) minus half the line (1px). Written as a calc over the
 * SAME rem the dot is sized in, not as a px constant — at any root font-size
 * but 16px a hardcoded `top: 5px` leaves the line visibly off every dot. */
.rail-seg {
  position: absolute;
  top: calc(.375rem - 1px);
  left: 50%;
  right: -50%;
  height: 2px;
  background: rgb(226 232 240); /* slate-200 — the track */
}

.rail-seg::after {
  content: "";
  display: block;
  height: 100%;
  width: 0;
  background: rgb(16 185 129); /* emerald-500 — the fill */
}

.rail-seg.is-done::after { width: 100%; }

/* The point of the whole component: the fill GROWS into the notch the заказ
 * just reached, and drains back out of one it left (a status can move
 * backwards — the select allows it, §4.1).
 *
 * Both animations END at the element's own resting width — is-new always rides
 * an is-done segment (base 100%), is-gone always an empty one (base 0) — so
 * neither needs animation-fill-mode, and a second swap landing mid-animation
 * cannot strand a bar at a length nobody asked for.
 *
 * A jump of several notches at once (Новый -> Готов in one pick of the select)
 * grows every crossed segment together rather than in sequence: a stagger would
 * need a per-segment delay, i.e. a number in the markup, and the whole run is
 * over in 320ms anyway. */
.rail-seg.is-new::after { animation: rail-fill .32s cubic-bezier(.16, 1, .3, 1); }
.rail-seg.is-gone::after { animation: rail-drain .32s cubic-bezier(.16, 1, .3, 1); }

@keyframes rail-fill { from { width: 0; } }
@keyframes rail-drain { from { width: 100%; } }

.rail-dot {
  position: relative; /* over the connector, which starts at this dot's centre */
  z-index: 1;
  width: .75rem;
  height: .75rem;
  border-radius: 9999px;
  background: rgb(226 232 240); /* slate-200 — not reached yet */
}

.rail-step.is-done .rail-dot { background: rgb(16 185 129); }

/* Where the заказ stands now: a halo, not a bigger dot — a size change here
 * would move the label under it and re-align the row on every status move. */
.rail-step.is-now .rail-dot { box-shadow: 0 0 0 4px rgb(209 250 229); } /* emerald-100 */

/* The reached dot pops ONCE. One overshoot and no bounce: the curve carries it
 * past 1 and settles there, which is the whole vocabulary this app's motion
 * gets for a step (a celebration is a different tier and is not built yet).
 * `transform` on an absolutely-centred flex child moves no other element. */
.rail-dot.is-hit { animation: rail-pop .32s cubic-bezier(.2, .9, .25, 1.2); }

@keyframes rail-pop { from { transform: scale(.55); } }

/* slate-500 and slate-700, not the slate-400/600 this started as: at .6875rem
 * slate-400 on white is ~2.6:1, well under AA, and the notches still AHEAD are
 * most of the rail's text on a desktop. The three-step ramp survives. */
.rail-label {
  font-size: .6875rem;
  line-height: 1.15;
  text-align: center;
  color: rgb(100 116 139); /* slate-500 — still ahead */
}

.rail-step.is-done .rail-label { color: rgb(51 65 85); } /* slate-700 */

.rail-step.is-now .rail-label {
  color: rgb(15 23 42); /* slate-900 */
  font-weight: 600;
}

/* On a phone six Russian words do not fit across ~360px, and shrinking them to
 * fit produces a row of unreadable stubs. They are hidden the sr-only way —
 * still announced, still the accessible name of each step — and the WORD for
 * where the заказ stands is not lost from the screen either: the status pill
 * directly under the rail is that word (conv. 43, "состояние заказа
 * читается словами" — never colour alone). */
@media (max-width: 640px) {
  .rail-label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ---- reduced motion ------------------------------------------------------ */

/* The colour fades stay: a background going from amber to nothing is what tells
 * the user WHERE the change landed, and it moves nothing. What goes is every
 * transform and the fast spin. */
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  #app-progress { transition: opacity .2s linear; }
  button[data-submit].htmx-request::after { animation-duration: 1.6s; }
  /* The rail still shows WHERE the заказ stands — that is its base state, not
   * an animation. What goes is the slide and the pop. */
  .rail-seg.is-new::after,
  .rail-seg.is-gone::after,
  .rail-dot.is-hit { animation: none; }
}
