Skip to content

DirectorySkills

Quote ticker: 1440, made in this run
Run 1Quote ticker“Add a scrolling band of short reviews under our hero. Reviews in brief.md.”
Runs in
  • Claude Code
  • Codex
  • Cursor
  • Gemini CLI
  • GitHub Copilot
  • OpenCode
Generates with
Your agent's model
Tested with
Claude Code 2.1.322Claude Sonnet 5.51 real run, 10 Nov
Needs
Node 20+Chrome
Licence
Recaply licence

Quote Ticker

Two rows of short quote chips gliding in opposite directions, with a pause button; stops on hover and with reduced motion.

$9.99once, yours to keep

Builds a testimonial ticker: rows of short quotes gliding sideways into your own project: two rows of rounded quote chips (portrait, short quote, name) gliding slowly in opposite directions with faded edges, a pause button, rows stop on hover or focus and sit still with reduced motion. Plain HTML, CSS and JavaScript, or matched to React, Next.js, Vue, Svelte, Astro or Tailwind when the project uses them. Comes with a working reference and a check that screenshots it at phone, tablet and desktop widths and flags sideways overflow, text under 12px, missing alt text and names, low contrast, no focus styles, motion that ignores reduced motion, leftover sample words and emoji icons. Use when a landing page wants a lively band of many short quotes, or someone asks for a scrolling, marquee or ticker testimonial band.

By RecaplyUpdated 12 Nov

Install Quote Ticker

After the download, ask your agent to add it, or run the line yourself.

Read https://recaply.co/skills/quote-ticker/install.md and follow it to install the quote-ticker skill from quote-ticker.zip.

Paste it into your agent. It reads our install steps, finds the ZIP you downloaded and adds the skill for you.

New to skills? How to install a skill

Similar skills

All 735 skills

Who Quote Ticker is for

What you get

  • One HTML file with its CSS inside: no build step, no framework
  • A testimonial ticker: rows of short quotes gliding sideways
  • Checked at phone and desktop widths

Good fit

  • When a landing page wants a lively band of many short quotes, or someone asks for a scrolling, marquee or ticker testimonial band

Not for

  • An app with a backend, sign-in or a database
  • Copying another company's site

About this skill

You say

Add a scrolling band of short reviews under our hero. Reviews in brief.md.

It said back

The scrolling review band is built, but your project had no hero or page to put it under. The folder held only brief.md, so I added a minimal page with a placeholder hero and put the band right after it.

Files (all in the project folder):

  • index.html: the placeholder hero (marked with a comment, replace it with your real one) and the review band.
  • style.css: cream background, brown #6B3A1E, Plus Jakarta Sans.
  • script.js: the Pause/Play button.
Show more

