{
  "id": "motion:motion-marquee",
  "slug": "motion-marquee",
  "kind": "motion",
  "title": {
    "ja": "マーキー",
    "en": "Marquee (scroll velocity)"
  },
  "aiName": "Marquee (scroll velocity)",
  "summary": {
    "ja": "流れ続け、スクロールで加速",
    "en": "Keeps moving at all times and speeds up as the page scrolls"
  },
  "number": 16,
  "group": "scroll-linked",
  "techniques": [
    "scroll-progress"
  ],
  "rules": [
    {
      "ja": "帯は止めない。スクロールは速さを足すだけ",
      "en": "The bands never stop; scrolling only adds speed"
    },
    {
      "ja": "継ぎ目が見えないよう、同じ内容を 2 組並べる",
      "en": "Two identical copies keep the loop seamless"
    },
    {
      "ja": "動きを減らす設定では、流さずに先頭の位置で止める",
      "en": "With reduced motion, hold the bands still at the start position"
    }
  ],
  "reproduction": {
    "prompt": {
      "ja": "マーキー — Marquee (scroll velocity)\n\n上下 2 本の帯（文字は見出し大、40〜72px）を 100vh の sticky 箱に置き、帯の外へ出る文字は overflow: hidden で隠す。\n上の帯は左へ、下の帯は右へ、基本速度 60px/s で止まらずに流れ続ける。\n1 フレームごとのスクロール量の 3 倍を速度に加算し、毎秒 2% まで減衰させる（スクロールを止めると 1 秒ほどで基本速度に近づく）。\n同じ文字の組を 2 つ並べ、組の幅ぶん流れたら位置を戻して、継ぎ目を見せない。\nrequestAnimationFrame と transform だけで動かし、ライブラリは使わない。\n\n外すと崩れる点:\n- 帯は止めない。スクロールは速さを足すだけ\n- 継ぎ目が見えないよう、同じ内容を 2 組並べる\n- 動きを減らす設定では、流さずに先頭の位置で止める\n\n動く実物（1 ファイルで完結する HTML）: https://design-vault.pages.dev/designs/motion-marquee/demo.html",
      "en": "Marquee (scroll velocity) — Marquee (scroll velocity)\n\nPlace two bands of text, one above the other, inside a 100vh sticky box, each in an overflow-hidden frame with large heading type (40–72px). The upper band moves left and the lower band moves right, both at a base speed of 60px per second and never stopping.\nEach frame, add three times that frame's scroll distance to a speed boost; the boost decays to 2% of its value every second, so scrolling briefly speeds the bands up.\nLay out two identical copies of the items and wrap the offset by the width of one copy so the loop has no seam.\nAnimate only transform with requestAnimationFrame; no libraries.\n\nDo not drop:\n- The bands never stop; scrolling only adds speed\n- Two identical copies keep the loop seamless\n- With reduced motion, hold the bands still at the start position\n\nA working example (one self-contained HTML file): https://design-vault.pages.dev/designs/motion-marquee/demo.html"
    },
    "code": {
      "url": "https://design-vault.pages.dev/designs/motion-marquee/demo.html",
      "html": "<!doctype html>\n<html lang=\"ja\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>16 マーキー — Marquee (scroll velocity)</title>\n<style>\n/* dv-motion-base:start */\n:root {\n  --ground: #FFFFFF;\n  --surface: #F3F5F8;\n  --ink: #14181F;\n  --muted: #5A6472;\n  --accent: #1F5FA8;\n  --rule: #D8DDE4;\n  --accent-soft: color-mix(in srgb, var(--accent) 12%, var(--ground));\n  --accent-mid: color-mix(in srgb, var(--accent) 45%, var(--ground));\n  --shade: color-mix(in srgb, var(--ink) 14%, transparent);\n\n  --font: \"Zen Kaku Gothic New\", \"Hiragino Sans\", \"Noto Sans JP\", \"Yu Gothic\", system-ui, sans-serif;\n  --mono: ui-monospace, \"SF Mono\", Menlo, Consolas, monospace;\n\n  --t-label: 13px;\n  --t-body: 16px;\n  --t-lead: 20px;\n  --t-h2: 32px;\n  --t-h1: clamp(40px, 7vw, 72px);\n\n  --s1: 4px;\n  --s2: 8px;\n  --s3: 16px;\n  --s4: 24px;\n  --s5: 40px;\n  --s6: 64px;\n  --s7: 104px;\n\n  --radius: 12px;\n  --ease: cubic-bezier(.2, .7, .2, 1);\n  --dur: .7s;\n  --measure: 1080px;\n}\n*, *::before, *::after { box-sizing: border-box; }\nhtml { -webkit-text-size-adjust: 100%; }\nbody {\n  margin: 0;\n  background: var(--ground);\n  color: var(--ink);\n  font-family: var(--font);\n  font-size: var(--t-body);\n  line-height: 1.7;\n  font-feature-settings: \"palt\";\n  overflow-x: hidden;\n}\nh1, h2, h3, p, ul, ol, figure { margin: 0; padding: 0; }\nul, ol { list-style: none; }\n.dv-wrap { width: min(100% - 2 * var(--s4), var(--measure)); margin-inline: auto; }\n.dv-label {\n  min-height: 72vh;\n  display: grid;\n  align-content: center;\n  gap: var(--s3);\n  padding-block: var(--s6);\n  border-bottom: 1px solid var(--rule);\n}\n.dv-num { font-family: var(--mono); font-size: var(--t-lead); font-weight: 700; color: var(--accent); letter-spacing: .04em; }\n.dv-name { font-size: var(--t-h1); font-weight: 700; line-height: 1.15; letter-spacing: .01em; }\n.dv-ai { font-family: var(--mono); font-size: var(--t-lead); color: var(--muted); }\n.dv-desc { font-size: var(--t-lead); color: var(--ink); }\n.dv-hint { font-size: var(--t-label); color: var(--muted); letter-spacing: .12em; margin-top: var(--s4); }\n.dv-stage { padding-block: var(--s7); }\n.dv-h2 { font-size: var(--t-h2); font-weight: 700; line-height: 1.3; }\n.dv-lead { font-size: var(--t-lead); color: var(--muted); }\n.dv-eyebrow { font-family: var(--mono); font-size: var(--t-label); color: var(--accent); letter-spacing: .12em; text-transform: uppercase; }\n.dv-card { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); padding: var(--s4); }\n.dv-ph {\n  border-radius: var(--radius);\n  background: linear-gradient(135deg, var(--accent-soft), var(--accent-mid) 60%, var(--accent));\n  aspect-ratio: 16 / 9;\n}\n.dv-ph--alt { background: linear-gradient(200deg, var(--surface), var(--accent-soft) 55%, var(--accent-mid)); }\n.dv-ph--ink { background: linear-gradient(160deg, var(--ink), color-mix(in srgb, var(--ink) 55%, var(--accent))); }\n.dv-end {\n  min-height: 60vh;\n  display: grid;\n  place-items: center;\n  border-top: 1px solid var(--rule);\n  color: var(--muted);\n  font-family: var(--mono);\n  font-size: var(--t-label);\n  letter-spacing: .12em;\n}\n/* dv-motion-base:end */\n\n/* ---- この演出だけの CSS。色は var(--…) か color-mix() のみ ---- */\n.marquee-zone { height: 300vh; }\n.marquee-stick { position: sticky; top: 0; height: 100vh; display: grid; align-content: center; gap: var(--s5); }\n.marquee { overflow: hidden; white-space: nowrap; border-block: 1px solid var(--rule); }\n.marquee--alt { background: var(--surface); }\n.marquee-track { display: flex; width: max-content; will-change: transform; }\n.marquee-item { padding-inline: var(--s5); padding-block: var(--s3); font-size: var(--t-h1); font-weight: 700; line-height: 1.3; color: var(--ink); }\n.marquee--alt .marquee-item { color: color-mix(in srgb, var(--ink) 55%, var(--ground)); }\n.marquee-note { padding-inline: var(--s3); font-family: var(--mono); font-size: var(--t-label); color: var(--muted); letter-spacing: .12em; }\n\n@media (prefers-reduced-motion: reduce) {\n  .marquee-track { transform: none; }\n}\n</style>\n</head>\n<body data-motion-number=\"16\">\n<header class=\"dv-label\">\n  <div class=\"dv-wrap\">\n    <p class=\"dv-num\">16</p>\n    <h1 class=\"dv-name\">マーキー</h1>\n    <p class=\"dv-ai\">Marquee (scroll velocity)</p>\n    <p class=\"dv-desc\">流れ続け、スクロールで加速</p>\n    <p class=\"dv-hint\">SCROLL ↓</p>\n  </div>\n</header>\n\n<main class=\"dv-stage\">\n  <section class=\"marquee-zone\">\n    <div class=\"marquee-stick\">\n      <div class=\"marquee\" data-dir=\"left\">\n        <div class=\"marquee-track\">\n          <span class=\"marquee-item\">見出しが入ります</span>\n          <span class=\"marquee-item\">見出しが入ります</span>\n          <span class=\"marquee-item\">見出しが入ります</span>\n          <span class=\"marquee-item\">見出しが入ります</span>\n        </div>\n      </div>\n      <p class=\"marquee-note\">スクロールすると流れが速くなります</p>\n      <div class=\"marquee marquee--alt\" data-dir=\"right\">\n        <div class=\"marquee-track\">\n          <span class=\"marquee-item\">本文のダミーです</span>\n          <span class=\"marquee-item\">本文のダミーです</span>\n          <span class=\"marquee-item\">本文のダミーです</span>\n          <span class=\"marquee-item\">本文のダミーです</span>\n        </div>\n      </div>\n    </div>\n  </section>\n</main>\n\n<footer class=\"dv-end\">END — 16 マーキー</footer>\n\n<script>\n/* dv-motion-helpers:start */\nconst dvReduce = matchMedia('(prefers-reduced-motion: reduce)').matches;\ndocument.documentElement.classList.add('js');\n\n/**\n * 画面に入った要素へ .is-in を付ける。\n * data-reveal=\"repeat\" の要素は画面を出たら外す（何度でも再生）。\n */\nfunction dvReveal(selector = '[data-reveal]', { threshold = 0.2, rootMargin = '0px 0px -8% 0px' } = {}) {\n  const els = [...document.querySelectorAll(selector)];\n  if (dvReduce || !('IntersectionObserver' in window)) {\n    els.forEach((el) => el.classList.add('is-in'));\n    return;\n  }\n  const io = new IntersectionObserver((entries) => {\n    for (const e of entries) {\n      if (e.isIntersecting) {\n        e.target.classList.add('is-in');\n        if (e.target.dataset.reveal !== 'repeat') io.unobserve(e.target);\n      } else if (e.target.dataset.reveal === 'repeat') {\n        e.target.classList.remove('is-in');\n      }\n    }\n  }, { threshold, rootMargin });\n  els.forEach((el) => io.observe(el));\n}\n\n/**\n * スクロール量を 0〜1 にして、各 [data-progress] 要素の CSS 変数 --p に書く。\n *   data-progress=\"through\" : 要素の上端が画面の下端に来たとき 0、下端が画面の上端を抜けたとき 1\n *   data-progress=\"pin\"     : 要素の上端が画面の上端に来たとき 0、下端が画面の下端に来たとき 1\n *                             （中に position: sticky の箱を持つ、背の高い区画用）\n *   data-progress=\"page\"    : ページ全体のスクロール量\n * onUpdate(el, p) を渡すと、値が変わるたびに呼ばれる。\n */\nfunction dvProgress(onUpdate) {\n  const els = [...document.querySelectorAll('[data-progress]')];\n  const clamp = (n) => Math.min(1, Math.max(0, n));\n  let queued = false;\n  const measure = () => {\n    queued = false;\n    const vh = window.innerHeight;\n    for (const el of els) {\n      const r = el.getBoundingClientRect();\n      let p;\n      if (el.dataset.progress === 'pin') p = r.height > vh ? -r.top / (r.height - vh) : 0;\n      else if (el.dataset.progress === 'page') {\n        const max = document.documentElement.scrollHeight - vh;\n        p = max > 0 ? window.scrollY / max : 0;\n      } else p = (vh - r.top) / (vh + r.height);\n      p = clamp(p);\n      el.style.setProperty('--p', p.toFixed(4));\n      if (onUpdate) onUpdate(el, p);\n    }\n  };\n  const request = () => {\n    if (!queued) { queued = true; requestAnimationFrame(measure); }\n  };\n  addEventListener('scroll', request, { passive: true });\n  addEventListener('resize', request);\n  measure();\n}\n/* dv-motion-helpers:end */\n\n/* ---- この演出だけの JS ---- */\n// 帯の中身を 2 組に増やし、組の幅ぶん流れたら先頭へ戻す。\nconst bands = [...document.querySelectorAll('.marquee')].map((el) => {\n  const track = el.querySelector('.marquee-track');\n  track.append(...[...track.children].map((c) => c.cloneNode(true)));\n  return { track, dir: el.dataset.dir === 'right' ? 1 : -1, x: 0 };\n});\n\n// 基本の速さ（60px/s）に、スクロールの速さで足した分を減衰させながら乗せる。\nlet lastY = window.scrollY;\nlet boost = 0;\nlet last = performance.now();\nconst frame = (now) => {\n  const dt = Math.min(0.05, (now - last) / 1000);\n  last = now;\n  const dy = Math.abs(window.scrollY - lastY);\n  lastY = window.scrollY;\n  boost = boost * Math.pow(0.02, dt) + dy * 3;\n  for (const b of bands) {\n    const half = b.track.scrollWidth / 2;\n    b.x += b.dir * (60 + boost) * dt;\n    b.x = ((b.x % half) + half) % half - half;\n    b.track.style.transform = `translate3d(${b.x.toFixed(2)}px, 0, 0)`;\n  }\n  requestAnimationFrame(frame);\n};\nif (!dvReduce) requestAnimationFrame(frame);\n</script>\n</body>\n</html>\n"
    }
  },
  "license": "Original",
  "catalogVersion": 1,
  "designSchemaVersion": 2,
  "capabilities": [
    "live-preview",
    "copy-prompt",
    "get-code"
  ],
  "reusePolicy": "construction-reuse",
  "urls": {
    "ja": "https://design-vault.pages.dev/designs/motion-marquee",
    "en": "https://design-vault.pages.dev/en/designs/motion-marquee",
    "json": "https://design-vault.pages.dev/ai/assets/motion-marquee.json",
    "designJson": null
  }
}