/* ---------------------------------------------------------------------------
   @camp.dev/bones/css: the stylesheet is the API.

   Everything keys on aria-busy="true". Under a busy element (or on it), a
   leaf with no element children becomes a text bar and a replaced element or
   form control becomes a block. Five data-bones-* attributes adjust that:
   data-bones-type="text|block" overrides the inferred shape,
   data-bones-lines="N" paints N stacked bars, data-bones-length="N" makes
   the bar N characters wide, data-bones-auto="off" exempts a subtree from
   inference, and data-bones-animate picks shimmer, pulse, or none. Outside a
   busy region every attribute is inert.

   Two cascade strengths, one activation condition. Keep this shape:
   - Inferred rules live in @layer bones-auto. They match arbitrary page
     elements, so page CSS must outrank them. The cost: a page color rule
     keeps an inferred bone's text visible over its bar.
   - Explicit rules (data-bones-type, data-bones-lines) are unlayered. The
     author asked for that bone, so a page rule like h3 { color: #333 } must
     not keep the text visible. They still need the busy scope.
   - The busy scope is :is([aria-busy="true"], [aria-busy="true"] *). The
     busy element itself is eligible, so aria-busy can sit on a component's
     root and a single-leaf component still paints.
   - Within each strength the data-bones-animate scopes beat the default
     because :is(:scope, :scope *) puts them at higher specificity, not
     because of source order. tests/browser/default-animation.test.ts and
     animation-scope.test.ts pin this.
   - The bar geometry is declared twice, once per strength, because a rule
     is either in a layer or not. tests/bones-css.test.ts pins the copies
     equal.
   - The bar is a pseudo-element that inherits the element's padding, so
     its content box is the element's content box and the bar sits inside a
     badge's padding. clip-path: inset(0 round r) content-box gives it
     rounded ends at the content edges as one path; border-radius could not,
     because a radius shrinks by the padding on the way in, and a layered
     mask (still used for the multi-line rows, which repeat) leaves seams
     where the browser snaps each layer's tile to device pixels on its own.
     Percentage padding resolves against a different box on the
     pseudo-element and misses; length padding is exact.
     The padding and box-sizing on the single bar are !important: a page
     reset such as `*, *::before, *::after { box-sizing: border-box;
     padding: 0 }` is unlayered, so it would otherwise beat the inferred copy
     and push the bar back out to the padding edge. No page styles a bone's
     own bar on purpose, and an important layered declaration wins over
     every normal one.
   - The enumerated data-bones-lines and data-bones-length blocks exist for
     browsers without advanced attr(). Delete them once Safari ships attr()
     with type().
   - --bone-radius must carry a unit. --bones-r feeds it to min(), where a
     unitless 0 is a <number> and invalidates the multi-line mask.
--------------------------------------------------------------------------- */

@layer bones-auto;

:root {
  /* Bones derive their color from the text color in effect at the bone, so
     they inherit the page's own contrast guarantee on any background in any
     scheme (BON-13). currentColor resolves where var() is used, not here.
     The alpha reset matters: bones hide their content by zeroing the alpha
     of the color property (never with the transparent keyword, which would
     discard the channels), and the mix restores it. */
  --bone-base: color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent);
  --bone-highlight: color-mix(in srgb, rgb(from currentcolor r g b / 1) 6%, transparent);
  --bone-radius: 4px;
  --bone-duration: 1.5s;
}

@keyframes bone-shimmer {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

@keyframes bone-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
}

/* Line count. Enumerated for every Baseline 2024 browser; advanced attr()
   reads any value where it exists (Chrome 133, Firefox 155, Safari after
   Interop 2026). Both tables set an inherited custom property, so they carry
   the busy scope like every other rule here: an attribute on an element
   outside a busy region must not reach the leaves of a busy region nested
   below it. The defaults live in var() fallbacks at the point of use, never
   as declarations in a higher-specificity rule that would beat these. */
