Traffic Filter Diagram with Allowed and Blocked Request Lines

A feature section that explains request filtering with one small diagram. Eight thin lines grow in from the left of a white panel. The green ones run the full width, passing behind a checkpoint card in the middle, and the red ones stop exactly at the card's left edge and end in a dot with a soft halo. Short pulses keep travelling along every line, and two tallies under the panel count up as they go. Three short points sit beside it. On phones the text stacks above the diagram and the card narrows to fit.

Code

// Adapted from Cult UI (https://www.cult-ui.com/docs/components/security-checkpoint)
// Copyright (c) 2023 Jordan-Gilliam. MIT License: https://we.inc/components/licenses
import { useCallback, useEffect, useRef, useState } from "react";
import { motion, useReducedMotion } from "framer-motion";
import { ShieldCheck } from "lucide-react";

/** One entry per request line, top to bottom. Allowed lines pass behind the card, blocked ones stop at it. */
const lines: ("allow" | "block")[] = ["allow", "block", "allow", "allow", "block", "allow", "block", "allow"];

const points = [
  { title: "Checked at the edge", text: "Every request is scored before it reaches your server, in a few milliseconds." },
  { title: "Real visitors never wait", text: "People with a normal browser pass straight through with no puzzle to solve." },
  { title: "Scripts stop at the door", text: "Scrapers and login stuffing tools are dropped and logged for you to review." },
];

const colour = {
  allow: { line: "linear-gradient(90deg, transparent 0%, rgba(110,231,183,0.25) 10%, rgba(52,211,153,0.6) 40%, rgba(16,185,129,0.9) 100%)", pulse: "#059669" },
  block: { line: "linear-gradient(90deg, transparent 0%, rgba(252,165,165,0.25) 10%, rgba(248,113,113,0.55) 40%, rgba(239,68,68,0.9) 100%)", pulse: "#dc2626" },
};

const GAP = 26; // pixels between lines

