{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "skeleton-shimmer",
  "type": "registry:block",
  "title": "Skeleton Shimmer",
  "description": "Skeleton cards lit by one shared shimmer: a single band of light sweeps the whole layout at any angle instead of each block running its own gradient, with text skeletons measured from the real lines. On load the light makes one last pass and content develops behind it in a wave, blur to sharp, with zero layout shift.",
  "docs": "Skeleton Shimmer was added to public/vividsites/skeleton-shimmer.html. Paste its markup, <style> and <script> where you want it on the page (or open the file to see it alone). Customise it with the CSS variables at the top. Docs and live demo: https://vividsites.app/c/skeleton-shimmer",
  "categories": [
    "ui",
    "loaders-cursors-feedback"
  ],
  "files": [
    {
      "path": "components/vividsites/skeleton-shimmer.html",
      "type": "registry:file",
      "target": "public/vividsites/skeleton-shimmer.html",
      "content": "<!--\n  vs-skeleton-shimmer: skeleton loading cards lit by ONE shimmer. The light is a single band that sweeps\n  the whole layout at an angle, so every block and every card catches the same pass of light instead\n  of each block running its own little gradient. Text skeletons are measured from the real text, one\n  bar per line, so the placeholder has the exact shape of what arrives. On load, content reveals in a\n  wave that travels the same way the light does: each block crossfades from skeleton to content with a\n  small blur-to-sharp and a 4px rise. The skeleton is an overlay on the real layout, so nothing shifts.\n\n  Markup\n    <div class=\"vs-skeleton-shimmer\" data-vs-skeleton-shimmer aria-busy=\"true\"> … </div>\n    Mark every placeholder block with .vs-skeleton-shimmer-b. The block's own box is the skeleton;\n    add data-vs-sk=\"text\" to a text element (put its text in a <span>) to get one bar per line.\n    data-vs-skeleton-shimmer-theme=\"light|dark\"\n  On load the light makes one last fast pass that leads the reveal wave.\n  JS\n    el.vsSkeleton.load()    reveal the content (aria-busy -> false); fires \"vs-skeleton-shimmer:load\"\n    el.vsSkeleton.reset()   back to skeleton (aria-busy -> true), the light restarts its sweep\n    el.vsSkeleton.loaded    boolean\n  Variables (read live)\n    --vs-skeleton-shimmer-angle     direction the light travels (CSS gradient angle)   110deg\n    --vs-skeleton-shimmer-speed     band speed, px per second                          800\n    --vs-skeleton-shimmer-band      half-width of the band, px                         130\n    --vs-skeleton-shimmer-reveal    how long the reveal wave takes to cross, ms        900\n    --vs-skeleton-shimmer-base / -hi / -soft    skeleton colour, band core, band glow\n  prefers-reduced-motion: a static skeleton (no light, no pulse) and a plain fade.\n  Font: Inter Tight 400/500 (Google Fonts).\n-->\n<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500&display=swap\">\n<style>\n  .vs-skeleton-shimmer {\n    --vs-skeleton-shimmer-angle: 110deg;\n    --vs-skeleton-shimmer-speed: 800;\n    --vs-skeleton-shimmer-band: 130;\n    --vs-skeleton-shimmer-reveal: 900;\n    --vs-skeleton-shimmer-base: #dfe2e9;\n    --vs-skeleton-shimmer-hi: rgba(255,255,255,.9);\n    --vs-skeleton-shimmer-soft: rgba(255,255,255,.34);\n    --vs-skeleton-shimmer-card: #fff;\n    --vs-skeleton-shimmer-ink: #15171b;\n    --vs-skeleton-shimmer-mute: rgba(21,23,27,.54);\n    --vs-skeleton-shimmer-ring: rgba(20,28,45,.09);\n    --vs-skeleton-shimmer-shadow: 0 1px 2px rgba(20,28,45,.05), 0 8px 18px -8px rgba(20,28,45,.12), 0 28px 50px -24px rgba(20,28,45,.18);\n    --vs-skeleton-shimmer-btn: #16181c;\n    --vs-skeleton-shimmer-btn-ink: #fff;\n    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px;\n    font: 400 14px/1.4 \"Inter Tight\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n    color: var(--vs-skeleton-shimmer-ink); -webkit-font-smoothing: antialiased;\n  }\n  .vs-skeleton-shimmer[data-vs-skeleton-shimmer-theme=\"dark\"] {\n    --vs-skeleton-shimmer-base: #24272d;\n    --vs-skeleton-shimmer-hi: rgba(255,255,255,.13);\n    --vs-skeleton-shimmer-soft: rgba(255,255,255,.045);\n    --vs-skeleton-shimmer-card: #17191d;\n    --vs-skeleton-shimmer-ink: #eef0f3;\n    --vs-skeleton-shimmer-mute: rgba(238,240,243,.5);\n    --vs-skeleton-shimmer-ring: rgba(255,255,255,.08);\n    --vs-skeleton-shimmer-shadow: 0 1px 2px rgba(0,0,0,.3), 0 10px 24px -10px rgba(0,0,0,.5), 0 30px 60px -28px rgba(0,0,0,.6);\n    --vs-skeleton-shimmer-btn: #eef0f3;\n    --vs-skeleton-shimmer-btn-ink: #121417;\n  }\n\n  /* ── the skeleton machinery ── */\n  .vs-skeleton-shimmer-b { position: relative; }\n  .vs-skeleton-shimmer-b[data-vs-sk=\"text\"] { width: fit-content; max-width: 100%; }\n  .vs-skeleton-shimmer-b[data-vs-sk=\"text\"] > span { display: inline-block; }\n  .vs-skeleton-shimmer-sk { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; transition: opacity .42s cubic-bezier(.3,0,.2,1) var(--vs-skeleton-shimmer-d, 0ms); }\n  .vs-skeleton-shimmer-bar {\n    position: absolute; overflow: hidden; border-radius: inherit; background: var(--vs-skeleton-shimmer-base);\n    animation: vs-skeleton-shimmer-pulse 1.8s cubic-bezier(.45,0,.55,1) infinite alternate;\n    transform: translateZ(0);\n  }\n  .vs-skeleton-shimmer-bar i {\n    position: absolute; will-change: transform; transform: translate3d(-9999px,0,0);\n    background: linear-gradient(var(--vs-skeleton-shimmer-angle),\n      transparent calc(50% - var(--vs-skeleton-shimmer-band) * 1px),\n      var(--vs-skeleton-shimmer-soft) calc(50% - var(--vs-skeleton-shimmer-band) * .5px),\n      var(--vs-skeleton-shimmer-hi) 50%,\n      var(--vs-skeleton-shimmer-soft) calc(50% + var(--vs-skeleton-shimmer-band) * .5px),\n      transparent calc(50% + var(--vs-skeleton-shimmer-band) * 1px));\n  }\n  @keyframes vs-skeleton-shimmer-pulse { from { opacity: 1; } to { opacity: .72; } }\n  .vs-skeleton-shimmer-b > :not(.vs-skeleton-shimmer-sk) {\n    opacity: 0; filter: blur(4px); transform: translateY(4px);\n    transition: opacity .5s cubic-bezier(.3,0,.2,1), filter .62s cubic-bezier(.2,.7,.2,1), transform .62s cubic-bezier(.2,.7,.2,1);\n    transition-delay: var(--vs-skeleton-shimmer-d, 0ms);\n  }\n  .vs-skeleton-shimmer.is-loaded .vs-skeleton-shimmer-b > :not(.vs-skeleton-shimmer-sk) { opacity: 1; filter: none; transform: none; }\n  .vs-skeleton-shimmer.is-loaded .vs-skeleton-shimmer-sk { opacity: 0; }\n  .vs-skeleton-shimmer.is-reset .vs-skeleton-shimmer-b > :not(.vs-skeleton-shimmer-sk),\n  .vs-skeleton-shimmer.is-reset .vs-skeleton-shimmer-sk { transition-duration: .3s; transition-delay: 0ms; }\n  @media (prefers-reduced-motion: reduce) {\n    .vs-skeleton-shimmer-bar { animation: none; }\n    .vs-skeleton-shimmer-bar i { display: none; }\n    .vs-skeleton-shimmer-b > :not(.vs-skeleton-shimmer-sk) { filter: none; transform: none; transition: opacity .35s; transition-delay: 0ms; }\n    .vs-skeleton-shimmer-sk { transition: opacity .35s; transition-delay: 0ms; }\n  }\n\n  /* ── example card ── */\n  .vs-skeleton-shimmer-card {\n    background: var(--vs-skeleton-shimmer-card); border-radius: 20px; padding: 10px 10px 16px; min-width: 0;\n    box-shadow: 0 0 0 .5px var(--vs-skeleton-shimmer-ring), inset 0 .5px 0 rgba(255,255,255,.7), var(--vs-skeleton-shimmer-shadow);\n  }\n  [data-vs-skeleton-shimmer-theme=\"dark\"] .vs-skeleton-shimmer-card { box-shadow: 0 0 0 .5px var(--vs-skeleton-shimmer-ring), inset 0 .5px 0 rgba(255,255,255,.06), var(--vs-skeleton-shimmer-shadow); }\n  .vs-skeleton-shimmer-media { aspect-ratio: 6 / 5; border-radius: 13px; }\n  .vs-skeleton-shimmer-body { display: grid; gap: 14px; padding: 15px 8px 0; }\n  .vs-skeleton-shimmer-title { margin: 0; font-weight: 500; font-size: 18.5px; line-height: 1.3; letter-spacing: -.01em; }\n  .vs-skeleton-shimmer-by { display: flex; align-items: center; gap: 11px; }\n  .vs-skeleton-shimmer-who { flex: 1; min-width: 0; display: grid; gap: 2px; }\n  .vs-skeleton-shimmer-meta { margin: 0; font-size: 13px; line-height: 1.38; color: var(--vs-skeleton-shimmer-mute); white-space: nowrap; }\n  .vs-skeleton-shimmer-meta b { font-weight: 500; color: var(--vs-skeleton-shimmer-ink); }\n  .vs-skeleton-shimmer-round { border-radius: 50%; flex: none; }\n  .vs-skeleton-shimmer-av {\n    display: grid; place-items: center; width: 38px; height: 38px; border-radius: 50%;\n    background: radial-gradient(90% 90% at 30% 20%, rgba(255,255,255,.28), rgba(255,255,255,0) 60%), var(--a, #3f6b74);\n    color: #fff; font-size: 12.5px; font-weight: 500; letter-spacing: .02em;\n    box-shadow: inset 0 0 0 .5px rgba(0,0,0,.2), inset 0 1px 0 rgba(255,255,255,.25);\n  }\n  .vs-skeleton-shimmer-pill { border-radius: 999px; flex: none; }\n  .vs-skeleton-shimmer-btn {\n    display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px 0 14px; border: 0; border-radius: 999px; cursor: pointer;\n    font-family: inherit; font-weight: 500; font-size: 12.5px; line-height: 1; color: var(--vs-skeleton-shimmer-btn-ink); background: var(--vs-skeleton-shimmer-btn);\n    box-shadow: inset 0 .5px 0 rgba(255,255,255,.18), 0 1px 2px rgba(0,0,0,.14);\n    -webkit-tap-highlight-color: transparent;\n  }\n  .vs-skeleton-shimmer-btn svg { width: 12px; height: 12px; transition: transform .3s cubic-bezier(.34,1.56,.64,1); }\n  .vs-skeleton-shimmer-btn:hover { background: color-mix(in srgb, var(--vs-skeleton-shimmer-btn) 84%, var(--vs-skeleton-shimmer-btn-ink)); }\n  .vs-skeleton-shimmer-btn:hover svg { transform: translateX(2px); }\n  .vs-skeleton-shimmer.is-loaded .vs-skeleton-shimmer-btn:active { transform: scale(.95); transition-duration: .1s; transition-delay: 0ms; }\n  .vs-skeleton-shimmer-btn:focus-visible { outline: 2px solid var(--vs-skeleton-shimmer-ink); outline-offset: 2px; }\n  .vs-skeleton-shimmer-btn:disabled { opacity: .4; cursor: not-allowed; }\n  .vs-skeleton-shimmer[aria-busy=\"true\"] .vs-skeleton-shimmer-btn { pointer-events: none; }\n\n  /* ── gradient \"photographs\" (no image files) ── */\n  .vs-skeleton-shimmer-photo { position: absolute; inset: 0; border-radius: inherit; overflow: hidden; isolation: isolate; }\n  .vs-skeleton-shimmer-photo i { position: absolute; display: block; }\n  .vs-skeleton-shimmer-photo::after { /* film grain + lens falloff */\n    content: \"\"; position: absolute; inset: 0; z-index: 5; pointer-events: none;\n    background:\n      radial-gradient(120% 95% at 50% 45%, rgba(0,0,0,0) 55%, rgba(8,12,20,.32)),\n      url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .5 0 0 0 0 .5 0 0 0 0 .5 0 0 0 .55 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\");\n    mix-blend-mode: overlay; opacity: .9;\n  }\n  /* 1 · tidepools, morning */\n  .vs-skeleton-shimmer-photo.is-tide {\n    background:\n      radial-gradient(30% 22% at 70% 44%, rgba(255,243,219,.95), rgba(255,236,205,.35) 45%, rgba(255,236,205,0) 75%),\n      linear-gradient(180deg, #86aac2 0%, #adc6d3 26%, #dcd9cb 44%, #f1dcbd 51.2%, #7ea3a8 51.4%, #5d8b93 58%, #3e6c76 72%, #274a53 100%);\n  }\n  .is-tide i:nth-child(1) { left: -4%; top: 40.5%; width: 50%; height: 11%; background: linear-gradient(#6a7f8c, #7d8f98); clip-path: polygon(0 100%, 0 46%, 9% 36%, 22% 14%, 33% 6%, 45% 20%, 58% 16%, 70% 40%, 84% 70%, 100% 100%); opacity: .8; filter: blur(.7px); }\n  .is-tide i:nth-child(2) { left: 58%; top: 51.4%; width: 22%; height: 34%; background: repeating-linear-gradient(180deg, rgba(255,242,215,.6) 0 1px, rgba(255,242,215,0) 1px 3.2px, rgba(255,242,215,.35) 3.2px 4px, rgba(255,242,215,0) 4px 7px); filter: blur(.5px); -webkit-mask: radial-gradient(50% 100% at 50% 0, #000 20%, transparent 80%); mask: radial-gradient(50% 100% at 50% 0, #000 20%, transparent 80%); }\n  .is-tide i:nth-child(3) { left: 0; right: 0; top: 51.4%; height: 12%; background: linear-gradient(180deg, rgba(200,220,222,.35), rgba(200,220,222,0)); }\n  .is-tide i:nth-child(4) { left: 0; bottom: 0; width: 64%; height: 38%; background: radial-gradient(120% 100% at 70% 0%, #4a5d5c, #26332f 50%, #141c1b); clip-path: polygon(0 26%, 8% 16%, 17% 20%, 28% 6%, 39% 14%, 50% 30%, 61% 36%, 72% 55%, 86% 72%, 100% 100%, 0 100%); }\n  .is-tide i:nth-child(5) { left: 10%; bottom: 6%; width: 26%; height: 9%; border-radius: 50%; background: radial-gradient(closest-side, rgba(150,190,196,.28), rgba(90,140,150,.12) 60%, transparent); filter: blur(1.5px); }\n  /* 2 · alpine meadow, first frost */\n  .vs-skeleton-shimmer-photo.is-frost { background: linear-gradient(180deg, #6f93b8 0%, #a4bbd0 28%, #dcd2c8 46%, #f6d5b2 57%, #f9e3c6 62%); }\n  .is-frost i:nth-child(1) { left: -2%; right: -2%; top: 24%; height: 36%; background: linear-gradient(#9fb1c7, #bcc6d1 80%); clip-path: polygon(0 100%, 0 62%, 8% 44%, 15% 50%, 24% 22%, 31% 30%, 38% 8%, 46% 26%, 55% 18%, 63% 40%, 72% 30%, 81% 48%, 90% 36%, 100% 56%, 100% 100%); opacity: .9; filter: blur(.9px); }\n  .is-frost i:nth-child(2) { inset: 0; background: radial-gradient(26% 16% at 66% 58%, rgba(255,233,196,.95), rgba(255,226,186,.4) 50%, rgba(255,226,186,0) 80%); }\n  .is-frost i:nth-child(3) { left: -2%; right: -2%; top: 40%; height: 30%; background: linear-gradient(170deg, #4b5d71, #5d6e7e 60%, #6b7986); clip-path: polygon(0 100%, 0 40%, 10% 26%, 19% 38%, 30% 12%, 40% 34%, 49% 30%, 58% 58%, 70% 70%, 80% 52%, 91% 64%, 100% 50%, 100% 100%); filter: blur(.35px); }\n  .is-frost i:nth-child(4) { left: 0; right: 0; bottom: 0; height: 36%; background: radial-gradient(60% 40% at 60% 0%, rgba(255,236,210,.45), rgba(255,236,210,0) 70%), repeating-linear-gradient(97deg, rgba(255,255,255,.06) 0 1px, rgba(255,255,255,0) 1px 5px), repeating-linear-gradient(84deg, rgba(40,55,40,.07) 0 1px, rgba(40,55,40,0) 1px 7px), linear-gradient(180deg, #dfe5e2, #bcc7bd 28%, #869482 66%, #5b6853); clip-path: polygon(0 18%, 20% 10%, 42% 6%, 62% 8%, 82% 3%, 100% 10%, 100% 100%, 0 100%); }\n  .is-frost i:nth-child(5) { left: 8%; bottom: 20%; width: 36%; height: 7%; border-radius: 50%; background: radial-gradient(closest-side, rgba(255,240,222,.55), rgba(255,240,222,0)); }\n  /* 3 · salt flat, dusk */\n  .vs-skeleton-shimmer-photo.is-salt { background: linear-gradient(180deg, #1a2a41 0%, #34496a 22%, #7f8794 38%, #d3946a 48%, #f0c38e 53.6%, #eab98a 53.8%, #c99a7d 58%, #7b7e8c 70%, #3a465c 86%, #232d40 100%); }\n  .is-salt i:nth-child(1) { left: -2%; right: -2%; top: 47.6%; height: 6.2%; background: linear-gradient(#262c3a, #353a48); clip-path: polygon(0 100%, 0 60%, 7% 40%, 15% 52%, 26% 20%, 36% 44%, 44% 36%, 52% 60%, 63% 30%, 74% 48%, 86% 22%, 94% 46%, 100% 40%, 100% 100%); }\n  .is-salt i:nth-child(2) { left: -2%; right: -2%; top: 53.8%; height: 4.6%; background: linear-gradient(#4b4e5a, rgba(90,90,104,0)); clip-path: polygon(0 0, 0 40%, 7% 60%, 15% 48%, 26% 80%, 36% 56%, 44% 64%, 52% 40%, 63% 70%, 74% 52%, 86% 78%, 94% 54%, 100% 60%, 100% 0); opacity: .55; }\n  .is-salt i:nth-child(3) { left: -30%; right: -30%; top: 54%; height: 70%; transform-origin: 50% 0; transform: perspective(160px) rotateX(62deg);\n    background: repeating-linear-gradient(58deg, rgba(255,236,214,.11) 0 1px, rgba(255,236,214,0) 1px 13px, rgba(255,236,214,.07) 13px 14px, rgba(255,236,214,0) 14px 23px), repeating-linear-gradient(-61deg, rgba(255,236,214,.11) 0 1px, rgba(255,236,214,0) 1px 17px, rgba(255,236,214,.06) 17px 18px, rgba(255,236,214,0) 18px 26px); filter: blur(.3px);\n    -webkit-mask: linear-gradient(180deg, transparent, #000 30%); mask: linear-gradient(180deg, transparent, #000 30%); }\n  .is-salt i:nth-child(4) { left: 71%; top: 17%; width: 3px; height: 3px; border-radius: 50%; background: #fff6e8; box-shadow: 0 0 6px 2px rgba(255,240,215,.6); }\n  .is-salt i:nth-child(5) { inset: 0; background: radial-gradient(40% 10% at 44% 53.7%, rgba(255,214,160,.7), rgba(255,214,160,0)); }\n\n  @media (max-width: 720px) {\n    .vs-skeleton-shimmer { grid-template-columns: minmax(0, 1fr); gap: 12px; }\n    .vs-skeleton-shimmer-card { display: grid; grid-template-columns: 104px minmax(0, 1fr); gap: 14px; padding: 8px 12px 8px 8px; border-radius: 18px; }\n    .vs-skeleton-shimmer-media { aspect-ratio: 1; border-radius: 12px; align-self: start; }\n    .vs-skeleton-shimmer-body { gap: 9px; padding: 3px 0 2px; align-content: start; }\n    .vs-skeleton-shimmer-title { font-size: 15.5px; }\n    .vs-skeleton-shimmer-by { flex-wrap: wrap; gap: 8px 9px; }\n    .vs-skeleton-shimmer-av { width: 30px; height: 30px; font-size: 11px; }\n    .vs-skeleton-shimmer-meta { font-size: 12px; }\n    .vs-skeleton-shimmer-btn { height: 28px; font-size: 12px; padding: 0 10px 0 12px; }\n    .vs-skeleton-shimmer-who { flex: 0 0 calc(100% - 39px); }\n    .vs-skeleton-shimmer-by > .vs-skeleton-shimmer-pill { margin-left: 39px; }\n  }\n</style>\n\n<div class=\"vs-skeleton-shimmer\" data-vs-skeleton-shimmer data-vs-skeleton-shimmer-theme=\"light\" aria-busy=\"true\" aria-label=\"Field journal\">\n  <article class=\"vs-skeleton-shimmer-card\">\n    <div class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-media\"><div class=\"vs-skeleton-shimmer-photo is-tide\" role=\"img\" aria-label=\"Rock pools under a low morning sun\"><i></i><i></i><i></i><i></i><i></i></div></div>\n    <div class=\"vs-skeleton-shimmer-body\">\n      <h3 class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-title\" data-vs-sk=\"text\"><span>Tidepool census at low water</span></h3>\n      <div class=\"vs-skeleton-shimmer-by\">\n        <div class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-round\"><span class=\"vs-skeleton-shimmer-av\" style=\"--a:#3d6a73\">IM</span></div>\n        <div class=\"vs-skeleton-shimmer-who\">\n          <p class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-meta\" data-vs-sk=\"text\"><span><b>Ines Marlow</b> · Kestrel Point</span></p>\n          <p class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-meta\" data-vs-sk=\"text\"><span>12 Sep · 41 species logged</span></p>\n        </div>\n        <div class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-pill\"><button class=\"vs-skeleton-shimmer-btn\" type=\"button\">Open<svg viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2.5 6h7M6.5 3l3 3-3 3\"/></svg></button></div>\n      </div>\n    </div>\n  </article>\n  <article class=\"vs-skeleton-shimmer-card\">\n    <div class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-media\"><div class=\"vs-skeleton-shimmer-photo is-frost\" role=\"img\" aria-label=\"Frosted alpine meadow at sunrise\"><i></i><i></i><i></i><i></i><i></i></div></div>\n    <div class=\"vs-skeleton-shimmer-body\">\n      <h3 class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-title\" data-vs-sk=\"text\"><span>First frost on the upper meadow</span></h3>\n      <div class=\"vs-skeleton-shimmer-by\">\n        <div class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-round\"><span class=\"vs-skeleton-shimmer-av\" style=\"--a:#9a6a45\">TR</span></div>\n        <div class=\"vs-skeleton-shimmer-who\">\n          <p class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-meta\" data-vs-sk=\"text\"><span><b>Tomas Reyk</b> · Alpine plot B</span></p>\n          <p class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-meta\" data-vs-sk=\"text\"><span>18 Sep · −3.2 °C at dawn</span></p>\n        </div>\n        <div class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-pill\"><button class=\"vs-skeleton-shimmer-btn\" type=\"button\">Open<svg viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2.5 6h7M6.5 3l3 3-3 3\"/></svg></button></div>\n      </div>\n    </div>\n  </article>\n  <article class=\"vs-skeleton-shimmer-card\">\n    <div class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-media\"><div class=\"vs-skeleton-shimmer-photo is-salt\" role=\"img\" aria-label=\"Salt flat at dusk with one bright star\"><i></i><i></i><i></i><i></i><i></i></div></div>\n    <div class=\"vs-skeleton-shimmer-body\">\n      <h3 class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-title\" data-vs-sk=\"text\"><span>Night count on the salt flat</span></h3>\n      <div class=\"vs-skeleton-shimmer-by\">\n        <div class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-round\"><span class=\"vs-skeleton-shimmer-av\" style=\"--a:#44506b\">AC</span></div>\n        <div class=\"vs-skeleton-shimmer-who\">\n          <p class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-meta\" data-vs-sk=\"text\"><span><b>Aiyana Cole</b> · Basin camp</span></p>\n          <p class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-meta\" data-vs-sk=\"text\"><span>22 Sep · 9 nightjars heard</span></p>\n        </div>\n        <div class=\"vs-skeleton-shimmer-b vs-skeleton-shimmer-pill\"><button class=\"vs-skeleton-shimmer-btn\" type=\"button\">Open<svg viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2.5 6h7M6.5 3l3 3-3 3\"/></svg></button></div>\n      </div>\n    </div>\n  </article>\n</div>\n\n<script>\n(() => {\n  if (window.VsSkeletonShimmer) return;\n  const RM = matchMedia('(prefers-reduced-motion: reduce)');\n  const PULSE = 3600; // one full pulse (1.8 s alternate), used to phase-lock bars made later\n\n  function init(root) {\n    if (root.vsSkeleton) return root.vsSkeleton;\n    const blocks = [...root.querySelectorAll('.vs-skeleton-shimmer-b')];\n    let bars = [], C = {}, W = 1, H = 1, pmin = 0, pmax = 1, raf = 0, revealT = 0, t0 = performance.now(), onscreen = true, stopAt = Infinity, rt = 0;\n    let loaded = root.classList.contains('is-loaded');\n    root.setAttribute('aria-busy', String(!loaded));\n\n    for (const b of blocks) {\n      if (!b.querySelector(':scope > .vs-skeleton-shimmer-sk')) {\n        const sk = document.createElement('span'); sk.className = 'vs-skeleton-shimmer-sk'; sk.setAttribute('aria-hidden', 'true'); b.appendChild(sk);\n      }\n    }\n\n    const readCfg = () => {\n      const cs = getComputedStyle(root), n = (k, d) => { const v = parseFloat(cs.getPropertyValue(k)); return isNaN(v) ? d : v; };\n      const a = n('--vs-skeleton-shimmer-angle', 110) * Math.PI / 180;\n      C = { dx: Math.sin(a), dy: -Math.cos(a), sp: Math.max(40, n('--vs-skeleton-shimmer-speed', 800)), bw: n('--vs-skeleton-shimmer-band', 150), rv: n('--vs-skeleton-shimmer-reveal', 900) };\n      const ps = [0, W * C.dx, H * C.dy, W * C.dx + H * C.dy];\n      pmin = Math.min(...ps); pmax = Math.max(...ps);\n    };\n\n    // one bar per block, or one per text line; each bar carries a shine layer big enough for any angle\n    const measure = () => {\n      const R = root.getBoundingClientRect(); W = R.width || 1; H = R.height || 1;\n      readCfg();\n      const lock = `-${((performance.now() % PULSE) / 1000).toFixed(3)}s`;\n      bars = [];\n      for (const b of blocks) {\n        const sk = b.querySelector(':scope > .vs-skeleton-shimmer-sk'); sk.textContent = '';\n        const br = b.getBoundingClientRect(); let rects = [];\n        if (b.dataset.vsSk === 'text' && b.firstElementChild) {\n          const c = b.firstElementChild, rg = document.createRange(); rg.selectNodeContents(c);\n          const tf = getComputedStyle(c).transform, shift = tf && tf !== 'none' ? new DOMMatrixReadOnly(tf).m42 : 0;\n          const lines = [];\n          for (const r of rg.getClientRects()) {\n            if (r.width < 1) continue;\n            const l = lines.find((q) => Math.abs(q.t - r.top) < r.height * .5);\n            if (l) { l.l = Math.min(l.l, r.left); l.r = Math.max(l.r, r.right); l.h = Math.max(l.h, r.height); } else lines.push({ t: r.top, l: r.left, r: r.right, h: r.height });\n          }\n          for (const l of lines) { const h = l.h * .58; rects.push({ x: l.l - br.left, y: l.t - shift - br.top + (l.h - h) / 2, w: l.r - l.l, h, rad: Math.min(6, h / 2) }); }\n        }\n        if (!rects.length) rects = [{ x: 0, y: 0, w: br.width, h: br.height, rad: null }];\n        for (const q of rects) {\n          const el = document.createElement('b'); el.className = 'vs-skeleton-shimmer-bar';\n          el.style.cssText = `left:${q.x}px;top:${q.y}px;width:${q.w}px;height:${q.h}px;animation-delay:${lock}` + (q.rad != null ? `;border-radius:${q.rad}px` : '');\n          const diag = Math.hypot(q.w, q.h), S = Math.ceil(diag + C.bw * 2 + 4);\n          const i = document.createElement('i'); i.style.cssText = `width:${S}px;height:${S}px;left:${(q.w - S) / 2}px;top:${(q.h - S) / 2}px`;\n          el.appendChild(i); sk.appendChild(el);\n          bars.push({ i, cx: br.left - R.left + q.x + q.w / 2, cy: br.top - R.top + q.y + q.h / 2, reach: diag / 2 + C.bw + 8, out: null });\n        }\n        b._p = { cx: br.left - R.left + br.width / 2, cy: br.top - R.top + br.height / 2 };\n      }\n      kick();\n    };\n\n    const kick = () => { if (!raf && onscreen && !RM.matches && performance.now() < stopAt) raf = requestAnimationFrame(tick); };\n    function tick(now) {\n      raf = 0;\n      if (!onscreen || RM.matches || now > stopAt) return;\n      const bw = C.bw, travel = pmax - pmin + bw * 3, sweep = travel / C.sp * 1000, period = sweep + Math.max(380, sweep * .32);\n      // while loading: a steady sweep; on load: one fast pass that leads the reveal wave\n      const s = revealT ? pmin - bw + ((now - revealT) / C.rv) * (pmax - pmin + bw * 2) : pmin - bw * 1.5 + (((now - t0) % period) / 1000) * C.sp;\n      for (const b of bars) {\n        const t = s - (b.cx * C.dx + b.cy * C.dy), out = Math.abs(t) > b.reach;\n        if (out && b.out === Math.sign(t)) continue;\n        b.out = out ? Math.sign(t) : 0;\n        const d = out ? Math.sign(t) * b.reach : t;\n        b.i.style.transform = `translate3d(${(d * C.dx).toFixed(2)}px,${(d * C.dy).toFixed(2)}px,0)`;\n      }\n      raf = requestAnimationFrame(tick);\n    }\n\n    const load = () => {\n      if (loaded) return; loaded = true; clearTimeout(rt);\n      readCfg();\n      const rm = RM.matches, span = pmax - pmin || 1;\n      for (const b of blocks) {\n        const p = b._p ? b._p.cx * C.dx + b._p.cy * C.dy : pmin;\n        b.style.setProperty('--vs-skeleton-shimmer-d', rm ? '0ms' : Math.round(((p - pmin) / span) * C.rv) + 'ms');\n      }\n      root.classList.remove('is-reset'); root.classList.add('is-loaded'); root.setAttribute('aria-busy', 'false');\n      revealT = performance.now(); stopAt = revealT + C.rv + 700;\n      for (const b of bars) b.out = null;\n      kick();\n      root.dispatchEvent(new CustomEvent('vs-skeleton-shimmer:load'));\n    };\n    const reset = () => {\n      loaded = false; stopAt = Infinity; revealT = 0; t0 = performance.now();\n      for (const b of bars) b.out = null;\n      root.classList.add('is-reset'); root.classList.remove('is-loaded'); root.setAttribute('aria-busy', 'true');\n      clearTimeout(rt); rt = setTimeout(() => root.classList.remove('is-reset'), 420);\n      kick();\n    };\n\n    let mq = 0;\n    const remeasure = () => { cancelAnimationFrame(mq); mq = requestAnimationFrame(measure); };\n    const ro = new ResizeObserver(remeasure); ro.observe(root);\n    const io = new IntersectionObserver((es) => { onscreen = es[es.length - 1].isIntersecting; kick(); }); io.observe(root);\n    const mo = new MutationObserver(() => { readCfg(); for (const b of bars) b.out = null; kick(); });\n    mo.observe(root, { attributes: true, attributeFilter: ['style', 'data-vs-skeleton-shimmer-theme'] });\n    RM.addEventListener && RM.addEventListener('change', kick);\n    if (document.fonts) document.fonts.ready.then(remeasure);\n    measure();\n\n    const api = {\n      load, reset, get loaded() { return loaded; },\n      destroy() { ro.disconnect(); io.disconnect(); mo.disconnect(); cancelAnimationFrame(raf); root.querySelectorAll('.vs-skeleton-shimmer-sk').forEach((s) => s.remove()); delete root.vsSkeleton; },\n    };\n    root.vsSkeleton = api;\n    return api;\n  }\n\n  window.VsSkeletonShimmer = { init };\n  const boot = () => document.querySelectorAll('[data-vs-skeleton-shimmer]').forEach(init);\n  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); else boot();\n})();\n</script>\n"
    }
  ],
  "meta": {
    "category": "ui",
    "group": "Loaders, cursors & feedback",
    "tags": [
      "skeleton",
      "loading",
      "shimmer",
      "placeholder",
      "cards",
      "reveal",
      "aria-busy"
    ],
    "tech": [
      "CSS",
      "JS"
    ],
    "deps": [
      "https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500&display=swap"
    ],
    "source": null,
    "free": true,
    "accent": "#3d6a73",
    "added": "2026-09-30",
    "demo": "https://vividsites.app/api/component/skeleton-shimmer/index.html",
    "thumbnail": "https://vividsites.app/api/component/skeleton-shimmer/thumb.jpg",
    "video": "https://vividsites.app/api/component/skeleton-shimmer/thumb.mp4",
    "page": "https://vividsites.app/c/skeleton-shimmer"
  }
}