/* ═══════════════════════════════════════════════════════════
   Progress & skeletons — showing that work is happening.

   Two different jobs, deliberately in one file because picking the wrong
   one is the common mistake:

   • PROGRESS (.tn-progress) — work with a KNOWN extent. Picking 9 of 14
     lines, syncing 340 of 1,200 articles, generating a report. It answers
     "how much is left", so it must never be used for work whose length is
     unknown just to look busy.

   • SKELETON (.tn-skeleton) — content whose SHAPE is known but whose data
     hasn't arrived. Better than a spinner for lists and tables because it
     doesn't move the layout when the data lands: the row was already the
     right height. A spinner in the same place causes a jump.

   If neither fits — work of unknown length with no known shape — that's the
   spinner in feedback.css.

   NOTE: this replaces a smell. Before this existed, order-picking progress
   in the record list was drawn with .tn-strength, the PASSWORD STRENGTH
   meter — a component whose three states mean weak/fair/strong, standing in
   for "9 of 14 picked". It looked right and meant nothing.
   ═══════════════════════════════════════════════════════════ */

.tn-progress {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.tn-progress-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--tn-space-md);
}

.tn-progress-label {
    font-size: var(--tn-font-size-sm);
    color: var(--tn-text-secondary);
}

.tn-progress-value {
    font-size: var(--tn-font-size-sm);
    font-weight: 700;
    color: var(--tn-text-primary);
    font-variant-numeric: tabular-nums;
}

.tn-progress-track {
    height: 8px;
    border-radius: 999px;
    background: var(--tn-bg-page);
    overflow: hidden;
}

.tn-progress-fill {
    height: 100%;
    width: 0;
    border-radius: 999px;
    background: var(--tn-accent);
    transition: width var(--tn-transition-slow);
}

/* Tones for a bar that also carries state — a picking run that stalled, a
   sync that partially failed. The value text beside it says which; colour
   is never the only signal. */
.tn-progress--success .tn-progress-fill { background: var(--tn-success); }
.tn-progress--warning .tn-progress-fill { background: var(--tn-warning); }
.tn-progress--danger .tn-progress-fill { background: var(--tn-danger); }

/* Slimmer variant for inside a row or a card footer */
.tn-progress--sm .tn-progress-track {
    height: 5px;
}

/* ── Indeterminate — work is happening but its extent is genuinely unknown.
   Distinct from the determinate bar on purpose: a bar that fills to a
   position it can't justify is a lie about progress. This one only ever
   travels. ── */
.tn-progress--indeterminate .tn-progress-fill {
    width: 35%;
    animation: tnProgressSlide 1.1s ease-in-out infinite;
}

@keyframes tnProgressSlide {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(340%); }
}

/* ═══════════════ Skeletons ═══════════════ */
.tn-skeleton {
    position: relative;
    overflow: hidden;
    border-radius: var(--tn-radius-sm);
    background: var(--tn-bg-hover);
    /* Skeletons stand in for text, so they need a height even when empty */
    min-height: 0.85em;
}

/* The shimmer is a moving highlight rather than a pulsing opacity, because
   a pulse reads as "broken/blinking" while a sweep reads as "loading". */
.tn-skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg,
        transparent,
        rgba(255, 255, 255, 0.45),
        transparent);
    animation: tnSkeletonSweep 1.3s infinite;
}

html[data-theme="philip-dark"] .tn-skeleton::after,
html[data-theme="dark-blue"] .tn-skeleton::after {
    background: linear-gradient(90deg,
        transparent,
        rgba(255, 255, 255, 0.07),
        transparent);
}

@keyframes tnSkeletonSweep {
    100% { transform: translateX(100%); }
}

/* Text lines. The last line is short, because real paragraphs end mid-line
   — a block of equal-length bars reads as a placeholder rectangle, not text. */
.tn-skeleton--text {
    height: 0.7em;
    margin: 0.25em 0;
    border-radius: 999px;
}

.tn-skeleton--title {
    height: 1.1em;
    width: 45%;
    border-radius: 999px;
}

.tn-skeleton-lines .tn-skeleton--text:last-child {
    width: 62%;
}

.tn-skeleton--circle {
    border-radius: 50%;
    flex-shrink: 0;
}

.tn-skeleton--block {
    height: 100%;
    border-radius: var(--tn-radius);
}

/* A skeleton row shaped like .tn-record / .tn-list-item, so the real content
   lands in the same place rather than shifting when it arrives. */
.tn-skeleton-row {
    display: flex;
    align-items: center;
    gap: var(--tn-space-md);
    padding: var(--tn-space-md);
    background: var(--tn-bg-card);
    border: 1px solid var(--tn-border);
    border-radius: var(--tn-radius-lg);
}

.tn-skeleton-row-body {
    flex: 1;
    min-width: 0;
}

.tn-skeleton-list {
    display: flex;
    flex-direction: column;
    gap: var(--tn-space-sm);
}

/* Motion here is decorative — the layout already communicates loading. */
@media (prefers-reduced-motion: reduce) {
    .tn-skeleton::after {
        animation: none;
    }

    .tn-progress--indeterminate .tn-progress-fill {
        animation: none;
        width: 100%;
        opacity: 0.5;
    }
}
