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:
@@ -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); }
|
||||
|
||||
@@ -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}">✕</button>
|
||||
|
||||
@@ -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: `
|
||||
|
||||
Reference in New Issue
Block a user