mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
cc684ab689
OpenCode syntax-highlights code in the transcript; Pulse rendered fenced blocks as flat text. Assistant answers are full of shell commands, configs, and query output, so add highlight.js with an infra grammar set (bash, json, yaml, ini, sql, dockerfile, nginx): - Lazy-loaded into its own vendor-highlight chunk; the entry chunk stays clean and the chunk only downloads with the rest of the preloaded app shell. - Highlighting runs over the sanitized DOM after the turn settles, so generated spans never pass through DOMPurify, streaming morphs are never fought, and the class allowlist stays closed to the model. The single carve-out is marked's fence-language hint, pattern-pinned to language-x on <code> only. - Token palette maps hljs classes to theme colors readable on the dark prose-pre background both app themes share. Per-block copy is intentionally not added: OpenCode has none, and the answer-level copy already exists.
768 lines
19 KiB
CSS
768 lines
19 KiB
CSS
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
/*
|
|
* Neutralize a Tailwind color/font-size collision: the dead `base` alias in
|
|
* the color palette was making Tailwind emit `color: var(--color-bg-base)` on
|
|
* `.text-base` / `.sm\:text-base`, painting text the same color as the
|
|
* background. The alias has been removed from tailwind.config.js; this rule
|
|
* keeps things visible until the next dev-server restart picks up that change.
|
|
*/
|
|
@layer utilities {
|
|
.text-base,
|
|
.sm\:text-base,
|
|
.md\:text-base,
|
|
.lg\:text-base,
|
|
.xl\:text-base,
|
|
.\32xl\:text-base {
|
|
color: inherit;
|
|
}
|
|
}
|
|
|
|
/* Update banner animation */
|
|
@keyframes slideDown {
|
|
from {
|
|
transform: translateY(-100%);
|
|
opacity: 0;
|
|
}
|
|
|
|
to {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.animate-slideDown {
|
|
animation: slideDown 0.3s ease-out;
|
|
}
|
|
|
|
/* Notification animations */
|
|
@keyframes slide-in-card {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateX(100%) scale(0.95);
|
|
filter: blur(4px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateX(0) scale(1);
|
|
filter: blur(0);
|
|
}
|
|
}
|
|
|
|
.animate-slide-in-card {
|
|
animation: slide-in-card 0.5s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
|
|
/* Custom styles */
|
|
@layer base {
|
|
:root {
|
|
/*
|
|
Layout tokens.
|
|
Goal: keep the app readable (not "widescreen"), but avoid self-inflicted cramping on large monitors.
|
|
*/
|
|
--pulse-shell-max-width: min(97vw, 1560px);
|
|
--pulse-shell-padding-inline: clamp(0.75rem, 1.2vw, 1.5rem);
|
|
--pulse-panel-padding: clamp(0.75rem, 1.4vw, 1.25rem);
|
|
|
|
/* Semantic Colors - Light Mode */
|
|
--color-bg-base: theme('colors.slate.100');
|
|
--color-bg-surface: theme('colors.white');
|
|
--color-bg-surface-hover: theme('colors.slate.50');
|
|
--color-bg-surface-alt: theme('colors.slate.100');
|
|
--color-border-subtle: theme('colors.slate.100');
|
|
--color-border-base: theme('colors.slate.200');
|
|
--color-text-base: theme('colors.slate.900');
|
|
--color-text-muted: theme('colors.slate.600');
|
|
--color-summary-row-bg: rgba(14, 165, 233, 0.05);
|
|
--color-summary-row-bg-hover: rgba(14, 165, 233, 0.08);
|
|
--color-summary-row-border: rgba(14, 165, 233, 0.16);
|
|
--color-summary-row-accent: rgba(14, 165, 233, 0.42);
|
|
--color-summary-group-member-preview-bg: rgba(14, 165, 233, 0.025);
|
|
--color-summary-group-member-preview-bg-hover: rgba(14, 165, 233, 0.04);
|
|
--color-summary-group-member-pinned-bg: rgba(14, 165, 233, 0.04);
|
|
--color-summary-group-member-pinned-bg-hover: rgba(14, 165, 233, 0.055);
|
|
--color-summary-group-member-pinned-accent: rgba(14, 165, 233, 0.22);
|
|
/* Shared product-table subgroup band. It should read as body segmentation, not table chrome. */
|
|
--color-grouped-table-row-bg: rgba(226, 232, 240, 0.72);
|
|
--color-grouped-table-row-bg-hover: rgba(203, 213, 225, 0.82);
|
|
--color-grouped-table-row-border: rgba(203, 213, 225, 0.9);
|
|
}
|
|
|
|
.dark {
|
|
/* Semantic Colors - Dark Mode */
|
|
--color-bg-base: theme('colors.slate.900');
|
|
--color-bg-surface: theme('colors.slate.800');
|
|
--color-bg-surface-hover: theme('colors.slate.700');
|
|
--color-bg-surface-alt: theme('colors.slate.800');
|
|
/* Fallback */
|
|
--color-border-subtle: theme('colors.slate.800');
|
|
--color-border-base: theme('colors.slate.700');
|
|
--color-text-base: theme('colors.slate.100');
|
|
--color-text-muted: theme('colors.slate.400');
|
|
--color-summary-row-bg: rgba(56, 189, 248, 0.08);
|
|
--color-summary-row-bg-hover: rgba(56, 189, 248, 0.12);
|
|
--color-summary-row-border: rgba(56, 189, 248, 0.18);
|
|
--color-summary-row-accent: rgba(56, 189, 248, 0.5);
|
|
--color-summary-group-member-preview-bg: rgba(56, 189, 248, 0.045);
|
|
--color-summary-group-member-preview-bg-hover: rgba(56, 189, 248, 0.065);
|
|
--color-summary-group-member-pinned-bg: rgba(56, 189, 248, 0.07);
|
|
--color-summary-group-member-pinned-bg-hover: rgba(56, 189, 248, 0.09);
|
|
--color-summary-group-member-pinned-accent: rgba(56, 189, 248, 0.28);
|
|
--color-grouped-table-row-bg: rgba(51, 65, 85, 0.58);
|
|
--color-grouped-table-row-bg-hover: rgba(51, 65, 85, 0.82);
|
|
--color-grouped-table-row-border: rgba(71, 85, 105, 0.85);
|
|
}
|
|
|
|
body {
|
|
background-color: var(--color-bg-base);
|
|
color: var(--color-text-base);
|
|
/* Ensure body scrollbar follows our styling */
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* Form elements: inherit surface background so they don't show white in dark mode */
|
|
select,
|
|
input:not([type='checkbox']):not([type='radio']):not([type='range']),
|
|
textarea {
|
|
background-color: var(--color-bg-surface);
|
|
color: var(--color-text-base);
|
|
}
|
|
|
|
/* Hide scrollbars during transitions to prevent flicker */
|
|
html {
|
|
overflow-y: scroll;
|
|
scroll-behavior: smooth;
|
|
}
|
|
}
|
|
|
|
@layer components {
|
|
/* Assistant code-block syntax highlighting (highlight.js tokens).
|
|
Chat code blocks always render on the dark prose-pre slate background,
|
|
so a single readable-on-dark palette serves both app themes. */
|
|
.prose pre code.hljs-highlighted .hljs-comment,
|
|
.prose pre code.hljs-highlighted .hljs-quote {
|
|
color: theme('colors.slate.400');
|
|
font-style: italic;
|
|
}
|
|
.prose pre code.hljs-highlighted .hljs-keyword,
|
|
.prose pre code.hljs-highlighted .hljs-selector-tag,
|
|
.prose pre code.hljs-highlighted .hljs-literal,
|
|
.prose pre code.hljs-highlighted .hljs-built_in {
|
|
color: theme('colors.violet.300');
|
|
}
|
|
.prose pre code.hljs-highlighted .hljs-string,
|
|
.prose pre code.hljs-highlighted .hljs-regexp,
|
|
.prose pre code.hljs-highlighted .hljs-addition {
|
|
color: theme('colors.emerald.300');
|
|
}
|
|
.prose pre code.hljs-highlighted .hljs-number,
|
|
.prose pre code.hljs-highlighted .hljs-symbol,
|
|
.prose pre code.hljs-highlighted .hljs-bullet {
|
|
color: theme('colors.amber.300');
|
|
}
|
|
.prose pre code.hljs-highlighted .hljs-title,
|
|
.prose pre code.hljs-highlighted .hljs-section,
|
|
.prose pre code.hljs-highlighted .hljs-name,
|
|
.prose pre code.hljs-highlighted .hljs-function {
|
|
color: theme('colors.sky.300');
|
|
}
|
|
.prose pre code.hljs-highlighted .hljs-attr,
|
|
.prose pre code.hljs-highlighted .hljs-attribute,
|
|
.prose pre code.hljs-highlighted .hljs-variable,
|
|
.prose pre code.hljs-highlighted .hljs-template-variable {
|
|
color: theme('colors.cyan.300');
|
|
}
|
|
.prose pre code.hljs-highlighted .hljs-meta,
|
|
.prose pre code.hljs-highlighted .hljs-deletion {
|
|
color: theme('colors.rose.300');
|
|
}
|
|
|
|
.touch-scroll {
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.app-scroll-shell {
|
|
scrollbar-gutter: stable;
|
|
}
|
|
|
|
.pulse-shell {
|
|
width: 100%;
|
|
max-width: var(--pulse-shell-max-width);
|
|
margin-inline: auto;
|
|
padding-inline: var(--pulse-shell-padding-inline);
|
|
transition: padding-inline 0.3s ease;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.pulse-shell {
|
|
padding-inline: 0.5rem;
|
|
}
|
|
}
|
|
|
|
/* Full-width mode - removes max-width constraint for large monitors */
|
|
.pulse-shell--full-width {
|
|
--pulse-shell-max-width: 100%;
|
|
--pulse-shell-padding-inline: clamp(1rem, 2vw, 3rem);
|
|
}
|
|
|
|
.pulse-panel {
|
|
padding: var(--pulse-panel-padding);
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.pulse-panel {
|
|
padding: 0.5rem;
|
|
}
|
|
}
|
|
|
|
/* Upward box-shadow shield for sticky chart panels.
|
|
Fills the transparent space-y-3 margin above the element so
|
|
scrolling text doesn't peek through when the element is stuck. */
|
|
.sticky-shield {
|
|
box-shadow: 0 -0.75rem 0 0 white;
|
|
}
|
|
|
|
.dark .sticky-shield {
|
|
box-shadow: 0 -0.75rem 0 0 #1f2937;
|
|
}
|
|
|
|
/* Card styles */
|
|
.card {
|
|
@apply bg-surface border border-border shadow-sm rounded-md;
|
|
}
|
|
|
|
.table-row {
|
|
@apply border-b border-border hover:bg-surface-hover;
|
|
}
|
|
|
|
.workload-row {
|
|
min-height: 32px;
|
|
}
|
|
|
|
.workload-row[data-workload-alert-accent='critical'] > td:first-child,
|
|
.host-row[data-workload-alert-accent='critical'] > td:first-child {
|
|
box-shadow: inset 4px 0 0 0 #ef4444;
|
|
}
|
|
|
|
.workload-row[data-workload-alert-accent='warning'] > td:first-child,
|
|
.host-row[data-workload-alert-accent='warning'] > td:first-child {
|
|
box-shadow: inset 4px 0 0 0 #eab308;
|
|
}
|
|
|
|
.workload-row[data-workload-alert-accent='acknowledged'] > td:first-child,
|
|
.host-row[data-workload-alert-accent='acknowledged'] > td:first-child {
|
|
box-shadow: inset 4px 0 0 0 #9ca3af;
|
|
}
|
|
|
|
tr.grouped-table-row > td,
|
|
tr.grouped-table-row > th {
|
|
background-color: var(--color-grouped-table-row-bg);
|
|
box-shadow:
|
|
inset 0 1px 0 var(--color-grouped-table-row-border),
|
|
inset 0 -1px 0 var(--color-grouped-table-row-border);
|
|
}
|
|
|
|
tr.grouped-table-row:hover > td,
|
|
tr.grouped-table-row:hover > th {
|
|
background-color: var(--color-grouped-table-row-bg-hover);
|
|
}
|
|
|
|
tr[data-summary-group-member-active='preview'] > td,
|
|
tr[data-summary-group-member-active='preview'] > th {
|
|
background-color: var(--color-summary-group-member-preview-bg);
|
|
}
|
|
|
|
tr[data-summary-group-member-active='preview']:hover > td,
|
|
tr[data-summary-group-member-active='preview']:hover > th {
|
|
background-color: var(--color-summary-group-member-preview-bg-hover);
|
|
}
|
|
|
|
tr[data-summary-group-member-active='pinned'] > td,
|
|
tr[data-summary-group-member-active='pinned'] > th {
|
|
background-color: var(--color-summary-group-member-pinned-bg);
|
|
}
|
|
|
|
tr[data-summary-group-member-active='pinned']:hover > td,
|
|
tr[data-summary-group-member-active='pinned']:hover > th {
|
|
background-color: var(--color-summary-group-member-pinned-bg-hover);
|
|
}
|
|
|
|
tr[data-summary-group-member-active='pinned'] > td:first-child,
|
|
tr[data-summary-group-member-active='pinned'] > th:first-child {
|
|
box-shadow: inset 1px 0 0 var(--color-summary-group-member-pinned-accent);
|
|
}
|
|
|
|
tr[data-summary-row-active='true'] > td,
|
|
tr[data-summary-row-active='true'] > th {
|
|
background-color: var(--color-summary-row-bg);
|
|
box-shadow:
|
|
inset 0 1px 0 var(--color-summary-row-border),
|
|
inset 0 -1px 0 var(--color-summary-row-border);
|
|
}
|
|
|
|
tr[data-summary-row-active='true']:hover > td,
|
|
tr[data-summary-row-active='true']:hover > th {
|
|
background-color: var(--color-summary-row-bg-hover);
|
|
}
|
|
|
|
tr[data-summary-row-active='true'] > td:first-child,
|
|
tr[data-summary-row-active='true'] > th:first-child {
|
|
box-shadow:
|
|
inset 3px 0 0 var(--color-summary-row-accent),
|
|
inset 0 1px 0 var(--color-summary-row-border),
|
|
inset 0 -1px 0 var(--color-summary-row-border);
|
|
}
|
|
|
|
tr[data-summary-row-active='true'] > td:last-child,
|
|
tr[data-summary-row-active='true'] > th:last-child {
|
|
box-shadow:
|
|
inset -1px 0 0 var(--color-summary-row-border),
|
|
inset 0 1px 0 var(--color-summary-row-border),
|
|
inset 0 -1px 0 var(--color-summary-row-border);
|
|
}
|
|
|
|
tr[data-summary-row-active='true'] > td:first-child:last-child,
|
|
tr[data-summary-row-active='true'] > th:first-child:last-child {
|
|
box-shadow:
|
|
inset 3px 0 0 var(--color-summary-row-accent),
|
|
inset -1px 0 0 var(--color-summary-row-border),
|
|
inset 0 1px 0 var(--color-summary-row-border),
|
|
inset 0 -1px 0 var(--color-summary-row-border);
|
|
}
|
|
|
|
/* Form input — reusable base for text inputs, textareas, selects */
|
|
.form-input {
|
|
@apply w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500 transition-colors disabled:opacity-50 disabled:cursor-not-allowed;
|
|
}
|
|
|
|
.form-select {
|
|
@apply form-input pr-8 appearance-none;
|
|
}
|
|
|
|
/* Progress bars */
|
|
.progress-fill-frame {
|
|
transition: width 320ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
will-change: width;
|
|
}
|
|
|
|
.progress-fill {
|
|
@apply h-full;
|
|
transition:
|
|
width 320ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
background-color 180ms ease-out;
|
|
}
|
|
|
|
.metric-fill-geometry {
|
|
transition:
|
|
x 320ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
width 320ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
fill 180ms ease-out;
|
|
will-change: x, width, fill;
|
|
}
|
|
|
|
.metric-fill-divider {
|
|
transition:
|
|
x1 320ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
x2 320ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
will-change: x1, x2;
|
|
}
|
|
|
|
.animated-number {
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.progress-fill-frame,
|
|
.progress-fill,
|
|
.metric-fill-geometry,
|
|
.metric-fill-divider {
|
|
transition: none;
|
|
will-change: auto;
|
|
}
|
|
}
|
|
|
|
/* Custom scrollbar - subtle styling */
|
|
.custom-scrollbar {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: theme('colors.gray.300') theme('colors.gray.100');
|
|
}
|
|
|
|
.dark .custom-scrollbar {
|
|
scrollbar-color: theme('colors.gray.700') theme('colors.gray.800');
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar {
|
|
width: 6px;
|
|
height: 6px;
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-track {
|
|
@apply bg-slate-100 dark:bg-slate-800;
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
@apply bg-slate-300 dark:bg-slate-700 rounded-full;
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
@apply bg-slate-400 dark:bg-slate-600;
|
|
}
|
|
}
|
|
|
|
/* Global scrollbar styling */
|
|
* {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
|
|
/* gray-400 with transparency */
|
|
}
|
|
|
|
.dark * {
|
|
scrollbar-color: rgba(55, 65, 81, 0.5) transparent;
|
|
/* gray-700 with transparency */
|
|
}
|
|
|
|
/* Make scrollbars even more subtle on hover */
|
|
*:hover {
|
|
scrollbar-color: rgba(156, 163, 175, 0.7) rgba(243, 244, 246, 0.5);
|
|
/* Slightly more visible on hover */
|
|
}
|
|
|
|
.dark *:hover {
|
|
scrollbar-color: rgba(75, 85, 99, 0.7) rgba(31, 41, 55, 0.3);
|
|
/* dark mode hover */
|
|
}
|
|
|
|
*::-webkit-scrollbar {
|
|
width: 6px;
|
|
height: 6px;
|
|
}
|
|
|
|
*::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
*::-webkit-scrollbar-thumb {
|
|
background-color: rgba(156, 163, 175, 0.5);
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.dark *::-webkit-scrollbar-thumb {
|
|
background-color: rgba(55, 65, 81, 0.5);
|
|
}
|
|
|
|
*::-webkit-scrollbar-thumb:hover {
|
|
background-color: rgba(156, 163, 175, 0.8);
|
|
}
|
|
|
|
.dark *::-webkit-scrollbar-thumb:hover {
|
|
background-color: rgba(75, 85, 99, 0.8);
|
|
}
|
|
|
|
/* Dark mode transitions - Only apply to elements that actually change colors */
|
|
body,
|
|
.card,
|
|
.table-row,
|
|
.bg-white,
|
|
.bg-slate-50,
|
|
.bg-slate-100,
|
|
.bg-slate-200,
|
|
.bg-slate-300,
|
|
.bg-slate-700,
|
|
.bg-slate-800,
|
|
.bg-slate-900,
|
|
.text-slate-500,
|
|
.text-slate-600,
|
|
.text-slate-700,
|
|
.text-slate-800,
|
|
.text-slate-900,
|
|
.border-slate-200,
|
|
.border-slate-300,
|
|
.border-slate-600,
|
|
.border-slate-700,
|
|
.dark\:bg-slate-700,
|
|
.dark\:bg-slate-800,
|
|
.dark\:bg-slate-900,
|
|
.dark\:text-slate-100,
|
|
.dark\:text-slate-200,
|
|
.dark\:text-slate-300,
|
|
.dark\:text-slate-400,
|
|
.dark\:border-slate-600,
|
|
.dark\:border-slate-700 {
|
|
transition:
|
|
background-color 150ms ease-in-out,
|
|
color 150ms ease-in-out,
|
|
border-color 150ms ease-in-out;
|
|
}
|
|
|
|
/* Pulse brand motion - visible, calm live-state rhythm. */
|
|
@keyframes pulse-brand-logo {
|
|
0%,
|
|
100% {
|
|
transform: scale(1);
|
|
}
|
|
|
|
46% {
|
|
transform: scale(1.035);
|
|
}
|
|
|
|
66% {
|
|
transform: scale(1.012);
|
|
}
|
|
}
|
|
|
|
@keyframes pulse-brand-wave {
|
|
0% {
|
|
opacity: 0.34;
|
|
transform: translateY(-50%) scale(0.9);
|
|
}
|
|
|
|
68%,
|
|
100% {
|
|
opacity: 0;
|
|
transform: translateY(-50%) scale(2);
|
|
}
|
|
}
|
|
|
|
@keyframes pulse-brand-bg {
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
46% {
|
|
opacity: 0.96;
|
|
transform: scale(1.025);
|
|
}
|
|
}
|
|
|
|
@keyframes pulse-brand-ring {
|
|
0% {
|
|
opacity: 0.9;
|
|
transform: scale(0.96);
|
|
stroke-width: 14;
|
|
}
|
|
|
|
56% {
|
|
opacity: 0.22;
|
|
transform: scale(1.32);
|
|
stroke-width: 5;
|
|
}
|
|
|
|
74% {
|
|
opacity: 0;
|
|
transform: scale(1.36);
|
|
stroke-width: 4;
|
|
}
|
|
|
|
75% {
|
|
opacity: 0;
|
|
transform: scale(0.96);
|
|
stroke-width: 14;
|
|
}
|
|
|
|
100% {
|
|
opacity: 0.9;
|
|
transform: scale(0.96);
|
|
stroke-width: 14;
|
|
}
|
|
}
|
|
|
|
@keyframes pulse-brand-center {
|
|
0%,
|
|
100% {
|
|
transform: scale(1);
|
|
}
|
|
|
|
46% {
|
|
transform: scale(0.86);
|
|
}
|
|
|
|
64% {
|
|
transform: scale(1.05);
|
|
}
|
|
}
|
|
|
|
.pulse-brand-lockup {
|
|
--pulse-brand-wave-color: rgba(37, 99, 235, 0.34);
|
|
position: relative;
|
|
}
|
|
|
|
.dark .pulse-brand-lockup {
|
|
--pulse-brand-wave-color: rgba(96, 165, 250, 0.4);
|
|
}
|
|
|
|
.pulse-brand-lockup::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 0;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 1px solid var(--pulse-brand-wave-color);
|
|
border-radius: 9999px;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transform: translateY(-50%) scale(0.9);
|
|
}
|
|
|
|
.pulse-brand-logo,
|
|
.pulse-brand-logo .pulse-bg,
|
|
.pulse-brand-logo .pulse-ring,
|
|
.pulse-brand-logo .pulse-center {
|
|
transform-origin: center;
|
|
transform-box: fill-box;
|
|
}
|
|
|
|
.animate-pulse-brand {
|
|
--pulse-brand-cycle: 3.4s;
|
|
}
|
|
|
|
.animate-pulse-brand::before {
|
|
animation: pulse-brand-wave var(--pulse-brand-cycle) ease-in-out infinite;
|
|
}
|
|
|
|
.animate-pulse-brand .pulse-brand-logo {
|
|
animation: pulse-brand-logo var(--pulse-brand-cycle) ease-in-out infinite;
|
|
will-change: transform;
|
|
}
|
|
|
|
.animate-pulse-brand .pulse-bg {
|
|
animation: pulse-brand-bg var(--pulse-brand-cycle) ease-in-out infinite;
|
|
will-change: opacity, transform;
|
|
}
|
|
|
|
.animate-pulse-brand .pulse-ring {
|
|
animation: pulse-brand-ring var(--pulse-brand-cycle) ease-in-out infinite;
|
|
will-change: opacity, stroke-width, transform;
|
|
}
|
|
|
|
.animate-pulse-brand .pulse-center {
|
|
animation: pulse-brand-center var(--pulse-brand-cycle) ease-in-out infinite;
|
|
will-change: transform;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.animate-pulse-brand,
|
|
.animate-pulse-brand::before,
|
|
.animate-pulse-brand .pulse-brand-logo,
|
|
.animate-pulse-brand .pulse-bg,
|
|
.animate-pulse-brand .pulse-ring,
|
|
.animate-pulse-brand .pulse-center {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
/* Stable table layout */
|
|
.table-fixed {
|
|
table-layout: fixed;
|
|
}
|
|
|
|
/* Prevent text from affecting column widths */
|
|
.table-fixed td,
|
|
.table-fixed th {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Keep name column single line to avoid wrapping while giving it more space */
|
|
.table-fixed td:first-child {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Hide scrollbars but keep functionality */
|
|
.scrollbar-hide {
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
|
|
.scrollbar-hide::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.pb-safe {
|
|
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
}
|
|
|
|
.pb-safe-or-20 {
|
|
padding-bottom: max(5rem, env(safe-area-inset-bottom, 5rem));
|
|
}
|
|
|
|
/* Login page animations */
|
|
@keyframes fade-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes slide-up {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes pulse-slow {
|
|
0%,
|
|
100% {
|
|
opacity: 0.25;
|
|
}
|
|
|
|
50% {
|
|
opacity: 0.75;
|
|
}
|
|
}
|
|
|
|
.animate-fade-in {
|
|
animation: fade-in 0.6s ease-out forwards;
|
|
}
|
|
|
|
.animate-slide-up {
|
|
animation: slide-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.2s both;
|
|
}
|
|
|
|
.delay-100 {
|
|
animation-delay: 100ms;
|
|
}
|
|
|
|
.delay-200 {
|
|
animation-delay: 200ms;
|
|
}
|
|
|
|
.delay-300 {
|
|
animation-delay: 300ms;
|
|
}
|
|
|
|
/* Staggered Entry Animation */
|
|
@keyframes enter {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.animate-enter {
|
|
animation: enter 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
|
|
}
|