:is([aria-busy="true"], [aria-busy="true"] *) {
  &[data-bones-lines="2"] {
    --bones-lines: 2;
  }
  &[data-bones-lines="3"] {
    --bones-lines: 3;
  }
  &[data-bones-lines="4"] {
    --bones-lines: 4;
  }
  &[data-bones-lines="5"] {
    --bones-lines: 5;
  }
  &[data-bones-lines="6"] {
    --bones-lines: 6;
  }
  &[data-bones-lines="7"] {
    --bones-lines: 7;
  }
  &[data-bones-lines="8"] {
    --bones-lines: 8;
  }

  /* Width in characters. Enumerated 1 through 40 for the same reason. */
  &[data-bones-length="1"] {
    --bones-length: 1;
  }
  &[data-bones-length="2"] {
    --bones-length: 2;
  }
  &[data-bones-length="3"] {
    --bones-length: 3;
  }
  &[data-bones-length="4"] {
    --bones-length: 4;
  }
  &[data-bones-length="5"] {
    --bones-length: 5;
  }
  &[data-bones-length="6"] {
    --bones-length: 6;
  }
  &[data-bones-length="7"] {
    --bones-length: 7;
  }
  &[data-bones-length="8"] {
    --bones-length: 8;
  }
  &[data-bones-length="9"] {
    --bones-length: 9;
  }
  &[data-bones-length="10"] {
    --bones-length: 10;
  }
  &[data-bones-length="11"] {
    --bones-length: 11;
  }
  &[data-bones-length="12"] {
    --bones-length: 12;
  }
  &[data-bones-length="13"] {
    --bones-length: 13;
  }
  &[data-bones-length="14"] {
    --bones-length: 14;
  }
  &[data-bones-length="15"] {
    --bones-length: 15;
  }
  &[data-bones-length="16"] {
    --bones-length: 16;
  }
  &[data-bones-length="17"] {
    --bones-length: 17;
  }
  &[data-bones-length="18"] {
    --bones-length: 18;
  }
  &[data-bones-length="19"] {
    --bones-length: 19;
  }
  &[data-bones-length="20"] {
    --bones-length: 20;
  }
  &[data-bones-length="21"] {
    --bones-length: 21;
  }
  &[data-bones-length="22"] {
    --bones-length: 22;
  }
  &[data-bones-length="23"] {
    --bones-length: 23;
  }
  &[data-bones-length="24"] {
    --bones-length: 24;
  }
  &[data-bones-length="25"] {
    --bones-length: 25;
  }
  &[data-bones-length="26"] {
    --bones-length: 26;
  }
  &[data-bones-length="27"] {
    --bones-length: 27;
  }
  &[data-bones-length="28"] {
    --bones-length: 28;
  }
  &[data-bones-length="29"] {
    --bones-length: 29;
  }
  &[data-bones-length="30"] {
    --bones-length: 30;
  }
  &[data-bones-length="31"] {
    --bones-length: 31;
  }
  &[data-bones-length="32"] {
    --bones-length: 32;
  }
  &[data-bones-length="33"] {
    --bones-length: 33;
  }
  &[data-bones-length="34"] {
    --bones-length: 34;
  }
  &[data-bones-length="35"] {
    --bones-length: 35;
  }
  &[data-bones-length="36"] {
    --bones-length: 36;
  }
  &[data-bones-length="37"] {
    --bones-length: 37;
  }
  &[data-bones-length="38"] {
    --bones-length: 38;
  }
  &[data-bones-length="39"] {
    --bones-length: 39;
  }
  &[data-bones-length="40"] {
    --bones-length: 40;
  }

  @supports (x: attr(x type(*))) {
    &[data-bones-lines] {
      --bones-lines: attr(data-bones-lines type(<number>), 1);
    }
    &[data-bones-length] {
      --bones-length: attr(data-bones-length type(<number>), 4);
    }
  }
}

/* ---------------------------------------------------------------------------
   Inferred. Loses to page CSS.
--------------------------------------------------------------------------- */

