|
56 | 56 | // --- Rainbow state --- |
57 | 57 | // Палитра обоев [[r,g,b], ...]. Пусто — режим выключен. |
58 | 58 | let palette = []; |
| 59 | + // Отображаемая палитра: каждый кадр плавно тянется к `palette`, чтобы |
| 60 | + // смена обоев не переключала цвета резко (как cross-fade фона виджета). |
| 61 | + let displayedPalette = []; |
59 | 62 | let rainbowEnabled = false; |
60 | 63 | let rainbowWaveIndex = 0; |
61 | 64 | // Beat-детектор низа (кик). |
|
79 | 82 | let glowPulse = 0; |
80 | 83 |
|
81 | 84 | // Цвет палитры в непрерывной позиции t (циклично, с интерполяцией). |
| 85 | + // Берём отображаемую палитру, чтобы переход между палитрами был плавным. |
82 | 86 | function samplePalette(t) { |
83 | | - const N = palette.length; |
| 87 | + const src = displayedPalette.length > 0 ? displayedPalette : palette; |
| 88 | + const N = src.length; |
84 | 89 | const f = (((t % 1) + 1) % 1) * N; |
85 | 90 | const i = Math.floor(f); |
86 | 91 | const frac = f - i; |
87 | | - const a = palette[i % N], b = palette[(i + 1) % N]; |
| 92 | + const a = src[i % N], b = src[(i + 1) % N]; |
88 | 93 | return [a[0] + (b[0] - a[0]) * frac, |
89 | 94 | a[1] + (b[1] - a[1]) * frac, |
90 | 95 | a[2] + (b[2] - a[2]) * frac]; |
|
127 | 132 | function advanceRainbow() { |
128 | 133 | if (palette.length === 0) return; |
129 | 134 | rainbowWaveIndex = (rainbowWaveIndex + 1) % palette.length; |
130 | | - targetColor = palette[rainbowWaveIndex].slice(); |
| 135 | + // Цель берём из отображаемой палитры: при смене обоев волна плавно |
| 136 | + // перетечёт в новые оттенки, а не прыгнет в ещё не переехавший цвет. |
| 137 | + const src = (displayedPalette.length === palette.length) ? displayedPalette : palette; |
| 138 | + targetColor = src[rainbowWaveIndex].slice(); |
131 | 139 | } |
132 | 140 |
|
133 | 141 |
|
|
442 | 450 | glowColor[2] = waveColor[2]; |
443 | 451 | } |
444 | 452 |
|
| 453 | + // Плавный переход палитры к новой (смена обоев): тянем каждый канал |
| 454 | + // каждого цвета. Темп 0.05/кадр ≈ то же ~1 с переливание, что и у фона. |
| 455 | + if (palette.length > 0) { |
| 456 | + if (displayedPalette.length !== palette.length) { |
| 457 | + // Стартуем от текущего цвета волны — переход без вспышки. |
| 458 | + displayedPalette = palette.map(() => waveColor.slice()); |
| 459 | + } |
| 460 | + for (let i = 0; i < palette.length; i++) { |
| 461 | + for (let ch = 0; ch < 3; ch++) { |
| 462 | + const d = palette[i][ch] - displayedPalette[i][ch]; |
| 463 | + displayedPalette[i][ch] = Math.abs(d) < 0.5 |
| 464 | + ? palette[i][ch] |
| 465 | + : displayedPalette[i][ch] + d * 0.05; |
| 466 | + } |
| 467 | + } |
| 468 | + } else if (displayedPalette.length > 0) { |
| 469 | + displayedPalette = []; |
| 470 | + } |
| 471 | + |
445 | 472 | // «Панч»: масштабируем всю картинку от центра на время удара. |
446 | 473 | const scaled = punch > 0.0005; |
447 | 474 | if (scaled) { |
|
497 | 524 | ? colors.filter(c => Array.isArray(c) && c.length === 3) |
498 | 525 | .map(c => vivify([c[0] | 0, c[1] | 0, c[2] | 0])) |
499 | 526 | : []; |
| 527 | + const wasEnabled = rainbowEnabled; |
500 | 528 | rainbowEnabled = !!enabled && palette.length > 0; |
501 | | - if (rainbowEnabled) { |
502 | | - // Стартуем с первого цвета палитры. |
| 529 | + if (rainbowEnabled && !wasEnabled) { |
| 530 | + // Сбрасываем позицию ТОЛЬКО при включении режима. При обычной смене |
| 531 | + // обоев позицию по битам не трогаем — цвета сами плавно перетекут |
| 532 | + // через displayedPalette и интерполяцию волны. |
503 | 533 | rainbowWaveIndex = 0; |
504 | | - targetColor = palette[0].slice(); |
| 534 | + const src = (displayedPalette.length === palette.length) ? displayedPalette : palette; |
| 535 | + targetColor = src[0].slice(); |
505 | 536 | beatCooldown = 0; |
506 | 537 | idleFrames = 0; |
507 | 538 | } |
|
0 commit comments