/* ============================================================================
   lightweightDBClient — animations for the write operations
   Loaded alongside the panel's /static/css/animations.css, which supplies the
   shared vocabulary (cp-flash-*, cp-shake, cp-ripple, and the --cp-anim-scale
   speed knob). Everything here is the part that only exists in this app: the
   result grid, its pending edit marks, and the error banner.

   Same gating as the panel's file — nothing runs without body.cp-anim-on,
   which app-animations.js mirrors from the parent panel — and the same
   calc(<base> * var(--cp-anim-scale)) duration form, so the panel's speed
   setting reaches in here too.

   Colours come from this app's own theme tokens (--red/--green/--accent in
   styles.css), so the marks match the grid they sit in rather than the panel
   chrome outside the iframe.
   ========================================================================= */

/* ------------------------------------------------------- row marked delete --
   The row already goes line-through + dimmed the moment it is marked (see
   tr.row-deleted in styles.css). That state arrives instantly, on a full grid
   re-render, which reads as the row having always looked that way. This draws
   the transition it skipped: a red wash sweeping across, the row settling into
   the struck-out state as it passes.

   One-shot, applied by markRow() after the re-render, removed on animationend
   — NOT a rule on tr.row-deleted itself, or every already-marked row would
   replay it on every subsequent render. */
body.cp-anim-on .cp-row-deleting {
  animation: cp-row-delete calc(520ms * var(--cp-anim-scale)) cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes cp-row-delete {
  0%   { background: rgba(239, 106, 76, 0.34); transform: translateX(0); }
  25%  { background: rgba(239, 106, 76, 0.28); transform: translateX(3px); }
  100% { background: rgba(239, 106, 76, 0); transform: translateX(0); }
}

/* Taking the mark back is the safe direction and gets the calmer version of
   the same idea — no displacement, and the app's green rather than red. */
body.cp-anim-on .cp-row-restoring {
  animation: cp-row-restore calc(460ms * var(--cp-anim-scale)) ease-out;
}
@keyframes cp-row-restore {
  0%   { background: rgba(67, 190, 114, 0.28); }
  100% { background: rgba(67, 190, 114, 0); }
}

/* The strike-through itself, drawn rather than switched on. A pseudo-element
   line that grows across the cell, so it reads as the row being crossed out.
   It is on the same one-shot class, so it plays once per mark. */
body.cp-anim-on .cp-row-deleting .cell { position: relative; }
body.cp-anim-on .cp-row-deleting .cell::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  height: 1px;
  width: 0;
  background: var(--red, #EF6A4C);
  animation: cp-strike calc(320ms * var(--cp-anim-scale)) ease-out forwards;
}
@keyframes cp-strike { to { width: 100%; } }

/* ------------------------------------------------------ cell marked update --
   td.cell-pending is the standing "this value is not what the database holds"
   mark. This is the moment it is made: the new value lands with a pulse in the
   accent colour, so an edit two screens down the grid is still visible.
   Scoped to .cell so it cannot leak onto anything else that gets the class. */
body.cp-anim-on .cell.cp-cell-updating {
  animation: cp-cell-update calc(700ms * var(--cp-anim-scale)) cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes cp-cell-update {
  0%   { background: rgba(76, 194, 255, 0.42); box-shadow: inset 0 0 0 1px var(--accent, #4CC2FF); }
  60%  { background: rgba(76, 194, 255, 0.16); }
  100% { background: rgba(76, 194, 255, 0); box-shadow: inset 0 0 0 1px rgba(76, 194, 255, 0); }
}

/* Setting a cell to NULL is an update, but a subtractive one, so it gets its
   own colour rather than the accent — the grid already prints NULL in a muted
   style and a blue flash would fight it. */
body.cp-anim-on .cell.cp-cell-nulling {
  animation: cp-cell-null calc(700ms * var(--cp-anim-scale)) cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes cp-cell-null {
  0%   { background: rgba(167, 139, 250, 0.38); }
  100% { background: rgba(167, 139, 250, 0); }
}

/* ------------------------------------------------------------ apply landed --
   After Apply succeeds the grid is re-read from the database, so there are no
   marks left to animate and nothing that says the write is what produced this
   new content. The whole grid gets one green wash instead. */
body.cp-anim-on .cp-grid-applied {
  animation: cp-grid-applied calc(900ms * var(--cp-anim-scale)) ease-out;
}
@keyframes cp-grid-applied {
  0%   { background: rgba(67, 190, 114, 0.20); }
  100% { background: rgba(67, 190, 114, 0); }
}

/* ---------------------------------------------------------- error banner --
   The banner is already the loudest thing on screen, so this is only the
   arrival: it slides down and shakes once, which is what distinguishes a new
   failure from the banner that was already sitting there. */
body.cp-anim-on .cp-banner-in {
  animation: cp-banner-in calc(400ms * var(--cp-anim-scale)) cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes cp-banner-in {
  0%   { opacity: 0; transform: translateY(-8px); }
  60%  { opacity: 1; transform: translateY(2px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* Reduce motion: the marks stay (they carry the state), the movement goes.
   Same rule as the panel's file — a person who asked for less motion still
   needs to see which row is about to be deleted. */
body.cp-anim-reduced .cp-row-deleting,
body.cp-anim-reduced .cp-row-restoring,
body.cp-anim-reduced .cp-row-deleting .cell::after,
body.cp-anim-reduced .cell.cp-cell-updating,
body.cp-anim-reduced .cell.cp-cell-nulling,
body.cp-anim-reduced .cp-grid-applied,
body.cp-anim-reduced .cp-banner-in { animation: none; }
body.cp-anim-reduced .cp-row-deleting .cell::after { display: none; }
