@nimblegiant/stilts 0.2.0-alpha.3 → 0.2.0-alpha.4

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": "@nimblegiant/stilts",
3
- "version": "0.2.0-alpha.3",
3
+ "version": "0.2.0-alpha.4",
4
4
  "description": "Nimble Giant's whimsical yet disciplined design system",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -82,4 +82,4 @@
82
82
  "access": "public",
83
83
  "registry": "https://registry.npmjs.org"
84
84
  }
85
- }
85
+ }
@@ -4,13 +4,22 @@ import { Sun, Moon, Menu, X, ArrowRight } from "lucide-react";
4
4
  interface MainNavProps {
5
5
  currentPath: string;
6
6
  isPortfolioPage?: boolean;
7
+ navLinks?: { href: string; label: string }[];
7
8
  }
8
9
 
9
- export default function MainNav({ currentPath, isPortfolioPage = false }: MainNavProps) {
10
+ export default function MainNav({
11
+ currentPath,
12
+ isPortfolioPage = false,
13
+ navLinks = [],
14
+ }: MainNavProps) {
10
15
  const [theme, setTheme] = useState<"light" | "dark">(() => {
11
16
  // Initialize theme from document during first render (client-side only)
12
17
  if (typeof document !== "undefined") {
13
- return (document.documentElement.getAttribute("data-theme") as "light" | "dark") || "light";
18
+ return (
19
+ (document.documentElement.getAttribute("data-theme") as
20
+ | "light"
21
+ | "dark") || "light"
22
+ );
14
23
  }
15
24
  return "light";
16
25
  });
@@ -64,14 +73,6 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
64
73
  localStorage.setItem("theme", newTheme);
65
74
  }, [theme]);
66
75
 
