Circuit Board Diagram with Pulses Flowing into a Chip

A how-it-works section built around a small circuit board. Eight grey traces bend their way from the left and right edges to a dark chip in the middle. Orange pulses of light travel in along the left traces, the chip's window glows, and blue pulses travel out along the right ones, each at its own pace. Three cards underneath name the stages and rise in one after another. The board is a single SVG that scales with the page, and the chip label is HTML so it stays readable on a phone, where the cards stack.

Code

// Adapted from Cult UI (https://cult-ui.com/docs/components/circuit-board)
// Copyright (c) 2023 Jordan-Gilliam. MIT License: https://we.inc/components/licenses
import { motion, useReducedMotion } from "framer-motion";
import { ArrowUpRight } from "lucide-react";

/**
 * Copper traces on an 800 x 300 board. Each starts at an edge and ends under the chip in the middle.
 * `side` picks the pulse colour and direction: "in" runs toward the chip, "out" away from it.
 */
const traces: { d: string; side: "in" | "out"; dur: number; delay: number }[] = [
  { d: "M0 40 H190 Q210 40 210 60 V110 Q210 130 230 130 H340", side: "in", dur: 2.6, delay: 0 },
  { d: "M0 110 H120 Q140 110 140 126 Q140 142 160 142 H340", side: "in", dur: 2.1, delay: 0.7 },
  { d: "M0 190 H120 Q140 190 140 174 Q140 158 160 158 H340", side: "in", dur: 2.4, delay: 1.3 },
  { d: "M0 260 H190 Q210 260 210 240 V190 Q210 170 230 170 H340", side: "in", dur: 2.9, delay: 0.4 },
  { d: "M460 130 H570 Q590 130 590 110 V60 Q590 40 610 40 H800", side: "out", dur: 2.7, delay: 0.9 },
  { d: "M460 142 H640 Q660 142 660 126 Q660 110 680 110 H800", side: "out", dur: 2.2, delay: 0.2 },
  { d: "M460 158 H640 Q660 158 660 174 Q660 190 680 190 H800", side: "out", dur: 2.5, delay: 1.6 },
  { d: "M460 170 H570 Q590 170 590 190 V240 Q590 260 610 260 H800", side: "out", dur: 3, delay: 1.1 },
];

/** Solder points where a trace bends or ends. */
const pads: [number, number][] = [[210, 85], [140, 126], [140, 174], [210, 215], [590, 85], [660, 126], [660, 174], [590, 215]];

const cards = [
  { step: "Collect", title: "Every reading, as it happens", text: "Meters, inverters and batteries report in over whatever link they have. Nothing is sampled away." },
  { step: "Decide", title: "One model of the whole site", text: "The controller weighs price, weather and demand four times a second and picks where each watt goes." },
  { step: "Dispatch", title: "Orders out in milliseconds", text: "Charge, hold or sell. Each device gets its instruction before the next reading arrives." },
];

const COLORS = { in: "#f97316", out: "#0ea5e9" };

