/* Shimmer skeleton placeholders used by app/ui/skeleton.mjs.
 * The base is a themed muted block; a moving highlight gradient
 * sweeps across to signal loading. Colors derive from theme.css
 * custom properties so light and dark modes both work. */

/* .page uses margin:0 auto inside a flex column, so it shrinks to fit its
 * content's intrinsic width. A skeleton's placeholder bars use percentage
 * widths with almost no intrinsic width, so the page would collapse to a
 * skinny centered strip until real content arrives ("skinny loading
 * syndrome"). Filling the available width — still bounded by the page's own
 * max-width — keeps the skeleton and the loaded page at the same comfortable
 * width, so there is no reflow when content swaps in. */
.page {
 width: 100%;
}

.skeleton {
 --skeleton-base: var(--secondary);
 --skeleton-glow: rgba(
  255,
  255,
  255,
  0.5
 );
 display: block;
 position: relative;
 overflow: hidden;
 border-radius: var(--radius, 6px);
 background: var(--skeleton-base);
}

.skeleton::after {
 content: '';
 position: absolute;
 inset: 0;
 transform: translateX(-100%);
 background: linear-gradient(
  90deg,
  transparent,
  var(--skeleton-glow),
  transparent
 );
 animation: skeleton-shimmer 1.4s
  ease-in-out infinite;
}

body[data-color-mode='dark'] .skeleton,
body.color-mode-dark .skeleton {
 --skeleton-glow: rgba(
  255,
  255,
  255,
  0.06
 );
}

@media (prefers-color-scheme: dark) {
 body[data-color-mode='system']
  .skeleton,
 body.color-mode-system .skeleton {
  --skeleton-glow: rgba(
   255,
   255,
   255,
   0.06
  );
 }
}

@keyframes skeleton-shimmer {
 100% {
  transform: translateX(100%);
 }
}

.skeleton-line {
 height: 12px;
 width: 100%;
 border-radius: 6px;
}

.skeleton-title {
 height: 18px;
 width: 40%;
 border-radius: 6px;
}

.skeleton-desc {
 height: 12px;
 border-radius: 6px;
 opacity: 0.7;
}

.skeleton-line.skeleton-sub {
 height: 10px;
 opacity: 0.7;
}

.skeleton-line + .skeleton-line {
 margin-top: 8px;
}

.skeleton-block {
 width: 100%;
 border-radius: var(--radius, 6px);
}

/* .skeleton-card keeps the real .card flex column + padding, so the
 * header and content placeholders line up with the horizontal inset
 * of a real card and never touch the border. */

.skeleton-stack {
 display: flex;
 flex-direction: column;
 gap: 14px;
}

.skeleton-row {
 display: flex;
 align-items: flex-start;
 gap: 12px;
 padding: 12px;
 border: 1px solid var(--border);
 border-radius: var(--radius, 8px);
}

.skeleton-row-body {
 flex: 1;
 min-width: 0;
}

.skeleton-dot {
 width: 32px;
 height: 32px;
 border-radius: 999px;
 flex-shrink: 0;
}

/* Reuses the real .pill shell (border + 4px/10px padding), so the dot and
 * label shimmer stay inset from the border. */
.skeleton-pill {
 pointer-events: none;
}

.skeleton-pill-dot {
 width: 8px;
 height: 8px;
 border-radius: 9999px;
 flex-shrink: 0;
}

.skeleton-table {
 display: flex;
 flex-direction: column;
 gap: 1px;
 border: 1px solid var(--border);
 border-radius: var(--radius, 8px);
 overflow: hidden;
}

.skeleton-tr {
 display: flex;
 gap: 16px;
 padding: 14px 16px;
 background: var(--card);
}

.skeleton-tr + .skeleton-tr {
 border-top: 1px solid var(--border);
}

.skeleton-cell {
 flex: 1;
 min-width: 0;
}

/* Filter tabs shown while a page's data loads: visible but inert. */
.task-filter[disabled] {
 cursor: default;
 opacity: 0.6;
}

.task-filter[disabled]:hover {
 color: var(--muted-foreground);
}

.task-filter-count-skeleton {
 display: inline-block;
 width: 16px;
 height: 12px;
 border-radius: 6px;
 vertical-align: middle;
}

/* Billing summary-card placeholders: the icon + label render for real,
 * so only the value and sub line are sized to match and avoid a shift. */
.summary-card-value-skeleton {
 display: block;
 width: 65%;
 height: 20px;
}

.summary-card-sub-skeleton {
 display: block;
 width: 80%;
 height: 10px;
 opacity: 0.7;
}

/* Tab bar shown while a tabbed page loads: real labels, but inert. */
.tabs[data-skeleton]
 .tab-trigger[disabled] {
 cursor: default;
}

.tabs[data-skeleton]
 .tab-trigger[disabled]:not(.active) {
 opacity: 0.65;
}

/* The shell wrappers that reveal a titled skeleton and then swap in the
 * real .page must not break the .content -> .page flex/height chain,
 * otherwise .page loses its min-height:100% and bottom-pinned elements
 * (e.g. the Stripe footer's margin-top:auto) stop floating to the
 * bottom. display:contents makes the wrapper layout-transparent. */
.billing-shell,
.connections-shell,
.contracts-shell,
.admin-shell {
 display: contents;
}

@media (prefers-reduced-motion: reduce) {
 .skeleton::after {
  animation: none;
 }
}
