@layer components {
  /* The shape of content that hasn't arrived.
   *
   * There is no skeleton system here and there shouldn't be one. A placeholder
   * is whatever you put inside a lazy Turbo Frame — Turbo throws it away when
   * the response lands, so there is nothing to clear and no second partial to
   * keep in step with the real one. This is only the pulse and the fill.
   *
   * It occupies space on purpose. A spinner collapses the region it stands in,
   * which drops the scroll height and bounces the scrollbar out from under
   * whoever was reading; a block the size of the thing coming holds the layout
   * still.
   */
  .placeholder {
    --placeholder-height: var(--space-and-half);
    /* A column of identical bars reads as a chart rather than as text. A
       caller that is standing in for lines of prose varies this down the
       stack — shared/_placeholder_rows takes the widths and cycles them —
       and a caller standing in for a block leaves it alone. */
    --placeholder-width: 100%;
    /* Long enough to read as breathing rather than blinking. Derived from the
       duration scale so the gallery's slow motion reaches it. */
    --placeholder-duration: calc(var(--duration-slow) * 4);

    display: block;
    block-size: var(--placeholder-height);
    inline-size: var(--placeholder-width);
    border-radius: var(--radius-small);
    background: var(--surface-sunk);
    animation: placeholder-pulse var(--placeholder-duration) var(--ease-out) infinite;

    /* Nothing here is content, and a screen reader announcing a dozen empty
       boxes is worse than silence. */
    @media (prefers-reduced-motion: reduce) {
      animation: none;
      opacity: var(--opacity-soft);
    }
  }

  .placeholder--fill {
    --placeholder-height: 100%;
  }

  @keyframes placeholder-pulse {
    50% {
      opacity: var(--opacity-muted);
    }
  }
}