@layer bones-auto {
  :is([aria-busy="true"], [aria-busy="true"] *) {
    /* Text: any element with no child elements, minus explicit markup, opted
       out subtrees, and the tags that get block treatment or nothing. */
    &:not(:has(*)):not(
        [data-bones-type],
        [data-bones-type] *,
        [data-bones-lines],
        [data-bones-lines] *,
        [data-bones-auto="off"],
        [data-bones-auto="off"] *
      ):not(
        img,
        svg,
        video,
        canvas,
        picture,
        iframe,
        embed,
        object,
        audio,
        button,
        input,
        select,
        textarea,
        progress,
        meter,
        hr,
        br,
        svg *,
        picture *,
        select *,
        object *
      ) {
      --bones-bar-top: calc((1lh + 1cap) / 2 - 1ex);
      --bones-r: max(0.01px, min(var(--bone-radius), 0.5ex));
      --bones-inset: max(0px, var(--bones-r) - 1px);
      color: rgb(from currentcolor r g b / 0);
      position: relative;
      min-width: calc(var(--bones-length, 4) * 1ch);
      min-height: 1lh;
      &::before {
        content: "\200b";
      }
      /* min-width does nothing on an inline box. An empty inline leaf (a
         <span> with no data yet) gets its width from an inline-block ::before
         instead, so its bar has something to span. Non-empty leaves keep the
         zero-width space, which adds no width and moves no text. */
      &:empty::before {
        content: "";
        display: inline-block;
        min-width: calc(var(--bones-length, 4) * 1ch);
      }
      &::after {
        content: "";
        position: absolute;
        inset-inline: 0;
        top: var(--bones-bar-top);
        height: 1ex;
        padding: inherit !important;
        box-sizing: content-box !important;
        clip-path: inset(0 round var(--bones-r)) content-box;
        pointer-events: none;
        animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
        background: linear-gradient(
          90deg,
          var(--bone-base) 25%,
          var(--bone-highlight) 50%,
          var(--bone-base) 75%
        );
        background-size: 200% 100%;
      }
      /* Width variance: block-level leaves cycle 85 / 100 / 92 / 60 so runs
         of lines look organic. A cap, not a width, so an inline-block or a
         flex-item leaf (a badge, a pill) keeps its content width instead of
         stretching to a share of the row. */
      &:nth-child(4n + 1) {
        max-width: 85%;
      }
      &:nth-child(4n + 2) {
        max-width: 100%;
      }
      &:nth-child(4n + 3) {
        max-width: 92%;
      }
      &:nth-child(4n) {
        max-width: 60%;
      }
    }

    /* Block: replaced elements and form controls fill their own box. A text
       bar across an avatar or a button looks wrong. Leafness is not required;
       svg and select routinely have element children. A loaded image paints
       over its own background, so the picture is pushed out of its content
       box (BON-15); only img, video, and canvas honor that. transition: none
       keeps a page `transition: all` from sliding it out on the way in. */
    &:is(
        img,
        svg,
        video,
        canvas,
        picture,
        iframe,
        embed,
        object,
        audio,
        button,
        input,
        select,
        textarea,
        progress,
        meter
      ):not(
        [data-bones-type],
        [data-bones-type] *,
        [data-bones-lines],
        [data-bones-lines] *,
        [data-bones-auto="off"],
        [data-bones-auto="off"] *
      ) {
      color: rgb(from currentcolor r g b / 0);
      border-radius: var(--bone-radius);
      border-color: transparent;
      object-position: 9999px 0;
      transition: none;
      animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
      background: linear-gradient(
        90deg,
        var(--bone-base) 25%,
        var(--bone-highlight) 50%,
        var(--bone-base) 75%
      );
      background-size: 200% 100%;
    }

    /* An img with no src: Chrome renders it as inline alt text, ignores its
       width and height, and draws a broken-image glyph. inline-block restores
       the box; the transparent pixel replaces the alt rendering. Firefox and
       Safari draw no glyph, so this is a Chrome fix that is harmless
       elsewhere. A CSP that blocks data: images brings the glyph back. */
    &:is(img):is(:not([src]), [src=""]):not(
        [data-bones-type],
        [data-bones-type] *,
        [data-bones-lines],
        [data-bones-lines] *,
        [data-bones-auto="off"],
        [data-bones-auto="off"] *
      ) {
      display: inline-block;
      content: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
    }
  }

  @media (prefers-reduced-motion: reduce) {
    :is([aria-busy="true"], [aria-busy="true"] *) {
      &:not(:has(*)):not(
          [data-bones-type],
          [data-bones-type] *,
          [data-bones-lines],
          [data-bones-lines] *,
          [data-bones-auto="off"],
          [data-bones-auto="off"] *
        ):not(
          img,
          svg,
          video,
          canvas,
          picture,
          iframe,
          embed,
          object,
          audio,
          button,
          input,
          select,
          textarea,
          progress,
          meter,
          hr,
          br,
          svg *,
          picture *,
          select *,
          object *
        )::after,
      &:is(
          img,
          svg,
          video,
          canvas,
          picture,
          iframe,
          embed,
          object,
          audio,
          button,
          input,
          select,
          textarea,
          progress,
          meter
        ):not(
          [data-bones-type],
          [data-bones-type] *,
          [data-bones-lines],
          [data-bones-lines] *,
          [data-bones-auto="off"],
          [data-bones-auto="off"] *
        ) {
        animation: bone-pulse 2s ease-in-out infinite;
        background: var(--bone-base);
        background-size: auto;
      }
    }
  }

  @media (forced-colors: active) {
    :is([aria-busy="true"], [aria-busy="true"] *) {
      &:not(:has(*)):not(
          [data-bones-type],
          [data-bones-type] *,
          [data-bones-lines],
          [data-bones-lines] *,
          [data-bones-auto="off"],
          [data-bones-auto="off"] *
        ):not(
          img,
          svg,
          video,
          canvas,
          picture,
          iframe,
          embed,
          object,
          audio,
          button,
          input,
          select,
          textarea,
          progress,
          meter,
          hr,
          br,
          svg *,
          picture *,
          select *,
          object *
        )::after,
      &:is(
          img,
          svg,
          video,
          canvas,
          picture,
          iframe,
          embed,
          object,
          audio,
          button,
          input,
          select,
          textarea,
          progress,
          meter
        ):not(
          [data-bones-type],
          [data-bones-type] *,
          [data-bones-lines],
          [data-bones-lines] *,
          [data-bones-auto="off"],
          [data-bones-auto="off"] *
        ) {
        background-color: GrayText;
        forced-color-adjust: none;
      }
    }
  }

  /* :is(:scope, :scope *) lets the attribute work from the bone itself, from
     the busy element, or from any ancestor: a scoped selector's implicit
     :scope prefix matches strict descendants only (BON-17). The reduced-motion
     override sits inside each animated scope so it matches at the scope's own
     specificity; "none" has no override because none still means none. */
  @scope ([data-bones-animate="shimmer"]) to ([data-bones-animate]:not([data-bones-animate="shimmer"])) {
    :is([aria-busy="true"], [aria-busy="true"] *):is(:scope, :scope *) {
      &:not(:has(*)):not(
          [data-bones-type],
          [data-bones-type] *,
          [data-bones-lines],
          [data-bones-lines] *,
          [data-bones-auto="off"],
          [data-bones-auto="off"] *
        ):not(
          img,
          svg,
          video,
          canvas,
          picture,
          iframe,
          embed,
          object,
          audio,
          button,
          input,
          select,
          textarea,
          progress,
          meter,
          hr,
          br,
          svg *,
          picture *,
          select *,
          object *
        )::after,
      &:is(
          img,
          svg,
          video,
          canvas,
          picture,
          iframe,
          embed,
          object,
          audio,
          button,
          input,
          select,
          textarea,
          progress,
          meter
        ):not(
          [data-bones-type],
          [data-bones-type] *,
          [data-bones-lines],
          [data-bones-lines] *,
          [data-bones-auto="off"],
          [data-bones-auto="off"] *
        ) {
        animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
        background: linear-gradient(
          90deg,
          var(--bone-base) 25%,
          var(--bone-highlight) 50%,
          var(--bone-base) 75%
        );
        background-size: 200% 100%;
      }
      @media (prefers-reduced-motion: reduce) {
        &:not(:has(*)):not(
            [data-bones-type],
            [data-bones-type] *,
            [data-bones-lines],
            [data-bones-lines] *,
            [data-bones-auto="off"],
            [data-bones-auto="off"] *
          ):not(
            img,
            svg,
            video,
            canvas,
            picture,
            iframe,
            embed,
            object,
            audio,
            button,
            input,
            select,
            textarea,
            progress,
            meter,
            hr,
            br,
            svg *,
            picture *,
            select *,
            object *
          )::after,
        &:is(
            img,
            svg,
            video,
            canvas,
            picture,
            iframe,
            embed,
            object,
            audio,
            button,
            input,
            select,
            textarea,
            progress,
            meter
          ):not(
            [data-bones-type],
            [data-bones-type] *,
            [data-bones-lines],
            [data-bones-lines] *,
            [data-bones-auto="off"],
            [data-bones-auto="off"] *
          ) {
          animation: bone-pulse 2s ease-in-out infinite;
          background: var(--bone-base);
          background-size: auto;
        }
      }
    }
  }

  @scope ([data-bones-animate="pulse"]) to ([data-bones-animate]:not([data-bones-animate="pulse"])) {
    :is([aria-busy="true"], [aria-busy="true"] *):is(:scope, :scope *) {
      &:not(:has(*)):not(
          [data-bones-type],
          [data-bones-type] *,
          [data-bones-lines],
          [data-bones-lines] *,
          [data-bones-auto="off"],
          [data-bones-auto="off"] *
        ):not(
          img,
          svg,
          video,
          canvas,
          picture,
          iframe,
          embed,
          object,
          audio,
          button,
          input,
          select,
          textarea,
          progress,
          meter,
          hr,
          br,
          svg *,
          picture *,
          select *,
          object *
        )::after,
      &:is(
          img,
          svg,
          video,
          canvas,
          picture,
          iframe,
          embed,
          object,
          audio,
          button,
          input,
          select,
          textarea,
          progress,
          meter
        ):not(
          [data-bones-type],
          [data-bones-type] *,
          [data-bones-lines],
          [data-bones-lines] *,
          [data-bones-auto="off"],
          [data-bones-auto="off"] *
        ) {
        animation: bone-pulse var(--bone-duration) ease-in-out infinite;
        background: var(--bone-base);
        background-size: auto;
      }
      @media (prefers-reduced-motion: reduce) {
        &:not(:has(*)):not(
            [data-bones-type],
            [data-bones-type] *,
            [data-bones-lines],
            [data-bones-lines] *,
            [data-bones-auto="off"],
            [data-bones-auto="off"] *
          ):not(
            img,
            svg,
            video,
            canvas,
            picture,
            iframe,
            embed,
            object,
            audio,
            button,
            input,
            select,
            textarea,
            progress,
            meter,
            hr,
            br,
            svg *,
            picture *,
            select *,
            object *
          )::after,
        &:is(
            img,
            svg,
            video,
            canvas,
            picture,
            iframe,
            embed,
            object,
            audio,
            button,
            input,
            select,
            textarea,
            progress,
            meter
          ):not(
            [data-bones-type],
            [data-bones-type] *,
            [data-bones-lines],
            [data-bones-lines] *,
            [data-bones-auto="off"],
            [data-bones-auto="off"] *
          ) {
          animation: bone-pulse 2s ease-in-out infinite;
        }
      }
    }
  }

  @scope ([data-bones-animate="none"]) to ([data-bones-animate]:not([data-bones-animate="none"])) {
    :is([aria-busy="true"], [aria-busy="true"] *):is(:scope, :scope *) {
      &:not(:has(*)):not(
          [data-bones-type],
          [data-bones-type] *,
          [data-bones-lines],
          [data-bones-lines] *,
          [data-bones-auto="off"],
          [data-bones-auto="off"] *
        ):not(
          img,
          svg,
          video,
          canvas,
          picture,
          iframe,
          embed,
          object,
          audio,
          button,
          input,
          select,
          textarea,
          progress,
          meter,
          hr,
          br,
          svg *,
          picture *,
          select *,
          object *
        )::after,
      &:is(
          img,
          svg,
          video,
          canvas,
          picture,
          iframe,
          embed,
          object,
          audio,
          button,
          input,
          select,
          textarea,
          progress,
          meter
        ):not(
          [data-bones-type],
          [data-bones-type] *,
          [data-bones-lines],
          [data-bones-lines] *,
          [data-bones-auto="off"],
          [data-bones-auto="off"] *
        ) {
        animation: none;
        background: var(--bone-base);
        background-size: auto;
      }
    }
  }
}

