ふわふわ浮遊キャラ群

パステル色のキャラクターたちが各々の周期でふわふわ漂い、順番にシュッと弾みます。ゆるふわ系のギャラリーやランディングの賑わいに。サイン波だけで作る軽量な群体アニメーションです。

#floating#sine-wave#cute#characters

ライブデモ

使用例(お題: アイドルグループ Sakura)

この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- Sakura: メンバーのゆるキャラがふわふわ漂う公式サイトの賑わい背景 -->
<div class="stage" aria-label="ふわふわ浮遊する桜キャラ6体">
  <div class="floaty" style="--i:0; --size:64px; --c:#FFD9E0; left:12%; top:28%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
      <circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
      <path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
      <path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
    </svg>
  </div>
  <div class="floaty" style="--i:1; --size:70px; --c:#FFC2D6; left:34%; top:60%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
      <circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
      <path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
      <path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
    </svg>
  </div>
  <div class="floaty" style="--i:2; --size:76px; --c:#FBE3EC; left:52%; top:20%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
      <circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
      <path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
      <path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
    </svg>
  </div>
  <div class="floaty" style="--i:3; --size:82px; --c:#FFB8CE; left:70%; top:54%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
      <circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
      <path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
      <path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
    </svg>
  </div>
  <div class="floaty" style="--i:4; --size:88px; --c:#FFD9E0; left:84%; top:30%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
      <circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
      <path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
      <path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
    </svg>
  </div>
  <div class="floaty" style="--i:5; --size:94px; --c:#FFC2D6; left:24%; top:74%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#5a2b3a"/><circle cx="62" cy="48" r="5" fill="#5a2b3a"/>
      <circle cx="31" cy="60" r="6" fill="#FF7DA8"/><circle cx="69" cy="60" r="6" fill="#FF7DA8"/>
      <path d="M44 64 Q50 70 56 64" stroke="#5a2b3a" stroke-width="2.4" fill="none" stroke-linecap="round"/>
      <path d="M50 4 l4 -8 l-8 0 z" fill="#FF7DA8"/>
    </svg>
  </div>

  <p class="wordmark" aria-hidden="true">桜 <span>OFFICIAL</span></p>
</div>
CSS
:root{
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-inout:  cubic-bezier(0.65, 0, 0.35, 1);
}
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; min-height: 100%; background: #1a1020; }

.stage {
  position: relative;
  width: 100%;
  height: 100vh;
  min-height: 220px;
  max-height: 100%;
  overflow: hidden;
  background: radial-gradient(120% 90% at 50% 0%, #2a1730 0%, #1a1020 70%);
}

.floaty {
  position: absolute;
  width: var(--size);
  height: var(--size);
  margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2);
  will-change: transform;
  animation: floaty calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate;
  animation-delay: calc(var(--i) * -0.7s);
}
.blob {
  display: block; width: 100%; height: 100%;
  transform-origin: center bottom;
  transition: transform .4s var(--ease-spring);
}
.floaty.active .blob { transform: scale(1.25); }

.shadow {
  position: absolute; left: 50%; bottom: -8%;
  width: 62%; height: 12%; margin-left: -31%;
  background: rgba(0,0,0,.4);
  filter: blur(6px);
  border-radius: 50%;
  transform-origin: center;
  animation: footy calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate-reverse;
  animation-delay: calc(var(--i) * -0.7s);
}

.wordmark {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  margin: 0; display: flex; align-items: baseline; gap: 10px;
  font-family: "Hiragino Mincho ProN", serif; font-weight: 700;
  font-size: clamp(40px, 12vw, 96px);
  color: rgba(255, 209, 224, .14);
  letter-spacing: .04em; pointer-events: none;
}
.wordmark span { font-family: system-ui, sans-serif; font-size: .22em; letter-spacing: .4em; }

@keyframes floaty {
  0%   { transform: translate(-8px, -14px) rotate(-4deg); }
  100% { transform: translate(8px, 14px) rotate(4deg); }
}
@keyframes footy { 0% { transform: scaleX(.85); } 100% { transform: scaleX(1); } }
JavaScript
// 浮遊は CSS。順番にシュッと弾む疑似hoverだけ制御する(デモと同じロジック)。
(() => {
  const items = [...document.querySelectorAll('.floaty')];
  if (!items.length) return;
  let i = 0;
  const tick = () => {
    items.forEach(el => el.classList.remove('active'));
    items[i % items.length].classList.add('active');
    i++;
  };
  tick();
  setInterval(tick, 2200);
})();

