atagen

malice

public
Star0
Watch0
0d4a4ccdc6de9bccfbce2e18ebf2c7df4f30c734
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/src/lib/motion.ts

d07bca5da4aeatagen2026-10-26 03:48 UTC1
import { cubicOut } from 'svelte/easing';
d07bca5da4aeatagen2026-10-26 03:48 UTC2
import { fade, slide, type TransitionConfig } from 'svelte/transition';
d07bca5da4aeatagen2026-10-26 03:48 UTC3
d07bca5da4aeatagen2026-10-26 03:48 UTC4
const entering = new Set<string>();
d07bca5da4aeatagen2026-10-26 03:48 UTC5
const leaving = new Set<string>();
d07bca5da4aeatagen2026-10-26 03:48 UTC6
let busyUntil = 0;
d07bca5da4aeatagen2026-10-26 03:48 UTC7
d07bca5da4aeatagen2026-10-26 03:48 UTC8
export function motionDuration(ms: number): number {
d07bca5da4aeatagen2026-10-26 03:48 UTC9
  return typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : ms;
d07bca5da4aeatagen2026-10-26 03:48 UTC10
}
d07bca5da4aeatagen2026-10-26 03:48 UTC11
d07bca5da4aeatagen2026-10-26 03:48 UTC12
export function motionActive(): boolean {
d07bca5da4aeatagen2026-10-26 03:48 UTC13
  return performance.now() < busyUntil;
d07bca5da4aeatagen2026-10-26 03:48 UTC14
}
d07bca5da4aeatagen2026-10-26 03:48 UTC15
d07bca5da4aeatagen2026-10-26 03:48 UTC16
function noteBusy(ms: number): void {
d07bca5da4aeatagen2026-10-26 03:48 UTC17
  busyUntil = Math.max(busyUntil, performance.now() + ms + 60);
d07bca5da4aeatagen2026-10-26 03:48 UTC18
}
d07bca5da4aeatagen2026-10-26 03:48 UTC19
d07bca5da4aeatagen2026-10-26 03:48 UTC20
export function setRowMotion(enter: Iterable<string>, leave: Iterable<string>): void {
d07bca5da4aeatagen2026-10-26 03:48 UTC21
  entering.clear();
d07bca5da4aeatagen2026-10-26 03:48 UTC22
  leaving.clear();
d07bca5da4aeatagen2026-10-26 03:48 UTC23
  for (const key of enter) entering.add(key);
d07bca5da4aeatagen2026-10-26 03:48 UTC24
  for (const key of leave) leaving.add(key);
d07bca5da4aeatagen2026-10-26 03:48 UTC25
}
d07bca5da4aeatagen2026-10-26 03:48 UTC26
d07bca5da4aeatagen2026-10-26 03:48 UTC27
function rowKey(node: Element): string {
d07bca5da4aeatagen2026-10-26 03:48 UTC28
  const { item, row } = (node as HTMLElement).dataset;
d07bca5da4aeatagen2026-10-26 03:48 UTC29
  return item ?? row ?? '';
d07bca5da4aeatagen2026-10-26 03:48 UTC30
}
d07bca5da4aeatagen2026-10-26 03:48 UTC31
0d4a4ccdc6deatagen2026-10-27 05:19 UTC32
/** Sub-pixel sizes from many rows easing out together made the pinned bottom flip between roundings. */
0d4a4ccdc6deatagen2026-10-27 05:19 UTC33
function wholePixels(css: string): string {
0d4a4ccdc6deatagen2026-10-27 05:19 UTC34
  return css.replace(/(-?\d*\.?\d+)px/g, (_, n: string) => `${Math.round(Number(n))}px`);
0d4a4ccdc6deatagen2026-10-27 05:19 UTC35
}
0d4a4ccdc6deatagen2026-10-27 05:19 UTC36
d07bca5da4aeatagen2026-10-26 03:48 UTC37
function reveal(node: Element, ms: number, axis: 'x' | 'y' = 'y'): TransitionConfig {
d07bca5da4aeatagen2026-10-26 03:48 UTC38
  const duration = motionDuration(ms);
d07bca5da4aeatagen2026-10-26 03:48 UTC39
  if (duration === 0) return { duration: 0 };
d07bca5da4aeatagen2026-10-26 03:48 UTC40
  noteBusy(duration);
d07bca5da4aeatagen2026-10-26 03:48 UTC41
  const base = slide(node, { duration, easing: cubicOut, axis });
0d4a4ccdc6deatagen2026-10-27 05:19 UTC42
  return { duration, easing: cubicOut, css: (t, u) => `${wholePixels(base.css?.(t, u) ?? '')};opacity: ${t};` };
d07bca5da4aeatagen2026-10-26 03:48 UTC43
}
d07bca5da4aeatagen2026-10-26 03:48 UTC44
d07bca5da4aeatagen2026-10-26 03:48 UTC45
export function rowIn(node: Element): TransitionConfig {
d07bca5da4aeatagen2026-10-26 03:48 UTC46
  return entering.has(rowKey(node)) ? reveal(node, 200) : { duration: 0 };
d07bca5da4aeatagen2026-10-26 03:48 UTC47
}
d07bca5da4aeatagen2026-10-26 03:48 UTC48
d07bca5da4aeatagen2026-10-26 03:48 UTC49
export function rowOut(node: Element): TransitionConfig {
d07bca5da4aeatagen2026-10-26 03:48 UTC50
  return leaving.has(rowKey(node)) ? reveal(node, 160) : { duration: 0 };
d07bca5da4aeatagen2026-10-26 03:48 UTC51
}
d07bca5da4aeatagen2026-10-26 03:48 UTC52
d07bca5da4aeatagen2026-10-26 03:48 UTC53
export function expand(node: Element, params: { axis?: 'x' | 'y' } = {}): TransitionConfig {
d07bca5da4aeatagen2026-10-26 03:48 UTC54
  return reveal(node, 170, params.axis);
d07bca5da4aeatagen2026-10-26 03:48 UTC55
}
d07bca5da4aeatagen2026-10-26 03:48 UTC56
d07bca5da4aeatagen2026-10-26 03:48 UTC57
export function dissolve(node: Element): TransitionConfig {
d07bca5da4aeatagen2026-10-26 03:48 UTC58
  const duration = motionDuration(150);
d07bca5da4aeatagen2026-10-26 03:48 UTC59
  if (duration > 0) noteBusy(duration);
d07bca5da4aeatagen2026-10-26 03:48 UTC60
  return fade(node, { duration });
d07bca5da4aeatagen2026-10-26 03:48 UTC61
}
d07bca5da4aeatagen2026-10-26 03:48 UTC62
0d4a4ccdc6deatagen2026-10-27 05:19 UTC63
const settling = new WeakSet<HTMLElement>();
0d4a4ccdc6deatagen2026-10-27 05:19 UTC64
d07bca5da4aeatagen2026-10-26 03:48 UTC65
export function settleHeight(node: HTMLElement, from: number): void {
d07bca5da4aeatagen2026-10-26 03:48 UTC66
  const duration = motionDuration(180);
d07bca5da4aeatagen2026-10-26 03:48 UTC67
  const to = node.offsetHeight;
0d4a4ccdc6deatagen2026-10-27 05:19 UTC68
  if (duration === 0 || Math.abs(to - from) < 2 || settling.has(node) || node.getAnimations({ subtree: true }).length > 0) return;
d07bca5da4aeatagen2026-10-26 03:48 UTC69
  noteBusy(duration);
0d4a4ccdc6deatagen2026-10-27 05:19 UTC70
  settling.add(node);
0d4a4ccdc6deatagen2026-10-27 05:19 UTC71
  const start = performance.now();
0d4a4ccdc6deatagen2026-10-27 05:19 UTC72
  node.style.overflow = 'hidden';
0d4a4ccdc6deatagen2026-10-27 05:19 UTC73
  const step = (now: number) => {
0d4a4ccdc6deatagen2026-10-27 05:19 UTC74
    const t = Math.min(1, (now - start) / duration);
0d4a4ccdc6deatagen2026-10-27 05:19 UTC75
    if (t < 1) {
0d4a4ccdc6deatagen2026-10-27 05:19 UTC76
      node.style.height = `${Math.round(from + (to - from) * cubicOut(t))}px`;
0d4a4ccdc6deatagen2026-10-27 05:19 UTC77
      requestAnimationFrame(step);
0d4a4ccdc6deatagen2026-10-27 05:19 UTC78
    } else {
0d4a4ccdc6deatagen2026-10-27 05:19 UTC79
      node.style.height = '';
0d4a4ccdc6deatagen2026-10-27 05:19 UTC80
      node.style.overflow = '';
0d4a4ccdc6deatagen2026-10-27 05:19 UTC81
      settling.delete(node);
0d4a4ccdc6deatagen2026-10-27 05:19 UTC82
    }
0d4a4ccdc6deatagen2026-10-27 05:19 UTC83
  };
0d4a4ccdc6deatagen2026-10-27 05:19 UTC84
  node.style.height = `${Math.round(from)}px`;
0d4a4ccdc6deatagen2026-10-27 05:19 UTC85
  requestAnimationFrame(step);
d07bca5da4aeatagen2026-10-26 03:48 UTC86
}