/*
 * Motion and feedback, on top of app.css. Paired with js/ui.js.
 *
 * Rules this file keeps:
 *   - Decoration only. Nothing here is needed to read a page or send a form.
 *   - Anything that starts hidden does so through a CSS animation with a
 *     backwards fill, never through a class that JavaScript has to remove. If
 *     ui.js fails to load the animation still ends, and the page is still there.
 *     Without JavaScript the reveal rules do not apply at all (no html.js).
 *   - prefers-reduced-motion: reduce switches every animation here off. Final
 *     states are shown at once; skeletons become plain grey.
 *   - Sideways motion is written with logical properties or mirrored for RTL.
 */

:root {
  --ui-fast: 120ms;
  --ui-base: 200ms;
  --ui-slow: 320ms;
  --ui-ease: cubic-bezier(.2, .7, .2, 1);        /* settles: entrances */
  --ui-ease-in: cubic-bezier(.4, 0, 1, 1);       /* leaves: exits */
  --ui-stagger: 40ms;
  --ui-save-bar: 0px;                            /* set by ui.js when a sticky save bar shows */
}

/* --- page transitions ----------------------------------------------------- */

/* Same-origin page loads cross-fade where the browser supports it; others
   ignore the rule. No script is involved. */
@view-transition { navigation: auto; }
::view-transition-old(root),
::view-transition-new(root) { animation-duration: 160ms; animation-timing-function: var(--ui-ease); }

/* --- press feedback ------------------------------------------------------- */

.btn, .press, .list-group-item-action {
  transition: transform var(--ui-fast) var(--ui-ease), background-color var(--ui-fast), border-color var(--ui-fast),
              color var(--ui-fast), box-shadow var(--ui-fast);
}
.btn:active:not(:disabled):not(.disabled),
.press:active,
.list-group-item-action:active { transform: scale(.97); }
/* A wide row shrinking 3% moves its edges a lot; rows dip less. */
.list-group-item-action:active, tr.press:active { transform: scale(.99); }

/* --- staggered reveal ----------------------------------------------------- */

/* data-reveal on a container: its first 12 children rise in one after the
   other; the rest are simply there (cheap on a 200-row list). .reveal on a
   single element does the same for that element. */
@keyframes ui-rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
.js [data-reveal] > :nth-child(-n+12),
.js .reveal {
  animation: ui-rise var(--ui-slow) var(--ui-ease) backwards;
  animation-delay: calc(var(--ui-i, 0) * var(--ui-stagger));
}
[data-reveal] > :nth-child(2)  { --ui-i: 1; }
[data-reveal] > :nth-child(3)  { --ui-i: 2; }
[data-reveal] > :nth-child(4)  { --ui-i: 3; }
[data-reveal] > :nth-child(5)  { --ui-i: 4; }
[data-reveal] > :nth-child(6)  { --ui-i: 5; }
[data-reveal] > :nth-child(7)  { --ui-i: 6; }
[data-reveal] > :nth-child(8)  { --ui-i: 7; }
[data-reveal] > :nth-child(9)  { --ui-i: 8; }
[data-reveal] > :nth-child(10) { --ui-i: 9; }
[data-reveal] > :nth-child(11) { --ui-i: 10; }
[data-reveal] > :nth-child(12) { --ui-i: 11; }

/* --- count-up ------------------------------------------------------------- */

[data-count] { font-variant-numeric: tabular-nums; }
.is-counting { display: inline-block; text-align: inherit; }

/* --- submit buttons: busy ------------------------------------------------- */

/* The label stays in the button (and its width with it) but goes transparent;
   a ring sits on top. ui.js sets --ui-busy-color to the label's own colour. */
@keyframes ui-spin { to { transform: rotate(360deg); } }
.is-busy {
  position: relative;
  color: transparent !important;
  pointer-events: none;
  cursor: progress;
}
/* The busy text lock() adds stays for screen readers (it is visually hidden already). */
.is-busy > :not([data-ui-busy]) { visibility: hidden; }
.is-busy::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 1.1em;
  height: 1.1em;
  border: .15em solid var(--ui-busy-color, currentColor);
  border-inline-end-color: transparent;
  border-radius: 50%;
  animation: ui-spin .7s linear infinite;
}

/* --- confirm dialog ------------------------------------------------------- */

.ui-confirm .modal-content { border: 0; border-radius: 1rem; box-shadow: var(--farm-shadow-lg); }
.ui-confirm .modal-body { padding: 1.5rem 1.5rem 1rem; font-size: 1.05rem; }
.ui-confirm .modal-title { font-size: .8rem; font-weight: 700; letter-spacing: .05em;
                           text-transform: uppercase; color: var(--farm-muted); margin-bottom: .5rem; }
