/*
 * 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.
 */

/* An entrance fills backwards and never forwards. Filled forwards, the end of the move stays applied
   once it is over, and the end is not "none" but the translateY(0) it was interpolated to: a
   transform, which makes the element the containing block of anything fixed inside it. */
@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 notice goes back down to where it 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 anything fixed to the window, and it would slide
   while it ran */
.screen:not([hidden]) > * { animation: rise var(--dur) var(--ease-out) backwards; }

/* 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) backwards;
  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) backwards; }
.is-changed { animation: bump var(--dur-slow) var(--ease-out); }

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

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

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

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

/* -------------------------------------------------------------------------------- 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;
}

/* 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) backwards; }

/* ------------------------------------------------------------------------ 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;
  }
}
