@m13v/seo-components 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +52 -0
  2. package/src/components/AnimatedBeam.tsx +273 -0
  3. package/src/components/AnimatedChecklist.tsx +68 -0
  4. package/src/components/AnimatedCodeBlock.tsx +68 -0
  5. package/src/components/AnimatedDemo.tsx +186 -0
  6. package/src/components/AnimatedMetric.tsx +54 -0
  7. package/src/components/AnimatedSection.tsx +31 -0
  8. package/src/components/ArticleMeta.tsx +51 -0
  9. package/src/components/BackgroundGrid.tsx +59 -0
  10. package/src/components/BeforeAfter.tsx +121 -0
  11. package/src/components/BentoGrid.tsx +67 -0
  12. package/src/components/Breadcrumbs.tsx +40 -0
  13. package/src/components/CodeComparison.tsx +115 -0
  14. package/src/components/ComparisonTable.tsx +66 -0
  15. package/src/components/FaqSection.tsx +40 -0
  16. package/src/components/FlowDiagram.tsx +86 -0
  17. package/src/components/GlowCard.tsx +58 -0
  18. package/src/components/GradientText.tsx +43 -0
  19. package/src/components/InlineCta.tsx +50 -0
  20. package/src/components/InlineTestimonial.tsx +53 -0
  21. package/src/components/LottiePlayer.tsx +63 -0
  22. package/src/components/Marquee.tsx +67 -0
  23. package/src/components/MetricsRow.tsx +32 -0
  24. package/src/components/MorphingText.tsx +133 -0
  25. package/src/components/MotionSequence.tsx +150 -0
  26. package/src/components/NumberTicker.tsx +68 -0
  27. package/src/components/OrbitingCircles.tsx +83 -0
  28. package/src/components/ParallaxSection.tsx +56 -0
  29. package/src/components/Particles.tsx +268 -0
  30. package/src/components/ProofBand.tsx +66 -0
  31. package/src/components/ProofBanner.tsx +31 -0
  32. package/src/components/RemotionClip.tsx +216 -0
  33. package/src/components/SequenceDiagram.tsx +144 -0
  34. package/src/components/ShimmerButton.tsx +51 -0
  35. package/src/components/ShineBorder.tsx +87 -0
  36. package/src/components/StepTimeline.tsx +108 -0
  37. package/src/components/StickyBottomCta.tsx +53 -0
  38. package/src/components/TerminalOutput.tsx +93 -0
  39. package/src/components/TextShimmer.tsx +59 -0
  40. package/src/components/TypingAnimation.tsx +54 -0
  41. package/src/index.ts +69 -0
  42. package/src/lib/json-ld.ts +116 -0