What it did in run 1

  1. quote-ticker
  2. ls -la && cat brief.md
  3. cd ./.claude/skills/quote-ticker && ls -R reference scripts | head -30; cat reference/index.html reference/style.css reference/script.js
  4. cd ./.claude/skills/quote-ticker/reference && cat style.css script.js; sed 's/<svg.*<\/svg>/[svg]/' index.html | head -c 3000
  5. ./.claude/skills/quote-ticker/reference/style.css
  6. ./.claude/skills/quote-ticker/reference/script.js
  7. cd . && cp .claude/skills/quote-ticker/reference/script.js /dev/null; cat > /tmp/gen.mjs <<'EOF' import fs from 'fs'; const q=[["Best sourdough in Bristol","Kate M.","KM","#E8C9A0"],["Saturday mornings sorted","Dev P.","DP","#D9B08C"],["The cinnamon buns!!","Ana R.","AR","#F0D5B5"]]; const chip=(x,hid)=><li class="chip${hid?' dup':''}"${hid?' aria-hidden="true"':''}><span class="face" aria-hidden="true"><svg viewBox="0 0 100 100" focusable="false"><rect width="100" height="100" fill="${x[3]}"/><text x="50" y="50" text-anchor="middle" dominant-baseline="central" font-family="Plus Jakarta Sans,system-ui,sans-serif" font-weight="800" font-size="38" fill="#6B3A1E">${x[2]}</text></svg></span><span><b>“${x[0]}”</b><span class="role">${x[1]} · App Store review</span></span></li>; const row=(cls,order)=>{const set=order.map(i=>q[i]);const real=set.map(x=>chip(x,false));const fill=[...set,...set].map(x=>chip(x,true));return <div class="tk-row ${cls}"><ul class="tk-track">${real.join('')}${fill.join('')}<!-- seamless loop copy -->${[...set,...set,...set].map(x=>chip(x,true)).join('')}</ul></div>}; const html=<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Crumb Club</title> <meta name="description" content="Crumb Club: fresh bakery on subscription."> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;700;800&display=swap"> <link rel="stylesheet" href="style.css"> </head> <body> <a class="skip" href="#main">Skip to content</a> <main id="main"> <!-- PLACEHOLDER HERO: replace this section with your real hero; the ticker section below goes right after it. --> <section class="hero"><h1>Crumb Club</h1><p>Fresh bakes, delivered on repeat.</p></section> <section class="t-sec ticker" id="love" aria-labelledby="love-h"><div class="sec-head center"><h2 id="love-h">Loved by <em>Crumb Club</em> members</h2><p class="sub">Short notes from our app store page.</p></div><div class="tk-ctrl"><button class="pause" type="button" aria-pressed="false" data-pause>Pause reviews</button></div> ${row('tk-left',[0,1,2])} ${row('tk-right',[2,0,1])} </section> </main> <script src="script.js"></script> </body> </html> ; fs.writeFileSync('index.html',html); EOF node /tmp/gen.mjs cat > style.css <<'EOF' :root{--bg:#FBF3E4;--surface:#FFFFFF;--ink:#3A2314;--muted:#6B5444;--line:#E8D3B3;--accent:#6B3A1E;--on-accent:#FFFFFF;--em:#6B3A1E;--font-display:'Plus Jakarta Sans',system-ui,sans-serif;--font-body:'Plus Jakarta Sans',system-ui,sans-serif} *,*::before,*::after{box-sizing:border-box} html{-webkit-text-size-adjust:100%} body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--font-body);font-size:1.0625rem;line-height:1.6;-webkit-font-smoothing:antialiased} svg{max-width:100%;display:block} h1,h2{font-family:var(--font-display);line-height:1.1;margin:0;text-wrap:balance;font-weight:800} p{margin:0}ul{margin:0;padding:0;list-style:none} button{font:inherit;color:inherit} :focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:4px} .skip{position:absolute;left:-9999px;top:12px;z-index:100;background:var(--ink);color:var(--bg);padding:.6rem 1rem;border-radius:8px;text-decoration:none} .skip:focus{left:12px} .hero{padding:clamp(64px,10vw,120px) 20px;text-align:center} .hero h1{font-size:clamp(2.4rem,6vw,4rem)} .hero p{margin-top:.8rem;color:var(--muted)} .t-sec{padding-block:clamp(48px,7vw,96px)} .sec-head{width:min(100% - 40px,1180px);margin:0 auto clamp(28px,4vw,48px);text-align:center} .sec-head h2{font-size:clamp(1.9rem,4.2vw,3rem);letter-spacing:-.02em} .sec-head h2 em{font-style:italic;color:var(--em)} .sec-head .sub{color:var(--muted);margin:.7rem auto 0;max-width:56ch} .face{display:block;width:40px;height:40px;border-radius:50%;overflow:hidden;flex:none} .face svg{width:100%;height:100%} .pause{display:inline-flex;align-items:center;min-height:44px;padding:.4rem 1.1rem;border-radius:999px;border:1.5px solid var(--line);background:var(--surface);color:var(--ink);font-weight:700;cursor:pointer} .pause:hover{border-color:var(--accent)} .ticker{overflow:hidden} .tk-ctrl{display:flex;justify-content:center;margin:-.6rem 0 2rem} .tk-row{overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);padding-block:8px} .tk-row+.tk-row{margin-top:12px} .tk-track{display:flex;gap:14px;width:max-content;animation:tk 60s linear infinite} .tk-right .tk-track{animation-direction:reverse} .tk-row:hover .tk-track,.tk-row:focus-within .tk-track,.paused .tk-track{animation-play-state:paused} @keyframes tk{to{transform:translateX(calc(-50% - 7px))}} .chip{display:flex;align-items:center;gap:.8rem;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:.55rem 1.4rem .55rem .55rem;box-shadow:0 6px 16px -12px rgba(58,35,20,.5);max-width:440px} .chip b{display:block;font-size:1rem;line-height:1.3;font-weight:700} .chip .role{font-size:.9rem;color:var(--muted)} @media (prefers-reduced-motion:reduce){.tk-track{animation:none;width:auto;flex-wrap:wrap;justify-content:center;padding-inline:20px}.chip.dup{display:none}.tk-row{-webkit-mask-image:none;mask-image:none}.tk-ctrl{display:none}} @media (max-width:560px){.chip{max-width:300px;border-radius:24px}} EOF cat > script.js <<'EOF' const sect = document.querySelector('.ticker'); const pb = document.querySelector('[data-pause]'); pb.addEventListener('click', () => { const on = pb.getAttribute('aria-pressed') !== 'true'; pb.setAttribute('aria-pressed', String(on)); pb.textContent = on ? 'Play reviews' : 'Pause reviews'; sect.classList.toggle('paused', on); }); EOF node .claude/skills/quote-ticker/scripts/check.mjs index.html --out check 2>&1 | tail -30
  8. /tmp/gen.mjs
  9. ./style.css
  10. ./script.js
  11. ls -la /tmp/gen.mjs; head -3 /tmp/gen.mjs
  12. /tmp/qt-gen/gen.mjs
  13. W=.; node /tmp/qt-gen/gen.mjs $W && cd $W && node .claude/skills/quote-ticker/scripts/check.mjs index.html --out check 2>&1 | tail -30
  14. cd . && node /tmp/qt-gen/gen.mjs . && node .claude/skills/quote-ticker/scripts/check.mjs index.html --out check 2>&1 | tail -30
  15. reduced-motion|reduce
  16. cat >> script.js <<'EOF' // With reduced motion the rows sit still (CSS), so also hide the pause button state and unpause. const reduce = window.matchMedia('(prefers-reduced-motion: reduce)'); const syncMotion = () => { if (reduce.matches) { pb.setAttribute('aria-pressed', 'false'); pb.textContent = 'Pause reviews'; sect.classList.remove('paused'); } }; reduce.addEventListener('change', syncMotion); syncMotion(); EOF node .claude/skills/quote-ticker/scripts/check.mjs index.html --out check 2>&1 | tail
  17. ./script.js
  18. node .claude/skills/quote-ticker/scripts/check.mjs index.html --out check 2>&1 | tail
  19. ./check/1440.png
  20. ./check/390.png
  21. Done