実装ガイド

使いどころ

ゆるい世界観のトップやキャラクター系サイトの賑わい背景に。複数キャラが各々の周期で漂い順に弾むことで「生きている」印象を与えます。

実装時の注意点

浮遊はCSSのinfiniteアニメ(translate/rotate)で、個体ごとに周期と負のanimation-delayを散らして位相をずらします。順番に弾む疑似hoverだけJSのsetIntervalでクラスを付与。影を浮遊と逆位相で伸縮させると接地感が出ます。

対応ブラウザ

CSS transform/animation・カスタムプロパティは全モダンブラウザで安定し、入力前提なく自動で動きます。prefers-reduced-motion時は弾みを止める配慮を入れ、対応状況は実機で確認してください。

よくある失敗

全個体に同じdelayを与えると一斉に動いて不自然です。負のdelayで位相をばらしましょう。弾みのscaleを大きくしすぎると酔いやすく、要素数を増やしすぎると低スペック端末で重くなります。

応用例

blob形状をマスコットに差し替える、色をブランドカラーに、クリックで反応させる、視差で奥行きを付けるなどに展開できます。

コード

HTML
<!-- ふわふわ浮遊キャラ群:サイン波で漂い、順番にシュッと弾む -->
<div class="stage" aria-label="ふわふわ浮遊するキャラクター6体">
  <div class="floaty" style="--i:0; --size:64px; --c:#FFD9E0; left:12%; top:28%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
  <div class="floaty" style="--i:1; --size:70px; --c:#D9F2FF; left:34%; top:60%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
  <div class="floaty" style="--i:2; --size:76px; --c:#FFF3C9; left:52%; top:20%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
  <div class="floaty" style="--i:3; --size:82px; --c:#E2FFE9; left:70%; top:54%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
  <div class="floaty" style="--i:4; --size:88px; --c:#FFD9E0; left:84%; top:30%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
  <div class="floaty" style="--i:5; --size:94px; --c:#D9F2FF; left:24%; top:74%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
</div>
CSS
:root{
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-spring:   cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-inout:    cubic-bezier(0.65, 0, 0.35, 1);
}
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; min-height: 100%; background: #0F1117; }

.stage {
  position: relative;
  width: 100%;
  height: 100vh;
  min-height: 220px;
  max-height: 100%;
  overflow: hidden;
  background: #0F1117;
}

/* 各個体。--i で周期・遅延を散らして位相をずらす */
.floaty {
  position: absolute;
  width: var(--size);
  height: var(--size);
  margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2); /* left/top を中心基準に */
  will-change: transform;
  animation: floaty calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate;
  animation-delay: calc(var(--i) * -0.7s);
}

.blob {
  display: block; width: 100%; height: 100%;
  transform-origin: center bottom;
  transition: transform .4s var(--ease-spring);
}
/* 疑似hover:順番に .active が回り、シュッと拡大 */
.floaty.active .blob { transform: scale(1.25); }

/* 影:浮遊と逆位相で伸縮 */
.shadow {
  position: absolute; left: 50%; bottom: -8%;
  width: 62%; height: 12%; margin-left: -31%;
  background: rgba(0,0,0,.35);
  filter: blur(6px);
  border-radius: 50%;
  transform-origin: center;
  animation: footy calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate-reverse;
  animation-delay: calc(var(--i) * -0.7s);
}