@@ -0,0 +1,93 @@
1
+ "use client";
2
+
3
+ import { motion, useInView } from "framer-motion";
4
+ import { useRef, useState, useEffect } from "react";
5
+
6
+ interface TerminalLine {
7
+ text: string;
8
+ type?: "command" | "output" | "success" | "error" | "info";
9
+ delay?: number;
10
+ }
11
+
12
+ interface TerminalOutputProps {
13
+ lines: TerminalLine[];
14
+ title?: string;
15
+ }
16
+
17
+ const typeColors: Record<string, string> = {
18
+ command: "text-emerald-400",
19
+ output: "text-zinc-400",
20
+ success: "text-emerald-500",
21
+ error: "text-red-400",
22
+ info: "text-amber-400",
23
+ };
24
+
25
+ const prefixes: Record<string, string> = {
26
+ command: "$ ",
27
+ output: " ",
28
+ success: "\u2713 ",
29
+ error: "\u2717 ",
30
+ info: "\u2139 ",
31
+ };
32
+
33
+ export function TerminalOutput({ lines, title = "Terminal" }: TerminalOutputProps) {
34
+ const ref = useRef<HTMLDivElement>(null);
35
+ const isInView = useInView(ref, { once: true, margin: "-40px" });
36
+ const [visibleCount, setVisibleCount] = useState(0);
37
+
38
+ useEffect(() => {
39
+ if (!isInView) return;
40
+ let i = 0;
41
+ const showNext = () => {
42
+ i++;
43
+ setVisibleCount(i);
44
+ if (i < lines.length) {
45
+ const nextDelay = lines[i]?.delay ?? (lines[i]?.type === "command" ? 400 : 120);
46
+ setTimeout(showNext, nextDelay);
47
+ }
48
+ };
49
+ const firstDelay = lines[0]?.delay ?? 300;
50
+ setTimeout(showNext, firstDelay);
51
+ }, [isInView, lines]);
52
+
53
+ return (
54
+ <motion.div
55
+ ref={ref}
56
+ className="my-8 rounded-2xl border border-zinc-800 bg-[#0d1117] overflow-hidden"
57
+ initial={{ opacity: 0, y: 12 }}
58
+ whileInView={{ opacity: 1, y: 0 }}
59
+ viewport={{ once: true }}
60
+ transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
61
+ >
62
+ <div className="flex items-center gap-2 px-4 py-2.5 border-b border-zinc-800">
63
+ <div className="flex gap-1.5">
64
+ <span className="w-2.5 h-2.5 rounded-full bg-red-400/60" />
65
+ <span className="w-2.5 h-2.5 rounded-full bg-amber-400/60" />
66
+ <span className="w-2.5 h-2.5 rounded-full bg-emerald-400/60" />
67
+ </div>
68
+ <span className="text-[11px] font-mono text-[#8b949e] ml-1">{title}</span>
69
+ </div>
70
+ <div className="p-4 font-mono text-sm min-h-[120px]">
71
+ {lines.slice(0, visibleCount).map((line, i) => (
72
+ <motion.div
73
+ key={i}
74
+ initial={{ opacity: 0, y: 4 }}
75
+ animate={{ opacity: 1, y: 0 }}
76
+ transition={{ duration: 0.15 }}
77
+ className={`${typeColors[line.type || "output"]} leading-relaxed`}
78
+ >
79
+ <span className="opacity-50">{prefixes[line.type || "output"]}</span>
80
+ {line.text}
81
+ </motion.div>
82
+ ))}
83
+ {visibleCount < lines.length && isInView && (
84
+ <motion.span
85
+ className="inline-block w-[7px] h-[14px] bg-emerald-500 align-middle"
86
+ animate={{ opacity: [1, 0] }}
87
+ transition={{ duration: 0.6, repeat: Infinity, repeatType: "reverse" }}
88
+ />
89
+ )}
90
+ </div>
91
+ </motion.div>
92
+ );
93
+ }
@@ -0,0 +1,59 @@
1
+ "use client";
2
+
3
+ import { CSSProperties, ReactNode } from "react";
4
+
5
+ /**
6
+ * TextShimmer: renders inline text with a teal shimmer highlight that
7
+ * sweeps across continuously. Useful for drawing attention to a tagline,
8
+ * status label, or short phrase.
9
+ *
10
+ * Requires the `shimmer` keyframe in your Tailwind config:
11
+ * ```
12
+ * keyframes: {
13
+ * shimmer: {
14
+ * "0%, 90%, 100%": {
15
+ * "background-position": "calc(-100% - var(--shimmer-width)) 0",
16
+ * },
17
+ * "30%, 60%": {
18
+ * "background-position": "calc(100% + var(--shimmer-width)) 0",
19
+ * },
20
+ * },
21
+ * },
22
+ * animation: { shimmer: "shimmer 8s infinite" }
23
+ * ```
24
+ *
25
+ * @example
26
+ * <TextShimmer>Now in beta</TextShimmer>
27
+ */
28
+
29
+ interface TextShimmerProps {
30
+ children: ReactNode;
31
+ className?: string;
32
+ /** Width of the shimmer highlight in pixels. */
33
+ shimmerWidth?: number;
34
+ }
35
+
36
+ export function TextShimmer({
37
+ children,
38
+ className,
39
+ shimmerWidth = 100,
40
+ }: TextShimmerProps) {
41
+ const base = [
42
+ "mx-auto max-w-md text-zinc-500",
43
+ "animate-shimmer bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shimmer-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]",
44
+ "bg-gradient-to-r from-zinc-100 via-teal-600/80 via-50% to-zinc-100",
45
+ ].join(" ");
46
+
47
+ return (
48
+ <p
49
+ style={
50
+ {
51
+ "--shimmer-width": `${shimmerWidth}px`,
52
+ } as CSSProperties
53
+ }
54
+ className={className ? `${base} ${className}` : base}
55
+ >
56
+ {children}
57
+ </p>
58
+ );
59
+ }
@@ -0,0 +1,54 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ /**
6
+ * TypingAnimation: displays a string character by character, simulating a
7
+ * typewriter effect. Good for hero headlines, terminal-style intros, or
8
+ * any text you want to reveal progressively.
9
+ *
10
+ * @example
11
+ * <TypingAnimation text="Build and ship in minutes" duration={80} />
12
+ */
13
+
14
+ interface TypingAnimationProps {
15
+ /** The full string to type out. */
16
+ text: string;
17
+ /** Milliseconds between each character. Defaults to 200. */
18
+ duration?: number;
19
+ /** Additional Tailwind classes. */
20
+ className?: string;
21
+ }
22
+
23
+ export function TypingAnimation({
24
+ text,
25
+ duration = 200,
26
+ className,
27
+ }: TypingAnimationProps) {
28
+ const [displayedText, setDisplayedText] = useState<string>("");
29
+ const [i, setI] = useState<number>(0);
30
+
31
+ useEffect(() => {
32
+ const typingEffect = setInterval(() => {
33
+ if (i < text.length) {
34
+ setDisplayedText((prev) => prev + text.charAt(i));
35
+ setI(i + 1);
36
+ } else {
37
+ clearInterval(typingEffect);
38
+ }
39
+ }, duration);
40
+
41
+ return () => {
42
+ clearInterval(typingEffect);
43
+ };
44
+ }, [duration, i, text]);
45
+
46
+ const base =
47
+ "font-display text-center text-4xl font-bold leading-[5rem] tracking-[-0.02em] drop-shadow-sm text-zinc-900";
48
+
49
+ return (
50
+ <h1 className={className ? `${base} ${className}` : base}>
51
+ {displayedText ? displayedText : text}
52
+ </h1>
53
+ );
54
+ }
package/src/index.ts ADDED
@@ -0,0 +1,69 @@
1
+ // JSON-LD structured data helpers
2
+ export {
3
+ breadcrumbListSchema,
4
+ faqPageSchema,
5
+ articleSchema,
6
+ howToSchema,
7
+ } from "./lib/json-ld";
8
+ export type {
9
+ BreadcrumbItem,
10
+ FaqItem,
11
+ ArticleSchemaInput,
12
+ HowToStepInput,
13
+ } from "./lib/json-ld";
14
+
15
+ // Trust-signal components
16
+ export { Breadcrumbs } from "./components/Breadcrumbs";
17
+ export type { BreadcrumbCrumb } from "./components/Breadcrumbs";
18
+ export { ArticleMeta } from "./components/ArticleMeta";
19
+ export { ProofBand } from "./components/ProofBand";
20
+ export { FaqSection } from "./components/FaqSection";
21
+ export { InlineTestimonial } from "./components/InlineTestimonial";
22
+ export { ComparisonTable } from "./components/ComparisonTable";
23
+ export type { ComparisonRow } from "./components/ComparisonTable";
24
+
25
+ // Content display components
26
+ export { AnimatedCodeBlock } from "./components/AnimatedCodeBlock";
27
+ export { TerminalOutput } from "./components/TerminalOutput";
28
+ export { FlowDiagram } from "./components/FlowDiagram";
29
+ export { SequenceDiagram } from "./components/SequenceDiagram";
30
+ export { CodeComparison } from "./components/CodeComparison";
31
+ export { AnimatedChecklist } from "./components/AnimatedChecklist";
32
+ export { AnimatedSection } from "./components/AnimatedSection";
33
+ export { AnimatedMetric } from "./components/AnimatedMetric";
34
+ export { MetricsRow } from "./components/MetricsRow";
35
+
36
+ // CTA components
37
+ export { InlineCta } from "./components/InlineCta";
38
+ export { StickyBottomCta } from "./components/StickyBottomCta";
39
+
40
+ // Proof / social proof
41
+ export { ProofBanner } from "./components/ProofBanner";
42
+
43
+ // Rich layout components
44
+ export { BentoGrid } from "./components/BentoGrid";
45
+ export type { BentoCard } from "./components/BentoGrid";
46
+ export { BeforeAfter } from "./components/BeforeAfter";
47
+ export { AnimatedDemo } from "./components/AnimatedDemo";
48
+ export { GlowCard } from "./components/GlowCard";
49
+ export { ParallaxSection } from "./components/ParallaxSection";
50
+ export { StepTimeline } from "./components/StepTimeline";
51
+ export { MotionSequence } from "./components/MotionSequence";
52
+
53
+ // Remotion video + Lottie
54
+ export { RemotionClip, ConceptReveal } from "./components/RemotionClip";
55
+ export { LottiePlayer } from "./components/LottiePlayer";
56
+
57
+ // Magic UI style components
58
+ export { Marquee } from "./components/Marquee";
59
+ export { AnimatedBeam } from "./components/AnimatedBeam";
60
+ export { OrbitingCircles } from "./components/OrbitingCircles";
61
+ export { NumberTicker } from "./components/NumberTicker";
62
+ export { ShimmerButton } from "./components/ShimmerButton";
63
+ export { GradientText } from "./components/GradientText";
64
+ export { BackgroundGrid } from "./components/BackgroundGrid";
65
+ export { MorphingText } from "./components/MorphingText";
66
+ export { Particles } from "./components/Particles";
67
+ export { ShineBorder } from "./components/ShineBorder";
68
+ export { TextShimmer } from "./components/TextShimmer";
69
+ export { TypingAnimation } from "./components/TypingAnimation";
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Shared JSON-LD structured data helpers.
3
+ * Product-agnostic: caller provides publisher name, URL, logo, author, etc.
4
+ */
5
+
6
+ export interface BreadcrumbItem {
7
+ name: string;
8
+ url: string;
9
+ }
10
+
11
+ export interface FaqItem {
12
+ q: string;
13
+ a: string;
14
+ }
15
+
16
+ export function breadcrumbListSchema(items: BreadcrumbItem[]) {
17
+ return {
18
+ "@context": "https://schema.org",
19
+ "@type": "BreadcrumbList",
20
+ itemListElement: items.map((item, i) => ({
21
+ "@type": "ListItem",
22
+ position: i + 1,
23
+ name: item.name,
24
+ item: item.url,
25
+ })),
26
+ };
27
+ }
28
+
29
+ export function faqPageSchema(faqs: FaqItem[], id?: string) {
30
+ return {
31
+ "@context": "https://schema.org",
32
+ "@type": "FAQPage",
33
+ ...(id ? { "@id": id } : {}),
34
+ mainEntity: faqs.map((f) => ({
35
+ "@type": "Question",
36
+ name: f.q,
37
+ acceptedAnswer: {
38
+ "@type": "Answer",
39
+ text: f.a,
40
+ },
41
+ })),
42
+ };
43
+ }
44
+
45
+ export interface ArticleSchemaInput {
46
+ headline: string;
47
+ description: string;
48
+ url: string;
49
+ datePublished: string;
50
+ dateModified?: string;
51
+ author: string;
52
+ authorUrl?: string;
53
+ publisherName: string;
54
+ publisherUrl: string;
55
+ publisherLogo?: string;
56
+ image?: string;
57
+ articleType?: "Article" | "BlogPosting" | "TechArticle";
58
+ }
59
+
60
+ export function articleSchema({
61
+ headline,
62
+ description,
63
+ url,
64
+ datePublished,
65
+ dateModified,
66
+ author,
67
+ authorUrl,
68
+ publisherName,
69
+ publisherUrl,
70
+ publisherLogo,
71
+ image,
72
+ articleType = "Article",
73
+ }: ArticleSchemaInput) {
74
+ return {
75
+ "@context": "https://schema.org",
76
+ "@type": articleType,
77
+ headline,
78
+ description,
79
+ datePublished,
80
+ dateModified: dateModified || datePublished,
81
+ author: {
82
+ "@type": "Person",
83
+ name: author,
84
+ ...(authorUrl ? { url: authorUrl } : {}),
85
+ },
86
+ publisher: {
87
+ "@type": "Organization",
88
+ name: publisherName,
89
+ url: publisherUrl,
90
+ ...(publisherLogo ? { logo: publisherLogo } : {}),
91
+ },
92
+ url,
93
+ mainEntityOfPage: url,
94
+ ...(image ? { image } : {}),
95
+ };
96
+ }
97
+
98
+ export interface HowToStepInput {
99
+ name: string;
100
+ text: string;
101
+ }
102
+
103
+ export function howToSchema(name: string, description: string, steps: HowToStepInput[]) {
104
+ return {
105
+ "@context": "https://schema.org",
106
+ "@type": "HowTo",
107
+ name,
108
+ description,
109
+ step: steps.map((s, i) => ({
110
+ "@type": "HowToStep",
111
+ position: i + 1,
112
+ name: s.name,
113
+ text: s.text,
114
+ })),
115
+ };
116
+ }