@m13v/seo-components 0.3.0 → 0.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m13v/seo-components",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "39 animated React components for programmatic SEO pages. Remotion video, Magic UI style animations, trust signals, JSON-LD helpers. Teal/cyan brand, light-theme only.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -59,9 +59,9 @@ export function AnimatedBeam({
59
59
  >
60
60
  <defs>
61
61
  <linearGradient id="beam-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
62
- <stop offset="0%" stopColor="#14b8a6" stopOpacity="0" />
63
- <stop offset="50%" stopColor="#14b8a6" stopOpacity="1" />
64
- <stop offset="100%" stopColor="#14b8a6" stopOpacity="0" />
62
+ <stop offset="0%" stopColor="var(--seo-accent)" stopOpacity="0" />
63
+ <stop offset="50%" stopColor="var(--seo-accent)" stopOpacity="1" />
64
+ <stop offset="100%" stopColor="var(--seo-accent)" stopOpacity="0" />
65
65
  </linearGradient>
66
66
  <filter id="glow">
67
67
  <feGaussianBlur stdDeviation="3" result="blur" />
@@ -183,7 +183,7 @@ export function AnimatedBeam({
183
183
  width="144"
184
184
  height="72"
185
185
  rx="36"
186
- fill="#14b8a6"
186
+ fill="var(--seo-accent)"
187
187
  filter="url(#glow)"
188
188
  />
189
189
  <rect
@@ -27,7 +27,7 @@ export function ArticleMeta({
27
27
  }: ArticleMetaProps) {
28
28
  const showUpdated = dateModified && dateModified !== datePublished;
29
29
  return (
30
- <div className={`flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-zinc-500 ${className}`}>
30
+ <div className={`flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-zinc-500 max-w-4xl mx-auto px-6 ${className}`}>
31
31
  <div className="flex items-center gap-2">
32
32
  <div className="w-7 h-7 rounded-full bg-gradient-to-br from-teal-500/40 to-teal-500/10 flex items-center justify-center text-xs font-semibold text-white">
33
33
  {author.charAt(0)}
@@ -26,8 +26,8 @@ export function BackgroundGrid({
26
26
  }: BackgroundGridProps) {
27
27
  const bg =
28
28
  pattern === "dots"
29
- ? `radial-gradient(circle, rgba(20, 184, 166, 0.18) 1px, transparent 1px)`
30
- : `linear-gradient(rgba(20, 184, 166, 0.12) 1px, transparent 1px), linear-gradient(90deg, rgba(20, 184, 166, 0.12) 1px, transparent 1px)`;
29
+ ? `radial-gradient(circle, rgba(var(--seo-accent-rgb), 0.18) 1px, transparent 1px)`
30
+ : `linear-gradient(rgba(var(--seo-accent-rgb), 0.12) 1px, transparent 1px), linear-gradient(90deg, rgba(var(--seo-accent-rgb), 0.12) 1px, transparent 1px)`;
31
31
 
32
32
  return (
33
33
  <div
@@ -49,7 +49,7 @@ export function BackgroundGrid({
49
49
  className="absolute inset-0 pointer-events-none"
50
50
  style={{
51
51
  background:
52
- "radial-gradient(ellipse at center, rgba(20, 184, 166, 0.15) 0%, transparent 60%)",
52
+ "radial-gradient(ellipse at center, rgba(var(--seo-accent-rgb), 0.15) 0%, transparent 60%)",
53
53
  }}
54
54
  />
55
55
  )}
@@ -15,7 +15,7 @@ interface BreadcrumbsProps {
15
15
  export function Breadcrumbs({ items, className = "", align = "start" }: BreadcrumbsProps) {
16
16
  const justify = align === "center" ? "justify-center" : "justify-start";
17
17
  return (
18
- <nav aria-label="Breadcrumb" className={className}>
18
+ <nav aria-label="Breadcrumb" className={`max-w-4xl mx-auto px-6 ${className}`}>
19
19
  <ol className={`flex flex-wrap items-center ${justify} gap-2 text-sm text-zinc-500`}>
20
20
  {items.map((item, i) => {
21
21
  const isLast = i === items.length - 1;
@@ -12,7 +12,7 @@ export function FaqSection({
12
12
  className = "",
13
13
  }: FaqSectionProps) {
14
14
  return (
15
- <section className={className}>
15
+ <section className={`max-w-4xl mx-auto px-6 my-16 ${className}`}>
16
16
  <h2 className="text-3xl sm:text-4xl font-bold text-zinc-900 mb-8">{heading}</h2>
17
17
  <div className="space-y-4">
18
18
  {items.map((faq) => (
@@ -16,7 +16,7 @@ interface GlowCardProps {
16
16
  export function GlowCard({
17
17
  children,
18
18
  className = "",
19
- glowColor = "rgba(20, 184, 166, 0.15)",
19
+ glowColor = "rgba(var(--seo-accent-rgb), 0.15)",
20
20
  }: GlowCardProps) {
21
21
  const ref = useRef<HTMLDivElement>(null);
22
22
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
@@ -24,8 +24,8 @@ export function GradientText({
24
24
  }: GradientTextProps) {
25
25
  const gradient =
26
26
  variant === "rainbow"
27
- ? "linear-gradient(90deg, #06b6d4, #14b8a6, #10b981, #14b8a6, #06b6d4)"
28
- : "linear-gradient(90deg, #06b6d4, #14b8a6, #0d9488, #14b8a6, #06b6d4)";
27
+ ? "linear-gradient(90deg, var(--seo-accent-gradient-from), var(--seo-accent), #10b981, var(--seo-accent), var(--seo-accent-gradient-from))"
28
+ : "linear-gradient(90deg, var(--seo-accent-gradient-from), var(--seo-accent), var(--seo-accent-dark), var(--seo-accent), var(--seo-accent-gradient-from))";
29
29
 
30
30
  return (
31
31
  <motion.span
@@ -51,7 +51,8 @@ interface ParticlesProps {
51
51
  ease?: number;
52
52
  size?: number;
53
53
  refresh?: boolean;
54
- /** Hex color for particles. Defaults to teal (#14b8a6). */
54
+ /** Hex color for particles. Pass your brand accent color. Defaults to teal (#14b8a6).
55
+ * CSS custom properties are not supported here (canvas context). */
55
56
  color?: string;
56
57
  vx?: number;
57
58
  vy?: number;
@@ -15,9 +15,9 @@ export function ProofBand({
15
15
  const hasHalf = rating - fullStars >= 0.3;
16
16
 
17
17
  return (
18
- <div className={`text-sm text-zinc-500 mb-8 w-full ${className}`}>
18
+ <div className={`text-sm text-zinc-500 mb-8 max-w-4xl mx-auto px-6 ${className}`}>
19
19
  {/* Stars + rating on its own line */}
20
- <div className="flex items-center gap-2 mb-2 justify-center sm:justify-start">
20
+ <div className="flex items-center gap-2 mb-2">
21
21
  <div className="flex items-center gap-0.5" aria-label={`${rating} out of 5`}>
22
22
  {Array.from({ length: 5 }, (_, i) => (
23
23
  <svg
@@ -43,7 +43,7 @@ export function ProofBand({
43
43
 
44
44
  {/* Highlights as a clean vertical list */}
45
45
  {highlights.length > 0 && (
46
- <div className="flex flex-col gap-1 items-center sm:items-start">
46
+ <div className="flex flex-col gap-1">
47
47
  {highlights.map((h, i) => (
48
48
  <div key={i} className="flex items-center gap-2">
49
49
  <svg
@@ -14,6 +14,10 @@ interface ConceptRevealProps {
14
14
  subtitle?: string;
15
15
  captions: string[];
16
16
  accent?: "teal" | "cyan";
17
+ /** Override hex color for the primary gradient stop. If set, takes precedence over `accent`. */
18
+ accentHex?: string;
19
+ /** Override hex color for the secondary (dark) gradient stop. If set, takes precedence over `accent`. */
20
+ accentHexDark?: string;
17
21
  }
18
22
 
19
23
  export function ConceptReveal({
@@ -21,6 +25,8 @@ export function ConceptReveal({
21
25
  subtitle,
22
26
  captions,
23
27
  accent = "teal",
28
+ accentHex,
29
+ accentHexDark,
24
30
  }: ConceptRevealProps) {
25
31
  const frame = useCurrentFrame();
26
32
  const { fps, durationInFrames } = useVideoConfig();
@@ -42,8 +48,8 @@ export function ConceptReveal({
42
48
  extrapolateRight: "clamp",
43
49
  });
44
50
 
45
- const bgA = accent === "teal" ? "#14b8a6" : "#06b6d4";
46
- const bgB = accent === "teal" ? "#0d9488" : "#0891b2";
51
+ const bgA = accentHex ?? (accent === "teal" ? "#14b8a6" : "#06b6d4");
52
+ const bgB = accentHexDark ?? (accent === "teal" ? "#0d9488" : "#0891b2");
47
53
 
48
54
  return (
49
55
  <AbsoluteFill
@@ -166,6 +172,10 @@ interface RemotionClipProps {
166
172
  subtitle?: string;
167
173
  captions: string[];
168
174
  accent?: "teal" | "cyan";
175
+ /** Override hex color for the primary gradient stop. If set, takes precedence over `accent`. */
176
+ accentHex?: string;
177
+ /** Override hex color for the secondary (dark) gradient stop. If set, takes precedence over `accent`. */
178
+ accentHexDark?: string;
169
179
  durationInFrames?: number;
170
180
  fps?: number;
171
181
  width?: number;
@@ -186,6 +196,8 @@ export function RemotionClip({
186
196
  subtitle,
187
197
  captions,
188
198
  accent = "teal",
199
+ accentHex,
200
+ accentHexDark,
189
201
  durationInFrames = 150,
190
202
  fps = 30,
191
203
  width = 1280,
@@ -209,7 +221,7 @@ export function RemotionClip({
209
221
  loop={loop}
210
222
  controls
211
223
  style={{ width: "100%", height: "100%" }}
212
- inputProps={{ title, subtitle, captions, accent }}
224
+ inputProps={{ title, subtitle, captions, accent, accentHex, accentHexDark }}
213
225
  />
214
226
  </div>
215
227
  );
@@ -30,7 +30,8 @@ interface ShineBorderProps {
30
30
  borderRadius?: number;
31
31
  borderWidth?: number;
32
32
  duration?: number;
33
- /** Hex color(s) for the shine gradient. Defaults to teal. */
33
+ /** Hex color(s) for the shine gradient. Pass your brand accent color. Defaults to teal.
34
+ * CSS custom properties are not supported here (conic gradient border trick). */
34
35
  color?: TColorProp;
35
36
  className?: string;
36
37
  children: React.ReactNode;
package/src/styles.css CHANGED
@@ -1,3 +1,24 @@
1
1
  /* @m13v/seo-components: import this file in your globals.css to scan component classes */
2
2
  /* Usage: @import "@seo/components/src/styles.css"; */
3
3
  @source ".";
4
+
5
+ /*
6
+ * Accent color tokens. Override in your globals.css to match your brand.
7
+ * Example for amber brand:
8
+ * :root {
9
+ * --seo-accent: #f59e0b;
10
+ * --seo-accent-light: #fbbf24;
11
+ * --seo-accent-dark: #d97706;
12
+ * --seo-accent-rgb: 245, 158, 11;
13
+ * --seo-accent-gradient-from: #f59e0b;
14
+ * --seo-accent-gradient-to: #d97706;
15
+ * }
16
+ */
17
+ :root {
18
+ --seo-accent: #14b8a6;
19
+ --seo-accent-light: #2dd4bf;
20
+ --seo-accent-dark: #0d9488;
21
+ --seo-accent-rgb: 20, 184, 166;
22
+ --seo-accent-gradient-from: #06b6d4;
23
+ --seo-accent-gradient-to: #14b8a6;
24
+ }