Skip to content

DirectorySkills

Portrait tile quotes: 1440, made in this run
Run 1Portrait tile quotes“We want a grid of staff photos where clicking one shows what they say about working here. Details in brief.md, photos in photos/.”
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, 12 Nov
Needs
Node 20+Chrome
Licence
Recaply licence

Portrait Tile Quotes

A grid of portrait tiles; choosing a tile shows that person's full quote in a large panel beside the grid.

$9.99once, yours to keep

Builds a testimonial section with a grid of portrait tiles that reveal each quote into your own project: a 3 by 2 grid of square portrait tiles on the left (the chosen tile gets a thick ring and its name), and a large quote panel on the right showing the chosen person's quote, name and role. 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 team, school, clinic or community wants many faces with one quote at a time, or someone asks for portrait tiles, a face grid or click-to-read testimonials.

By RecaplyUpdated 12 Nov

Install Portrait Tile Quotes

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

Read https://recaply.co/skills/portrait-tile-quotes/install.md and follow it to install the portrait-tile-quotes skill from portrait-tile-quotes.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 Portrait Tile Quotes is for

What you get

  • One HTML file with its CSS inside: no build step, no framework
  • A testimonial section with a grid of portrait tiles that reveal each quote
  • Checked at phone and desktop widths

Good fit

  • When a team, school, clinic or community wants many faces with one quote at a time

Not for

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

About this skill

You say

We want a grid of staff photos where clicking one shows what they say about working here. Details in brief.md, photos in photos/.

It said back

The staff grid is built: six photo tiles on the left and a large quote panel on the right. It follows the portrait-tile-quotes design in bottle green and gold. The automated check passes at 390, 768 and 1440px wide. That covers overflow, text size, contrast, focus styles, reduced motion and leftover sample words. I looked at the 390px and 1440px screenshots and both read cleanly. I didn't open the 768px one. I haven't tried clicking a tile or using the arrow keys in a browser.

Files (all in the project folder)

  • index.html, style.css and script.js are new. The page loads your six photos from photos/ as they are.
Show more