@keyframes floaty {
  0%   { transform: translate(-8px, -14px) rotate(-4deg); }
  100% { transform: translate(8px, 14px) rotate(4deg); }
}
@keyframes footy {
  0%   { transform: scaleX(.85); }
  100% { transform: scaleX(1); }
}
JavaScript
// 浮遊は CSS の infinite アニメ。ここでは「順番にシュッと弾む」疑似hoverだけ制御する。
(() => {
  const items = [...document.querySelectorAll('.floaty')];
  if (!items.length) return; // null安全

  let i = 0;
  const tick = () => {
    items.forEach(el => el.classList.remove('active'));
    items[i % items.length].classList.add('active');
    i++;
  };
  tick();
  // 座標は CSS 任せ。クラス付与のみなので setInterval でよい
  setInterval(tick, 2200);
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「ふわふわ浮遊キャラ群」の効果を追加してください。

# 追加してほしい効果
ふわふわ浮遊キャラ群(アニメーション & トランジション)
パステル色のキャラクターたちが各々の周期でふわふわ漂い、順番にシュッと弾みます。ゆるふわ系のギャラリーやランディングの賑わいに。サイン波だけで作る軽量な群体アニメーションです。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- ふわふわ浮遊キャラ群:サイン波で漂い、順番にシュッと弾む -->
<div class="stage" aria-label="ふわふわ浮遊するキャラクター6体">
  <div class="floaty" style="--i:0; --size:64px; --c:#FFD9E0; left:12%; top:28%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
  <div class="floaty" style="--i:1; --size:70px; --c:#D9F2FF; left:34%; top:60%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
  <div class="floaty" style="--i:2; --size:76px; --c:#FFF3C9; left:52%; top:20%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
  <div class="floaty" style="--i:3; --size:82px; --c:#E2FFE9; left:70%; top:54%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
  <div class="floaty" style="--i:4; --size:88px; --c:#FFD9E0; left:84%; top:30%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
  <div class="floaty" style="--i:5; --size:94px; --c:#D9F2FF; left:24%; top:74%;" aria-hidden="true">
    <span class="shadow"></span>
    <svg class="blob" viewBox="0 0 100 100" width="100%" height="100%">
      <path d="M50 6 C72 6 94 22 94 48 C94 76 74 94 50 94 C26 94 6 76 6 48 C6 22 28 6 50 6 Z" fill="var(--c)"/>
      <circle cx="38" cy="48" r="5" fill="#1A1A1A"/><circle cx="62" cy="48" r="5" fill="#1A1A1A"/>
      <circle cx="31" cy="60" r="6" fill="#FF9EB3"/><circle cx="69" cy="60" r="6" fill="#FF9EB3"/>
      <path d="M44 64 Q50 70 56 64" stroke="#1A1A1A" stroke-width="2.4" fill="none" stroke-linecap="round"/>
    </svg>
  </div>
</div>

【CSS】
:root{
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-spring:   cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-inout:    cubic-bezier(0.65, 0, 0.35, 1);
}
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; min-height: 100%; background: #0F1117; }

.stage {
  position: relative;
  width: 100%;
  height: 100vh;
  min-height: 220px;
  max-height: 100%;
  overflow: hidden;
  background: #0F1117;
}

/* 各個体。--i で周期・遅延を散らして位相をずらす */
.floaty {
  position: absolute;
  width: var(--size);
  height: var(--size);
  margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2); /* left/top を中心基準に */
  will-change: transform;
  animation: floaty calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate;
  animation-delay: calc(var(--i) * -0.7s);
}

.blob {
  display: block; width: 100%; height: 100%;
  transform-origin: center bottom;
  transition: transform .4s var(--ease-spring);
}
/* 疑似hover:順番に .active が回り、シュッと拡大 */
.floaty.active .blob { transform: scale(1.25); }

/* 影:浮遊と逆位相で伸縮 */
.shadow {
  position: absolute; left: 50%; bottom: -8%;
  width: 62%; height: 12%; margin-left: -31%;
  background: rgba(0,0,0,.35);
  filter: blur(6px);
  border-radius: 50%;
  transform-origin: center;
  animation: footy calc(3.2s + var(--i) * 0.4s) var(--ease-inout) infinite alternate-reverse;
  animation-delay: calc(var(--i) * -0.7s);
}

@keyframes floaty {
  0%   { transform: translate(-8px, -14px) rotate(-4deg); }
  100% { transform: translate(8px, 14px) rotate(4deg); }
}
@keyframes footy {
  0%   { transform: scaleX(.85); }
  100% { transform: scaleX(1); }
}

【JavaScript】
// 浮遊は CSS の infinite アニメ。ここでは「順番にシュッと弾む」疑似hoverだけ制御する。
(() => {
  const items = [...document.querySelectorAll('.floaty')];
  if (!items.length) return; // null安全

  let i = 0;
  const tick = () => {
    items.forEach(el => el.classList.remove('active'));
    items[i % items.length].classList.add('active');
    i++;
  };
  tick();
  // 座標は CSS 任せ。クラス付与のみなので setInterval でよい
  setInterval(tick, 2200);
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。