10 Nov 2026 · Claude Code 2.1.322 with Claude Sonnet 5.5 · 1 min 45 s · $0.30 of API use

Questions

Is Quote Ticker free?

No. Quote Ticker costs $9.99 once and stays in your Recaply account, or it comes with the Recaply plan ($19.99 a month), which opens every premium skill and prompt.

Which agents can use Quote Ticker?

Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.

What do I get when I buy Quote Ticker?

A ZIP of the whole skill folder (32 KB): the SKILL.md your agent follows and its 6 other files, the references, templates and scripts it uses. You download it from this page whenever you need it, and install it in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. The Recaply plan opens it too, with every other premium skill and prompt.

How do I install Quote Ticker?

Buy it, then download the ZIP from this page. For Claude Code, unzip it into .claude/skills/ in your project. For Codex, Cursor and Gemini CLI, unzip it and run npx skills add ./quote-ticker with your agent's flag.

What do I get from Quote Ticker?

One HTML file with its CSS inside: no build step, no framework. A testimonial ticker: rows of short quotes gliding sideways. Checked at phone and desktop widths.

What does Quote Ticker need?

Node 20+ and Chrome, and no API key. Each use costs nothing beyond your agent.

How big is Quote Ticker?

7 files, about 25,140 tokens in all. The SKILL.md is about 2,171 tokens, and your agent reads it only when the job fits.

What does a run cost?

Our 1 runs took 1 min 45 s to 1 min 45 s and $0.30 to $0.30 of API use each, in Claude Code with claude-sonnet-5-5. On a Claude plan, a run counts toward your usage instead.

Do I have to name Quote Ticker to use it?

Type /quote-ticker first to make sure it loads. Without it, Claude Code may judge a small job simple enough to do without the skill.