# Rotary Card File Testimonials with Typed Quote Cards That Tip over the Spindle and Land with a Bounce

> Testimonial slider built as a desk card file: quote cards with clipped portraits tip over the spindle and land with a bounce. Free React and HTML code.

A shadcn/ui block from the [@weinc](https://we.inc/components?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/weinc/rotary-card-file-testimonials-with-typed-quote-cards-that-tip-over-the-spindle-and-land-with-a-bounce).

- Page, with a live preview: https://shadcn.exchange/r/weinc/rotary-card-file-testimonials-with-typed-quote-cards-that-tip-over-the-spindle-and-land-with-a-bounce
- Screenshot: https://shadcn.exchange/shot/weinc/rotary-card-file-testimonials-with-typed-quote-cards-that-tip-over-the-spindle-and-land-with-a-bounce/light.png
- Registry item: https://we.inc/r/rotary-card-file-testimonials-with-typed-quote-cards-that-tip-over-the-spindle-and-land-with-a-bounce.json
- Listed: 2026-10-28, source last changed 2026-10-28

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/weinc/rotary-card-file-testimonials-with-typed-quote-cards-that-tip-over-the-spindle-and-land-with-a-bounce.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `framer-motion`

## Files

### `components/we-inc/rotary-card-file-testimonials-with-typed-quote-cards-that-tip-over-the-spindle-and-land-with-a-bounce.tsx`

```tsx
import { useCallback, useEffect, useRef, useState } from "react";
import { useReducedMotion } from "framer-motion";

/** The cards in the file. The wheel repeats them, so any number works. */
const people = [
  {
    name: "Margit Lindqvist",
    role: "Owner, Lindqvist Upholstery",
    quote: "I described the shop over the phone. Two days later it was on my screen, down to the brass tacks.",
    tab: "#e0a422",
    photo: { src: "https://images.pexels.com/photos/6625914/pexels-photo-6625914.jpeg?auto=compress&cs=tinysrgb&w=400", alt: "Smiling woman with wavy blonde hair in a quilted jacket against a lilac backdrop", tone: "#9c86c4", focus: "50% 22%" },
  },
  {
    name: "Felix Brandt",
    role: "Baker, Brandt and Crumb",
    quote: "The order form is so plain my grandfather uses it. He is ninety one and he hates everything.",
    tab: "#2f8f86",
    photo: { src: "https://images.pexels.com/photos/6345373/pexels-photo-6345373.jpeg?auto=compress&cs=tinysrgb&w=400", alt: "Young man in a striped top tilting his head with his eyes closed against a pale blue backdrop", tone: "#a9c9cf", focus: "60% 78%" },
  },
  {
    name: "Amara Diallo",
    role: "Trainer, Diallo Movement Studio",
    quote: "Class bookings used to live in my head. Now they live on one page and I get my evenings back.",
    tab: "#d9543a",
    photo: { src: "https://images.pexels.com/photos/3535468/pexels-photo-3535468.jpeg?auto=compress&cs=tinysrgb&w=400", alt: "Woman with long blonde braids resting an arm over her head against a pale yellow dotted wall", tone: "#ecd98c", focus: "58% 26%" },
  },
  {
    name: "Matteo Greco",
    role: "Chef, Osteria Greco",
    quote: "They photographed the ragu like it was a film star. Fully deserved, and the menu reads the same way.",
    tab: "#3b62c4",
    photo: { src: "https://images.pexels.com/photos/24876042/pexels-photo-24876042.jpeg?auto=compress&cs=tinysrgb&w=400", alt: "Chef in a yellow jacket, yellow hat and yellow glasses with his arms folded against a dark backdrop", tone: "#2a2014", focus: "50% 20%" },
  },
  {
    name: "Rafiq Islam",
    role: "Director, Northlight Films",
    quote: "First agency in ten years to send the files, the logins and a thank you note on the same day.",
    tab: "#7a8a3a",
    photo: { src: "https://images.pexels.com/photos/1486055/pexels-photo-1486055.jpeg?auto=compress&cs=tinysrgb&w=400", alt: "Man in dark sunglasses crossing his fists in front of his chest against a blue backdrop", tone: "#1d4f9c", focus: "56% 16%" },
  },
];

// Design pixels. Angles are measured from the reading position: 0 faces you, more is further over the top.
const N = 15; // cards on the wheel
const SW = 520; // scene
const SH = 606;
const AX = 338; // height of the spindle in the scene
const CARD_W = 400;
const LEN = 300; // spindle to the top edge of a card
const BODY = 262; // the card itself, the rest is the hub
const PITCH = 42; // how far above the file you are looking from
const BACK_STEP = 4.5; // spacing of the cards waiting behind
const FRONT_AT = 104; // where a card that has gone over comes to rest
const FRONT_STEP = 6;
const GRAVITY = 2100;

const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
const mod = (v: number, n: number) => ((v % n) + n) % n;
const slot = (r: number) => (r === 0 ? 0 : r <= 7 ? -BACK_STEP * r : FRONT_AT + FRONT_STEP * (N - r - 1));
const TYPE = `"American Typewriter", "Courier New", Courier, monospace`;
const svg = (s: string) => `url("data:image/svg+xml,${encodeURIComponent(s)}")`;
const FIBRE = svg(
  `<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='p' x='0' y='0' width='100%' height='100%'><feTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' seed='9' stitchTiles='stitch'/><feColorMatrix type='matrix' values='0 0 0 0 0.35  0 0 0 0 0.25  0 0 0 0 0.1  0 0 0 0.55 -0.17'/></filter><rect width='180' height='180' filter='url(#p)'/></svg>`,
);

type Card = { phi: number; v: number; target: number; fall: number };

export default function RotaryCardFile() {
  const reduce = !!useReducedMotion();
  const rootRef = useRef<HTMLDivElement>(null);
  const sceneRef = useRef<HTMLDivElement>(null);
  const cardRefs = useRef<(HTMLDivElement | null)[]>([]);
  const knobRefs = useRef<(HTMLSpanElement | null)[]>([]);
  const [cur, setCur] = useState(0);
  const [narrow, setNarrow] = useState(false);
  const curRef = useRef(0);
  const sim = useRef<Card[]>(Array.from({ length: N }, (_, j) => ({ phi: slot(j), v: 0, target: slot(j), fall: 0 })));
  const env = useRef({ touched: false, inside: false, visible: true, knob: 0, knobV: 0, wake: () => {}, drag: null as null | { y: number; moved: boolean } });

  const paint = useCallback((j: number) => {
    const el = cardRefs.current[j];
    if (!el) return;
    const c = sim.current[j];
    const m = mod(c.phi, 360);
    const hidden = m > 125 && m < 322; // under the base, on its way round to the back
    el.style.visibility = hidden ? "hidden" : "visible";
    if (hidden) return;
    el.style.transform = `rotateX(${(-c.phi).toFixed(2)}deg)`;
    // the face dims as it turns away from the lamp, the back brightens as it comes up to it
    const over = m > 180 ? 0 : m;
    el.style.setProperty("--dim", (clamp(over / 95, 0, 1) * 0.5).toFixed(3));
    el.style.setProperty("--deep", clamp((over - FRONT_AT) / 24, 0, 1).toFixed(3));
  }, []);

  const step = useCallback(
    (dir: 1 | -1, byHand: boolean) => {
      if (byHand) env.current.touched = true;
      const before = curRef.current;
      curRef.current = before + dir;
      setCur(curRef.current);
      sim.current.forEach((c, j) => {
        const rOld = mod(j - before, N);
        const base = slot(mod(j - curRef.current, N));
        let t = base + 360 * Math.round((c.target - base) / 360);
        if (dir > 0 && t < c.target - 0.5) t += 360;
        if (dir < 0 && t > c.target + 0.5) t -= 360;
        c.target = t;
        if ((dir > 0 && rOld === 0) || (dir < 0 && rOld === N - 1)) c.fall = dir;
        if (reduce) {
          c.phi = t;
          c.v = 0;
          c.fall = 0;
          paint(j);
        }
      });
      env.current.wake();
    },
    [paint, reduce],
  );

  useEffect(() => {
    const root = rootRef.current;
    const scene = sceneRef.current;
    if (!root || !scene) return;
    const measure = () => {
      const sc = clamp((root.clientWidth - 8) / SW, 0.55, 1.14);
      root.style.height = `${Math.round(SH * sc + 84)}px`;
      scene.style.transform = `translateX(-50%) scale(${sc.toFixed(4)})`;
      setNarrow(root.clientWidth < 560);
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(root);
    sim.current.forEach((_, j) => paint(j));
    return () => ro.disconnect();
  }, [paint]);

  useEffect(() => {
    if (reduce) return;
    const root = rootRef.current;
    if (!root) return;
    const e = env.current;
    let raf = 0;
    let last = performance.now();
    let wait = 0.5;
    const tick = (now: number) => {
      raf = 0;
      const dt = Math.min(0.03, (now - last) / 1000);
      last = now;
      if (!e.touched && !e.inside) {
        wait -= dt;
        if (wait < 0) {
          wait = 3.6;
          step(1, false);
        }
      }
      let hit = 0;
      sim.current.forEach((c, j) => {
        if (c.fall) {
          // pushed up to the top of its arc by a thumb, then it is gravity's
          const a = c.phi - PITCH;
          const push = c.fall * a < 6 ? c.fall * (GRAVITY * 0.92 + 1500) : 0;
          c.v += (GRAVITY * Math.sin((a * Math.PI) / 180) + push - 0.6 * c.v) * dt;
          c.phi += c.v * dt;
          if ((c.fall > 0 && c.phi >= c.target && c.v > 0) || (c.fall < 0 && c.phi <= c.target && c.v < 0)) {
            c.phi = c.target;
            if (Math.abs(c.v) > 120) hit = c.v;
            c.v *= -0.3;
            if (Math.abs(c.v) < 46) c.fall = 0;
          }
          paint(j);
        } else if (Math.abs(c.target - c.phi) > 0.01 || Math.abs(c.v) > 0.05) {
          c.v += (190 * (c.target - c.phi) - 21 * c.v) * dt;
          c.phi += c.v * dt;
          paint(j);
        }
      });
      // the pile it lands on gives a little
      if (hit) {
        sim.current.forEach((c) => {
          const m = mod(c.target, 360);
          if (!c.fall && (hit > 0 ? m >= FRONT_AT && m < 125 : m > 320)) c.v += hit * 0.09;
        });
      }
      e.knobV += (150 * (curRef.current * 22 - e.knob) - 19 * e.knobV) * dt;
      e.knob += e.knobV * dt;
      knobRefs.current.forEach((k) => k && (k.style.backgroundPositionY = `${e.knob.toFixed(1)}px`));
      const busy = sim.current.some((c) => c.fall || Math.abs(c.target - c.phi) > 0.01 || Math.abs(c.v) > 0.05) || Math.abs(e.knobV) > 0.2;
      if (e.visible && !document.hidden && (busy || (!e.touched && !e.inside))) raf = requestAnimationFrame(tick);
    };
    const start = () => {
      if (!raf && e.visible && !document.hidden) {
        last = performance.now();
        raf = requestAnimationFrame(tick);
      }
    };
    e.wake = start;
    const io = new IntersectionObserver(([en]) => {
      e.visible = en.isIntersecting;
      start();
    });
    io.observe(root);
    document.addEventListener("visibilitychange", start);
    start();
    return () => {
      cancelAnimationFrame(raf);
      io.disconnect();
      document.removeEventListener("visibilitychange", start);
      e.wake = () => {};
    };
  }, [paint, reduce, step]);

  // flick the cards: pull down to send one over, push up to bring one back
  const onDown = (ev: React.PointerEvent) => {
    if (ev.button !== 0) return;
    env.current.drag = { y: ev.clientY, moved: false };
    ev.currentTarget.setPointerCapture(ev.pointerId);
  };
  const onDrag = (ev: React.PointerEvent) => {
    const d = env.current.drag;
    if (!d) return;
    const dy = ev.clientY - d.y;
    if (Math.abs(dy) > 34) {
      step(dy > 0 ? 1 : -1, true);
      d.y = ev.clientY;
      d.moved = true;
    }
  };
  const onUp = () => {
    const d = env.current.drag;
    env.current.drag = null;
    if (d && !d.moved) step(1, true);
  };

  const now = people[mod(cur, people.length)];
  const knob = (side: "left" | "right") => (
    <button
      type="button"
      onClick={() => step(side === "right" ? 1 : -1, true)}
      aria-label={side === "right" ? "Next card" : "Previous card"}
      className="group absolute h-[104px] w-[40px] rounded-[9px] focus:outline-none focus-visible:ring-4 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-[#c8412b] active:brightness-125"
      style={{ top: AX - 52, [side]: 0, background: "linear-gradient(180deg, #050505 0%, #2c2c2e 9%, #6d6d72 20%, #3a3a3d 31%, #1b1b1d 55%, #0c0c0d 82%, #000 100%)", boxShadow: "0 22px 26px -12px rgba(60,8,0,0.55), inset 0 0 0 1px rgba(255,255,255,0.06)" }}
    >
      <span ref={(el) => void (knobRefs.current[side === "left" ? 0 : 1] = el)} aria-hidden="true" className="absolute inset-y-0 rounded-[3px]" style={{ left: side === "left" ? 9 : 4, right: side === "left" ? 4 : 9, backgroundImage: "repeating-linear-gradient(180deg, rgba(0,0,0,0.75) 0 2px, rgba(255,255,255,0.16) 2px 3px, rgba(0,0,0,0) 3px 7.33px)", WebkitMaskImage: "linear-gradient(180deg, transparent 2%, #000 14%, #000 86%, transparent 98%)", maskImage: "linear-gradient(180deg, transparent 2%, #000 14%, #000 86%, transparent 98%)" }} />
      <span aria-hidden="true" className="absolute inset-y-[3px] w-[6px] rounded-full" style={{ [side]: 1, background: "linear-gradient(180deg, #55585e 0%, #f6f8fb 18%, #9da2aa 34%, #4a4d53 60%, #d9dde3 84%, #3b3d42 100%)" }} />
    </button>
  );

  return (
    <section ref={rootRef} aria-label="Client card file" className="relative w-full select-none overflow-hidden bg-[#c8412b]" style={{ height: SH + 84, backgroundImage: "radial-gradient(90% 70% at 50% 34%, #e2573a 0%, #cf462d 45%, #a9301d 100%)" }}>
      <style>{`
        .rotary-card-file-face::after { content: ""; position: absolute; inset: 0; border-radius: inherit; background: #1a0d05; opacity: var(--dim, 0); pointer-events: none; }
        .rotary-card-file-back::after { content: ""; position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(180deg, rgba(255,255,255,0.22) 0, rgba(60,30,5,0) 26px, rgba(60,30,5,0.12) 80px, rgba(40,20,5,0.55) 210px); opacity: 1; pointer-events: none; }
        .rotary-card-file-back::before { content: ""; position: absolute; inset: 0; border-radius: inherit; background: #2a1408; opacity: calc(var(--deep, 0) * 0.3); pointer-events: none; }
      `}</style>

      <div ref={sceneRef} className="absolute left-1/2 top-7 origin-top" style={{ width: SW, height: SH, transform: "translateX(-50%)" }}>
        {/* shadow on the table */}
        <div aria-hidden="true" className="absolute left-1/2 h-[70px] w-[560px] -translate-x-1/2 rounded-[50%]" style={{ top: AX + 196, background: "radial-gradient(closest-side, rgba(70,10,0,0.62), rgba(70,10,0,0.3) 55%, rgba(70,10,0,0) 100%)" }} />

        {/* top of the base running back to the posts */}
        <div aria-hidden="true" className="absolute left-1/2 -translate-x-1/2" style={{ top: AX + 70, width: 496, height: 96, background: "linear-gradient(180deg, #050505, #1c1c1e 60%, #2a2a2d)", clipPath: "polygon(4% 0, 96% 0, 100% 100%, 0 100%)" }} />
        {/* posts and the bosses the spindle runs in */}
        {[-1, 1].map((sd) => (
          <div key={sd} aria-hidden="true">
            <span className="absolute w-[16px] rounded-t-[8px]" style={{ left: SW / 2 + sd * 214 - 8, top: AX - 10, height: 150, background: "linear-gradient(90deg, #4b4e54 0%, #f3f5f8 28%, #a3a8b0 46%, #54575d 72%, #c9cdd3 90%, #55585e 100%)", boxShadow: "0 0 0 0.5px rgba(0,0,0,0.5)" }} />
            <span className="absolute h-[44px] w-[44px] rounded-full" style={{ left: SW / 2 + sd * 214 - 22, top: AX - 22, background: "conic-gradient(from 210deg, #6a6d73, #f7f8fa 12%, #8e9299 25%, #e9ecf0 40%, #5a5d63 55%, #dfe2e7 70%, #777a80 85%, #6a6d73)", boxShadow: "0 3px 5px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(0,0,0,0.25)" }} />
          </div>
        ))}
        {knob("left")}
        {knob("right")}

        {/* the wheel */}
        <div className="absolute inset-0 cursor-grab touch-none active:cursor-grabbing" style={{ perspective: 2600, perspectiveOrigin: `50% ${AX - 130}px` }} onPointerDown={onDown} onPointerMove={onDrag} onPointerUp={onUp} onPointerCancel={() => (env.current.drag = null)} onPointerEnter={() => (env.current.inside = true)} onPointerLeave={() => (env.current.inside = false)}>
          <div aria-hidden="true" className="absolute" style={{ left: SW / 2, top: AX, width: 0, height: 0, transformStyle: "preserve-3d" }}>
            {/* hub */}
            <div className="absolute rounded-[5px]" style={{ left: -206, top: -34, width: 412, height: 68, background: "linear-gradient(180deg, #000 0%, #222224 16%, #47474b 30%, #232325 46%, #0b0b0c 78%, #000 100%)" }}>
              {[-120, 84].map((x) => (
                <span key={x} className="absolute inset-y-0 w-[36px]" style={{ left: 206 + x, background: "linear-gradient(180deg, #3a3c41 0%, #eceef2 22%, #9da1a8 38%, #46484d 62%, #17181a 100%)", boxShadow: "0 0 0 1px rgba(0,0,0,0.6)" }} />
              ))}
            </div>
            <div className="absolute" style={{ transformStyle: "preserve-3d" }}>
              {sim.current.map((_, j) => {
                const p = people[mod(j, people.length)];
                const tabbed = j % 2 === 0;
                const tabX = 30 + ((j * 97) % 250);
                const tab = (
                  <span className="absolute -top-[19px] flex h-[22px] w-[74px] items-start justify-center rounded-t-[7px] pt-[3px] text-[11px] font-bold leading-none text-white" style={{ left: tabX, background: `linear-gradient(180deg, rgba(255,255,255,0.28), rgba(255,255,255,0) 60%), ${p.tab}`, boxShadow: "inset 0 1px 0 rgba(255,255,255,0.4)", fontFamily: TYPE }}>
                    {p.name.split(" ")[1][0]}
                  </span>
                );
                return (
                  <div key={j} ref={(el) => void (cardRefs.current[j] = el)} className="absolute" style={{ left: -CARD_W / 2, top: -LEN, width: CARD_W, height: LEN, transformOrigin: "50% 100%", transformStyle: "preserve-3d", transform: `rotateX(${-slot(j)}deg)`, visibility: slot(j) > 125 ? "hidden" : "visible" }}>
                    {/* face */}
                    <div className="rotary-card-file-face absolute inset-x-0 top-0 rounded-b-[3px] rounded-t-[13px] bg-[#f4ecd9]" style={{ height: BODY, backfaceVisibility: "hidden", backgroundImage: `${FIBRE}, linear-gradient(180deg, #fbf5e6 0%, #f3ead4 62%, #d9cdb2 100%)`, boxShadow: "inset 0 0 0 1px rgba(90,60,20,0.22), inset 0 2px 0 rgba(255,255,255,0.7)" }}>
                      {tabbed && tab}
                      <span className="absolute right-[18px] top-[13px] font-serif text-[30px] font-bold leading-none text-[#c8412b]/85">{p.name.split(" ")[1][0]}</span>
                      <div className="absolute inset-x-0 top-0 px-[24px] pt-[26px]" style={{ fontFamily: TYPE, color: "#26211c" }}>
                        <div className="relative float-left mr-[18px] mt-[6px] -rotate-[3deg] bg-white p-[6px]" style={{ width: narrow ? 88 : 116, height: narrow ? 110 : 146, boxShadow: "0 1px 2px rgba(60,30,0,0.4), 0 6px 10px -4px rgba(60,30,0,0.35)" }}>
                          <div className="h-full w-full overflow-hidden" style={{ background: p.photo.tone }}>
                            <img src={p.photo.src} alt="" width={104} height={134} draggable={false} className="h-full w-full object-cover" style={{ objectPosition: p.photo.focus }} />
                          </div>
                          <svg viewBox="0 0 22 56" className="absolute -top-[18px] left-[10px] h-[56px] w-[22px]" fill="none" strokeLinecap="round">
                            <path d="M6 44V14a5 5 0 0 1 10 0v32a8 8 0 0 1-16 0V12" transform="translate(3 2)" stroke="rgba(40,20,0,0.35)" strokeWidth="2.4" />
                            <path d="M6 44V14a5 5 0 0 1 10 0v32a8 8 0 0 1-16 0V12" transform="translate(2 0)" stroke="#c9ced6" strokeWidth="2.2" />
                            <path d="M6 44V14a5 5 0 0 1 10 0v32a8 8 0 0 1-16 0V12" transform="translate(1.5 -0.4)" stroke="#fff" strokeWidth="0.7" opacity="0.85" />
                          </svg>
                        </div>
                        <div className="overflow-hidden">
                        <p className="pr-[22px] font-medium" style={{ fontSize: narrow ? 18.5 : 17, lineHeight: narrow ? "25px" : "26px" }}>{p.quote}</p>
                        <p className="mt-[12px] font-bold" style={{ fontSize: narrow ? 14.5 : 14 }}>{p.name}</p>
                        <p className="text-[#5b4a36]" style={{ fontSize: narrow ? 12.5 : 12 }}>{p.role}</p>
                        </div>
                      </div>
                      {[92, 296].map((x) => (
                        <span key={x} className="absolute bottom-0 h-[11px] w-[12px] rounded-t-full bg-[#191310]" style={{ left: x, boxShadow: "inset 0 2px 2px rgba(0,0,0,0.9), 0 -1px 0 rgba(255,255,255,0.35)" }} />
                      ))}
                      <span className="absolute inset-x-0 bottom-0 h-[70px] rounded-b-[3px]" style={{ background: "linear-gradient(180deg, rgba(60,30,5,0), rgba(60,30,5,0.34))" }} />
                    </div>
                    {/* back */}
                    <div className="rotary-card-file-back absolute inset-x-0 top-0 rounded-b-[3px] rounded-t-[13px] bg-[#efe5cf]" style={{ height: BODY, backfaceVisibility: "hidden", transform: "rotateY(180deg)", backgroundImage: `${FIBRE}, linear-gradient(180deg, #fdf8ea, #efe5cf)`, boxShadow: "inset 0 0 0 1px rgba(90,60,20,0.25), inset 0 3px 0 rgba(255,255,255,0.85)" }}>
                      {tabbed && <span className="absolute -top-[19px] h-[22px] w-[74px] rounded-t-[7px]" style={{ right: tabX, background: `linear-gradient(180deg, rgba(255,255,255,0.35), rgba(255,255,255,0.05)), ${p.tab}` }} />}
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        </div>

        {/* front of the base, which the cards that have gone over disappear behind */}
        <div aria-hidden="true" className="pointer-events-none absolute left-1/2 -translate-x-1/2 rounded-b-[20px] rounded-t-[10px]" style={{ top: AX + 152, width: 506, height: 84, background: "linear-gradient(180deg, #5c5c61 0%, #2b2b2e 5%, #151516 30%, #0a0a0b 72%, #000 100%)", boxShadow: "inset 0 1px 0 rgba(255,255,255,0.45), inset 0 -10px 16px rgba(0,0,0,0.7), 0 18px 22px -8px rgba(50,5,0,0.6)" }}>
          <span className="absolute inset-x-[14px] top-[13px] h-[3px] rounded-full" style={{ background: "linear-gradient(90deg, #6f7379, #f4f6f9 20%, #a6abb3 50%, #f4f6f9 80%, #6f7379)" }} />
          <span className="absolute left-1/2 top-[32px] flex h-[26px] -translate-x-1/2 items-center rounded-[3px] px-[14px] text-[9px] font-bold uppercase tracking-[0.34em] text-[#2a2a2d]" style={{ background: "linear-gradient(180deg, #f1f3f6, #b9bec6 55%, #8d929a)", boxShadow: "inset 0 1px 0 #fff, 0 1px 2px rgba(0,0,0,0.8)" }}>
            Kind words
          </span>
        </div>
      </div>

      <p className="pointer-events-none absolute bottom-4 left-5 text-[10px] font-bold uppercase leading-relaxed tracking-[0.22em] text-white">
        Card file
        <span className="block font-medium text-[#ffe3da]">Turn a knob or flick the cards</span>
      </p>

      <figure className="sr-only" aria-live="polite">
        <blockquote>{now.quote}</blockquote>
        <figcaption>
          {now.name}, {now.role}
        </figcaption>
      </figure>
    </section>
  );
}
```
