@n3wth/ui 0.4.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.
@@ -1 +1 @@
1
- {"version":3,"file":"NavLink.d.ts","sourceRoot":"","sources":["../../../src/molecules/NavLink/NavLink.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,SAAS,EAAc,MAAM,OAAO,CAAA;AAG7E,MAAM,WAAW,YAAa,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAC3E,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,MAAM,CAAA;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,eAAO,MAAM,OAAO,4GAoCnB,CAAA"}
1
+ {"version":3,"file":"NavLink.d.ts","sourceRoot":"","sources":["../../../src/molecules/NavLink/NavLink.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,SAAS,EAAc,MAAM,OAAO,CAAA;AAG7E,MAAM,WAAW,YAAa,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAC3E,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,MAAM,CAAA;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,eAAO,MAAM,OAAO,4GAuCnB,CAAA"}
@@ -13,6 +13,7 @@ export interface NavProps extends HTMLAttributes<HTMLElement> {
13
13
  onThemeToggle?: () => void;
14
14
  showThemeToggle?: boolean;
15
15
  fixed?: boolean;
16
+ hideOnScroll?: boolean;
16
17
  }
17
- export declare function Nav({ logo, logoHref, items, theme, onThemeToggle, showThemeToggle, fixed, className, ...props }: NavProps): import("react/jsx-runtime").JSX.Element;
18
+ export declare function Nav({ logo, logoHref, items, theme, onThemeToggle, showThemeToggle, fixed, hideOnScroll, className, ...props }: NavProps): import("react/jsx-runtime").JSX.Element;
18
19
  //# sourceMappingURL=Nav.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Nav.d.ts","sourceRoot":"","sources":["../../../src/organisms/Nav/Nav.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAAyB,MAAM,OAAO,CAAA;AAMlF,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,QAAS,SAAQ,cAAc,CAAC,WAAW,CAAC;IAC3D,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,wBAAgB,GAAG,CAAC,EAClB,IAAI,EACJ,QAAc,EACd,KAAU,EACV,KAAc,EACd,aAAa,EACb,eAAsB,EACtB,KAAa,EACb,SAAS,EACT,GAAG,KAAK,EACT,EAAE,QAAQ,2CAyJV"}
1
+ {"version":3,"file":"Nav.d.ts","sourceRoot":"","sources":["../../../src/organisms/Nav/Nav.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAA4C,MAAM,OAAO,CAAA;AAMrG,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,QAAS,SAAQ,cAAc,CAAC,WAAW,CAAC;IAC3D,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,wBAAgB,GAAG,CAAC,EAClB,IAAI,EACJ,QAAc,EACd,KAAU,EACV,KAAc,EACd,aAAa,EACb,eAAsB,EACtB,KAAa,EACb,YAAoB,EACpB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,QAAQ,2CAoLV"}
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "button",
4
+ "dependencies": [
5
+ "lucide-react"
6
+ ],
7
+ "files": [
8
+ {
9
+ "path": "src/atoms/Button/Button.tsx",
10
+ "content": "import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactNode,\n cloneElement,\n isValidElement,\n} from 'react'\nimport { cn } from '../../utils/cn'\n\nexport type ButtonSize = 'sm' | 'md' | 'lg'\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'primary' | 'secondary' | 'ghost' | 'glass'\n /** Size of the button. Can be a single value or responsive object */\n size?: ButtonSize | { base?: ButtonSize; md?: ButtonSize; lg?: ButtonSize }\n children: ReactNode\n isLoading?: boolean\n leftIcon?: ReactNode\n rightIcon?: ReactNode\n asChild?: boolean\n /** Ensures minimum 44px touch target for accessibility (WCAG 2.5.5) */\n touchTarget?: boolean\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n variant = 'primary',\n size = 'md',\n children,\n isLoading = false,\n leftIcon,\n rightIcon,\n className,\n disabled,\n asChild = false,\n touchTarget = false,\n ...props\n },\n ref\n ) => {\n const baseStyles = [\n 'inline-flex items-center justify-center gap-2',\n 'font-medium',\n 'border',\n 'rounded-full',\n 'transition-[transform,background-color,border-color,color,opacity] duration-200 ease-out',\n 'focus-ring',\n 'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none',\n // Active state for touch feedback\n 'active:scale-[0.96]',\n ]\n\n const variants = {\n primary: [\n 'bg-[var(--color-white)] text-[var(--color-bg)]',\n 'border-[var(--color-white)]',\n 'hover:scale-[1.02]',\n 'glow-white',\n ],\n secondary: [\n 'bg-transparent text-[var(--color-white)]',\n 'border-[var(--glass-border)]',\n 'hover:border-[var(--glass-highlight)] hover:bg-[var(--glass-bg)]',\n ],\n ghost: [\n 'bg-transparent text-[var(--color-grey-400)]',\n 'border-transparent',\n 'hover:text-[var(--color-white)] hover:bg-[var(--glass-bg)]',\n ],\n glass: [\n 'bg-[var(--glass-bg)] text-[var(--color-white)]',\n 'border-[var(--glass-border)]',\n 'backdrop-blur-lg',\n 'hover:bg-[rgba(255,255,255,0.1)] hover:border-[var(--glass-highlight)]',\n 'hover:scale-[1.02]',\n ],\n }\n\n const sizes = {\n sm: 'px-3 py-1.5 text-xs',\n md: 'px-4 py-2 text-sm',\n lg: 'px-6 py-3 text-base',\n }\n\n // Handle responsive size prop\n const getSizeClasses = () => {\n if (typeof size === 'string') {\n return sizes[size]\n }\n\n // Responsive size object\n const classes: string[] = []\n if (size.base) classes.push(sizes[size.base])\n if (size.md) classes.push(`md:${sizes[size.md].split(' ').join(' md:')}`)\n if (size.lg) classes.push(`lg:${sizes[size.lg].split(' ').join(' lg:')}`)\n\n // If no base specified, default to sm for mobile-first\n if (!size.base && (size.md || size.lg)) {\n classes.unshift(sizes.sm)\n }\n\n return classes.join(' ')\n }\n\n const touchTargetStyles = touchTarget ? 'min-w-[44px] min-h-[44px]' : ''\n\n const buttonClassName = cn(\n baseStyles,\n variants[variant],\n getSizeClasses(),\n touchTargetStyles,\n className\n )\n\n type ChildProps = { children?: ReactNode; className?: string }\n const childElement = isValidElement<ChildProps>(children) ? children : null\n\n const content = (\n <>\n {isLoading ? (\n <span className=\"w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin\" />\n ) : (\n leftIcon\n )}\n {asChild && childElement ? childElement.props.children : children}\n {!isLoading && rightIcon}\n </>\n )\n\n if (asChild && childElement) {\n return cloneElement(childElement, {\n className: cn(buttonClassName, childElement.props.className),\n ref,\n children: content,\n } as ChildProps)\n }\n\n return (\n <button\n ref={ref}\n className={buttonClassName}\n disabled={disabled || isLoading}\n {...props}\n >\n {content}\n </button>\n )\n }\n)\n\nButton.displayName = 'Button'\n",
11
+ "type": "registry:ui"
12
+ }
13
+ ],
14
+ "type": "registry:ui"
15
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "card",
4
+ "files": [
5
+ {
6
+ "path": "src/molecules/Card/Card.tsx",
7
+ "content": "import { type HTMLAttributes, type ReactNode } from 'react'\nimport { cn } from '../../utils/cn'\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n variant?: 'default' | 'glass' | 'interactive'\n padding?: 'none' | 'sm' | 'md' | 'lg'\n children: ReactNode\n}\n\nexport function Card({\n variant = 'default',\n padding = 'md',\n children,\n className,\n ...props\n}: CardProps) {\n const baseStyles = [\n 'rounded-2xl',\n 'border',\n 'transition-[background-color,border-color] duration-300 ease-out',\n ]\n\n const variants = {\n default: [\n 'bg-transparent',\n 'border-[var(--glass-border)]',\n ],\n glass: [\n 'bg-[var(--glass-bg)]',\n 'backdrop-blur-lg',\n 'border-[var(--glass-border)]',\n ],\n interactive: [\n 'bg-transparent',\n 'border-[var(--glass-border)]',\n 'hover:border-[var(--glass-highlight)]',\n 'hover:bg-[var(--glass-bg)]',\n 'cursor-pointer',\n 'gradient-border shine-sweep',\n ],\n }\n\n const paddings = {\n none: '',\n sm: 'p-3',\n md: 'p-5',\n lg: 'p-8',\n }\n\n return (\n <div\n className={cn(baseStyles, variants[variant], paddings[padding], className)}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nexport interface CardHeaderProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardHeader({ children, className, ...props }: CardHeaderProps) {\n return (\n <div className={cn('flex flex-col gap-1.5', className)} {...props}>\n {children}\n </div>\n )\n}\n\nexport interface CardTitleProps extends HTMLAttributes<HTMLHeadingElement> {\n children: ReactNode\n as?: 'h1' | 'h2' | 'h3' | 'h4'\n}\n\nexport function CardTitle({ children, as: Tag = 'h3', className, ...props }: CardTitleProps) {\n return (\n <Tag\n className={cn(\n 'font-display text-base font-semibold text-[var(--color-white)]',\n 'tracking-tight',\n className\n )}\n {...props}\n >\n {children}\n </Tag>\n )\n}\n\nexport interface CardDescriptionProps extends HTMLAttributes<HTMLParagraphElement> {\n children: ReactNode\n}\n\nexport function CardDescription({ children, className, ...props }: CardDescriptionProps) {\n return (\n <p\n className={cn(\n 'text-sm text-[var(--color-grey-400)]',\n 'line-clamp-2',\n className\n )}\n {...props}\n >\n {children}\n </p>\n )\n}\n\nexport interface CardContentProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardContent({ children, className, ...props }: CardContentProps) {\n return (\n <div className={cn('mt-4', className)} {...props}>\n {children}\n </div>\n )\n}\n\nexport interface CardFooterProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardFooter({ children, className, ...props }: CardFooterProps) {\n return (\n <div\n className={cn(\n 'mt-4 pt-4',\n 'border-t border-[var(--glass-border)]',\n 'flex items-center gap-3',\n className\n )}\n {...props}\n >\n {children}\n </div>\n )\n}\n",
8
+ "type": "registry:ui"
9
+ }
10
+ ],
11
+ "type": "registry:ui"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "hero",
4
+ "files": [
5
+ {
6
+ "path": "src/organisms/Hero/Hero.tsx",
7
+ "content": "import { type HTMLAttributes, type ReactNode } from 'react'\nimport { cn } from '../../utils/cn'\nimport { Badge } from '../../atoms/Badge'\nimport { Button } from '../../atoms/Button'\n\nexport interface HeroCTA {\n label: string\n href: string\n variant?: 'primary' | 'secondary' | 'ghost'\n}\n\nexport interface HeroProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n badge?: string\n title: ReactNode\n description?: ReactNode\n ctas?: HeroCTA[]\n align?: 'left' | 'center'\n size?: 'default' | 'large'\n gradient?: boolean\n}\n\nexport function Hero({\n badge,\n title,\n description,\n ctas = [],\n align = 'center',\n size = 'default',\n gradient = true,\n className,\n ...props\n}: HeroProps) {\n const alignments = {\n left: 'text-left items-start',\n center: 'text-center items-center',\n }\n\n const titleSizes = {\n default: 'text-4xl sm:text-5xl md:text-6xl',\n large: 'text-5xl sm:text-6xl md:text-7xl lg:text-8xl',\n }\n\n return (\n <section\n className={cn(\n 'relative',\n 'px-6 py-24 md:py-32 lg:py-40',\n className\n )}\n {...props}\n >\n <div\n className={cn(\n 'mx-auto max-w-4xl',\n 'flex flex-col gap-6',\n alignments[align]\n )}\n >\n {badge && (\n <Badge variant=\"default\" size=\"md\" className=\"animate-in\">\n {badge}\n </Badge>\n )}\n\n <h1\n className={cn(\n 'font-display font-semibold tracking-tight',\n titleSizes[size],\n gradient ? 'hero-gradient-text' : 'text-[var(--color-white)]',\n 'animate-in'\n )}\n style={{ animationDelay: '0.1s', textAlign: align === 'center' ? 'center' : 'left', width: '100%', display: 'block' }}\n >\n {title}\n </h1>\n\n {description && (\n <p\n className={cn(\n 'text-lg md:text-xl',\n 'text-[var(--color-grey-400)]',\n 'max-w-2xl leading-relaxed',\n 'animate-in'\n )}\n style={{ animationDelay: '0.2s' }}\n >\n {description}\n </p>\n )}\n\n {ctas.length > 0 && (\n <div\n className={cn(\n 'flex flex-wrap gap-4 mt-4',\n align === 'center' ? 'justify-center' : 'justify-start',\n 'animate-in'\n )}\n style={{ animationDelay: '0.3s' }}\n >\n {ctas.map((cta, index) => (\n <Button\n key={cta.href}\n variant={cta.variant || (index === 0 ? 'primary' : 'secondary')}\n size=\"lg\"\n asChild\n >\n <a href={cta.href}>{cta.label}</a>\n </Button>\n ))}\n </div>\n )}\n </div>\n </section>\n )\n}\n",
8
+ "type": "registry:ui"
9
+ }
10
+ ],
11
+ "type": "registry:ui"
12
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry.json",
3
+ "name": "n3wth-ui",
4
+ "homepage": "https://ui.newth.ai",
5
+ "items": [
6
+ {
7
+ "name": "button",
8
+ "type": "registry:ui",
9
+ "dependencies": ["lucide-react"],
10
+ "files": [
11
+ {
12
+ "path": "src/atoms/Button/Button.tsx",
13
+ "type": "registry:ui"
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "name": "card",
19
+ "type": "registry:ui",
20
+ "files": [
21
+ {
22
+ "path": "src/molecules/Card/Card.tsx",
23
+ "type": "registry:ui"
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "name": "hero",
29
+ "type": "registry:ui",
30
+ "files": [
31
+ {
32
+ "path": "src/organisms/Hero/Hero.tsx",
33
+ "type": "registry:ui"
34
+ }
35
+ ]
36
+ }
37
+ ]
38
+ }
package/dist/styles.css CHANGED
@@ -213,6 +213,25 @@ body {
213
213
  font-variant-numeric: tabular-nums;
214
214
  }
215
215
 
216
+ /* Apple-style text shadow for readability over shapes/backgrounds */
217
+ .text-glow {
218
+ text-shadow: 0 2px 20px rgba(0, 0, 0, 0.5),
219
+ 0 1px 3px rgba(0, 0, 0, 0.3);
220
+ }
221
+
222
+ .text-glow-subtle {
223
+ text-shadow: 0 1px 12px rgba(0, 0, 0, 0.4);
224
+ }
225
+
226
+ [data-theme='light'] .text-glow {
227
+ text-shadow: 0 2px 20px rgba(255, 255, 255, 0.5),
228
+ 0 1px 3px rgba(255, 255, 255, 0.3);
229
+ }
230
+
231
+ [data-theme='light'] .text-glow-subtle {
232
+ text-shadow: 0 1px 12px rgba(255, 255, 255, 0.4);
233
+ }
234
+
216
235
  /* Hero gradient text animation - enhanced with more dimension */
217
236
  .hero-gradient-text {
218
237
  background: linear-gradient(
@@ -304,13 +323,11 @@ body {
304
323
 
305
324
  /* Glass Nav */
306
325
  .glass-nav {
307
- background: rgba(0, 0, 0, 0.7);
308
- backdrop-filter: blur(20px) saturate(180%);
309
- -webkit-backdrop-filter: blur(20px) saturate(180%);
326
+ background: var(--color-bg);
310
327
  }
311
328
 
312
329
  [data-theme='light'] .glass-nav {
313
- background: rgba(255, 255, 255, 0.8);
330
+ background: var(--color-bg);
314
331
  }
315
332
 
316
333
  /* Glass Pill */
@@ -875,6 +892,230 @@ body {
875
892
  border-width: 0;
876
893
  }
877
894
 
895
+ /* ============================================================
896
+ Mobile-First Responsive Utilities
897
+ ============================================================ */
898
+
899
+ /* Touch-friendly minimum target sizes (WCAG 2.5.5 AAA = 44px) */
900
+ .touch-target {
901
+ min-width: 44px;
902
+ min-height: 44px;
903
+ }
904
+
905
+ .touch-target-sm {
906
+ min-width: 36px;
907
+ min-height: 36px;
908
+ }
909
+
910
+ /* Safe area insets for notched devices */
911
+ .safe-area-top {
912
+ padding-top: env(safe-area-inset-top);
913
+ }
914
+
915
+ .safe-area-bottom {
916
+ padding-bottom: env(safe-area-inset-bottom);
917
+ }
918
+
919
+ .safe-area-left {
920
+ padding-left: env(safe-area-inset-left);
921
+ }
922
+
923
+ .safe-area-right {
924
+ padding-right: env(safe-area-inset-right);
925
+ }
926
+
927
+ .safe-area-inset {
928
+ padding-top: env(safe-area-inset-top);
929
+ padding-bottom: env(safe-area-inset-bottom);
930
+ padding-left: env(safe-area-inset-left);
931
+ padding-right: env(safe-area-inset-right);
932
+ }
933
+
934
+ /* Mobile viewport height (accounts for mobile browser chrome) */
935
+ .min-h-screen-mobile {
936
+ min-height: 100vh;
937
+ min-height: 100dvh;
938
+ }
939
+
940
+ .h-screen-mobile {
941
+ height: 100vh;
942
+ height: 100dvh;
943
+ }
944
+
945
+ /* Disable hover effects on touch devices */
946
+ @media (hover: none) and (pointer: coarse) {
947
+ .glass-pill:hover,
948
+ .glass-card:hover,
949
+ .skill-card:hover,
950
+ .glow-white:hover,
951
+ .glow-accent:hover,
952
+ .link-hover:hover::after,
953
+ .gradient-border:hover::before,
954
+ .shine-sweep:hover::after {
955
+ transform: none;
956
+ box-shadow: none;
957
+ }
958
+
959
+ /* Use active states for touch feedback */
960
+ .glass-pill:active {
961
+ background: var(--glass-highlight);
962
+ transform: scale(0.98);
963
+ }
964
+
965
+ .glass-card:active,
966
+ .skill-card:active {
967
+ background: var(--glass-bg);
968
+ }
969
+
970
+ /* Disable shine sweep on touch */
971
+ .shine-sweep::after {
972
+ display: none;
973
+ }
974
+ }
975
+
976
+ /* Improved tap highlight for iOS */
977
+ @supports (-webkit-touch-callout: none) {
978
+ .tap-highlight-none {
979
+ -webkit-tap-highlight-color: transparent;
980
+ }
981
+
982
+ button,
983
+ a,
984
+ [role="button"] {
985
+ -webkit-tap-highlight-color: rgba(255, 255, 255, 0.1);
986
+ }
987
+
988
+ [data-theme='light'] button,
989
+ [data-theme='light'] a,
990
+ [data-theme='light'] [role="button"] {
991
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
992
+ }
993
+ }
994
+
995
+ /* Mobile-specific text sizing for readability */
996
+ @media (max-width: 767px) {
997
+ .text-fluid-sm {
998
+ font-size: clamp(0.75rem, 2.5vw, 0.875rem);
999
+ }
1000
+
1001
+ .text-fluid-base {
1002
+ font-size: clamp(0.875rem, 3vw, 1rem);
1003
+ }
1004
+
1005
+ .text-fluid-lg {
1006
+ font-size: clamp(1rem, 4vw, 1.25rem);
1007
+ }
1008
+
1009
+ .text-fluid-xl {
1010
+ font-size: clamp(1.25rem, 5vw, 1.5rem);
1011
+ }
1012
+
1013
+ .text-fluid-2xl {
1014
+ font-size: clamp(1.5rem, 6vw, 2rem);
1015
+ }
1016
+
1017
+ .text-fluid-3xl {
1018
+ font-size: clamp(1.875rem, 8vw, 3rem);
1019
+ }
1020
+
1021
+ /* Prevent zoom on input focus (iOS) */
1022
+ input[type="text"],
1023
+ input[type="email"],
1024
+ input[type="password"],
1025
+ input[type="search"],
1026
+ input[type="tel"],
1027
+ input[type="url"],
1028
+ input[type="number"],
1029
+ select,
1030
+ textarea {
1031
+ font-size: 16px;
1032
+ }
1033
+ }
1034
+
1035
+ /* Responsive spacing utilities */
1036
+ .gap-responsive {
1037
+ gap: 1rem;
1038
+ }
1039
+
1040
+ @media (min-width: 768px) {
1041
+ .gap-responsive {
1042
+ gap: 1.5rem;
1043
+ }
1044
+ }
1045
+
1046
+ @media (min-width: 1024px) {
1047
+ .gap-responsive {
1048
+ gap: 2rem;
1049
+ }
1050
+ }
1051
+
1052
+ .p-responsive {
1053
+ padding: 1rem;
1054
+ }
1055
+
1056
+ @media (min-width: 768px) {
1057
+ .p-responsive {
1058
+ padding: 1.5rem;
1059
+ }
1060
+ }
1061
+
1062
+ @media (min-width: 1024px) {
1063
+ .p-responsive {
1064
+ padding: 2rem;
1065
+ }
1066
+ }
1067
+
1068
+ /* Mobile container padding */
1069
+ .container-mobile {
1070
+ padding-left: 1rem;
1071
+ padding-right: 1rem;
1072
+ }
1073
+
1074
+ @media (min-width: 640px) {
1075
+ .container-mobile {
1076
+ padding-left: 1.5rem;
1077
+ padding-right: 1.5rem;
1078
+ }
1079
+ }
1080
+
1081
+ @media (min-width: 768px) {
1082
+ .container-mobile {
1083
+ padding-left: 2rem;
1084
+ padding-right: 2rem;
1085
+ }
1086
+ }
1087
+
1088
+ @media (min-width: 1024px) {
1089
+ .container-mobile {
1090
+ padding-left: 2.5rem;
1091
+ padding-right: 2.5rem;
1092
+ }
1093
+ }
1094
+
1095
+ /* Landscape orientation handling */
1096
+ @media (max-height: 500px) and (orientation: landscape) {
1097
+ .landscape-compact {
1098
+ padding-top: 0.5rem;
1099
+ padding-bottom: 0.5rem;
1100
+ }
1101
+
1102
+ .landscape-hidden {
1103
+ display: none;
1104
+ }
1105
+ }
1106
+
1107
+ /* Stack on mobile, row on larger screens */
1108
+ .stack-mobile {
1109
+ display: flex;
1110
+ flex-direction: column;
1111
+ }
1112
+
1113
+ @media (min-width: 768px) {
1114
+ .stack-mobile {
1115
+ flex-direction: row;
1116
+ }
1117
+ }
1118
+
878
1119
  /* ============================================================
879
1120
  Reduced Motion Preferences
880
1121
  ============================================================ */
@@ -963,4 +1204,101 @@ body {
963
1204
  .connection-line {
964
1205
  animation: none !important;
965
1206
  }
1207
+
1208
+ /* Disable gradient animations */
1209
+ .gradient-border::before {
1210
+ transition: none !important;
1211
+ }
1212
+
1213
+ /* Disable glow effects */
1214
+ .glow-white,
1215
+ .glow-accent,
1216
+ .focus-glow:focus-within {
1217
+ box-shadow: none !important;
1218
+ transition: none !important;
1219
+ }
1220
+
1221
+ /* Disable shine sweep */
1222
+ .shine-sweep::after {
1223
+ display: none !important;
1224
+ }
1225
+
1226
+ /* Ensure instant state changes */
1227
+ button,
1228
+ a,
1229
+ [role="button"],
1230
+ .glass-pill,
1231
+ .glass-card,
1232
+ .skill-card,
1233
+ .copy-btn,
1234
+ .command-box {
1235
+ transition: none !important;
1236
+ }
1237
+
1238
+ /* Keep functional focus indicators */
1239
+ .focus-ring:focus-visible {
1240
+ outline: 2px solid var(--color-accent);
1241
+ outline-offset: 2px;
1242
+ }
1243
+ }
1244
+
1245
+ /* ============================================================
1246
+ Responsive Shape Component
1247
+ ============================================================ */
1248
+
1249
+ .shape-responsive {
1250
+ width: var(--shape-size-base, 48px);
1251
+ height: var(--shape-size-base, 48px);
1252
+ }
1253
+
1254
+ @media (min-width: 640px) {
1255
+ .shape-responsive {
1256
+ width: var(--shape-size-sm, var(--shape-size-base, 48px));
1257
+ height: var(--shape-size-sm, var(--shape-size-base, 48px));
1258
+ }
1259
+ }
1260
+
1261
+ @media (min-width: 768px) {
1262
+ .shape-responsive {
1263
+ width: var(--shape-size-md, var(--shape-size-sm, var(--shape-size-base, 48px)));
1264
+ height: var(--shape-size-md, var(--shape-size-sm, var(--shape-size-base, 48px)));
1265
+ }
1266
+ }
1267
+
1268
+ @media (min-width: 1024px) {
1269
+ .shape-responsive {
1270
+ width: var(--shape-size-lg, var(--shape-size-md, var(--shape-size-sm, var(--shape-size-base, 48px))));
1271
+ height: var(--shape-size-lg, var(--shape-size-md, var(--shape-size-sm, var(--shape-size-base, 48px))));
1272
+ }
1273
+ }
1274
+
1275
+ /* ============================================================
1276
+ High Contrast Mode Support
1277
+ ============================================================ */
1278
+
1279
+ @media (prefers-contrast: high) {
1280
+ :root,
1281
+ [data-theme='dark'] {
1282
+ --glass-border: rgba(255, 255, 255, 0.3);
1283
+ --glass-highlight: rgba(255, 255, 255, 0.5);
1284
+ --color-grey-400: #a1a1a6;
1285
+ --color-grey-600: #8e8e93;
1286
+ }
1287
+
1288
+ [data-theme='light'] {
1289
+ --glass-border: rgba(0, 0, 0, 0.3);
1290
+ --glass-highlight: rgba(0, 0, 0, 0.5);
1291
+ --color-grey-400: #636366;
1292
+ --color-grey-600: #48484a;
1293
+ }
1294
+
1295
+ .glass-card,
1296
+ .glass-pill,
1297
+ .command-box {
1298
+ border-width: 2px;
1299
+ }
1300
+
1301
+ .focus-ring:focus-visible {
1302
+ outline-width: 3px;
1303
+ }
966
1304
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@n3wth/ui",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Atomic design system for Newth sites - flat, minimal, iOS-inspired",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -26,7 +26,8 @@
26
26
  "build": "tsc && vite build && node -e \"const fs=require('fs');const css=fs.readFileSync('src/styles.css','utf8').replace(/@import 'tailwindcss';\\n\\n/,'');fs.writeFileSync('dist/styles.css',css)\"",
27
27
  "lint": "eslint src",
28
28
  "prepublishOnly": "npm run build",
29
- "release:patch": "npm version patch -m 'chore: bump version to %s' && git push && git push --tags && gh release create v$(node -p \"require('./package.json').version\") --generate-notes",
29
+ "release:patch": "npm version patch -m 'chore: bump version to %s' && npm run registry:build && git add . && git commit -m 'chore: update registry' && git push && git push --tags && gh release create v$(node -p \"require('./package.json').version\") --generate-notes",
30
+ "registry:build": "npx shadcn@latest build",
30
31
  "release:minor": "npm version minor -m 'chore: bump version to %s' && git push && git push --tags && gh release create v$(node -p \"require('./package.json').version\") --generate-notes",
31
32
  "release:major": "npm version major -m 'chore: bump version to %s' && git push && git push --tags && gh release create v$(node -p \"require('./package.json').version\") --generate-notes"
32
33
  },
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "button",
4
+ "dependencies": [
5
+ "lucide-react"
6
+ ],
7
+ "files": [
8
+ {
9
+ "path": "src/atoms/Button/Button.tsx",
10
+ "content": "import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactNode,\n cloneElement,\n isValidElement,\n} from 'react'\nimport { cn } from '../../utils/cn'\n\nexport type ButtonSize = 'sm' | 'md' | 'lg'\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'primary' | 'secondary' | 'ghost' | 'glass'\n /** Size of the button. Can be a single value or responsive object */\n size?: ButtonSize | { base?: ButtonSize; md?: ButtonSize; lg?: ButtonSize }\n children: ReactNode\n isLoading?: boolean\n leftIcon?: ReactNode\n rightIcon?: ReactNode\n asChild?: boolean\n /** Ensures minimum 44px touch target for accessibility (WCAG 2.5.5) */\n touchTarget?: boolean\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n variant = 'primary',\n size = 'md',\n children,\n isLoading = false,\n leftIcon,\n rightIcon,\n className,\n disabled,\n asChild = false,\n touchTarget = false,\n ...props\n },\n ref\n ) => {\n const baseStyles = [\n 'inline-flex items-center justify-center gap-2',\n 'font-medium',\n 'border',\n 'rounded-full',\n 'transition-[transform,background-color,border-color,color,opacity] duration-200 ease-out',\n 'focus-ring',\n 'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none',\n // Active state for touch feedback\n 'active:scale-[0.96]',\n ]\n\n const variants = {\n primary: [\n 'bg-[var(--color-white)] text-[var(--color-bg)]',\n 'border-[var(--color-white)]',\n 'hover:scale-[1.02]',\n 'glow-white',\n ],\n secondary: [\n 'bg-transparent text-[var(--color-white)]',\n 'border-[var(--glass-border)]',\n 'hover:border-[var(--glass-highlight)] hover:bg-[var(--glass-bg)]',\n ],\n ghost: [\n 'bg-transparent text-[var(--color-grey-400)]',\n 'border-transparent',\n 'hover:text-[var(--color-white)] hover:bg-[var(--glass-bg)]',\n ],\n glass: [\n 'bg-[var(--glass-bg)] text-[var(--color-white)]',\n 'border-[var(--glass-border)]',\n 'backdrop-blur-lg',\n 'hover:bg-[rgba(255,255,255,0.1)] hover:border-[var(--glass-highlight)]',\n 'hover:scale-[1.02]',\n ],\n }\n\n const sizes = {\n sm: 'px-3 py-1.5 text-xs',\n md: 'px-4 py-2 text-sm',\n lg: 'px-6 py-3 text-base',\n }\n\n // Handle responsive size prop\n const getSizeClasses = () => {\n if (typeof size === 'string') {\n return sizes[size]\n }\n\n // Responsive size object\n const classes: string[] = []\n if (size.base) classes.push(sizes[size.base])\n if (size.md) classes.push(`md:${sizes[size.md].split(' ').join(' md:')}`)\n if (size.lg) classes.push(`lg:${sizes[size.lg].split(' ').join(' lg:')}`)\n\n // If no base specified, default to sm for mobile-first\n if (!size.base && (size.md || size.lg)) {\n classes.unshift(sizes.sm)\n }\n\n return classes.join(' ')\n }\n\n const touchTargetStyles = touchTarget ? 'min-w-[44px] min-h-[44px]' : ''\n\n const buttonClassName = cn(\n baseStyles,\n variants[variant],\n getSizeClasses(),\n touchTargetStyles,\n className\n )\n\n type ChildProps = { children?: ReactNode; className?: string }\n const childElement = isValidElement<ChildProps>(children) ? children : null\n\n const content = (\n <>\n {isLoading ? (\n <span className=\"w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin\" />\n ) : (\n leftIcon\n )}\n {asChild && childElement ? childElement.props.children : children}\n {!isLoading && rightIcon}\n </>\n )\n\n if (asChild && childElement) {\n return cloneElement(childElement, {\n className: cn(buttonClassName, childElement.props.className),\n ref,\n children: content,\n } as ChildProps)\n }\n\n return (\n <button\n ref={ref}\n className={buttonClassName}\n disabled={disabled || isLoading}\n {...props}\n >\n {content}\n </button>\n )\n }\n)\n\nButton.displayName = 'Button'\n",
11
+ "type": "registry:ui"
12
+ }
13
+ ],
14
+ "type": "registry:ui"
15
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "card",
4
+ "files": [
5
+ {
6
+ "path": "src/molecules/Card/Card.tsx",
7
+ "content": "import { type HTMLAttributes, type ReactNode } from 'react'\nimport { cn } from '../../utils/cn'\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n variant?: 'default' | 'glass' | 'interactive'\n padding?: 'none' | 'sm' | 'md' | 'lg'\n children: ReactNode\n}\n\nexport function Card({\n variant = 'default',\n padding = 'md',\n children,\n className,\n ...props\n}: CardProps) {\n const baseStyles = [\n 'rounded-2xl',\n 'border',\n 'transition-[background-color,border-color] duration-300 ease-out',\n ]\n\n const variants = {\n default: [\n 'bg-transparent',\n 'border-[var(--glass-border)]',\n ],\n glass: [\n 'bg-[var(--glass-bg)]',\n 'backdrop-blur-lg',\n 'border-[var(--glass-border)]',\n ],\n interactive: [\n 'bg-transparent',\n 'border-[var(--glass-border)]',\n 'hover:border-[var(--glass-highlight)]',\n 'hover:bg-[var(--glass-bg)]',\n 'cursor-pointer',\n 'gradient-border shine-sweep',\n ],\n }\n\n const paddings = {\n none: '',\n sm: 'p-3',\n md: 'p-5',\n lg: 'p-8',\n }\n\n return (\n <div\n className={cn(baseStyles, variants[variant], paddings[padding], className)}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nexport interface CardHeaderProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardHeader({ children, className, ...props }: CardHeaderProps) {\n return (\n <div className={cn('flex flex-col gap-1.5', className)} {...props}>\n {children}\n </div>\n )\n}\n\nexport interface CardTitleProps extends HTMLAttributes<HTMLHeadingElement> {\n children: ReactNode\n as?: 'h1' | 'h2' | 'h3' | 'h4'\n}\n\nexport function CardTitle({ children, as: Tag = 'h3', className, ...props }: CardTitleProps) {\n return (\n <Tag\n className={cn(\n 'font-display text-base font-semibold text-[var(--color-white)]',\n 'tracking-tight',\n className\n )}\n {...props}\n >\n {children}\n </Tag>\n )\n}\n\nexport interface CardDescriptionProps extends HTMLAttributes<HTMLParagraphElement> {\n children: ReactNode\n}\n\nexport function CardDescription({ children, className, ...props }: CardDescriptionProps) {\n return (\n <p\n className={cn(\n 'text-sm text-[var(--color-grey-400)]',\n 'line-clamp-2',\n className\n )}\n {...props}\n >\n {children}\n </p>\n )\n}\n\nexport interface CardContentProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardContent({ children, className, ...props }: CardContentProps) {\n return (\n <div className={cn('mt-4', className)} {...props}>\n {children}\n </div>\n )\n}\n\nexport interface CardFooterProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode\n}\n\nexport function CardFooter({ children, className, ...props }: CardFooterProps) {\n return (\n <div\n className={cn(\n 'mt-4 pt-4',\n 'border-t border-[var(--glass-border)]',\n 'flex items-center gap-3',\n className\n )}\n {...props}\n >\n {children}\n </div>\n )\n}\n",
8
+ "type": "registry:ui"
9
+ }
10
+ ],
11
+ "type": "registry:ui"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "hero",
4
+ "files": [
5
+ {
6
+ "path": "src/organisms/Hero/Hero.tsx",
7
+ "content": "import { type HTMLAttributes, type ReactNode } from 'react'\nimport { cn } from '../../utils/cn'\nimport { Badge } from '../../atoms/Badge'\nimport { Button } from '../../atoms/Button'\n\nexport interface HeroCTA {\n label: string\n href: string\n variant?: 'primary' | 'secondary' | 'ghost'\n}\n\nexport interface HeroProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n badge?: string\n title: ReactNode\n description?: ReactNode\n ctas?: HeroCTA[]\n align?: 'left' | 'center'\n size?: 'default' | 'large'\n gradient?: boolean\n}\n\nexport function Hero({\n badge,\n title,\n description,\n ctas = [],\n align = 'center',\n size = 'default',\n gradient = true,\n className,\n ...props\n}: HeroProps) {\n const alignments = {\n left: 'text-left items-start',\n center: 'text-center items-center',\n }\n\n const titleSizes = {\n default: 'text-4xl sm:text-5xl md:text-6xl',\n large: 'text-5xl sm:text-6xl md:text-7xl lg:text-8xl',\n }\n\n return (\n <section\n className={cn(\n 'relative',\n 'px-6 py-24 md:py-32 lg:py-40',\n className\n )}\n {...props}\n >\n <div\n className={cn(\n 'mx-auto max-w-4xl',\n 'flex flex-col gap-6',\n alignments[align]\n )}\n >\n {badge && (\n <Badge variant=\"default\" size=\"md\" className=\"animate-in\">\n {badge}\n </Badge>\n )}\n\n <h1\n className={cn(\n 'font-display font-semibold tracking-tight',\n titleSizes[size],\n gradient ? 'hero-gradient-text' : 'text-[var(--color-white)]',\n 'animate-in'\n )}\n style={{ animationDelay: '0.1s', textAlign: align === 'center' ? 'center' : 'left', width: '100%', display: 'block' }}\n >\n {title}\n </h1>\n\n {description && (\n <p\n className={cn(\n 'text-lg md:text-xl',\n 'text-[var(--color-grey-400)]',\n 'max-w-2xl leading-relaxed',\n 'animate-in'\n )}\n style={{ animationDelay: '0.2s' }}\n >\n {description}\n </p>\n )}\n\n {ctas.length > 0 && (\n <div\n className={cn(\n 'flex flex-wrap gap-4 mt-4',\n align === 'center' ? 'justify-center' : 'justify-start',\n 'animate-in'\n )}\n style={{ animationDelay: '0.3s' }}\n >\n {ctas.map((cta, index) => (\n <Button\n key={cta.href}\n variant={cta.variant || (index === 0 ? 'primary' : 'secondary')}\n size=\"lg\"\n asChild\n >\n <a href={cta.href}>{cta.label}</a>\n </Button>\n ))}\n </div>\n )}\n </div>\n </section>\n )\n}\n",
8
+ "type": "registry:ui"
9
+ }
10
+ ],
11
+ "type": "registry:ui"
12
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry.json",
3
+ "name": "n3wth-ui",
4
+ "homepage": "https://ui.newth.ai",
5
+ "items": [
6
+ {
7
+ "name": "button",
8
+ "type": "registry:ui",
9
+ "dependencies": ["lucide-react"],
10
+ "files": [
11
+ {
12
+ "path": "src/atoms/Button/Button.tsx",
13
+ "type": "registry:ui"
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "name": "card",
19
+ "type": "registry:ui",
20
+ "files": [
21
+ {
22
+ "path": "src/molecules/Card/Card.tsx",
23
+ "type": "registry:ui"
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "name": "hero",
29
+ "type": "registry:ui",
30
+ "files": [
31
+ {
32
+ "path": "src/organisms/Hero/Hero.tsx",
33
+ "type": "registry:ui"
34
+ }
35
+ ]
36
+ }
37
+ ]
38
+ }
@@ -54,6 +54,38 @@ module.exports = {
54
54
  '50%': { backgroundPosition: '100% 50%' },
55
55
  },
56
56
  },
57
+ // Mobile-first spacing scale
58
+ spacing: {
59
+ 'safe-top': 'env(safe-area-inset-top)',
60
+ 'safe-bottom': 'env(safe-area-inset-bottom)',
61
+ 'safe-left': 'env(safe-area-inset-left)',
62
+ 'safe-right': 'env(safe-area-inset-right)',
63
+ },
64
+ // Touch-friendly minimum sizes (WCAG 2.5.5)
65
+ minWidth: {
66
+ 'touch': '44px',
67
+ 'touch-sm': '36px',
68
+ },
69
+ minHeight: {
70
+ 'touch': '44px',
71
+ 'touch-sm': '36px',
72
+ 'screen-dvh': '100dvh',
73
+ },
74
+ height: {
75
+ 'screen-dvh': '100dvh',
76
+ },
77
+ // Fluid typography scale
78
+ fontSize: {
79
+ 'fluid-xs': 'clamp(0.75rem, 2vw, 0.875rem)',
80
+ 'fluid-sm': 'clamp(0.875rem, 2.5vw, 1rem)',
81
+ 'fluid-base': 'clamp(1rem, 3vw, 1.125rem)',
82
+ 'fluid-lg': 'clamp(1.125rem, 3.5vw, 1.25rem)',
83
+ 'fluid-xl': 'clamp(1.25rem, 4vw, 1.5rem)',
84
+ 'fluid-2xl': 'clamp(1.5rem, 5vw, 2rem)',
85
+ 'fluid-3xl': 'clamp(1.875rem, 6vw, 2.5rem)',
86
+ 'fluid-4xl': 'clamp(2.25rem, 8vw, 3.5rem)',
87
+ 'fluid-5xl': 'clamp(3rem, 10vw, 4.5rem)',
88
+ },
57
89
  },
58
90
  },
59
91
  }