export default function CircuitBoardDiagramWithPulsesFlowingIntoAChip() {
  const reduce = useReducedMotion();
  const fadeUp = {
    hidden: reduce ? { opacity: 1 } : { opacity: 0, y: 20, filter: "blur(8px)" },
    visible: { opacity: 1, y: 0, filter: "blur(0px)", transition: { type: "spring" as const, stiffness: 300, damping: 24 } },
  };

  return (
    <section className="bg-zinc-50 px-4 py-16 sm:px-6 sm:py-20">
      <style>{`
        @keyframes circuit-board-diagram-with-pulses-flowing-into-a-chip-run { from { stroke-dashoffset: 0.16; } to { stroke-dashoffset: -1; } }
        @keyframes circuit-board-diagram-with-pulses-flowing-into-a-chip-glow { 0%, 65%, 100% { opacity: 0.25; } 12%, 28% { opacity: 1; } }
        .circuit-board-diagram-with-pulses-flowing-into-a-chip-pulse { stroke-dasharray: 0.16 1; animation: circuit-board-diagram-with-pulses-flowing-into-a-chip-run linear infinite; }
        .circuit-board-diagram-with-pulses-flowing-into-a-chip-glow { animation: circuit-board-diagram-with-pulses-flowing-into-a-chip-glow 2.4s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .circuit-board-diagram-with-pulses-flowing-into-a-chip-pulse { animation: none; stroke-dashoffset: -0.5; }
          .circuit-board-diagram-with-pulses-flowing-into-a-chip-glow { animation: none; opacity: 0.7; }
        }
      `}</style>
      <div className="mx-auto max-w-5xl">
        <div className="mx-auto max-w-2xl text-center">
          <p className="text-sm font-semibold uppercase tracking-widest text-orange-600">How it works</p>
          <h2 className="mt-3 text-3xl font-semibold tracking-tight text-zinc-900 sm:text-4xl">Readings in on the left. Decisions out on the right.</h2>
          <p className="mt-4 text-base leading-7 text-zinc-600">A site controller for solar and storage that turns thousands of signals into one clear instruction per device.</p>
        </div>

        {/* On phones the board is drawn at 170% and cropped at the sides, so the chip stays a readable size. */}
        <div className="relative mx-auto mt-10 max-w-4xl overflow-hidden" style={{ maskImage: "linear-gradient(to right, transparent, #000 12%, #000 88%, transparent)", WebkitMaskImage: "linear-gradient(to right, transparent, #000 12%, #000 88%, transparent)" }}>
          <svg viewBox="0 0 800 300" fill="none" aria-hidden="true" className="-ml-[35%] h-auto w-[170%] max-w-none sm:ml-0 sm:w-full">
            {traces.map((t) => (
              <path key={t.d} d={t.d} stroke="#d4d4d8" strokeWidth="1.5" />
            ))}
            {traces.map((t) => (
              <g key={`pulse-${t.d}`}>
                <path d={t.d} pathLength={1} stroke={COLORS[t.side]} strokeOpacity="0.35" strokeWidth="6" strokeLinecap="round" className="circuit-board-diagram-with-pulses-flowing-into-a-chip-pulse blur-[3px]" style={{ animationDuration: `${t.dur}s`, animationDelay: `-${t.delay}s` }} />
                <path d={t.d} pathLength={1} stroke={COLORS[t.side]} strokeWidth="2" strokeLinecap="round" className="circuit-board-diagram-with-pulses-flowing-into-a-chip-pulse" style={{ animationDuration: `${t.dur}s`, animationDelay: `-${t.delay}s` }} />
              </g>
            ))}
            {pads.map(([x, y]) => (
              <circle key={`${x}-${y}`} cx={x} cy={y} r="4" fill="#fafafa" stroke="#a1a1aa" strokeWidth="1.5" />
            ))}

            {/* The chip: legs on both sides, a dark body and a lit window. */}
            {[130, 142, 158, 170].map((y) => (
              <g key={y}>
                <rect x="326" y={y - 3} width="16" height="6" rx="2" fill="#a1a1aa" />
                <rect x="458" y={y - 3} width="16" height="6" rx="2" fill="#a1a1aa" />
              </g>
            ))}
            <rect x="340" y="100" width="120" height="100" rx="14" fill="#18181b" />
            <rect x="340.5" y="100.5" width="119" height="99" rx="13.5" stroke="#52525b" />
            <rect x="352" y="112" width="96" height="76" rx="8" fill="#27272a" />
            <rect x="352" y="112" width="96" height="76" rx="8" fill="#f97316" fillOpacity="0.22" className="circuit-board-diagram-with-pulses-flowing-into-a-chip-glow" />
            <circle cx="362" cy="122" r="2.5" fill="#f97316" />
          </svg>
          {/* The label is HTML so it stays readable when the board shrinks on a phone. */}
          <p className="pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-[8px] font-semibold uppercase leading-tight tracking-wider text-white sm:text-xs sm:tracking-widest">
            Site<br />controller
          </p>
        </div>

        <motion.ol initial="hidden" animate="visible" variants={{ hidden: {}, visible: { transition: { staggerChildren: 0.1, delayChildren: 0.15 } } }} className="mt-10 grid gap-4 md:grid-cols-3">
          {cards.map((c, i) => (
            <motion.li key={c.step} variants={fadeUp} className="rounded-2xl border border-zinc-200 bg-white p-6">
              <p className="flex items-center gap-2 text-xs font-semibold uppercase tracking-widest text-zinc-500">
                <span className={`h-2 w-2 rounded-full ${i === 0 ? "bg-orange-500" : i === 1 ? "bg-zinc-900" : "bg-sky-500"}`} />
                {c.step}
              </p>
              <h3 className="mt-3 text-lg font-semibold text-zinc-900">{c.title}</h3>
              <p className="mt-2 text-sm leading-6 text-zinc-600">{c.text}</p>
              <a href="#" className="mt-4 inline-flex h-10 items-center gap-1 rounded-md text-sm font-semibold text-zinc-900 underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-orange-500">
                Read the spec <ArrowUpRight aria-hidden="true" className="h-4 w-4" />
              </a>
            </motion.li>
          ))}
        </motion.ol>
      </div>
    </section>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/circuit-board-diagram-with-pulses-flowing-into-a-chip.json

When to use it

How to customize it

Accessibility

Questions

How do the pulses move along the traces?

Each trace is drawn twice. The second copy has `pathLength="1"` and a dash pattern of one short dash followed by a long gap. A keyframe animation slides `stroke-dashoffset` from the start to minus one, which carries that dash from one end of the path to the other, round every corner.

Why do pulses on the right travel outward?

A dash always moves in the direction the path was drawn. The left paths are written from the edge toward the chip and the right paths from the chip toward the edge, so one animation gives both directions.

What makes the soft glow around each pulse?

Under each sharp pulse there is a wider copy at lower opacity with a small blur. The two move together, so the pulse has a halo without an SVG filter.

Can I add more traces?

Yes. Add a path that ends at the chip's left side (x 340) or starts at its right side (x 460) at one of the leg heights 130, 142, 158 or 170, or add more legs to the list beside the chip.

Does it need JavaScript to animate?

The board runs on CSS keyframes only. Framer Motion is used just for the entrance of the three cards and can be removed.

Adapted from Circuit Board in Cult UI, used under its open-source licence. Licences and credits

Related: Numbered How It Works Steps in a Row, Feature Grid with Light Beams on Divider Lines, Three Column Feature Grid with Icons.

More feature sections or all free UI components. Start for free to have a full site built around it.

More in Feature Sections

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap