feat(badges): slow pulse mode for rainbow punch hole markers
Adds punch_holes.slow_pulse cfg_json flag. When enabled, replaces the fast 2.5s linear hue-rotate with a 6s ease-in-out breathing animation that dims (0.55 brightness) to bright (1.30) while shifting 180° of hue and back. Same 120° phase offsets apply (2s apart). Form shows a Slow Pulse checkbox below the slot cards whenever at least one slot has rainbow enabled. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,7 @@ export interface BadgeTemplateCfg {
|
|||||||
center_fg?: string;
|
center_fg?: string;
|
||||||
center_bg?: string;
|
center_bg?: string;
|
||||||
center_rainbow?: boolean;
|
center_rainbow?: boolean;
|
||||||
|
slow_pulse?: boolean; // when true: slow breathing pulse instead of fast linear cycle
|
||||||
};
|
};
|
||||||
|
|
||||||
// Allow arbitrary extra keys to preserve forward-compatibility.
|
// Allow arbitrary extra keys to preserve forward-compatibility.
|
||||||
|
|||||||
@@ -342,6 +342,7 @@ const PH_RAINBOW_BG = 'rgba(255,34,0,0.25)';
|
|||||||
let punch_holes_left_rainbow = $derived(!!template_cfg?.punch_holes?.left_rainbow);
|
let punch_holes_left_rainbow = $derived(!!template_cfg?.punch_holes?.left_rainbow);
|
||||||
let punch_holes_right_rainbow = $derived(!!template_cfg?.punch_holes?.right_rainbow);
|
let punch_holes_right_rainbow = $derived(!!template_cfg?.punch_holes?.right_rainbow);
|
||||||
let punch_holes_center_rainbow = $derived(!!template_cfg?.punch_holes?.center_rainbow);
|
let punch_holes_center_rainbow = $derived(!!template_cfg?.punch_holes?.center_rainbow);
|
||||||
|
let punch_holes_slow_pulse = $derived(!!template_cfg?.punch_holes?.slow_pulse);
|
||||||
let punch_holes_colors = $derived.by(() => {
|
let punch_holes_colors = $derived.by(() => {
|
||||||
const ph = template_cfg?.punch_holes;
|
const ph = template_cfg?.punch_holes;
|
||||||
const shared_fg = ph?.fg || PH_DEFAULT_FG;
|
const shared_fg = ph?.fg || PH_DEFAULT_FG;
|
||||||
@@ -707,7 +708,9 @@ const code_to_icon: {
|
|||||||
starts at a different phase (0°, 120°, 240°) and they cycle in unison
|
starts at a different phase (0°, 120°, 240°) and they cycle in unison
|
||||||
at the same speed — tri-phase RGB rather than drifting randomly. -->
|
at the same speed — tri-phase RGB rather than drifting randomly. -->
|
||||||
{#if punch_holes_left && punch_holes_left_rainbow}
|
{#if punch_holes_left && punch_holes_left_rainbow}
|
||||||
<div class="punch_hole_marker punch_hole_rainbow_left pointer-events-none absolute"
|
<div class="punch_hole_marker pointer-events-none absolute"
|
||||||
|
class:punch_hole_rainbow_left={!punch_holes_slow_pulse}
|
||||||
|
class:punch_hole_pulse_left={punch_holes_slow_pulse}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style="top: calc(0.25in + 1mm); left: calc(0.375in + 1mm); width: calc(0.625in - 2mm); height: calc(0.125in - 2mm); z-index: 10;">
|
style="top: calc(0.25in + 1mm); left: calc(0.375in + 1mm); width: calc(0.625in - 2mm); height: calc(0.125in - 2mm); z-index: 10;">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 50 8" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="100%" height="100%" viewBox="0 0 50 8" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -728,7 +731,9 @@ const code_to_icon: {
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if punch_holes_right && punch_holes_right_rainbow}
|
{#if punch_holes_right && punch_holes_right_rainbow}
|
||||||
<div class="punch_hole_marker punch_hole_rainbow_right pointer-events-none absolute"
|
<div class="punch_hole_marker pointer-events-none absolute"
|
||||||
|
class:punch_hole_rainbow_right={!punch_holes_slow_pulse}
|
||||||
|
class:punch_hole_pulse_right={punch_holes_slow_pulse}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style="top: calc(0.25in + 1mm); right: calc(0.375in + 1mm); width: calc(0.625in - 2mm); height: calc(0.125in - 2mm); z-index: 10;">
|
style="top: calc(0.25in + 1mm); right: calc(0.375in + 1mm); width: calc(0.625in - 2mm); height: calc(0.125in - 2mm); z-index: 10;">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 50 8" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="100%" height="100%" viewBox="0 0 50 8" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -749,7 +754,9 @@ const code_to_icon: {
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if punch_holes_center && punch_holes_center_rainbow}
|
{#if punch_holes_center && punch_holes_center_rainbow}
|
||||||
<div class="punch_hole_marker punch_hole_rainbow_center pointer-events-none absolute"
|
<div class="punch_hole_marker pointer-events-none absolute"
|
||||||
|
class:punch_hole_rainbow_center={!punch_holes_slow_pulse}
|
||||||
|
class:punch_hole_pulse_center={punch_holes_slow_pulse}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style="top: calc(0.25in + 1mm); left: 50%; transform: translateX(-50%); width: calc(0.625in - 2mm); height: calc(0.125in - 2mm); z-index: 10;">
|
style="top: calc(0.25in + 1mm); left: 50%; transform: translateX(-50%); width: calc(0.625in - 2mm); height: calc(0.125in - 2mm); z-index: 10;">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 50 8" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="100%" height="100%" viewBox="0 0 50 8" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -1412,19 +1419,29 @@ const code_to_icon: {
|
|||||||
* active they form a proper tri-phase RGB cycle. Two active slots are 120° apart.
|
* 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.
|
* 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 {
|
@keyframes ae_punch_hole_rainbow {
|
||||||
from { filter: hue-rotate(0deg); }
|
from { filter: hue-rotate(0deg); }
|
||||||
to { filter: hue-rotate(360deg); }
|
to { filter: hue-rotate(360deg); }
|
||||||
}
|
}
|
||||||
.punch_hole_rainbow_left { animation: ae_punch_hole_rainbow 2.5s 0.000s linear infinite; }
|
.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_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; }
|
.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); }
|
||||||
|
}
|
||||||
|
.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; }
|
.punch_hole_print_rgb { display: none; }
|
||||||
@media print {
|
@media print {
|
||||||
.punch_hole_rainbow_left,
|
.punch_hole_rainbow_left, .punch_hole_rainbow_right, .punch_hole_rainbow_center,
|
||||||
.punch_hole_rainbow_right,
|
.punch_hole_pulse_left, .punch_hole_pulse_right, .punch_hole_pulse_center { display: none; }
|
||||||
.punch_hole_rainbow_center { display: none; }
|
|
||||||
.punch_hole_print_rgb { display: block; }
|
.punch_hole_print_rgb { display: block; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ let cfg_punch_holes_right_rainbow = $state(false);
|
|||||||
let cfg_punch_holes_center_fg = $state('');
|
let cfg_punch_holes_center_fg = $state('');
|
||||||
let cfg_punch_holes_center_bg = $state('');
|
let cfg_punch_holes_center_bg = $state('');
|
||||||
let cfg_punch_holes_center_rainbow = $state(false);
|
let cfg_punch_holes_center_rainbow = $state(false);
|
||||||
|
// Applies to all rainbow slots: slow breathing pulse instead of fast cycle
|
||||||
|
let cfg_punch_holes_slow_pulse = $state(false);
|
||||||
// Header bottom border. Empty color = no border.
|
// Header bottom border. Empty color = no border.
|
||||||
let cfg_header_border_color = $state('');
|
let cfg_header_border_color = $state('');
|
||||||
let cfg_header_border_width = $state('');
|
let cfg_header_border_width = $state('');
|
||||||
@@ -197,6 +199,7 @@ async function load_template(id: string) {
|
|||||||
cfg_punch_holes_center_fg = parsed_cfg?.punch_holes?.center_fg ?? '';
|
cfg_punch_holes_center_fg = parsed_cfg?.punch_holes?.center_fg ?? '';
|
||||||
cfg_punch_holes_center_bg = parsed_cfg?.punch_holes?.center_bg ?? '';
|
cfg_punch_holes_center_bg = parsed_cfg?.punch_holes?.center_bg ?? '';
|
||||||
cfg_punch_holes_center_rainbow = parsed_cfg?.punch_holes?.center_rainbow ?? false;
|
cfg_punch_holes_center_rainbow = parsed_cfg?.punch_holes?.center_rainbow ?? false;
|
||||||
|
cfg_punch_holes_slow_pulse = parsed_cfg?.punch_holes?.slow_pulse ?? false;
|
||||||
cfg_header_border_color = parsed_cfg.header_border_color ?? '';
|
cfg_header_border_color = parsed_cfg.header_border_color ?? '';
|
||||||
cfg_header_border_width = parsed_cfg.header_border_width ?? '';
|
cfg_header_border_width = parsed_cfg.header_border_width ?? '';
|
||||||
cfg_header_padding_top = parsed_cfg.header_padding_top ?? '';
|
cfg_header_padding_top = parsed_cfg.header_padding_top ?? '';
|
||||||
@@ -297,6 +300,7 @@ async function handle_submit() {
|
|||||||
if (cfg_punch_holes_center_fg.trim()) ph.center_fg = cfg_punch_holes_center_fg.trim();
|
if (cfg_punch_holes_center_fg.trim()) ph.center_fg = cfg_punch_holes_center_fg.trim();
|
||||||
if (cfg_punch_holes_center_bg.trim()) ph.center_bg = cfg_punch_holes_center_bg.trim();
|
if (cfg_punch_holes_center_bg.trim()) ph.center_bg = cfg_punch_holes_center_bg.trim();
|
||||||
if (cfg_punch_holes_center_rainbow) ph.center_rainbow = true;
|
if (cfg_punch_holes_center_rainbow) ph.center_rainbow = true;
|
||||||
|
if (cfg_punch_holes_slow_pulse) ph.slow_pulse = true;
|
||||||
cfg_obj.punch_holes = ph;
|
cfg_obj.punch_holes = ph;
|
||||||
} else {
|
} else {
|
||||||
delete cfg_obj.punch_holes;
|
delete cfg_obj.punch_holes;
|
||||||
@@ -477,6 +481,13 @@ function toggle_cfg_controls_auth_editable(key: string) {
|
|||||||
{@render hole_colors('Left', cfg_punch_holes_left, cfg_punch_holes_left_fg, cfg_punch_holes_left_bg, cfg_punch_holes_left_rainbow, (v) => cfg_punch_holes_left_fg = v, (v) => cfg_punch_holes_left_bg = v, (v) => cfg_punch_holes_left_rainbow = v)}
|
{@render hole_colors('Left', cfg_punch_holes_left, cfg_punch_holes_left_fg, cfg_punch_holes_left_bg, cfg_punch_holes_left_rainbow, (v) => cfg_punch_holes_left_fg = v, (v) => cfg_punch_holes_left_bg = v, (v) => cfg_punch_holes_left_rainbow = v)}
|
||||||
{@render hole_colors('Right', cfg_punch_holes_right, cfg_punch_holes_right_fg, cfg_punch_holes_right_bg, cfg_punch_holes_right_rainbow, (v) => cfg_punch_holes_right_fg = v, (v) => cfg_punch_holes_right_bg = v, (v) => cfg_punch_holes_right_rainbow = v)}
|
{@render hole_colors('Right', cfg_punch_holes_right, cfg_punch_holes_right_fg, cfg_punch_holes_right_bg, cfg_punch_holes_right_rainbow, (v) => cfg_punch_holes_right_fg = v, (v) => cfg_punch_holes_right_bg = v, (v) => cfg_punch_holes_right_rainbow = v)}
|
||||||
{@render hole_colors('Center', cfg_punch_holes_center, cfg_punch_holes_center_fg, cfg_punch_holes_center_bg, cfg_punch_holes_center_rainbow, (v) => cfg_punch_holes_center_fg = v, (v) => cfg_punch_holes_center_bg = v, (v) => cfg_punch_holes_center_rainbow = v)}
|
{@render hole_colors('Center', cfg_punch_holes_center, cfg_punch_holes_center_fg, cfg_punch_holes_center_bg, cfg_punch_holes_center_rainbow, (v) => cfg_punch_holes_center_fg = v, (v) => cfg_punch_holes_center_bg = v, (v) => cfg_punch_holes_center_rainbow = v)}
|
||||||
|
|
||||||
|
{#if cfg_punch_holes_left_rainbow || cfg_punch_holes_right_rainbow || cfg_punch_holes_center_rainbow}
|
||||||
|
<label class="label flex items-center gap-2 pt-1 border-t border-surface-200-800">
|
||||||
|
<input type="checkbox" bind:checked={cfg_punch_holes_slow_pulse} class="checkbox" />
|
||||||
|
<span class="text-sm">Slow Pulse <span class="text-xs text-surface-400 italic">(gentle breathing instead of fast cycle)</span></span>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="label">
|
<label class="label">
|
||||||
|
|||||||
Reference in New Issue
Block a user