/*
Skeleton

A placeholder shape that pulses while content loads. Two uses:

  <Skeleton class="w-..." />          a sized shape on its own
  <Skeleton loading={x}>content</Skeleton>
                                      wraps the real content and keeps its
                                      box: the content is laid out (hidden)
                                      while loading, so nothing moves when
                                      it appears.

  .skeleton[data-shape="block" | "text" | "circle"]
  .skeleton[data-loading]             the pulse and the muted fill
  .skeleton-line                      one bar of a text skeleton
*/

@layer components {

.skeleton {
  box-sizing:    border-box;
  display:       block;
  border-radius: var(--radius-item);
}

.skeleton.is-inline {
  display:        inline-block;
  vertical-align: bottom;   /* centered in the line, never taller than it */
}

.skeleton[data-loading] {
  color:      transparent;   /* bare text (Radix's <Skeleton>Loading</Skeleton>) is not painted */
  background: var(--bg-muted);
  animation:  skeleton-pulse calc(var(--duration-loop) * 4) var(--ease-in-out) infinite;
  cursor:     progress;
}

/* Inline around text (Radix Themes "With text"): the skeleton flows with
   the text, so each line of it gets its own bar and the paragraph keeps
   its exact shape. The text is transparent while loading. */
.skeleton.is-inline:not(:empty) {
  display:                    inline;
  vertical-align:             baseline;
  box-decoration-break:       clone;
  -webkit-box-decoration-break: clone;
}

.skeleton.is-inline[data-loading]:not(:empty) {
  color:       transparent;
  user-select: none;
}

/* One wrapped control (Radix Themes' With children): Radix merges the
   skeleton onto the child, so the placeholder is the child's own box. A
   block wrapper around an inline control (a switch, a button, an avatar)
   would instead be as wide as the row and as tall as the text strut. So
   around a lone control the wrapper is a flex box as wide as the control,
   with no stretch: exactly the control's box, loaded or loading.
   Everything else keeps block layout, so the wrapper never changes the
   layout of what it wraps: a block child (a card, a paragraph) already
   fills the row at its own height, and CSS cannot see text nodes, so any
   other lone element may sit in a sentence ("Plan <Badge>Pro</Badge>
   renews today") and has to flow with it. The list is the controls, which
   never stand in running text with an auto width; a sized skeleton (width,
   or a class) still wins. */
.skeleton[data-shape="block"]:not(.is-inline):has(> :only-child:is(button, .button, .switch, .checkbox, .avatar, .avatar-group, .spinner, input:is([type="checkbox"], [type="radio"]), progress, meter)) {
  display:     flex;
  align-items: flex-start;
}

/* The width sits at the specificity of .skeleton alone, so every sizing
   rule (a responsive width, a class, an inline width) still wins. */
.skeleton:where([data-shape="block"]:not(.is-inline):has(> :only-child:is(button, .button, .switch, .checkbox, .avatar, .avatar-group, .spinner, input:is([type="checkbox"], [type="radio"]), progress, meter))) {
  width: fit-content;
}

/* Wrapped content keeps its layout and hides while loading. */
.skeleton[data-loading] > :not(.skeleton-line) {
  visibility: hidden;
}

.skeleton[data-shape="circle"] {
  aspect-ratio:  1;
  border-radius: var(--radius-full);
}

/* A shape with no content and no size of its own: one line of body text.
   Zero specificity, so a caller's class that sizes it (a height, or
   height: auto with an aspect-ratio for media) always wins. Only a height
   of its own counts: min-height and max-height bound the line, they do not
   replace it (an inline style starts with the property or follows a space
   or a semicolon, so "-height" never matches). */
:where(.skeleton[data-shape="block"]:empty:not([style^="height"], [style*=" height"], [style*=";height"])) {
  height: 1lh;
}

.skeleton[data-shape="text"][data-loading] {
  display:        flex;
  flex-direction: column;
  gap:            calc(1lh - 1em);
  background:     none;
  animation:      none;
  border-radius:  0;
}

.skeleton-line {
  height:        1em;
  border-radius: var(--radius-item);
  background:    var(--bg-muted);
  animation:     skeleton-pulse calc(var(--duration-loop) * 4) var(--ease-in-out) infinite;
}

.skeleton-line:last-child:not(:first-child) {
  width: 60%;
}

/* RESPONSIVE VALUES -------------------------------------------------------
   A responsive width, height, bound or margin (Radix Themes' per
   breakpoint objects) writes --skeleton-<prop>-<breakpoint> and names it in
   data-responsive. Breakpoints are the Elements ones (--bp-sm .. --bp-2xl,
   elements man style/tokens/scale); media queries cannot read tokens, so
   their rem values are written here, as the rest of the package does. */

.skeleton[data-responsive~="width-initial"] { width: var(--skeleton-width-initial); }
.skeleton[data-responsive~="height-initial"] { height: var(--skeleton-height-initial); }
.skeleton[data-responsive~="min-width-initial"] { min-width: var(--skeleton-min-width-initial); }
.skeleton[data-responsive~="max-width-initial"] { max-width: var(--skeleton-max-width-initial); }
.skeleton[data-responsive~="min-height-initial"] { min-height: var(--skeleton-min-height-initial); }
.skeleton[data-responsive~="max-height-initial"] { max-height: var(--skeleton-max-height-initial); }
.skeleton[data-responsive~="margin-top-initial"] { margin-top: var(--skeleton-margin-top-initial); }
.skeleton[data-responsive~="margin-right-initial"] { margin-right: var(--skeleton-margin-right-initial); }
.skeleton[data-responsive~="margin-bottom-initial"] { margin-bottom: var(--skeleton-margin-bottom-initial); }
.skeleton[data-responsive~="margin-left-initial"] { margin-left: var(--skeleton-margin-left-initial); }

@media (min-width: 40rem) {
  .skeleton[data-responsive~="width-sm"] { width: var(--skeleton-width-sm); }
  .skeleton[data-responsive~="height-sm"] { height: var(--skeleton-height-sm); }
  .skeleton[data-responsive~="min-width-sm"] { min-width: var(--skeleton-min-width-sm); }
  .skeleton[data-responsive~="max-width-sm"] { max-width: var(--skeleton-max-width-sm); }
  .skeleton[data-responsive~="min-height-sm"] { min-height: var(--skeleton-min-height-sm); }
  .skeleton[data-responsive~="max-height-sm"] { max-height: var(--skeleton-max-height-sm); }
  .skeleton[data-responsive~="margin-top-sm"] { margin-top: var(--skeleton-margin-top-sm); }
  .skeleton[data-responsive~="margin-right-sm"] { margin-right: var(--skeleton-margin-right-sm); }
  .skeleton[data-responsive~="margin-bottom-sm"] { margin-bottom: var(--skeleton-margin-bottom-sm); }
  .skeleton[data-responsive~="margin-left-sm"] { margin-left: var(--skeleton-margin-left-sm); }
}

@media (min-width: 48rem) {
  .skeleton[data-responsive~="width-md"] { width: var(--skeleton-width-md); }
  .skeleton[data-responsive~="height-md"] { height: var(--skeleton-height-md); }
  .skeleton[data-responsive~="min-width-md"] { min-width: var(--skeleton-min-width-md); }
  .skeleton[data-responsive~="max-width-md"] { max-width: var(--skeleton-max-width-md); }
  .skeleton[data-responsive~="min-height-md"] { min-height: var(--skeleton-min-height-md); }
  .skeleton[data-responsive~="max-height-md"] { max-height: var(--skeleton-max-height-md); }
  .skeleton[data-responsive~="margin-top-md"] { margin-top: var(--skeleton-margin-top-md); }
  .skeleton[data-responsive~="margin-right-md"] { margin-right: var(--skeleton-margin-right-md); }
  .skeleton[data-responsive~="margin-bottom-md"] { margin-bottom: var(--skeleton-margin-bottom-md); }
  .skeleton[data-responsive~="margin-left-md"] { margin-left: var(--skeleton-margin-left-md); }
}

@media (min-width: 64rem) {
  .skeleton[data-responsive~="width-lg"] { width: var(--skeleton-width-lg); }
  .skeleton[data-responsive~="height-lg"] { height: var(--skeleton-height-lg); }
  .skeleton[data-responsive~="min-width-lg"] { min-width: var(--skeleton-min-width-lg); }
  .skeleton[data-responsive~="max-width-lg"] { max-width: var(--skeleton-max-width-lg); }
  .skeleton[data-responsive~="min-height-lg"] { min-height: var(--skeleton-min-height-lg); }
  .skeleton[data-responsive~="max-height-lg"] { max-height: var(--skeleton-max-height-lg); }
  .skeleton[data-responsive~="margin-top-lg"] { margin-top: var(--skeleton-margin-top-lg); }
  .skeleton[data-responsive~="margin-right-lg"] { margin-right: var(--skeleton-margin-right-lg); }
  .skeleton[data-responsive~="margin-bottom-lg"] { margin-bottom: var(--skeleton-margin-bottom-lg); }
  .skeleton[data-responsive~="margin-left-lg"] { margin-left: var(--skeleton-margin-left-lg); }
}

@media (min-width: 80rem) {
  .skeleton[data-responsive~="width-xl"] { width: var(--skeleton-width-xl); }
  .skeleton[data-responsive~="height-xl"] { height: var(--skeleton-height-xl); }
  .skeleton[data-responsive~="min-width-xl"] { min-width: var(--skeleton-min-width-xl); }
  .skeleton[data-responsive~="max-width-xl"] { max-width: var(--skeleton-max-width-xl); }
  .skeleton[data-responsive~="min-height-xl"] { min-height: var(--skeleton-min-height-xl); }
  .skeleton[data-responsive~="max-height-xl"] { max-height: var(--skeleton-max-height-xl); }
  .skeleton[data-responsive~="margin-top-xl"] { margin-top: var(--skeleton-margin-top-xl); }
  .skeleton[data-responsive~="margin-right-xl"] { margin-right: var(--skeleton-margin-right-xl); }
  .skeleton[data-responsive~="margin-bottom-xl"] { margin-bottom: var(--skeleton-margin-bottom-xl); }
  .skeleton[data-responsive~="margin-left-xl"] { margin-left: var(--skeleton-margin-left-xl); }
}

@media (min-width: 96rem) {
  .skeleton[data-responsive~="width-2xl"] { width: var(--skeleton-width-2xl); }
  .skeleton[data-responsive~="height-2xl"] { height: var(--skeleton-height-2xl); }
  .skeleton[data-responsive~="min-width-2xl"] { min-width: var(--skeleton-min-width-2xl); }
  .skeleton[data-responsive~="max-width-2xl"] { max-width: var(--skeleton-max-width-2xl); }
  .skeleton[data-responsive~="min-height-2xl"] { min-height: var(--skeleton-min-height-2xl); }
  .skeleton[data-responsive~="max-height-2xl"] { max-height: var(--skeleton-max-height-2xl); }
  .skeleton[data-responsive~="margin-top-2xl"] { margin-top: var(--skeleton-margin-top-2xl); }
  .skeleton[data-responsive~="margin-right-2xl"] { margin-right: var(--skeleton-margin-right-2xl); }
  .skeleton[data-responsive~="margin-bottom-2xl"] { margin-bottom: var(--skeleton-margin-bottom-2xl); }
  .skeleton[data-responsive~="margin-left-2xl"] { margin-left: var(--skeleton-margin-left-2xl); }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton[data-loading],
  .skeleton-line {
    animation: none;
  }
}

@keyframes skeleton-pulse {
  50% { opacity: 0.5; }
}

}