export default function TrafficFilterDiagramWithAllowedAndBlockedRequestLines() {
  const reduce = useReducedMotion();
  const box = useRef<HTMLDivElement>(null);
  const card = useRef<HTMLDivElement>(null);
  const [mounted, setMounted] = useState(false);
  const [size, setSize] = useState({ width: 0, cardLeft: 0 });
  const [counts, setCounts] = useState({ allowed: 12480, blocked: 311 });

  const measure = useCallback(() => {
    if (!box.current || !card.current) return;
    const b = box.current.getBoundingClientRect();
    const c = card.current.getBoundingClientRect();
    setSize({ width: b.width, cardLeft: c.left - b.left });
  }, []);

  useEffect(() => {
    measure();
    setMounted(true);
    const ro = new ResizeObserver(measure);
    if (box.current) ro.observe(box.current);
    return () => ro.disconnect();
  }, [measure]);

  // The tallies tick up while requests flow.
  useEffect(() => {
    if (reduce) return;
    let n = 0;
    const t = window.setInterval(() => {
      n += 1;
      setCounts((c) => ({ allowed: c.allowed + 3 + (n % 4), blocked: c.blocked + (n % 3 === 0 ? 1 : 0) }));
    }, 900);
    return () => window.clearInterval(t);
  }, [reduce]);

  const ease = "cubic-bezier(0.16, 1, 0.3, 1)";

  return (
    <section className="bg-zinc-50 px-4 py-14 sm:px-6 sm:py-20">
      <div className="mx-auto grid max-w-5xl items-center gap-10 lg:grid-cols-[1fr_1.15fr]">
        <div>
          <p className="inline-flex items-center gap-2 text-sm font-semibold text-emerald-700">
            <ShieldCheck className="h-4 w-4" aria-hidden="true" /> Gatehouse
          </p>
          <h2 className="mt-3 text-3xl font-semibold tracking-tight text-zinc-950 sm:text-4xl">Bad traffic stops at the door.</h2>
          <ul className="mt-6 space-y-4">
            {points.map((p) => (
              <li key={p.title} className="border-l-2 border-zinc-200 pl-4">
                <h3 className="text-sm font-semibold text-zinc-950">{p.title}</h3>
                <p className="mt-0.5 text-sm text-zinc-600">{p.text}</p>
              </li>
            ))}
          </ul>
        </div>

        <div>
          <div ref={box} className="relative grid h-[280px] place-items-center overflow-hidden rounded-2xl bg-white ring-1 ring-zinc-200" role="img" aria-label="Diagram: green request lines pass behind a checkpoint card and continue, red request lines stop at the card.">
            <div aria-hidden="true" className="absolute inset-0 flex flex-col justify-center" style={{ gap: GAP }}>
              {lines.map((kind, i) => {
                const end = kind === "allow" ? size.width : size.cardLeft;
                return (
                  <div key={i} className="relative h-0.5">
                    <div className="h-0.5 rounded-full" style={{ width: mounted ? end : 0, background: colour[kind].line, transition: reduce ? "none" : `width 0.9s ${ease} ${i * 0.07 + 0.15}s` }} />
                    {/* a short bright pulse travelling along the line */}
                    {!reduce && end > 0 && (
                      <motion.span
                        className="absolute left-0 top-1/2 -mt-[2px] h-1 w-10 rounded-full"
                        style={{ background: `linear-gradient(90deg, transparent, ${colour[kind].pulse})` }}
                        initial={{ x: -40, opacity: 0 }}
                        animate={{ x: [-40, end - 40], opacity: [0, 1, 1, kind === "allow" ? 1 : 0] }}
                        transition={{ duration: kind === "allow" ? 2.2 : 1.5, ease: kind === "allow" ? "linear" : "easeOut", repeat: Infinity, repeatDelay: 0.5 + (i % 3) * 0.4, delay: 0.9 + i * 0.23 }}
                      />
                    )}
                    {kind === "block" && (
                      <>
                        <span className="absolute top-1/2 h-6 w-6 -translate-y-1/2 rounded-full" style={{ left: size.cardLeft - 12, background: "radial-gradient(circle, rgba(252,165,165,0.5) 0%, rgba(252,165,165,0.15) 50%, transparent 70%)", opacity: mounted ? 1 : 0, transition: `opacity 0.3s ease ${i * 0.07 + 0.9}s` }} />
                        <span className="absolute top-1/2 h-[7px] w-[7px] -translate-y-1/2 rounded-full bg-red-500" style={{ left: size.cardLeft - 7, opacity: mounted ? 1 : 0, transition: `opacity 0.3s ease ${i * 0.07 + 0.95}s` }} />
                      </>
                    )}
                  </div>
                );
              })}
            </div>

            <div
              ref={card}
              className="relative z-10 flex w-[min(300px,calc(100%-96px))] flex-col gap-3 rounded-2xl border border-black/10 bg-white px-5 py-4 shadow-[0_1px_3px_rgba(0,0,0,0.04),0_10px_24px_rgba(0,0,0,0.07)]"
              style={{ opacity: mounted ? 1 : 0, transform: mounted ? "scale(1)" : "scale(0.97)", transition: reduce ? "none" : `opacity 0.4s ease 0.1s, transform 0.4s ${ease} 0.1s` }}
            >
              <div className="flex items-center gap-2">
                <span className="h-5 w-[3px] rounded-sm bg-emerald-600" />
                <p className="flex-1 text-sm tracking-tight text-zinc-900">
                  <strong className="font-semibold">Gatehouse</strong> Checkpoint
                </p>
                <span className="flex items-center gap-[3px]" aria-hidden="true">
                  <span className="h-[5px] w-[5px] rounded-full bg-zinc-900" />
                  <span className="h-[5px] w-[5px] rounded-full bg-zinc-400" />
                  <span className="h-[5px] w-[5px] rounded-full bg-zinc-300" />
                </span>
              </div>
              <p className="border-l border-black/10 pl-3.5 text-[13px] leading-[19px] text-zinc-600">Checking that each visitor is a person. Scripted requests are turned away here.</p>
            </div>
          </div>

          <dl className="mt-4 grid grid-cols-2 gap-4">
            <div className="rounded-xl bg-white px-4 py-3 ring-1 ring-zinc-200">
              <dt className="flex items-center gap-2 text-xs font-medium text-zinc-600">
                <span className="h-2 w-2 rounded-full bg-emerald-500" /> Let through today
              </dt>
              <dd className="mt-1 text-2xl font-semibold tabular-nums tracking-tight text-zinc-950">{counts.allowed.toLocaleString("en-US")}</dd>
            </div>
            <div className="rounded-xl bg-white px-4 py-3 ring-1 ring-zinc-200">
              <dt className="flex items-center gap-2 text-xs font-medium text-zinc-600">
                <span className="h-2 w-2 rounded-full bg-red-500" /> Turned away
              </dt>
              <dd className="mt-1 text-2xl font-semibold tabular-nums tracking-tight text-zinc-950">{counts.blocked.toLocaleString("en-US")}</dd>
            </div>
          </dl>
        </div>
      </div>
    </section>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/traffic-filter-diagram-with-allowed-and-blocked-request-lines.json

When to use it

How to customize it

Accessibility

Questions

How do the red lines stop exactly at the card?

After mounting, the component measures the left edge of the card relative to the panel and uses that number as the width of every blocked line and the position of its end dot. A `ResizeObserver` repeats the measurement when the panel changes size.

Why do the green lines go behind the card?

They are full width and sit on a lower layer than the card, which has a solid white background. Visually they enter on one side and leave on the other, which reads as passing the check.

Are the numbers real?

No. They are demo values that tick on a timer. Replace the `counts` state with figures from your own data, or remove the list under the panel.

Can I use it on a dark background?

Yes. Change the panel and card to dark fills such as `bg-zinc-900`, lighten the text, and keep the same line gradients: they are bright enough for both.

Does it need a canvas or SVG?

No. Every line, dot and pulse is a plain element with a gradient background, so it scales with the layout and is easy to restyle.

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

Related: Circuit Board Diagram with Pulses Flowing into a Chip, Animated Beam Connecting Integrations, Alternating Feature Rows with Illustrations.

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