/* --- crate loading skeletons ------------------------------------------- */ .loading-status { font: 700 11px/1 var(--mono); letter-spacing: 0.6px; color: var(--ink-muted); margin: 0 0 14px; } /* Invisible until the delay elapses, then fades in over --dur-fast: a fast load resolves before 200ms and this never paints at all, so it reads as "instant" instead of a one-frame flash. `both` holds opacity:0 through the delay (backwards) and opacity:1 after (forwards). */ @keyframes skeleton-in { from { opacity: 0; } to { opacity: 1; } } .skeleton-card, .skeleton-row { animation: skeleton-in var(--dur-fast) var(--ease-out-cubic) 200ms both; } .skeleton-card { border: 2px dashed var(--ink); background: var(--surface); } /* `filter` rather than `opacity`: the fade-in animation above already owns `opacity` on this element, and its forwards fill would otherwise clobber a plain `opacity: .66` the moment the animation ends. */ .skeleton-card--dim, .skeleton-row--dim { filter: opacity(66%); } .skeleton-cover { display: flex; align-items: center; justify-content: center; height: 150px; background: var(--bg); border-bottom: 2px dashed var(--ink); } .skeleton-cover__glyph { font-size: 48px; color: var(--line); } .skeleton-lines { display: flex; flex-direction: column; gap: 8px; padding: 12px 10px; } .skeleton-bar { height: 10px; background: var(--line); } .skeleton-bar--w70 { width: 70%; } .skeleton-bar--w48 { width: 48%; } .skeleton-bar--w33 { width: 33%; } /* --- crate loading skeletons (rows variant) ---------------------------- */ /* Matches `.row` (06-crate.css)'s flush box exactly, not a boxed card: the swap from skeleton to real rows must not shift layout. */ .skeleton-row { display: flex; align-items: center; gap: 12px; padding: 12px 0; } .skeleton-row + .skeleton-row { border-top: 1px solid var(--line); } .skeleton-row__thumb { width: 48px; height: 48px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; background: var(--bg); border: 2px dashed var(--ink); } .skeleton-row__thumb .skeleton-cover__glyph { font-size: 20px; } .skeleton-row__lines { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; } /* --- crate empty (first-run) state ------------------------------------- */ .empty-state { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 12px; background: var(--surface); border: 2px solid var(--ink); box-shadow: var(--shadow-md); padding: 28px 20px 22px; } .empty-state .vinyl { margin-bottom: 4px; } .empty-state__title { font: 400 26px/1 var(--display); letter-spacing: 0.5px; margin: 0; } .empty-state__sub { font: italic 400 14px/1.4 var(--serif); color: var(--ink-muted); margin: 0 0 6px; } /* Mono, non-italic variant for empty states whose voice is drier than the crate's own (e.g. the feed's). */ .empty-state__sub--mono { font: 400 13px/1.5 var(--mono); font-style: normal; } .empty-state .btn { width: 100%; } .empty-state__import { margin-top: 4px; font: 700 12px/1.3 var(--mono); color: var(--ink-muted); text-decoration: underline; } /* --- crate offline error state ----------------------------------------- */ .error-state { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 12px; background: var(--surface); border: 3px solid var(--danger); box-shadow: var(--shadow-md); padding: 24px 20px 20px; } .error-state__sticker { background: var(--danger); color: var(--cream-fixed); border: 2px solid var(--ink); box-shadow: var(--shadow-xs); padding: 5px 10px; font: 700 12px/1 var(--mono); letter-spacing: 0.5px; text-transform: uppercase; } .error-state__title { font: 400 24px/1.05 var(--display); letter-spacing: 0.5px; margin: 0; } .error-state__body { font: 400 13px/1.5 var(--mono); color: var(--ink-muted); margin: 0 0 4px; } .error-state .btn { width: 100%; } .error-state__hint { font: 400 11px/1.3 var(--mono); color: var(--ink-muted); margin: 0; }