[dir="rtl"] .ui-confirm .modal-title { letter-spacing: normal; font-size: .9rem; }   /* Nastaliq must stay joined */
.ui-confirm .modal-footer { border-top: 0; padding: 0 1.5rem 1.5rem; gap: .5rem; }
.ui-confirm .modal-footer .btn { min-width: 6.5rem; margin: 0; }
.ui-confirm.fade .modal-dialog { transform: scale(.96) translateY(6px); transition: transform var(--ui-base) var(--ui-ease); }
.ui-confirm.show .modal-dialog { transform: none; }
@media (max-width: 575.98px) {
  .ui-confirm .modal-footer { flex-direction: column-reverse; align-items: stretch; }
  .ui-confirm .modal-footer .btn { width: 100%; }
}

/* --- toasts --------------------------------------------------------------- */

/* Top-end on a wide screen (top-left in Urdu, since inset-inline-end flips);
   along the bottom on a phone, above a sticky save bar if there is one. */
.ui-toasts {
  position: fixed;
  z-index: 1090;
  top: var(--ui-toast-top, 4.75rem);   /* ui.js: below the navbar and the page head */
  inset-inline-end: 1rem;
  width: min(24rem, calc(100vw - 2rem));
  pointer-events: none;
}
.ui-toasts-list { display: flex; flex-direction: column; gap: .5rem; }
.ui-toasts-list:not(:empty) + .ui-toasts-list:not(:empty) { margin-top: .5rem; }
.ui-toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: .85rem 1rem;
  border-radius: .75rem;
  background: #141414;
  color: #fff;
  box-shadow: var(--farm-shadow-lg);
  border-inline-start: 4px solid #8a8a8a;
  overflow-wrap: anywhere;
  animation: ui-toast-in-end var(--ui-slow) var(--ui-ease) backwards;
}
.ui-toast-msg { flex: 1 1 auto; min-width: 0; padding-top: .1rem; }
.ui-toast .btn-close {
  flex: 0 0 auto;
  /* A 44 px target around the 1em icon, pulled into the padding so the
     toast does not grow. */
  box-sizing: border-box;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  margin-block: -.6rem;
  margin-inline-end: -.6rem;
  background-size: 1em;
  filter: invert(1) grayscale(1) brightness(2);
  opacity: .8;
}
.ui-toast-success { border-inline-start-color: #fff; }
.ui-toast-warning { border-inline-start-color: var(--farm-amber); }
.ui-toast-danger  { background: #7d1010; border-inline-start-color: #fbeaea; }
.ui-toast.is-leaving { opacity: 0; transform: scale(.96); transition: opacity var(--ui-base) var(--ui-ease-in), transform var(--ui-base) var(--ui-ease-in); }

@keyframes ui-toast-in-end   { from { opacity: 0; transform: translateX(110%); } }
@keyframes ui-toast-in-start { from { opacity: 0; transform: translateX(-110%); } }
@keyframes ui-toast-in-up    { from { opacity: 0; transform: translateY(110%); } }
[dir="rtl"] .ui-toast { animation-name: ui-toast-in-start; }

@media (max-width: 575.98px) {
  .ui-toasts {
    top: auto;
    bottom: calc(1rem + var(--ui-save-bar) + env(safe-area-inset-bottom, 0px));
    inset-inline: 1rem;
    width: auto;
  }
  .ui-toast, [dir="rtl"] .ui-toast { animation-name: ui-toast-in-up; }
}

/* --- skeletons ------------------------------------------------------------ */

@keyframes ui-shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }
.skeleton,
[data-skeleton].is-loading {
  background-color: #ececec;
  background-image: linear-gradient(90deg, #ececec 0%, #f7f7f7 50%, #ececec 100%);
  background-size: 200% 100%;
  animation: ui-shimmer 1.3s linear infinite;
  border-radius: var(--farm-radius, .5rem);
}
.skeleton { display: block; min-height: 1em; color: transparent; }
[data-skeleton].is-loading > img,
[data-skeleton].is-loading > .leaflet-container { opacity: 0; }
[data-skeleton] > img { transition: opacity var(--ui-base) var(--ui-ease); }

/* --- sticky save bar ------------------------------------------------------ */

/* On a phone the main button of a long form stays in reach. */
@media (max-width: 575.98px) {
  [data-sticky-save] {
    position: sticky;
    bottom: 0;
    z-index: 1010;
    background: var(--farm-cream, #f5f5f5);
    padding-bottom: calc(.75rem + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -6px 16px rgba(0, 0, 0, .06);
  }
  /* A bar that is not a card footer gets the footer's room above the button. */
  [data-sticky-save]:not(.card-footer) { padding-top: .75rem; }
}

/* --- inline validation ---------------------------------------------------- */

@keyframes ui-nudge { 0%, 100% { transform: none; } 30% { transform: translateX(-3px); } 60% { transform: translateX(3px); } }
/* One small nudge when a field turns invalid, never a loop. */
.ui-checked.is-invalid { animation: ui-nudge 240ms var(--ui-ease) 1; }
/* A message ui.js wrote and then emptied (under an input group it stays d-block): no gap. */
.invalid-feedback[data-ui-feedback]:empty { margin-top: 0; }

/* --- empty states --------------------------------------------------------- */

/* A drawing, a line saying what is missing, and the one next step. The
   compact form is a small row, for a panel or a table cell. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: .35rem;
  padding: 2rem 1rem;
  color: var(--farm-muted);
}
.empty-state-art { width: 4rem; height: 4rem; margin-bottom: .25rem; color: var(--farm-ink); }
.empty-state-art svg { display: block; width: 100%; height: 100%; }
.empty-state-body { display: flex; flex-direction: column; align-items: center; gap: .35rem; min-width: 0; }
.empty-state-title { margin: 0; font-size: 1.05rem; font-weight: 700; color: var(--farm-ink); }
.empty-state-text { margin: 0; max-width: 28rem; }
.empty-state-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: .25rem 1rem; margin-top: .75rem; }
.empty-state .btn { min-height: 2.75rem; display: inline-flex; align-items: center; }
.empty-state-link { display: inline-flex; align-items: center; min-height: 2.75rem; font-weight: 600; }
/* A panel's empty state: a small drawing with the words centred under it. */
.empty-state-compact { gap: .35rem; padding: 1rem .75rem; }
.empty-state-compact .empty-state-art { flex: 0 0 auto; width: 2.25rem; height: 2.25rem; margin: 0; }
.empty-state-compact .empty-state-body { gap: .15rem; }
.empty-state-compact .empty-state-title { font-size: .95rem; font-weight: 600; }
.empty-state-compact .empty-state-text { font-size: .875rem; }
.empty-state-compact .empty-state-actions { margin-top: .25rem; }
td.empty-cell { padding: 0 !important; }
td.empty-cell::before { content: none !important; }
.js .empty-state-art { animation: ui-pop var(--ui-slow) var(--ui-ease) backwards; }
@keyframes ui-pop { from { opacity: 0; transform: scale(.8); } }

/* --- instant filter ------------------------------------------------------- */

[data-filter-empty] td, div[data-filter-empty] { text-align: center; color: var(--farm-muted); }

/* === Pages (Task 2): what each page does with the pieces above ============ */

/* --- bars that fill: progress, scores ------------------------------------- */

/* A bar grows from its start edge on first paint (the right edge in Urdu). */
@keyframes ui-grow { from { transform: scaleX(0); } }
.js main .progress-bar {
  transform-origin: left center;
  animation: ui-grow 700ms var(--ui-ease) 120ms backwards;
}
[dir="rtl"].js main .progress-bar { transform-origin: right center; }

/* --- dashboard: today's tasks, trend arrows ------------------------------- */

.task-card { transition: transform var(--ui-fast) var(--ui-ease), box-shadow var(--ui-fast), border-color var(--ui-fast); }
.task-card:hover, .task-card:focus-visible { transform: translateY(-2px); box-shadow: var(--farm-shadow); }
.task-card:active { transform: scale(.97); }

/* --- kind chips: the chosen one glides to its new place ------------------- */

/* Choosing a kind is a link to the server's filtered list. Where the browser
   cross-fades the page (View Transitions), the dark chip is its own layer and
   slides from the old choice to the new one. Elsewhere it settles in. */
.kind-chips [aria-current] { view-transition-name: kind-chip; }
::view-transition-group(kind-chip) { animation-duration: var(--ui-slow); animation-timing-function: var(--ui-ease); }
@keyframes ui-settle { from { transform: scale(.9); opacity: .55; } }
@supports not (view-transition-name: none) {
  .js .kind-chips [aria-current] { animation: ui-settle var(--ui-base) var(--ui-ease) backwards; }
}
/* The kind picker on the add forms: radio chips; the checked one settles in. */
.js .kind-pick .btn-check:checked + .btn { animation: ui-settle var(--ui-base) var(--ui-ease) backwards; }

/* --- animal page: photo header, section bar, timelines --------------------- */

@keyframes ui-zoom { from { transform: scale(1.12); opacity: 0; } }
.js .animal-hero img,
.js .photo-zoom img { animation: ui-zoom 900ms var(--ui-ease) backwards; }
.section-bar a { transition: background-color var(--ui-fast), color var(--ui-fast); }
@media (prefers-reduced-motion: no-preference) {
  html:has(.section-bar) { scroll-behavior: smooth; }
}

/* The weight line draws itself left to right; the dots follow. */
@keyframes ui-draw { from { stroke-dashoffset: 1; } }
@keyframes ui-fade { from { opacity: 0; } }
.js .chart-line { stroke-dasharray: 1; animation: ui-draw 1s var(--ui-ease) 150ms backwards; }
.js .chart-dot { animation: ui-fade var(--ui-base) ease 900ms backwards; }

/* --- vets: stars, tracker, countdown, pin, rating saved -------------------- */

/* Stars grow under the pointer or the keyboard, and bounce once when chosen. */
@keyframes ui-star-pop { 45% { transform: scale(1.35); } }
.star-input label { transition: transform var(--ui-fast) var(--ui-ease), color var(--ui-fast); }
.star-input label:hover,
.star-input input:focus-visible + label { transform: scale(1.2); }
.js .star-input input:checked + label { animation: ui-star-pop 320ms var(--ui-ease); }

/* The tracker: the line fills to the last step done at an even pace, and each
   dot pops as the line reaches it (--dot-delay, set per step in vet_request.php). */
@keyframes ui-line { from { width: 0; } }
.js .vet-steps::after { animation: ui-line 700ms linear 150ms backwards; }
.js .vet-step-item.is-on .vet-step-dot { animation: ui-pop var(--ui-base) var(--ui-ease) backwards; animation-delay: var(--dot-delay, 0ms); }

/* The countdown ring empties as the request's time runs out. */
.countdown-left { transition: stroke-dashoffset 600ms var(--ui-ease), stroke var(--ui-base); }

/* A pin drops onto the map the first time it is drawn (Leaflet positions the
   icon with transform, so the drop uses the separate translate property). */
@keyframes ui-pin-drop {
  0%   { translate: 0 -48px; opacity: 0; }
  55%  { translate: 0 0; opacity: 1; }
  75%  { translate: 0 -8px; }
  100% { translate: 0 0; }
}
.leaflet-marker-icon.ui-pin-drop { animation: ui-pin-drop 650ms cubic-bezier(.3, .7, .4, 1) backwards; }
.leaflet-marker-shadow.ui-pin-drop { animation: ui-fade 650ms ease backwards; }

/* Rating saved: a ring and eight dots burst around a check mark that draws. */
@keyframes ui-burst-ring { from { transform: scale(.4); opacity: .9; } to { transform: scale(1.9); opacity: 0; } }
@keyframes ui-burst-dot  { from { transform: rotate(var(--a)) translateY(0) scale(1); opacity: 1; }
                           to   { transform: rotate(var(--a)) translateY(-2.9rem) scale(.4); opacity: 0; } }
@keyframes ui-check-draw { from { stroke-dashoffset: 1; } }
.rate-burst-check { animation: ui-pop var(--ui-slow) var(--ui-ease) backwards; }
.rate-burst-check path { stroke-dasharray: 1; animation: ui-check-draw 420ms var(--ui-ease) 180ms backwards; }
.rate-burst-ring { animation: ui-burst-ring 700ms var(--ui-ease) 120ms both; }
.rate-burst-dots i { animation: ui-burst-dot 760ms var(--ui-ease) 160ms both; }

/* --- public vets pages: the landing page's entrance ------------------------ */

.pub-vets a.vet-card { transition: transform var(--ui-base) var(--ui-ease), box-shadow var(--ui-base) var(--ui-ease); }
.pub-vets a.vet-card:hover, .pub-vets a.vet-card:focus-visible { transform: translateY(-3px); box-shadow: var(--farm-shadow-lg); }

/* --- reduced motion: all of it off ---------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
  .btn, .press, .list-group-item-action, [data-skeleton] > img { transition: none; }
  .btn:active:not(:disabled):not(.disabled), .press:active, .list-group-item-action:active, tr.press:active { transform: none; }
  .js [data-reveal] > *, .js .reveal, .ui-toast, [dir="rtl"] .ui-toast, .ui-checked.is-invalid,
  .js .empty-state-art, .skeleton, [data-skeleton].is-loading { animation: none !important; }
  .ui-toast.is-leaving { transition: none; }
  .ui-confirm.fade .modal-dialog { transform: none; transition: none; }
  /* A still ring with a gap still says "working". */
  .is-busy::after { animation: none !important; border-style: dotted; }
  /* Task 2: pages. Final states at once; the burst keeps only its check mark. */
  .js main .progress-bar, .js .kind-chips [aria-current], .js .kind-pick .btn-check:checked + .btn,
  .js .animal-hero img, .js .photo-zoom img, .js .chart-line, .js .chart-dot,
  .js .star-input input:checked + label, .js .vet-steps::after, .js .vet-step-item.is-on .vet-step-dot,
  .leaflet-marker-icon.ui-pin-drop, .leaflet-marker-shadow.ui-pin-drop,
  .rate-burst-check, .rate-burst-check path { animation: none !important; }
  .rate-burst-ring, .rate-burst-dots { display: none; }
  .task-card, .star-input label, .countdown-left, .section-bar a, .pub-vets a.vet-card { transition: none; }
}
