# Кривые и градиенты — `$.curve`

Одна форма плавности на весь движок. До этой подсистемы каждый модуль писал
свою формулу затухания — вспышка, разгон камеры, размер частицы, прозрачность
шлейфа, — и все они были разными. Теперь есть общий язык: `t → значение`.

```js
$.ready(() => {
    const pop  = $.curve.use('pop');          // готовая кривая
    const fire = $.curve.gradient('#fff2a8 → #ff6b1a → #7a1f00');

    $('#hero').tween({ y: -40 }, 0.25, { ease: pop });   // твин понимает кривую
    $('#fire').color(fire($.time.now() % 1));
});
```

---

## 1. Значения и градиенты

| Вызов | Что делает |
|---|---|
| `$.curve.make(values, opts?)` | кривая значений |
| `$.curve.gradient(stops, opts?)` | кривая цвета |
| `$.curve.define(name, values, opts?)` | объявить именованную кривую |
| `$.curve.use(name)` | готовая или объявленная кривая по имени (в журнал уйдёт подсказка, если имени нет) |
| `$.curve.get(name)` | то же, но без записи в журнал; `null`, если нет |
| `$.curve.names()` | имена: свои и встроенные |
| `$.curve.resolve(value, fallback?)` | функция, число или имя → функция `t → y` |
| `$.curve.remove(name)` / `clear()` | забыть именованные кривые |

Кривая — это **функция** `(t) => y`, у неё есть методы:

| Метод | Смысл |
|---|---|
| `curve(t)` | значение в точке (без зажима: за пределами — крайние значения) |
| `curve.at(t)` | то же с зажимом `t` в 0..1 |
| `curve.range(n)` | `n` равномерных сэмплов — для sparkline, буфера, полосы |
| `curve.points()` | точки кривой `[{ x, y }]` |
| `curve.plus(other)` | сложить с другой кривой или числом |
| `curve.mode` | режим интерполяции |

## 2. Как задаются точки

```js
$.curve.make([0, 1, 0.2]);                      // равномерно: (0,0) (0.5,1) (1,0.2)
$.curve.make([[0, 0], [0.8, 1], [1, 0.5]]);     // x задан явно
$.curve.make([{ x: 0, y: 0 }, { x: 1, y: 1 }]); // точками
```

Список значений раскладывается равномерно по `0..1`; пары `[x, y]` и объекты
`{x, y}` позволяют управлять положением точки по времени — например, чтобы
быстрый подъём занимал 10% времени, а медленный спад — остальные 90%.

## 3. Режимы (`mode`)

| Режим | Что делает | Кому |
|---|---|---|
| `linear` | прямая между точками (по умолчанию) | предсказуемые тайминги |
| `step` | ступенька: значение держится до следующей точки | светофор, кадры, «щелчки» |
| `smooth` | гладкая интерполяция (Catmull-Rom) | затухания, «дыхание» |
| `spline` | то же, крайние касательные нулевые | то же, но без выбросов на концах |

```js
const blink = $.curve.make([[0, 1], [0.5, 1], [0.5, 0], [1, 0]], { mode: 'step' });
```

## 4. Встроенные кривые

`linear`, `easeIn`, `easeOut`, `easeInOut`, `pop` (с перелётом), `bounce`,
`pulse` (0→1→0), `spike` (0→0→1→0→0), `fadeIn`, `fadeOut`.

Всё, что принимает `ease`, понимает и кривую: `$.tween`, переходы камеры,
эффекты. Число вместо кривой тоже принимается — `$.curve.resolve(2)` отдаст
постоянную функцию.

## 5. Градиенты

```js
const fire = $.curve.gradient('#fff2a8 → #ff6b1a → #7a1f00');   // строка
const hp   = $.curve.gradient([
    { at: 0,   color: '#ff2d2d' },
    { at: 0.6, color: '#ffd23d' },
    { at: 1,   color: '#3ddc84' },
]);
const ramp = $.curve.gradient(['#000', '#fff']);                 // равномерно
```

| Метод | Возвращает |
|---|---|
| `gradient(t)` | упакованный RGBA — как `engine.rgba` |
| `gradient.at(t)` | `[r, g, b, a]` числами 0..255 (с зажимом) |
| `gradient.range(n)` | `n` цветов по сетке |
| `gradient.stops()` | исходные стопы `[{ x, color }]` |

Цвета принимаются как `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb(...)`,
`transparent` и упакованным числом. Интерполяция **покомпонентная** (не в
sRGB): так переход между тёмными цветами не «выцветает» в серое.

## 6. Ограничения

* **шестнадцатикратное превышение возможно**: сплайн не «зажимает» значение,
  и `pop` даёт `> 1` в середине — это специально, но будьте осторожны там, где
  значение идёт в альфу или размер (зажмите `.at()`, он тоже не зажимает
  значение, только `t`);
* **нет параметрических кривых Безье**: только интерполяция по точкам. Для
  UI-анимаций с касательными нужен свой `ease`-функцией — кривая принимает её
  как есть;
* **градиент считается на CPU**: по одному вызову на цвет. Для заливки области
  это дорого — рисуйте полосами (`gradient.range(n)`) или используйте
  вершинные цвета;
* **нет узлов-градиентов**: заливка градиентом прямоугольника не появилась,
  только цвета по параметру.

## 7. Проверка

```bash
# чистые функции: раскладка точек, режимы, разбор цветов, каналы градиента
build/_deps/quickjs-build/qjs tests/js/curve_test.mjs

# в движке: реестр имён и совместная работа с твинами
python3 tests/agent/highlevel_curve_test.py
```