/* ---------------------------------------------------------------------------
   Explicit. Wins over page CSS. Same geometry as the inferred rules above;
   the test suite pins the copies equal.
--------------------------------------------------------------------------- */

:is([aria-busy="true"], [aria-busy="true"] *) {
  &:is([data-bones-type="text"], [data-bones-lines]) {
    --bones-bar-top: calc((1lh + 1cap) / 2 - 1ex);
    --bones-r: max(0.01px, min(var(--bone-radius), 0.5ex));
    --bones-inset: max(0px, var(--bones-r) - 1px);
    color: rgb(from currentcolor r g b / 0);
    position: relative;
    min-width: calc(var(--bones-length, 4) * 1ch);
    min-height: calc(var(--bones-lines, 1) * 1lh);
    &::before {
      content: "\200b";
    }
    &::after {
      content: "";
      position: absolute;
      inset-inline: 0;
      top: var(--bones-bar-top);
      height: 1ex;
      padding: inherit !important;
      box-sizing: content-box !important;
      clip-path: inset(0 round var(--bones-r)) content-box;
      pointer-events: none;
      animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
      background: linear-gradient(
        90deg,
        var(--bone-base) 25%,
        var(--bone-highlight) 50%,
        var(--bone-base) 75%
      );
      background-size: 200% 100%;
    }
  }
  &[data-bones-type="text"]:empty::before {
    content: "";
    display: inline-block;
    min-width: calc(var(--bones-length, 4) * 1ch);
  }

  /* Width in characters. A modifier, not a type: an inferred leaf with a
     length is still inferred. Unlayered so a page width rule does not beat
     a length the author wrote into the markup. On an inline box width does
     nothing; the empty leaf's ::before carries the length there. */
  &[data-bones-length] {
    width: calc(var(--bones-length, 4) * 1ch);
    max-width: 100%;
  }

  /* N lines in one element. ::after is the single bar above, moved to the
     bottom row and shortened. ::before covers the rows above it and a
     three-layer mask tiled every 1lh cuts it into bars: a stripe body inset
     by the corner radius, and a left and a right cap, each a whole ellipse
     (rx = radius, ry = half the bar) drawn one radius in from the end of a
     half-width tile. The layers combine as a union, and the browser snaps
     each layer's tile to device pixels on its own, so nothing visible may
     sit on a tile edge: the ellipse's inner half hides under the body, its
     outer half is the cap, and the body overlaps it by a pixel
     (--bones-inset) so a snapped tile edge cannot open a gap. At the default
     4px the ends read as rounded corners; at 999px they clamp to a pill.
     Because ::before fills
     the box down to the last row, an element taller than N lines still gets
     a bar on every row. */
  &[data-bones-lines] {
    display: block;
    --bones-last: 60%;
    &::before {
      content: "";
      position: absolute;
      inset: 0 0 1lh 0;
      padding: inherit;
      box-sizing: content-box;
      pointer-events: none;
      animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
      background: linear-gradient(
        90deg,
        var(--bone-base) 25%,
        var(--bone-highlight) 50%,
        var(--bone-base) 75%
      );
      background-size: 200% 100%;
      mask-image:
        linear-gradient(
          to bottom,
          transparent var(--bones-bar-top),
          #000 var(--bones-bar-top) calc(var(--bones-bar-top) + 1ex),
          transparent calc(var(--bones-bar-top) + 1ex)
        ),
        radial-gradient(
          ellipse var(--bones-r) 0.5ex at var(--bones-r) calc(var(--bones-bar-top) + 0.5ex),
          #000 calc(100% - 0.5px),
          transparent 100%
        ),
        radial-gradient(
          ellipse var(--bones-r) 0.5ex at calc(100% - var(--bones-r))
            calc(var(--bones-bar-top) + 0.5ex),
          #000 calc(100% - 0.5px),
          transparent 100%
        );
      mask-size:
        calc(100% - 2 * var(--bones-inset)) 1lh,
        50% 1lh,
        50% 1lh;
      mask-position:
        var(--bones-inset) 0,
        left 0,
        right 0;
      mask-repeat: repeat-y;
      mask-origin: content-box;
      mask-clip: content-box;
    }
    &::after {
      top: auto;
      bottom: calc(1lh - var(--bones-bar-top) - 1ex);
      width: var(--bones-last);
    }
  }

  &[data-bones-type="block"] {
    color: rgb(from currentcolor r g b / 0);
    border-radius: var(--bone-radius);
    border-color: transparent;
    object-position: 9999px 0;
    transition: none;
    animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
    background: linear-gradient(
      90deg,
      var(--bone-base) 25%,
      var(--bone-highlight) 50%,
      var(--bone-base) 75%
    );
    background-size: 200% 100%;
  }
  &[data-bones-type="block"] > * {
    visibility: hidden;
  }
  &[data-bones-type="block"]:is(img):is(:not([src]), [src=""]) {
    display: inline-block;
    content: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
  }
}