67
- const navLinks = [
68
- { href: "/", label: "HOME" },
69
- { href: "/about", label: "ABOUT" },
70
- { href: "/services", label: "SERVICES" },
71
- { href: "/products", label: "PRODUCTS" },
72
- { href: "/contact", label: "CONTACT" },
73
- ];
74
-
75
76
  const isActive = (href: string) => {
76
77
  if (href === "/") return currentPath === "/";
77
78
  return currentPath.startsWith(href);
@@ -91,7 +92,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
91
92
  role="navigation"
92
93
  aria-label="Main navigation"
93
94
  className={`fixed top-0 right-0 left-0 z-[var(--z-nav)] border-b border-[var(--color-border)]/0 transition-all duration-300 ease-out ${
94
- isScrolled ? "bg-[var(--color-surface-overlay)]/0 backdrop-blur-sm" : "bg-transparent"
95
+ isScrolled
96
+ ? "bg-[var(--color-surface-overlay)]/0 backdrop-blur-sm"
97
+ : "bg-transparent"
95
98
  } `}
96
99
  >
97
100
  <div className="container-xl container flex h-20 items-center justify-between sm:p-6 lg:p-14">
@@ -103,7 +106,11 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
103
106
  >
104
107
  {/* Light mode logo (dark logo on light bg) */}
105
108
  <img
106
- src={showLightText ? "/images/logo-light.svg" : "/images/logo-dark.svg"}
109
+ src={
110
+ showLightText
111
+ ? "/images/logo-light.svg"
112
+ : "/images/logo-dark.svg"
113
+ }
107
114
  alt=""
108
115
  aria-hidden="true"
109
116
  className="h-10 w-auto"
@@ -174,7 +181,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
174
181
  ? "text-white/70 hover:text-white"
175
182
  : "text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]"
176
183
  } `}
177
- aria-label={isMenuOpen ? "Close navigation menu" : "Open navigation menu"}
184
+ aria-label={
185
+ isMenuOpen ? "Close navigation menu" : "Open navigation menu"
186
+ }
178
187
  aria-expanded={isMenuOpen}
179
188
  aria-controls="mobile-menu"
180
189
  >
@@ -194,7 +203,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
194
203
  aria-modal="true"
195
204
  aria-label="Navigation menu"
196
205
  className={`fixed inset-0 z-[calc(var(--z-nav)-1)] transition-all duration-300 ease-out md:hidden ${
197
- isMenuOpen ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
206
+ isMenuOpen
207
+ ? "pointer-events-auto opacity-100"
208
+ : "pointer-events-none opacity-0"
198
209
  } `}
199
210
  >
200
211
  {/* Backdrop */}
@@ -216,9 +227,15 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
216
227
  <li
217
228
  key={link.href}
218
229
  className={`transition-all duration-300 ${
219
- isMenuOpen ? "translate-x-0 opacity-100" : "translate-x-8 opacity-0"
230
+ isMenuOpen
231
+ ? "translate-x-0 opacity-100"
232
+ : "translate-x-8 opacity-0"
220
233
  } `}
221
- style={{ transitionDelay: isMenuOpen ? `${index * 50 + 100}ms` : "0ms" }}
234
+ style={{
235
+ transitionDelay: isMenuOpen
236
+ ? `${index * 50 + 100}ms`
237
+ : "0ms",
238
+ }}
222
239
  >
223
240
  <a
224
241
  href={link.href}
@@ -233,7 +250,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
233
250
  {link.label}
234
251
  <ArrowRight
235
252
  className={`h-4 w-4 transition-all duration-200 ${
236
- isActive(link.href) ? "opacity-50" : "opacity-0 group-hover:opacity-30"
253
+ isActive(link.href)
254
+ ? "opacity-50"
255
+ : "opacity-0 group-hover:opacity-30"
237
256
  } `}
238
257
  aria-hidden="true"
239
258
  />
@@ -246,7 +265,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
246
265
  {/* Footer section with theme toggle */}
247
266
  <div
248
267
  className={`border-t border-[var(--color-border)] pt-6 transition-all duration-300 ${
249
- isMenuOpen ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"
268
+ isMenuOpen
269
+ ? "translate-y-0 opacity-100"
270
+ : "translate-y-4 opacity-0"
250
271
  } `}
251
272
  style={{ transitionDelay: isMenuOpen ? "300ms" : "0ms" }}
252
273
  >
@@ -10,26 +10,35 @@ interface ContentPageProps {
10
10
  children: React.ReactNode;
11
11
  }
12
12
 
13
- export default function ContentPage({ title, subtitle, lastUpdated, children }: ContentPageProps) {
13
+ export default function ContentPage({
14
+ title,
15
+ subtitle,
16
+ lastUpdated,
17
+ children,
18
+ }: ContentPageProps) {
14
19
  return (
15
20
  <>
16
21
  <section className="pt-32 pb-12">
17
- <div className="container-xl container">
22
+ <div className="container-md container">
18
23
  <h1 className="mb-4 text-4xl font-bold text-[var(--color-text-primary)] md:text-5xl">
19
24
  {title}
20
25
  </h1>
21
26
  {subtitle && (
22
- <h2 className="mb-2 text-xl text-[var(--color-text-secondary)]">{subtitle}</h2>
27
+ <h2 className="mb-2 text-xl text-[var(--color-text-secondary)]">
28
+ {subtitle}
29
+ </h2>
23
30
  )}
24
31
  {lastUpdated && (
25
- <p className="text-[var(--color-text-muted)]">Last updated: {lastUpdated}</p>
32
+ <p className="text-[var(--color-text-muted)]">
33
+ Last updated: {lastUpdated}
34
+ </p>
26
35
  )}
27
36
  </div>
28
37
  </section>
29
38
 
30
39
  <section className="py-12">
31
- <div className="container-xl container">
32
- <div className="prose prose-lg lg:max-w-3/4">{children}</div>
40
+ <div className="container-md container">
41
+ <div className="prose max-w-full">{children}</div>
33
42
  </div>
34
43
  </section>
35
44
  </>
@@ -0,0 +1,134 @@
1
+ import { ReactNode } from "react";
2
+ import { AuthorMeta, Author } from "../ui/AuthorMeta";
3
+
4
+ export interface ListCardProps {
5
+ href: string;
6
+ title: string;
7
+ description: string;
8
+ tagline?: string;
9
+ thumbnail?: string;
10
+ tags?: string[];
11
+ meta?: {
12
+ date?: string;
13
+ author?: Author;
14
+ };
15
+ imageAlt?: string;
16
+ animationDelay?: number;
17
+ specialImageComponent?: boolean;
18
+ imageBgClass?: string;
19
+ imageDark?: string;
20
+ imageLight?: string;
21
+ children?: ReactNode;
22
+ footerSlot?: ReactNode;
23
+ }
24
+
25
+ /**
26
+ * ListCard Component
27
+ * Displays a card with optional image, title, description, meta, and tags
28
+ * Used for products, blog posts, and other list items
29
+ */
30
+ export function ListCard({
31
+ href,
32
+ title,
33
+ description,
34
+ tagline,
35
+ thumbnail,
36
+ tags,
37
+ meta,
38
+ imageAlt = "Card image",
39
+ animationDelay,
40
+ specialImageComponent = false,
41
+ imageBgClass = "bg-[var(--color-surface-muted)]",
42
+ children,
43
+ imageDark,
44
+ imageLight,
45
+ footerSlot,
46
+ }: ListCardProps) {
47
+ const animationStyle = animationDelay
48
+ ? { animationDelay: `${animationDelay}s` }
49
+ : undefined;
50
+
51
+ return (
52
+ <a
53
+ href={href}
54
+ className="hero-animate group flex flex-col overflow-hidden rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] opacity-0 shadow-sm transition-all duration-300 hover:border-[var(--color-primary)] hover:shadow-lg"
55
+ style={animationStyle}
56
+ >
57
+ {/* Image/Thumbnail */}
58
+ {thumbnail && !specialImageComponent && (
59
+ <div
60
+ className={`relative aspect-video w-full overflow-hidden rounded-t-lg ${imageBgClass}`}
61
+ >
62
+ <img
63
+ src={thumbnail}
64
+ alt={imageAlt}
65
+ className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
66
+ />
67
+ </div>
68
+ )}
69
+
70
+ {/* Special image component slot */}
71
+ <div className="relative flex aspect-video w-full items-center justify-center overflow-hidden rounded-t-lg bg-gradient-to-br from-[var(--color-dot-magenta)] via-[var(--color-dot-purple)] to-[var(--color-dot-turquoise)] p-12">
72
+ <img
73
+ src={imageDark}
74
+ alt={title}
75
+ className="dark-mode-hidden h-full w-full object-contain transition-transform duration-300 group-hover:scale-105"
76
+ />
77
+ <img
78
+ src={imageLight}
79
+ alt={title}
80
+ className="dark-mode-visible h-full w-full object-contain transition-transform duration-300 group-hover:scale-105"
81
+ />
82
+ </div>
83
+
84
+ {/* Content */}
85
+ <div className="flex flex-1 flex-col p-6">
86
+ {/* Meta (optional - for date, author) */}
87
+ {meta && (
88
+ <div className="mb-2 flex items-center gap-2 text-xs text-[var(--color-text-muted)]">
89
+ {meta.author && <AuthorMeta author={meta.author} />}
90
+ {meta.date && meta.author && <span>•</span>}
91
+ {meta.date && <span>{meta.date}</span>}
92
+ </div>
93
+ )}
94
+
95
+ {/* Title */}
96
+ <h3 className="font-heading mb-2 text-xl font-semibold text-[var(--color-text-primary)]">
97
+ {title}
98
+ </h3>
99
+ {tagline && (
100
+ <p className="mb-3 text-sm font-medium text-[var(--color-primary)]">
101
+ {tagline}
102
+ </p>
103
+ )}
104
+
105
+ {/* Description */}
106
+ <p className="mb-4 flex-1 text-sm leading-relaxed text-[var(--color-text-secondary)]">
107
+ {description}
108
+ </p>
109
+
110
+ {/* Tags */}
111
+ {tags && tags.length > 0 && (
112
+ <div className="mt-auto flex flex-wrap gap-2 pt-4">
113
+ {tags.map((tag) => (
114
+ <span
115
+ key={tag}
116
+ className="rounded-full border border-[var(--color-border)] bg-[var(--color-surface-muted)] px-3 py-1 text-xs font-medium text-[var(--color-text-secondary)]"
117
+ >
118
+ {tag}
119
+ </span>
120
+ ))}
121
+ </div>
122
+ )}
123
+
124
+ {/* Custom content slot */}
125
+ {children}
126
+ </div>
127
+
128
+ {/* Footer slot */}
129
+ {footerSlot}
130
+ </a>
131
+ );
132
+ }
133
+
134
+ export default ListCard;
@@ -2,23 +2,29 @@
2
2
  * Pattern Components
3
3
  */
4
4
 
5
- export { default as PageHero } from './PageHero';
6
- export { default as VideoHero } from './VideoHero';
7
- export { default as TeamGrid } from './TeamGrid';
8
- export { default as ServiceSection } from './ServiceSection';
9
- export { default as ProcessSteps } from './ProcessSteps';
10
- export { default as StatBar } from './StatBar';
11
- export { default as IndustryGrid } from './IndustryGrid';
12
- export { default as ContentPage } from './ContentPage';
13
- export { TerminalWindow, TerminalChrome } from './TerminalWindow';
14
- export type { TerminalLine, TerminalWindowProps, TerminalChromeProps } from './TerminalWindow';
15
- export { default as TerminalHero } from './TerminalHero';
16
- export type { TerminalHeroProps } from './TerminalHero';
17
- export { CTAButton } from './CTAButton';
18
- export type { CTAButtonProps } from './CTAButton';
19
- export { ProductCard } from './ProductCard';
20
- export type { ProductCardProps } from './ProductCard';
21
- export { CodeBlock } from './CodeBlock';
22
- export type { CodeBlockProps } from './CodeBlock';
23
- export { FeatureShowcase } from './FeatureShowcase';
24
- export type { FeatureShowcaseProps, Feature } from './FeatureShowcase';
5
+ export { default as PageHero } from "./PageHero";
6
+ export { default as VideoHero } from "./VideoHero";
7
+ export { default as TeamGrid } from "./TeamGrid";
8
+ export { default as ServiceSection } from "./ServiceSection";
9
+ export { default as ProcessSteps } from "./ProcessSteps";
10
+ export { default as StatBar } from "./StatBar";
11
+ export { default as IndustryGrid } from "./IndustryGrid";
12
+ export { default as ContentPage } from "./ContentPage";
13
+ export { TerminalWindow, TerminalChrome } from "./TerminalWindow";
14
+ export type {
15
+ TerminalLine,
16
+ TerminalWindowProps,
17
+ TerminalChromeProps,
18
+ } from "./TerminalWindow";
19
+ export { default as TerminalHero } from "./TerminalHero";
20
+ export type { TerminalHeroProps } from "./TerminalHero";
21
+ export { CTAButton } from "./CTAButton";
22
+ export type { CTAButtonProps } from "./CTAButton";
23
+ export { ProductCard } from "./ProductCard";
24
+ export type { ProductCardProps } from "./ProductCard";
25
+ export { CodeBlock } from "./CodeBlock";
26
+ export type { CodeBlockProps } from "./CodeBlock";
27
+ export { FeatureShowcase } from "./FeatureShowcase";
28
+ export type { FeatureShowcaseProps, Feature } from "./FeatureShowcase";
29
+ export { ListCard } from "./ListCard";
30
+ export type { ListCardProps } from "./ListCard";
@@ -0,0 +1,44 @@
1
+ export interface Author {
2
+ name: string;
3
+ url?: string;
4
+ avatar?: string;
5
+ }
6
+
7
+ export interface AuthorMetaProps {
8
+ author: Author;
9
+ linkable?: boolean;
10
+ }
11
+
12
+ /**
13
+ * AuthorMeta Component
14
+ * Displays author information with optional avatar
15
+ */
16
+ export function AuthorMeta({ author, linkable = false }: AuthorMetaProps) {
17
+ const content = (
18
+ <span className="flex items-center gap-1">
19
+ {author.avatar && (
20
+ <img
21
+ src={author.avatar}
22
+ alt={author.name}
23
+ className="h-4 w-4 rounded-full object-cover"
24
+ />
25
+ )}
26
+ <span>{author.name}</span>
27
+ </span>
28
+ );
29
+
30
+ if (linkable && author.url) {
31
+ return (
32
+ <a
33
+ href={author.url}
34
+ className="flex items-center gap-1 transition-colors hover:text-[var(--color-primary)]"
35
+ >
36
+ {content}
37
+ </a>
38
+ );
39
+ }
40
+
41
+ return content;
42
+ }
43
+
44
+ export default AuthorMeta;
@@ -2,4 +2,6 @@
2
2
  * UI Components
3
3
  */
4
4
 
5
- export { default as Toast } from './Toast';
5
+ export { default as Toast } from "./Toast";
6
+ export { AuthorMeta } from "./AuthorMeta";
7
+ export type { AuthorMetaProps, Author } from "./AuthorMeta";