@nimblegiant/stilts 0.2.0-alpha.1 → 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.1",
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",
@@ -32,7 +32,7 @@
32
32
  "package.json"
33
33
  ],
34
34
  "scripts": {
35
- "build": "tsup && cp -r src/styles dist/styles",
35
+ "build": "tsup && mkdir -p dist/styles && cp src/styles/*.css dist/styles/",
36
36
  "dev": "tsup --watch",
37
37
  "typecheck": "tsc --noEmit",
38
38
  "lint": "eslint src --ext .ts,.tsx",
@@ -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,13 +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: "/contact", label: "CONTACT" },
72
- ];
73
-
74
76
  const isActive = (href: string) => {
75
77
  if (href === "/") return currentPath === "/";
76
78
  return currentPath.startsWith(href);
@@ -90,7 +92,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
90
92
  role="navigation"
91
93
  aria-label="Main navigation"
92
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 ${
93
- 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"
94
98
  } `}
95
99
  >
96
100
  <div className="container-xl container flex h-20 items-center justify-between sm:p-6 lg:p-14">
@@ -102,7 +106,11 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
102
106
  >
103
107
  {/* Light mode logo (dark logo on light bg) */}
104
108
  <img
105
- 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
+ }
106
114
  alt=""
107
115
  aria-hidden="true"
108
116
  className="h-10 w-auto"
@@ -173,7 +181,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
173
181
  ? "text-white/70 hover:text-white"
174
182
  : "text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]"
175
183
  } `}
176
- aria-label={isMenuOpen ? "Close navigation menu" : "Open navigation menu"}
184
+ aria-label={
185
+ isMenuOpen ? "Close navigation menu" : "Open navigation menu"
186
+ }
177
187
  aria-expanded={isMenuOpen}
178
188
  aria-controls="mobile-menu"
179
189
  >
@@ -193,7 +203,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
193
203
  aria-modal="true"
194
204
  aria-label="Navigation menu"
195
205
  className={`fixed inset-0 z-[calc(var(--z-nav)-1)] transition-all duration-300 ease-out md:hidden ${
196
- 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"
197
209
  } `}
198
210
  >
199
211
  {/* Backdrop */}
@@ -215,9 +227,15 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
215
227
  <li
216
228
  key={link.href}
217
229
  className={`transition-all duration-300 ${
218
- 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"
219
233
  } `}
220
- style={{ transitionDelay: isMenuOpen ? `${index * 50 + 100}ms` : "0ms" }}
234
+ style={{
235
+ transitionDelay: isMenuOpen
236
+ ? `${index * 50 + 100}ms`
237
+ : "0ms",
238
+ }}
221
239
  >
222
240
  <a
223
241
  href={link.href}
@@ -232,7 +250,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
232
250
  {link.label}
233
251
  <ArrowRight
234
252
  className={`h-4 w-4 transition-all duration-200 ${
235
- 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"
236
256
  } `}
237
257
  aria-hidden="true"
238
258
  />
@@ -245,7 +265,9 @@ export default function MainNav({ currentPath, isPortfolioPage = false }: MainNa
245
265
  {/* Footer section with theme toggle */}
246
266
  <div
247
267
  className={`border-t border-[var(--color-border)] pt-6 transition-all duration-300 ${
248
- 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"
249
271
  } `}
250
272
  style={{ transitionDelay: isMenuOpen ? "300ms" : "0ms" }}
251
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";
File without changes
File without changes
File without changes