@media (prefers-reduced-motion: reduce) {
  :is([aria-busy="true"], [aria-busy="true"] *) {
    &:is([data-bones-type="text"], [data-bones-lines])::after,
    &[data-bones-lines]::before,
    &[data-bones-type="block"] {
      animation: bone-pulse 2s ease-in-out infinite;
      background: var(--bone-base);
      background-size: auto;
    }
  }
}

@media (forced-colors: active) {
  :is([aria-busy="true"], [aria-busy="true"] *) {
    &:is([data-bones-type="text"], [data-bones-lines])::after,
    &[data-bones-lines]::before,
    &[data-bones-type="block"] {
      background-color: GrayText;
      forced-color-adjust: none;
    }
  }
}

@scope ([data-bones-animate="shimmer"]) to ([data-bones-animate]:not([data-bones-animate="shimmer"])) {
  :is([aria-busy="true"], [aria-busy="true"] *):is(:scope, :scope *) {
    &:is([data-bones-type="text"], [data-bones-lines])::after,
    &[data-bones-lines]::before,
    &[data-bones-type="block"] {
      animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
      background: linear-gradient(
        90deg,
        var(--bone-base) 25%,
        var(--bone-highlight) 50%,
        var(--bone-base) 75%
      );
      background-size: 200% 100%;
    }
    @media (prefers-reduced-motion: reduce) {
      &:is([data-bones-type="text"], [data-bones-lines])::after,
      &[data-bones-lines]::before,
      &[data-bones-type="block"] {
        animation: bone-pulse 2s ease-in-out infinite;
        background: var(--bone-base);
        background-size: auto;
      }
    }
  }
}

@scope ([data-bones-animate="pulse"]) to ([data-bones-animate]:not([data-bones-animate="pulse"])) {
  :is([aria-busy="true"], [aria-busy="true"] *):is(:scope, :scope *) {
    &:is([data-bones-type="text"], [data-bones-lines])::after,
    &[data-bones-lines]::before,
    &[data-bones-type="block"] {
      animation: bone-pulse var(--bone-duration) ease-in-out infinite;
      background: var(--bone-base);
      background-size: auto;
    }
    @media (prefers-reduced-motion: reduce) {
      &:is([data-bones-type="text"], [data-bones-lines])::after,
      &[data-bones-lines]::before,
      &[data-bones-type="block"] {
        animation: bone-pulse 2s ease-in-out infinite;
      }
    }
  }
}

@scope ([data-bones-animate="none"]) to ([data-bones-animate]:not([data-bones-animate="none"])) {
  :is([aria-busy="true"], [aria-busy="true"] *):is(:scope, :scope *) {
    &:is([data-bones-type="text"], [data-bones-lines])::after,
    &[data-bones-lines]::before,
    &[data-bones-type="block"] {
      animation: none;
      background: var(--bone-base);
      background-size: auto;
    }
  }
}
