/*
 * Copyright (c) 2026 by Gabriele Proietti Mattia <pm.gabriele@gmail.com>.
 *
 * All Rights Reserved.
 */

/*
 * What moves, written with the design system's own tokens and never with values of its own.
 *
 * The system's rule for motion (readme, "Motion") is: restrained and quick, 200ms with --ease-out
 * for colour and lift, 120ms for a press, fades and small translations only, no bounces, no
 * parallax, no loops, and prefers-reduced-motion respected. This file keeps to it: only transform
 * and opacity are animated, so the browser never lays the page out again for a frame of it, and
 * the one thing that repeats, the skeleton's shimmer, is a background that is only painted. The
 * hover and press states of the controls sit with the controls in styles.css; this file is the
 * keyframes, the entrances, the leaves, the skeleton, the open state of the sheets and, last of
 * all, the switch that turns every bit of it off.
 *
 * motion.js adds and takes off the classes used here (is-fresh, is-new, is-changed, is-leaving,
 * is-entering, is-switching, is-open, is-hit). Where an element is only shown and hidden with the
 * hidden attribute, the entrance needs no script: the browser plays an animation again each time
 * an element goes from display:none back into the page.
 */

@keyframes rise {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

@keyframes fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes bump {
  0% { transform: scale(1); }
  40% { transform: scale(1.03); }
  100% { transform: scale(1); }
}

@keyframes shimmer {
  from { background-position: -160px 0; }
  to { background-position: calc(100% + 160px) 0; }
}

/* a row that is removed leaves to the side, the way it would be dismissed */
@keyframes leave {
  to { opacity: 0; transform: translateX(12px); }
}

/* the toast and the notice go back down to where they came from */
@keyframes sink {
  to { opacity: 0; transform: translateY(8px); }
}

/* a bar of a chart: the script sets where it ends (transform: scaleX), this is where it starts */
@keyframes grow {
  from { transform: scaleX(0); }
}

/* the bars of an admin chart rise from their baseline and its lines are swept in from the left
   (motion.js drawIn puts is-drawing on the figure; the geometry is the chart's own) */
@keyframes rise-up {
  from { transform: scaleY(0); }
}

@keyframes sweep {
  from { opacity: 0; transform: scaleX(0); }
}

/* ------------------------------------------------------------------- screens and lists */

#app:not([hidden]) { animation: fade var(--dur) var(--ease-out) both; }

/* what a tab shows comes in. It is the children that rise and not the screen: a transform on the
   screen would make it the containing block of the round buttons, which are fixed to the window,
   and they would slide while it ran */
.screen:not([hidden]) > :not(.fab) { animation: rise var(--dur) var(--ease-out) both; }

/* a list drawn from nothing: its rows come in one after the other, the first eight, a fifth of
   the press duration apart. A list drawn again with rows in it is not marked, and does not move */
.is-fresh > * {
  /* where the row stands in the list; motion.js writes the real one on each row */
  --i: 0;
  animation: rise var(--dur) var(--ease-out) both;
  animation-delay: calc(var(--i) * var(--dur-fast) / 5);
}

/* a row that was not in the list a moment ago, and a number that is not what it was */
.is-new { animation: rise var(--dur) var(--ease-out) both; }
.is-changed { animation: bump var(--dur-slow) var(--ease-out); }

/* the whole list after its filter changed: a single fade, not a staggered entrance */
.is-filtered { animation: fade var(--dur) var(--ease-out) both; }

/* the body of the lot sheet when it shows another view of the lot */
.is-switching { animation: fade var(--dur-fast) var(--ease-out) both; }

.is-entering { animation: rise var(--dur) var(--ease-out) both; }

.is-leaving {
  animation: leave var(--dur-fast) var(--ease-in-out) both;
  pointer-events: none;
}

.notice.is-leaving, .toast.is-leaving { animation: sink var(--dur-fast) var(--ease-in-out) both; }

/* blocks the page opens and closes with the hidden attribute: a confirmation, the suggestions
   under a field, an error under a form, an empty state */
.reveal:not([hidden]), .danger:not([hidden]), .suggestions:not([hidden]), .login__error:not([hidden]) {
  animation: rise var(--dur-fast) var(--ease-out) both;
}

/* -------------------------------------------------------------------------------- sheets */

/* a sheet is hidden with the hidden attribute once it has slid away; the open state is what
   styles.css has them start from: the scrim transparent, the card below the screen */
.sheet.is-open {
  opacity: 1;
  pointer-events: auto;
  transition: opacity var(--dur) var(--ease-out);
}

.sheet.is-open .sheet__card {
  transform: none;
  transition: transform var(--dur-slow) var(--ease-out);
}

/* ---------------------------------------------------------------------------- the skeleton */

/* where something will be while it is being read: a grey block with a light running across it.
   Shown for the first read only, and never more than a handful at once */
.skeleton {
  height: 72px;
  border-radius: var(--radius-lg);
  background-color: var(--surface-2);
  background-image: linear-gradient(90deg, transparent, var(--border), transparent);
  background-repeat: no-repeat;
  background-size: 160px 100%;
  animation: shimmer calc(var(--dur-slow) * 3) linear infinite;
}

.skeleton.scan__skeleton {
  height: auto;
  aspect-ratio: 4 / 3;
  max-height: 40vh;
  border-radius: var(--radius-md);
}

/* the placeholders of a list that is a box of suggestions are as high as its rows */
.suggestions .skeleton { height: 44px; border-radius: 0; }

/* the card of the landing, the sign in and the expired session comes in when the screen does */
.landing:not([hidden]) > .landing__card { animation: rise var(--dur) var(--ease-out) both; }

/* the video, once the camera is on: it comes in, and a code read frames it for a moment */
.scan__video.is-on { animation: fade var(--dur) var(--ease-out) both; }
.scan__video.is-hit { box-shadow: var(--shadow-focus); animation: bump var(--dur-slow) var(--ease-out); }

/* a picture is shown when it has arrived and not as it arrives line by line */
img.thumb.is-loaded { opacity: 1; }

/* ------------------------------------------------------------------------ admin charts */

.chart.is-drawing .chart__bar {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: rise-up var(--dur-slow) var(--ease-out) both;
}

.chart.is-drawing .chart__line, .chart.is-drawing .chart__area, .chart.is-drawing .chart__dot {
  transform-box: fill-box;
  transform-origin: 0% 50%;
  animation: sweep var(--dur-slow) var(--ease-out) both;
}

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

/* One switch for all of it. 1ms and not 0, so that the animationend and transitionend that the
   script waits for are still delivered; the shimmer plays its single iteration and stops, a
   grey block that is still readable as a placeholder. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}