What it did in run 1

  1. portrait-tile-quotes
  2. cat brief.md && ls -la . photos
  3. cd ./.claude/skills/portrait-tile-quotes && ls -R . | head -30 && cat reference/index.html reference/style.css reference/script.js && node scripts/check.mjs --ready; file ../../../photos/*
  4. cd ./.claude/skills/portrait-tile-quotes; ls -R .
  5. ./.claude/skills/portrait-tile-quotes/reference/index.html
  6. ./.claude/skills/portrait-tile-quotes/reference/style.css
  7. ./.claude/skills/portrait-tile-quotes/reference/script.js
  8. cd .; sips -g pixelWidth -g pixelHeight photos/*.jpg | paste - - - ; node .claude/skills/portrait-tile-quotes/scripts/check.mjs --ready
  9. ./photos/ms-patel.jpg
  10. sips -g pixelWidth -g pixelHeight ./photos/*.jpg
  11. node ./.claude/skills/portrait-tile-quotes/scripts/check.mjs --ready
  12. cd . && python3 -I - <<'EOF' import html people=[ ("Anjali Patel","Anjali","Year 2 teacher","ms-patel.jpg","The children here are curious about everything. Every day someone asks a question I didn't see coming."), ("Rhys Davies","Rhys","Year 5 teacher","mr-davies.jpg","We plan together as a year team, so nobody is on their own on a Sunday night."), ("Ewa Kowalski","Ewa","Teaching assistant","mrs-kowalski.jpg","I've been here eleven years. Watching a child read a whole page for the first time still gets me."), ("Funmi Adeyemi","Funmi","Head teacher","mrs-adeyemi.jpg","We protect teachers' time: no marking in the evenings and one staff meeting a week."), ("Gary Briggs","Gary","School cook","mr-briggs.jpg","I cook 240 lunches a day from scratch, and the children help choose the menu."), ("Liam O'Connell","Liam","Caretaker","mr-oconnell.jpg","Every morning I open the gates and get a dozen good mornings. Not many jobs give you that."), ] e=html.escape tiles="".join(f'<button class="pt-tile" type="button" aria-pressed="{"true" if i==0 else "false"}" data-i="{i}"><img src="photos/{p[3]}" alt="" width="400" height="400" loading="{"eager" if i<3 else "lazy"}"><span class="pt-name" aria-hidden="true">{e(p[1])}</span><span class="sr-only">{e(p[0])}, {e(p[2].lower())}</span></button>' for i,p in enumerate(people)) qs="".join(f'<figure class="pt-q"{"" if i==0 else " hidden"}><span class="pt-mark" aria-hidden="true">&ldquo;</span><blockquote><p>{e(p[4],quote=False)}</p></blockquote><figcaption><b>{e(p[0])}</b><span class="role">{e(p[2])}</span></figcaption></figure>' for i,p in enumerate(people)) page=f'''<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Work with us | Oakfield Primary School</title> <meta name="description" content="Hear from the teachers, support staff, cook and caretaker who work at Oakfield Primary School."> <meta property="og:title" content="Work with us | Oakfield Primary School"> <meta property="og:description" content="Hear from the people who work at Oakfield Primary School."> <meta property="og:type" content="website"> <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=Fraunces:opsz,wght@9..144,400;9..144,600&family=Work+Sans:wght@400;500;600&display=swap"> <link rel="stylesheet" href="style.css"> </head> <body> <a class="skip" href="#main">Skip to content</a> <main id="main"> <section class="t-sec" id="staff" aria-labelledby="staff-h"><div class="sec-head"><h2 id="staff-h">Why our staff <em>love</em> working here</h2><p class="sub">Choose a photo to read what each of us says about Oakfield.</p></div><div class="wrap pt-in"><div class="pt-grid" role="group" aria-label="Choose a member of staff">{tiles}</div><div class="pt-panel" aria-live="polite">{qs}</div></div></section> </main> <script src="script.js" defer></script> </body> </html> ''' open("index.html","w").write(page) EOF cat > script.js <<'EOF' document.documentElement.classList.add('js'); const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); const tiles = [...document.querySelectorAll('.pt-tile')]; const qs = [...document.querySelectorAll('.pt-q')]; const pick = (i) => { tiles.forEach((t, k) => t.setAttribute('aria-pressed', String(k === i))); qs.forEach((q, k) => { q.hidden = k !== i; }); if (!reduceMotion.matches) { qs[i].classList.remove('in'); void qs[i].offsetWidth; qs[i].classList.add('in'); } }; tiles.forEach((t, i) => { t.addEventListener('click', () => pick(i)); t.addEventListener('keydown', (e) => { const d = { ArrowRight: 1, ArrowDown: 3, ArrowLeft: -1, ArrowUp: -3 }[e.key]; if (d) { e.preventDefault(); const j = (i + d + tiles.length) % tiles.length; tiles[j].focus(); pick(j); } }); }); EOF cp .claude/skills/portrait-tile-quotes/reference/style.css style.css python3 -I - <<'EOF' s=open("style.css").read() s=s.replace("--bg:#FFF4EC;--surface:#FFFFFF;--ink:#2A1A14;--muted:#66514A;--line:#F0DCCF;--accent:#B4452A;--on-accent:#FFFFFF;--em:#A13D24;","--bg:#F7F3E8;--surface:#FFFFFF;--ink:#14281E;--muted:#4F5F55;--line:#E3DCC6;--accent:#0F4A30;--on-accent:#FFFFFF;--em:#0F4A30;--gold:#E0A526;") s=s.replace(".pt-tile svg{width:100%;height:auto;aspect-ratio:1;border-radius:18px}",".pt-tile img{width:100%;height:auto;aspect-ratio:1;object-fit:cover;border-radius:18px}") s=s.replace(".pt-tile[aria-pressed=true] svg",".pt-tile[aria-pressed=true] img") s=s.replace("color:#fff;font-size:.85rem","color:var(--gold);font-size:.875rem") s=s.replace("rgba(42,26,20,.45)","rgba(20,40,30,.45)") s=s.replace(".pt-mark{display:block;font-family:var(--font-display);font-size:5rem;line-height:.7;height:.45em;color:var(--accent)}",".pt-mark{display:block;font-family:var(--font-display);font-size:5rem;line-height:.7;height:.45em;color:var(--gold)}") s=s.replace(".pt-tile{position:relative;",".pt-tile{position:relative;min-height:44px;") open("style.css","w").write(s) EOF node .claude/skills/portrait-tile-quotes/scripts/check.mjs index.html --out check
  13. ./index.html
  14. ./script.js
  15. ./style.css
  16. node ./.claude/skills/portrait-tile-quotes/scripts/check.mjs ./index.html --out ./check
  17. ./check/1440.png
  18. ./check/390.png
  19. Done

12 Nov 2026 · Claude Code 2.1.322 with Claude Sonnet 5.5 · 1 min 17 s · $0.26 of API use

Questions

Is Portrait Tile Quotes free?

No. Portrait Tile Quotes 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 Portrait Tile Quotes?

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

What do I get when I buy Portrait Tile Quotes?

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 Portrait Tile Quotes?

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 ./portrait-tile-quotes with your agent's flag.

What do I get from Portrait Tile Quotes?

One HTML file with its CSS inside: no build step, no framework. A testimonial section with a grid of portrait tiles that reveal each quote. Checked at phone and desktop widths.

What does Portrait Tile Quotes need?

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

How big is Portrait Tile Quotes?

7 files, about 21,332 tokens in all. The SKILL.md is about 2,160 tokens, and your agent reads it only when the job fits.

What does a run cost?

Our 1 runs took 1 min 17 s to 1 min 17 s and $0.26 to $0.26 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 Portrait Tile Quotes to use it?

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