fix(badges): perceptually uniform rainbow via OKLCH + @property
Replaces filter:hue-rotate() with CSS @property --ph-hue/--ph-lit animated as numbers, applied as oklch() colors on SVG rect/line children. OKLCH keeps perceptual lightness constant across hue rotation — no more brown/dark-blue variance between slots. Pulse mode animates --ph-lit 0.42→0.78 for breathing. Adds slow_pulse cfg_json flag + form checkbox. @property inherits:true lets the animated value cascade from the div to its SVG children without per-child animation declarations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1419,24 +1419,58 @@ const code_to_icon: {
|
||||
* active they form a proper tri-phase RGB cycle. Two active slots are 120° apart.
|
||||
* Negative animation-delay starts each slot mid-cycle at its designated phase.
|
||||
*/
|
||||
/* Cycle mode (default): fast linear hue rotation, 120° phase apart per slot */
|
||||
@keyframes ae_punch_hole_rainbow {
|
||||
from { filter: hue-rotate(0deg); }
|
||||
to { filter: hue-rotate(360deg); }
|
||||
/*
|
||||
* Perceptually uniform punch-hole rainbow via OKLCH + @property.
|
||||
*
|
||||
* WHY NOT hue-rotate(): CSS filter hue-rotate() works in RGB space — perceptual
|
||||
* brightness varies across hues (yellow ≫ blue), so slots at different phases look
|
||||
* noticeably lighter or darker than each other.
|
||||
*
|
||||
* FIX: @property lets the browser interpolate --ph-hue as a number. Applying colors
|
||||
* as oklch(L C H) rotates hue at CONSTANT perceptual lightness — all three slots
|
||||
* stay visually equally bright regardless of phase position.
|
||||
*
|
||||
* inherits: true — the animated value cascades to SVG children (rect, line) so
|
||||
* each child reads the parent div's current --ph-hue without its own animation.
|
||||
*/
|
||||
@property --ph-hue {
|
||||
syntax: '<number>';
|
||||
inherits: true;
|
||||
initial-value: 0;
|
||||
}
|
||||
@property --ph-lit {
|
||||
syntax: '<number>';
|
||||
inherits: true;
|
||||
initial-value: 0.65;
|
||||
}
|
||||
.punch_hole_rainbow_left { animation: ae_punch_hole_rainbow 2.5s 0.000s linear infinite; }
|
||||
.punch_hole_rainbow_right { animation: ae_punch_hole_rainbow 2.5s -0.833s linear infinite; }
|
||||
.punch_hole_rainbow_center { animation: ae_punch_hole_rainbow 2.5s -1.667s linear infinite; }
|
||||
|
||||
/* Pulse mode: slow breathing — dim→bright while hue shifts 180° and back.
|
||||
Same 120° phase offsets (6s ÷ 3 = 2s per slot) so they breathe in turn. */
|
||||
@keyframes ae_punch_hole_pulse {
|
||||
0%, 100% { filter: hue-rotate(0deg) brightness(0.55); }
|
||||
50% { filter: hue-rotate(180deg) brightness(1.30); }
|
||||
/* Cycle: hue rotates, lightness stays at 0.65 */
|
||||
@keyframes ae_ph_cycle { from { --ph-hue: 0; } to { --ph-hue: 360; } }
|
||||
|
||||
/* Pulse: hue shifts 180° while lightness breathes 0.42 → 0.78 → 0.42 */
|
||||
@keyframes ae_ph_pulse {
|
||||
0%, 100% { --ph-hue: 0; --ph-lit: 0.42; }
|
||||
50% { --ph-hue: 180; --ph-lit: 0.78; }
|
||||
}
|
||||
|
||||
/* 120° phase offsets: 2.5s ÷ 3 ≈ 0.833s | 6s ÷ 3 = 2s */
|
||||
.punch_hole_rainbow_left { animation: ae_ph_cycle 2.5s 0.000s linear infinite; }
|
||||
.punch_hole_rainbow_right { animation: ae_ph_cycle 2.5s -0.833s linear infinite; }
|
||||
.punch_hole_rainbow_center { animation: ae_ph_cycle 2.5s -1.667s linear infinite; }
|
||||
.punch_hole_pulse_left { animation: ae_ph_pulse 6s 0.000s ease-in-out infinite; }
|
||||
.punch_hole_pulse_right { animation: ae_ph_pulse 6s -2.000s ease-in-out infinite; }
|
||||
.punch_hole_pulse_center { animation: ae_ph_pulse 6s -4.000s ease-in-out infinite; }
|
||||
|
||||
/* CSS overrides the static inline SVG fill/stroke attributes for animated markers. */
|
||||
.punch_hole_rainbow_left rect, .punch_hole_rainbow_right rect, .punch_hole_rainbow_center rect,
|
||||
.punch_hole_pulse_left rect, .punch_hole_pulse_right rect, .punch_hole_pulse_center rect {
|
||||
fill: oklch(var(--ph-lit) 0.20 var(--ph-hue) / 0.40);
|
||||
stroke: oklch(var(--ph-lit) 0.25 var(--ph-hue));
|
||||
}
|
||||
.punch_hole_rainbow_left line, .punch_hole_rainbow_right line, .punch_hole_rainbow_center line,
|
||||
.punch_hole_pulse_left line, .punch_hole_pulse_right line, .punch_hole_pulse_center line {
|
||||
stroke: oklch(var(--ph-lit) 0.25 var(--ph-hue));
|
||||
}
|
||||
.punch_hole_pulse_left { animation: ae_punch_hole_pulse 6s 0.000s ease-in-out infinite; }
|
||||
.punch_hole_pulse_right { animation: ae_punch_hole_pulse 6s -2.000s ease-in-out infinite; }
|
||||
.punch_hole_pulse_center { animation: ae_punch_hole_pulse 6s -4.000s ease-in-out infinite; }
|
||||
|
||||
.punch_hole_print_rgb { display: none; }
|
||||
@media print {
|
||||
|
||||
Reference in New Issue
Block a user