Add rotating gradient border on running LED target cards

Animated conic gradient spins around the card edge using CSS Houdini
@property for smooth angle interpolation. Skips the left edge when
a custom card color stripe is assigned (data-has-color attribute).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 11:18:19 +03:00
parent ff7b595032
commit 012e9f5ddb
3 changed files with 64 additions and 1 deletions

View File

@@ -99,6 +99,68 @@ section {
);
}
/* ── Running target: rotating gradient border ── */
@property --border-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.card-running {
border-color: transparent;
}
/* When card has a custom color stripe, keep it and shift the animated border away from the left edge */
.card-running[data-has-color]::before {
inset: 0 0 0 3px;
border-left: none;
border-radius: 0 8px 8px 0;
}
.card-running::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
border: 2px solid transparent;
background:
conic-gradient(
from var(--border-angle),
var(--primary-color),
rgba(255,255,255,0.1) 25%,
var(--primary-color) 50%,
rgba(255,255,255,0.1) 75%,
var(--primary-color)
) border-box;
-webkit-mask:
linear-gradient(#fff 0 0) padding-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask:
linear-gradient(#fff 0 0) padding-box,
linear-gradient(#fff 0 0);
mask-composite: exclude;
pointer-events: none;
z-index: 2;
animation: rotateBorder 4s linear infinite;
}
@keyframes rotateBorder {
to { --border-angle: 360deg; }
}
[data-theme="light"] .card-running::before {
background:
conic-gradient(
from var(--border-angle),
var(--primary-color),
rgba(0,0,0,0.05) 25%,
var(--primary-color) 50%,
rgba(0,0,0,0.05) 75%,
var(--primary-color)
) border-box;
}
/* ── Card entrance animation ── */
@keyframes cardEnter {
from { opacity: 0; transform: translateY(12px); }

View File

@@ -102,7 +102,7 @@ export function wrapCard({
const actionsClass = type === 'template-card' ? 'template-card-actions' : 'card-actions';
const colorStyle = cardColorStyle(id);
return `
<div class="${type}${classes ? ' ' + classes : ''}" ${dataAttr}="${id}"${colorStyle ? ` style="${colorStyle}"` : ''}>
<div class="${type}${classes ? ' ' + classes : ''}" ${dataAttr}="${id}"${colorStyle ? ` style="${colorStyle}" data-has-color="1"` : ''}>
<div class="card-top-actions">
${topButtons}
<button class="card-remove-btn" onclick="${removeOnclick}" title="${removeTitle}">&#x2715;</button>

View File

@@ -978,6 +978,7 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSo
return wrapCard({
dataAttr: 'data-target-id',
id: target.id,
classes: isProcessing ? 'card-running' : '',
removeOnclick: `deleteTarget('${target.id}')`,
removeTitle: t('common.delete'),
content: `