@ugurdemirel/landcraft 0.1.18 → 0.1.20
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/README.md +109 -31
- package/dist/cjs/components/Hero/Hero.cjs +1 -1
- package/dist/cjs/components/Hero/Hero.cjs.map +1 -1
- package/dist/cjs/components/Hero/variants/Parallax.cjs +3 -0
- package/dist/cjs/components/Hero/variants/Parallax.cjs.map +1 -0
- package/dist/es/components/Hero/Hero.js +5 -4
- package/dist/es/components/Hero/Hero.js.map +1 -1
- package/dist/es/components/Hero/variants/Parallax.js +116 -0
- package/dist/es/components/Hero/variants/Parallax.js.map +1 -0
- package/dist/index.d.ts +19 -1
- package/dist/styles.css +44 -10
- package/dist/theme.css +11 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Landcraft
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
A marketing / landing-page React component library — `@ugurdemirel/landcraft` —
|
|
6
|
+
built on Tailwind CSS 4 with token-based theming and automatic WCAG contrast,
|
|
7
|
+
plus a Storybook playground for previewing and customizing every component.
|
|
8
|
+
|
|
9
|
+
## Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
landcraft/
|
|
13
|
+
├─ packages/ui/ @ugurdemirel/landcraft — component library
|
|
14
|
+
│ ├─ src/components/ Button, Badge, Card, Section, Navbar, MegaMenu, Hero,
|
|
15
|
+
│ │ LogoCloud, FeatureGrid, FeatureShowcase, Stats, Pricing,
|
|
16
|
+
│ │ Testimonials, CTA, FAQ, AccordionShowcase, Footer,
|
|
17
|
+
│ │ Newsletter, Blog, Prose, Modal, LanguageSwitcher,
|
|
18
|
+
│ │ Container, Stack
|
|
19
|
+
│ ├─ src/styles/ CSS variables (tokens) + Tailwind entry point
|
|
20
|
+
│ └─ src/styles/theme.css Tailwind v4 theme (CSS) for consumer projects
|
|
21
|
+
└─ apps/storybook/ Storybook (React + Vite) — preview & customize
|
|
22
|
+
```
|
|
6
23
|
|
|
7
24
|
## Install
|
|
8
25
|
|
|
9
26
|
```bash
|
|
10
27
|
pnpm add @ugurdemirel/landcraft
|
|
11
|
-
# peers: react >=18, tailwindcss ^4
|
|
28
|
+
# peers: react >=18, react-dom >=18, tailwindcss ^4
|
|
12
29
|
```
|
|
13
30
|
|
|
14
31
|
```tsx
|
|
@@ -16,18 +33,17 @@ import { Button, Hero } from "@ugurdemirel/landcraft";
|
|
|
16
33
|
import "@ugurdemirel/landcraft/styles.css";
|
|
17
34
|
```
|
|
18
35
|
|
|
19
|
-
|
|
20
|
-
|
|
36
|
+
Or reuse the theme in your own Tailwind v4 project by importing the CSS theme
|
|
37
|
+
file — no JS config or preset needed:
|
|
21
38
|
|
|
22
39
|
```css
|
|
23
|
-
/* app.css */
|
|
40
|
+
/* app.css — wire it up with @tailwindcss/vite, @tailwindcss/postcss or the CLI */
|
|
24
41
|
@import "tailwindcss";
|
|
25
42
|
@import "@ugurdemirel/landcraft/theme.css";
|
|
26
43
|
```
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
typography plugin automatically.
|
|
45
|
+
The theme registers every color/font/radius/shadow token and the typography
|
|
46
|
+
plugin automatically.
|
|
31
47
|
|
|
32
48
|
> Tailwind v4 auto-detects classes in your own source but skips `node_modules`.
|
|
33
49
|
> If you build the library's components from your own CSS (instead of importing
|
|
@@ -43,25 +59,32 @@ Every component accepts an `option` / `variant` prop — same API, different loo
|
|
|
43
59
|
|
|
44
60
|
| Component | Options |
|
|
45
61
|
| --- | --- |
|
|
62
|
+
| Button | `primary` · `dark` · `outline` · `ghost` · `link` |
|
|
63
|
+
| Badge | `soft` · `solid` · `outline` · `dot` |
|
|
64
|
+
| Card | `outlined` · `elevated` · `inset` |
|
|
46
65
|
| Navbar | `classic` · `floating` · `inverse` |
|
|
47
|
-
|
|
|
66
|
+
| MegaMenu | `classic` · `floating` · `inverse` |
|
|
67
|
+
| Hero | `split` · `centered` · `statement` · `parallax` |
|
|
48
68
|
| LogoCloud | `quiet` · `marquee` · `strip` |
|
|
49
69
|
| FeatureGrid | `columns` · `bento` · `editorialRows` |
|
|
70
|
+
| FeatureShowcase | `mediaSide`: `left` · `right` |
|
|
50
71
|
| Stats | `editorial` · `hairline` · `cells` · `ticker` |
|
|
51
72
|
| Pricing | `cards` · `bento` · `compact` |
|
|
52
73
|
| Testimonials | `grid` · `carousel` · `marquee` |
|
|
53
74
|
| CTA | `panel` · `surface` · `inverse` |
|
|
54
|
-
| FAQ | `accordion` · `split` · `cards` |
|
|
75
|
+
| FAQ | `accordion` · `split` · `cards` (`allowMultiple`, `defaultOpen`) |
|
|
55
76
|
| Footer | `classic` · `minimal` · `editorial` |
|
|
56
77
|
| Newsletter | `inline` · `card` · `underline` |
|
|
57
78
|
| Blog (BlogSection/BlogCard) | `card` · `row` (`limit` shows the latest N posts) |
|
|
79
|
+
| Modal | `size`: `sm` · `md` · `lg` |
|
|
80
|
+
| LanguageSwitcher | `dropdown` · `modal` |
|
|
58
81
|
|
|
59
|
-
**Brand mark.** `Navbar` and `Footer` accept a custom logo in place
|
|
60
|
-
wordmark: pass any node to `logo` (SVG, `<img>`, component…), or use
|
|
82
|
+
**Brand mark.** `Navbar`, `MegaMenu` and `Footer` accept a custom logo in place
|
|
83
|
+
of the wordmark: pass any node to `logo` (SVG, `<img>`, component…), or use
|
|
61
84
|
`logoSrc`/`logoAlt` for a quick image (`logoClassName` tunes its size, defaults
|
|
62
85
|
to `h-7 w-auto`).
|
|
63
86
|
|
|
64
|
-
## Theme
|
|
87
|
+
## Theme & customization
|
|
65
88
|
|
|
66
89
|
Every design decision lives on CSS custom properties on `:root` and can be
|
|
67
90
|
**overridden from outside** — color, type, radii and shadows. Override any token
|
|
@@ -154,22 +177,23 @@ dark-mode variants.
|
|
|
154
177
|
|
|
155
178
|
### Contrast guarantees
|
|
156
179
|
|
|
157
|
-
1. **Paired tokens.** Every surface color ships with a matching `--color-on-*`
|
|
158
|
-
themes change together.
|
|
159
|
-
2. **Dynamic surfaces.**
|
|
160
|
-
the CSS `contrast-color()` function to pick the
|
|
161
|
-
dark palette
|
|
180
|
+
1. **Paired tokens.** Every surface color ships with a matching `--color-on-*`
|
|
181
|
+
text color, so readability is preserved as themes change together.
|
|
182
|
+
2. **Dynamic surfaces.** `customColor` props, gradient Hero/CTA panels and
|
|
183
|
+
highlighted pricing cards use the CSS `contrast-color()` function to pick the
|
|
184
|
+
most readable text color. Switch to a dark palette and the text flips
|
|
185
|
+
automatically.
|
|
162
186
|
|
|
163
187
|
### Icons
|
|
164
188
|
|
|
165
189
|
No emojis. The library ships one consistent 24×24 stroke icon set (Lucide-style):
|
|
166
|
-
`ArrowRight`, `Check`, `Zap`, `ShieldCheck`, `Layers`, `Rocket`, `Mail`, … —
|
|
167
|
-
them from the package.
|
|
190
|
+
`ArrowRight`, `Check`, `Zap`, `ShieldCheck`, `Layers`, `Rocket`, `Mail`, … —
|
|
191
|
+
import them from the package.
|
|
168
192
|
|
|
169
193
|
## Layout
|
|
170
194
|
|
|
171
|
-
`Container` and `Stack` are the single source of the container + gutter rules
|
|
172
|
-
across Section, Footer, CTA and BlogSection (`mx-auto w-full max-w-* px-5 sm:px-8`):
|
|
195
|
+
`Container` and `Stack` are the single source of the container + gutter rules
|
|
196
|
+
used across Section, Footer, CTA and BlogSection (`mx-auto w-full max-w-* px-5 sm:px-8`):
|
|
173
197
|
|
|
174
198
|
```tsx
|
|
175
199
|
import { Container, Stack } from "@ugurdemirel/landcraft";
|
|
@@ -185,9 +209,9 @@ import { Container, Stack } from "@ugurdemirel/landcraft";
|
|
|
185
209
|
|
|
186
210
|
## Content
|
|
187
211
|
|
|
188
|
-
`Prose` wraps `@tailwindcss/typography` and re-skins its colors and type through
|
|
189
|
-
same tokens (headings use `--font-display`, `<pre>` uses the ink surface,
|
|
190
|
-
and `prose-neutral` adapt to the palette):
|
|
212
|
+
`Prose` wraps `@tailwindcss/typography` and re-skins its colors and type through
|
|
213
|
+
the same tokens (headings use `--font-display`, `<pre>` uses the ink surface,
|
|
214
|
+
both `prose` and `prose-neutral` adapt to the palette):
|
|
191
215
|
|
|
192
216
|
```bash
|
|
193
217
|
pnpm add @ugurdemirel/landcraft @tailwindcss/typography
|
|
@@ -210,6 +234,60 @@ Projects using the theme need `@tailwindcss/typography` (optional peer; the
|
|
|
210
234
|
|
|
211
235
|
## Components
|
|
212
236
|
|
|
213
|
-
Button, Badge, Card (+Header/Title/Content/Footer), Section (+Header),
|
|
214
|
-
|
|
215
|
-
|
|
237
|
+
Button, Badge, Card (+Header/Title/Description/Content/Footer), Section (+Header),
|
|
238
|
+
Navbar, MegaMenu, Hero, LogoCloud, FeatureGrid/FeatureCard, FeatureShowcase,
|
|
239
|
+
Stats, Pricing, Testimonials/TestimonialCard, CTA, FAQ, AccordionShowcase,
|
|
240
|
+
Footer, Newsletter, Blog (BlogSection/BlogCard), Modal, LanguageSwitcher,
|
|
241
|
+
Prose (+ProseLead), Container, Stack, Slot, plus the icon set.
|
|
242
|
+
|
|
243
|
+
## What you can do in Storybook
|
|
244
|
+
|
|
245
|
+
- **Preview** every component and its stories from the left panel.
|
|
246
|
+
- **Customize** props live via the **Controls** panel (variant, size, customColor, text…).
|
|
247
|
+
- **Re-theme** from the **Palette** toolbar (indigo / forest / crimson / midnight-dark).
|
|
248
|
+
Every component restyles instantly through `--color-*` tokens — a live demo of
|
|
249
|
+
"design from the outside".
|
|
250
|
+
- **Audit accessibility** via the a11y panel (AA/AAA contrast results).
|
|
251
|
+
|
|
252
|
+
## Framework-aware links
|
|
253
|
+
|
|
254
|
+
Landcraft renders plain `<a>` tags by default so it works anywhere. When you use
|
|
255
|
+
a router (Next.js, Remix, React Router…), pass your framework's `<Link>` so
|
|
256
|
+
navigation stays client-side and server-side correct.
|
|
257
|
+
|
|
258
|
+
**Single links — `asChild` on `Button`** (the button styles are applied to the
|
|
259
|
+
child element instead of a `<button>`):
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
import { Button } from "@ugurdemirel/landcraft";
|
|
263
|
+
import Link from "next/link";
|
|
264
|
+
|
|
265
|
+
<Button asChild>
|
|
266
|
+
<Link href="/pricing">Get started</Link>
|
|
267
|
+
</Button>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Link lists — `LinkComponent` prop** on `Navbar`, `MegaMenu`, `Footer`, and
|
|
271
|
+
`BlogSection`/`BlogCard` (defaults to `<a>`):
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
import { Navbar, Footer } from "@ugurdemirel/landcraft";
|
|
275
|
+
import Link from "next/link";
|
|
276
|
+
|
|
277
|
+
<Navbar links={links} LinkComponent={Link} />
|
|
278
|
+
<Footer columns={columns} LinkComponent={Link} />
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The low-level `Slot` used to implement `asChild` is also exported if you want to
|
|
282
|
+
compose it yourself.
|
|
283
|
+
|
|
284
|
+
## Commands
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
pnpm install # install dependencies
|
|
288
|
+
pnpm build # builds @ugurdemirel/landcraft (JS + CSS + types)
|
|
289
|
+
pnpm storybook # starts Storybook (http://localhost:6006)
|
|
290
|
+
pnpm build:storybook # produces a static Storybook build (storybook-static/)
|
|
291
|
+
pnpm typecheck # typechecks all packages
|
|
292
|
+
pnpm --filter @ugurdemirel/landcraft test # run the component/unit tests
|
|
293
|
+
```
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),i=require("react"),a=require("./variants/Split.cjs"),s=require("./variants/Centered.cjs"),u=require("./variants/Statement.cjs"),l=require("./variants/Parallax.cjs"),o=i.forwardRef(({variant:n="split",...e},r)=>n==="centered"?t.jsx(s.HeroCentered,{ref:r,...e}):n==="statement"?t.jsx(u.HeroStatement,{ref:r,...e}):n==="parallax"?t.jsx(l.HeroParallax,{ref:r,...e}):t.jsx(a.HeroSplit,{ref:r,...e}));o.displayName="Hero";exports.Hero=o;
|
|
2
2
|
//# sourceMappingURL=Hero.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Hero.cjs","sources":["../../../../src/components/Hero/Hero.tsx"],"sourcesContent":["import { forwardRef } from \"react\";\nimport type { HeroProps } from \"./types\";\nimport { HeroSplit } from \"./variants/Split\";\nimport { HeroCentered } from \"./variants/Centered\";\nimport { HeroStatement } from \"./variants/Statement\";\n\nexport const Hero = forwardRef<HTMLElement, HeroProps>(({ variant = \"split\", ...props }, ref) => {\n if (variant === \"centered\") return <HeroCentered ref={ref} {...props} />;\n if (variant === \"statement\") return <HeroStatement ref={ref} {...props} />;\n return <HeroSplit ref={ref} {...props} />;\n});\nHero.displayName = \"Hero\";"],"names":["Hero","forwardRef","variant","props","ref","HeroCentered","HeroStatement","jsx","HeroSplit"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Hero.cjs","sources":["../../../../src/components/Hero/Hero.tsx"],"sourcesContent":["import { forwardRef } from \"react\";\nimport type { HeroProps } from \"./types\";\nimport { HeroSplit } from \"./variants/Split\";\nimport { HeroCentered } from \"./variants/Centered\";\nimport { HeroStatement } from \"./variants/Statement\";\nimport { HeroParallax } from \"./variants/Parallax\";\n\nexport const Hero = forwardRef<HTMLElement, HeroProps>(({ variant = \"split\", ...props }, ref) => {\n if (variant === \"centered\") return <HeroCentered ref={ref} {...props} />;\n if (variant === \"statement\") return <HeroStatement ref={ref} {...props} />;\n if (variant === \"parallax\") return <HeroParallax ref={ref} {...props} />;\n return <HeroSplit ref={ref} {...props} />;\n});\nHero.displayName = \"Hero\";"],"names":["Hero","forwardRef","variant","props","ref","HeroCentered","HeroStatement","HeroParallax","jsx","HeroSplit"],"mappings":"0RAOaA,EAAOC,EAAAA,WAAmC,CAAC,CAAE,QAAAC,EAAU,QAAS,GAAGC,CAAA,EAASC,IACnFF,IAAY,iBAAoBG,EAAAA,aAAA,CAAa,IAAAD,EAAW,GAAGD,EAAO,EAClED,IAAY,kBAAqBI,EAAAA,cAAA,CAAc,IAAAF,EAAW,GAAGD,EAAO,EACpED,IAAY,iBAAoBK,EAAAA,aAAA,CAAa,IAAAH,EAAW,GAAGD,EAAO,EAC/DK,EAAAA,IAACC,EAAAA,UAAA,CAAU,IAAAL,EAAW,GAAGD,CAAA,CAAO,CACxC,EACDH,EAAK,YAAc"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),a=require("react"),i=require("../../../utils/cn.cjs"),u=require("../parts.cjs"),D=18,F=5,P=.7;function C(r,d,l){d.current=r,typeof l=="function"?l(r):l&&(l.current=r)}function _(r){return{x:(r.x-50)*4,y:(r.y-50)*4}}const N=a.forwardRef(({className:r,eyebrow:d,title:l,description:x,primaryAction:f,secondaryAction:m,meta:h,media:p,logos:w=[],id:M,...T},E)=>{const y=a.useRef(null),[b,j]=a.useState({x:0,y:0}),[R,$]=a.useState(!1),[o,v]=a.useState(!1);a.useEffect(()=>{var s;if(typeof window.matchMedia!="function")return;const e=window.matchMedia("(prefers-reduced-motion: reduce)"),n=()=>v(e.matches);return v(e.matches),(s=e.addEventListener)==null||s.call(e,"change",n),()=>{var c;return(c=e.removeEventListener)==null?void 0:c.call(e,"change",n)}},[]),a.useEffect(()=>{if(o)return;const e=requestAnimationFrame(()=>$(!0));return()=>cancelAnimationFrame(e)},[o]);const L=e=>{if(o)return;const n=y.current;if(!n)return;const s=n.getBoundingClientRect();j({x:((e.clientX-s.left)/s.width-.5)*2,y:((e.clientY-s.top)/s.height-.5)*2})},S=()=>j({x:0,y:0}),g=R||o;return t.jsxs("section",{ref:e=>C(e,y,E),id:M,onMouseMove:L,onMouseLeave:S,className:i.cn("relative w-full overflow-hidden border-b border-border bg-background",r),...T,children:[w.map((e,n)=>{const s=e.depth??D,c=_(e),A=`translate(calc(-50% + ${c.x}px), calc(-50% + ${c.y}px)) scale(0.6)`,H="translate(-50%, -50%)",O=o?"translate(0px, 0px)":`translate(${b.x*s}px, ${b.y*s}px)`;return t.jsx("div",{"aria-hidden":!0,"data-logo":e.label,className:"pointer-events-none absolute z-10 hidden transition-[transform,opacity] duration-700 ease-out md:block",style:{left:`${e.x}%`,top:`${e.y}%`,transform:g?H:A,opacity:g?1:0,transitionDelay:`${n*90}ms`},children:t.jsx("div",{className:"transition-transform duration-200 ease-out",style:{transform:O},children:t.jsx("div",{className:"flex h-16 w-16 items-center justify-center rounded-2xl border border-border bg-surface shadow-raised",style:o?void 0:{animation:`hero-float ${F}s ease-in-out infinite`,animationDelay:`-${n*P}s`},children:e.icon})})},e.label)}),t.jsxs("div",{className:i.cn(u.heroContainer,"relative z-20 pt-24 pb-20 text-center sm:pt-32 sm:pb-24"),children:[t.jsx(u.HeroEyebrow,{eyebrow:d}),t.jsx("h1",{className:i.cn(u.heroTitleBase,"mt-7 max-w-4xl text-5xl leading-[1.04] sm:text-6xl lg:text-7xl"),children:l}),x?t.jsx("p",{className:"mx-auto mt-7 max-w-xl text-pretty leading-relaxed text-muted-foreground sm:text-lg",children:x}):null,f||m?t.jsx("div",{className:"mt-10 justify-center",children:t.jsx(u.HeroActions,{primaryAction:f,secondaryAction:m})}):null,h?t.jsx("ul",{className:"mt-14 flex flex-wrap items-center justify-center gap-x-9 gap-y-3",children:h.map((e,n)=>t.jsxs("li",{className:i.cn("flex items-center gap-2 text-sm text-muted-foreground",n>0&&"sm:border-l sm:border-border sm:pl-9"),children:[e.icon?t.jsx("span",{className:"text-foreground/35",children:e.icon}):null,e.label]},e.label))}):null,p?t.jsx("div",{className:"mt-16 w-full",children:p}):null]})]})});N.displayName="HeroParallax";exports.HeroParallax=N;
|
|
3
|
+
//# sourceMappingURL=Parallax.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Parallax.cjs","sources":["../../../../../src/components/Hero/variants/Parallax.tsx"],"sourcesContent":["\"use client\";\nimport {\n forwardRef,\n useEffect,\n useRef,\n useState,\n type ForwardedRef,\n type MouseEvent,\n type MutableRefObject,\n} from \"react\";\nimport { cn } from \"../../../utils/cn\";\nimport { HeroActions, HeroEyebrow, heroContainer, heroTitleBase } from \"../parts\";\nimport type { HeroLogo, HeroProps } from \"../types\";\n\nconst DEFAULT_DEPTH = 18;\nconst FLOAT_DURATION = 5; // seconds per breathing cycle\nconst FLOAT_STAGGER = 0.7; // seconds of desync between consecutive tiles\n\nfunction setSectionRef(\n node: HTMLElement | null,\n localRef: MutableRefObject<HTMLElement | null>,\n forwarded: ForwardedRef<HTMLElement>,\n) {\n localRef.current = node;\n if (typeof forwarded === \"function\") forwarded(node);\n else if (forwarded) (forwarded as MutableRefObject<HTMLElement | null>).current = node;\n}\n\nfunction tileEntryOffset(logo: HeroLogo) {\n // Push the tile further out from the centre along the line it already sits on,\n // so it appears to fly in from off-screen.\n return { x: (logo.x - 50) * 4, y: (logo.y - 50) * 4 };\n}\n\n/** Parallax — centred copy with floating logo tiles that react to the mouse. */\nexport const HeroParallax = forwardRef<HTMLElement, HeroProps>(\n (\n { className, eyebrow, title, description, primaryAction, secondaryAction, meta, media, logos = [], id, ...props },\n ref,\n ) => {\n const sectionRef = useRef<HTMLElement | null>(null);\n const [offset, setOffset] = useState({ x: 0, y: 0 });\n const [entered, setEntered] = useState(false);\n const [reduceMotion, setReduceMotion] = useState(false);\n\n useEffect(() => {\n if (typeof window.matchMedia !== \"function\") return;\n const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n const onChange = () => setReduceMotion(mq.matches);\n setReduceMotion(mq.matches);\n mq.addEventListener?.(\"change\", onChange);\n return () => mq.removeEventListener?.(\"change\", onChange);\n }, []);\n\n useEffect(() => {\n if (reduceMotion) return;\n const raf = requestAnimationFrame(() => setEntered(true));\n return () => cancelAnimationFrame(raf);\n }, [reduceMotion]);\n\n const handleMouseMove = (e: MouseEvent<HTMLElement>) => {\n if (reduceMotion) return;\n const el = sectionRef.current;\n if (!el) return;\n const rect = el.getBoundingClientRect();\n setOffset({\n x: ((e.clientX - rect.left) / rect.width - 0.5) * 2,\n y: ((e.clientY - rect.top) / rect.height - 0.5) * 2,\n });\n };\n\n const handleMouseLeave = () => setOffset({ x: 0, y: 0 });\n\n const showResting = entered || reduceMotion;\n\n return (\n <section\n ref={(node) => setSectionRef(node, sectionRef, ref)}\n id={id}\n onMouseMove={handleMouseMove}\n onMouseLeave={handleMouseLeave}\n className={cn(\"relative w-full overflow-hidden border-b border-border bg-background\", className)}\n {...props}\n >\n {logos.map((logo, i) => {\n const depth = logo.depth ?? DEFAULT_DEPTH;\n const entry = tileEntryOffset(logo);\n const entryTransform = `translate(calc(-50% + ${entry.x}px), calc(-50% + ${entry.y}px)) scale(0.6)`;\n const restingTransform = \"translate(-50%, -50%)\";\n const parallaxTransform = reduceMotion\n ? \"translate(0px, 0px)\"\n : `translate(${offset.x * depth}px, ${offset.y * depth}px)`;\n\n return (\n <div\n key={logo.label}\n aria-hidden\n data-logo={logo.label}\n className=\"pointer-events-none absolute z-10 hidden transition-[transform,opacity] duration-700 ease-out md:block\"\n style={{\n left: `${logo.x}%`,\n top: `${logo.y}%`,\n transform: showResting ? restingTransform : entryTransform,\n opacity: showResting ? 1 : 0,\n transitionDelay: `${i * 90}ms`,\n }}\n >\n <div\n className=\"transition-transform duration-200 ease-out\"\n style={{ transform: parallaxTransform }}\n >\n <div\n className=\"flex h-16 w-16 items-center justify-center rounded-2xl border border-border bg-surface shadow-raised\"\n style={\n reduceMotion\n ? undefined\n : {\n animation: `hero-float ${FLOAT_DURATION}s ease-in-out infinite`,\n animationDelay: `-${i * FLOAT_STAGGER}s`,\n }\n }\n >\n {logo.icon}\n </div>\n </div>\n </div>\n );\n })}\n\n <div className={cn(heroContainer, \"relative z-20 pt-24 pb-20 text-center sm:pt-32 sm:pb-24\")}>\n <HeroEyebrow eyebrow={eyebrow} />\n <h1 className={cn(heroTitleBase, \"mt-7 max-w-4xl text-5xl leading-[1.04] sm:text-6xl lg:text-7xl\")}>\n {title}\n </h1>\n {description ? (\n <p className=\"mx-auto mt-7 max-w-xl text-pretty leading-relaxed text-muted-foreground sm:text-lg\">\n {description}\n </p>\n ) : null}\n {primaryAction || secondaryAction ? (\n <div className=\"mt-10 justify-center\">\n <HeroActions primaryAction={primaryAction} secondaryAction={secondaryAction} />\n </div>\n ) : null}\n {meta ? (\n <ul className=\"mt-14 flex flex-wrap items-center justify-center gap-x-9 gap-y-3\">\n {meta.map((m, i) => (\n <li\n key={m.label}\n className={cn(\n \"flex items-center gap-2 text-sm text-muted-foreground\",\n i > 0 && \"sm:border-l sm:border-border sm:pl-9\",\n )}\n >\n {m.icon ? <span className=\"text-foreground/35\">{m.icon}</span> : null}\n {m.label}\n </li>\n ))}\n </ul>\n ) : null}\n {media ? <div className=\"mt-16 w-full\">{media}</div> : null}\n </div>\n </section>\n );\n },\n);\nHeroParallax.displayName = \"HeroParallax\";\n"],"names":["DEFAULT_DEPTH","FLOAT_DURATION","FLOAT_STAGGER","setSectionRef","node","localRef","forwarded","tileEntryOffset","logo","HeroParallax","forwardRef","className","eyebrow","title","description","primaryAction","secondaryAction","meta","media","logos","id","props","ref","sectionRef","useRef","offset","setOffset","useState","entered","setEntered","reduceMotion","setReduceMotion","useEffect","mq","onChange","_a","raf","handleMouseMove","el","rect","handleMouseLeave","showResting","jsxs","cn","i","depth","entry","entryTransform","restingTransform","parallaxTransform","jsx","heroContainer","HeroEyebrow","heroTitleBase","HeroActions","m"],"mappings":"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"}
|
|
@@ -2,10 +2,11 @@ import { jsx as t } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef as m } from "react";
|
|
3
3
|
import { HeroSplit as i } from "./variants/Split.js";
|
|
4
4
|
import { HeroCentered as f } from "./variants/Centered.js";
|
|
5
|
-
import { HeroStatement as
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { HeroStatement as a } from "./variants/Statement.js";
|
|
6
|
+
import { HeroParallax as l } from "./variants/Parallax.js";
|
|
7
|
+
const n = m(({ variant: o = "split", ...r }, e) => o === "centered" ? /* @__PURE__ */ t(f, { ref: e, ...r }) : o === "statement" ? /* @__PURE__ */ t(a, { ref: e, ...r }) : o === "parallax" ? /* @__PURE__ */ t(l, { ref: e, ...r }) : /* @__PURE__ */ t(i, { ref: e, ...r }));
|
|
8
|
+
n.displayName = "Hero";
|
|
8
9
|
export {
|
|
9
|
-
|
|
10
|
+
n as Hero
|
|
10
11
|
};
|
|
11
12
|
//# sourceMappingURL=Hero.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Hero.js","sources":["../../../../src/components/Hero/Hero.tsx"],"sourcesContent":["import { forwardRef } from \"react\";\nimport type { HeroProps } from \"./types\";\nimport { HeroSplit } from \"./variants/Split\";\nimport { HeroCentered } from \"./variants/Centered\";\nimport { HeroStatement } from \"./variants/Statement\";\n\nexport const Hero = forwardRef<HTMLElement, HeroProps>(({ variant = \"split\", ...props }, ref) => {\n if (variant === \"centered\") return <HeroCentered ref={ref} {...props} />;\n if (variant === \"statement\") return <HeroStatement ref={ref} {...props} />;\n return <HeroSplit ref={ref} {...props} />;\n});\nHero.displayName = \"Hero\";"],"names":["Hero","forwardRef","variant","props","ref","HeroCentered","HeroStatement","jsx","HeroSplit"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Hero.js","sources":["../../../../src/components/Hero/Hero.tsx"],"sourcesContent":["import { forwardRef } from \"react\";\nimport type { HeroProps } from \"./types\";\nimport { HeroSplit } from \"./variants/Split\";\nimport { HeroCentered } from \"./variants/Centered\";\nimport { HeroStatement } from \"./variants/Statement\";\nimport { HeroParallax } from \"./variants/Parallax\";\n\nexport const Hero = forwardRef<HTMLElement, HeroProps>(({ variant = \"split\", ...props }, ref) => {\n if (variant === \"centered\") return <HeroCentered ref={ref} {...props} />;\n if (variant === \"statement\") return <HeroStatement ref={ref} {...props} />;\n if (variant === \"parallax\") return <HeroParallax ref={ref} {...props} />;\n return <HeroSplit ref={ref} {...props} />;\n});\nHero.displayName = \"Hero\";"],"names":["Hero","forwardRef","variant","props","ref","HeroCentered","HeroStatement","HeroParallax","jsx","HeroSplit"],"mappings":";;;;;;AAOO,MAAMA,IAAOC,EAAmC,CAAC,EAAE,SAAAC,IAAU,SAAS,GAAGC,EAAA,GAASC,MACnFF,MAAY,+BAAoBG,GAAA,EAAa,KAAAD,GAAW,GAAGD,GAAO,IAClED,MAAY,gCAAqBI,GAAA,EAAc,KAAAF,GAAW,GAAGD,GAAO,IACpED,MAAY,+BAAoBK,GAAA,EAAa,KAAAH,GAAW,GAAGD,GAAO,IAC/D,gBAAAK,EAACC,GAAA,EAAU,KAAAL,GAAW,GAAGD,EAAA,CAAO,CACxC;AACDH,EAAK,cAAc;"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as d, jsx as t } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as O, useRef as C, useState as u, useEffect as w } from "react";
|
|
4
|
+
import { cn as i } from "../../../utils/cn.js";
|
|
5
|
+
import { HeroEyebrow as P, heroTitleBase as S, HeroActions as _, heroContainer as k } from "../parts.js";
|
|
6
|
+
const z = 18, B = 5, G = 0.7;
|
|
7
|
+
function U(s, c, a) {
|
|
8
|
+
c.current = s, typeof a == "function" ? a(s) : a && (a.current = s);
|
|
9
|
+
}
|
|
10
|
+
function I(s) {
|
|
11
|
+
return { x: (s.x - 50) * 4, y: (s.y - 50) * 4 };
|
|
12
|
+
}
|
|
13
|
+
const X = O(
|
|
14
|
+
({ className: s, eyebrow: c, title: a, description: f, primaryAction: m, secondaryAction: x, meta: h, media: p, logos: M = [], id: T, ...E }, R) => {
|
|
15
|
+
const y = C(null), [b, v] = u({ x: 0, y: 0 }), [$, L] = u(!1), [o, g] = u(!1);
|
|
16
|
+
w(() => {
|
|
17
|
+
var r;
|
|
18
|
+
if (typeof window.matchMedia != "function") return;
|
|
19
|
+
const e = window.matchMedia("(prefers-reduced-motion: reduce)"), n = () => g(e.matches);
|
|
20
|
+
return g(e.matches), (r = e.addEventListener) == null || r.call(e, "change", n), () => {
|
|
21
|
+
var l;
|
|
22
|
+
return (l = e.removeEventListener) == null ? void 0 : l.call(e, "change", n);
|
|
23
|
+
};
|
|
24
|
+
}, []), w(() => {
|
|
25
|
+
if (o) return;
|
|
26
|
+
const e = requestAnimationFrame(() => L(!0));
|
|
27
|
+
return () => cancelAnimationFrame(e);
|
|
28
|
+
}, [o]);
|
|
29
|
+
const A = (e) => {
|
|
30
|
+
if (o) return;
|
|
31
|
+
const n = y.current;
|
|
32
|
+
if (!n) return;
|
|
33
|
+
const r = n.getBoundingClientRect();
|
|
34
|
+
v({
|
|
35
|
+
x: ((e.clientX - r.left) / r.width - 0.5) * 2,
|
|
36
|
+
y: ((e.clientY - r.top) / r.height - 0.5) * 2
|
|
37
|
+
});
|
|
38
|
+
}, j = () => v({ x: 0, y: 0 }), N = $ || o;
|
|
39
|
+
return /* @__PURE__ */ d(
|
|
40
|
+
"section",
|
|
41
|
+
{
|
|
42
|
+
ref: (e) => U(e, y, R),
|
|
43
|
+
id: T,
|
|
44
|
+
onMouseMove: A,
|
|
45
|
+
onMouseLeave: j,
|
|
46
|
+
className: i("relative w-full overflow-hidden border-b border-border bg-background", s),
|
|
47
|
+
...E,
|
|
48
|
+
children: [
|
|
49
|
+
M.map((e, n) => {
|
|
50
|
+
const r = e.depth ?? z, l = I(e), D = `translate(calc(-50% + ${l.x}px), calc(-50% + ${l.y}px)) scale(0.6)`, F = "translate(-50%, -50%)", H = o ? "translate(0px, 0px)" : `translate(${b.x * r}px, ${b.y * r}px)`;
|
|
51
|
+
return /* @__PURE__ */ t(
|
|
52
|
+
"div",
|
|
53
|
+
{
|
|
54
|
+
"aria-hidden": !0,
|
|
55
|
+
"data-logo": e.label,
|
|
56
|
+
className: "pointer-events-none absolute z-10 hidden transition-[transform,opacity] duration-700 ease-out md:block",
|
|
57
|
+
style: {
|
|
58
|
+
left: `${e.x}%`,
|
|
59
|
+
top: `${e.y}%`,
|
|
60
|
+
transform: N ? F : D,
|
|
61
|
+
opacity: N ? 1 : 0,
|
|
62
|
+
transitionDelay: `${n * 90}ms`
|
|
63
|
+
},
|
|
64
|
+
children: /* @__PURE__ */ t(
|
|
65
|
+
"div",
|
|
66
|
+
{
|
|
67
|
+
className: "transition-transform duration-200 ease-out",
|
|
68
|
+
style: { transform: H },
|
|
69
|
+
children: /* @__PURE__ */ t(
|
|
70
|
+
"div",
|
|
71
|
+
{
|
|
72
|
+
className: "flex h-16 w-16 items-center justify-center rounded-2xl border border-border bg-surface shadow-raised",
|
|
73
|
+
style: o ? void 0 : {
|
|
74
|
+
animation: `hero-float ${B}s ease-in-out infinite`,
|
|
75
|
+
animationDelay: `-${n * G}s`
|
|
76
|
+
},
|
|
77
|
+
children: e.icon
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
},
|
|
83
|
+
e.label
|
|
84
|
+
);
|
|
85
|
+
}),
|
|
86
|
+
/* @__PURE__ */ d("div", { className: i(k, "relative z-20 pt-24 pb-20 text-center sm:pt-32 sm:pb-24"), children: [
|
|
87
|
+
/* @__PURE__ */ t(P, { eyebrow: c }),
|
|
88
|
+
/* @__PURE__ */ t("h1", { className: i(S, "mt-7 max-w-4xl text-5xl leading-[1.04] sm:text-6xl lg:text-7xl"), children: a }),
|
|
89
|
+
f ? /* @__PURE__ */ t("p", { className: "mx-auto mt-7 max-w-xl text-pretty leading-relaxed text-muted-foreground sm:text-lg", children: f }) : null,
|
|
90
|
+
m || x ? /* @__PURE__ */ t("div", { className: "mt-10 justify-center", children: /* @__PURE__ */ t(_, { primaryAction: m, secondaryAction: x }) }) : null,
|
|
91
|
+
h ? /* @__PURE__ */ t("ul", { className: "mt-14 flex flex-wrap items-center justify-center gap-x-9 gap-y-3", children: h.map((e, n) => /* @__PURE__ */ d(
|
|
92
|
+
"li",
|
|
93
|
+
{
|
|
94
|
+
className: i(
|
|
95
|
+
"flex items-center gap-2 text-sm text-muted-foreground",
|
|
96
|
+
n > 0 && "sm:border-l sm:border-border sm:pl-9"
|
|
97
|
+
),
|
|
98
|
+
children: [
|
|
99
|
+
e.icon ? /* @__PURE__ */ t("span", { className: "text-foreground/35", children: e.icon }) : null,
|
|
100
|
+
e.label
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
e.label
|
|
104
|
+
)) }) : null,
|
|
105
|
+
p ? /* @__PURE__ */ t("div", { className: "mt-16 w-full", children: p }) : null
|
|
106
|
+
] })
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
X.displayName = "HeroParallax";
|
|
113
|
+
export {
|
|
114
|
+
X as HeroParallax
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=Parallax.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Parallax.js","sources":["../../../../../src/components/Hero/variants/Parallax.tsx"],"sourcesContent":["\"use client\";\nimport {\n forwardRef,\n useEffect,\n useRef,\n useState,\n type ForwardedRef,\n type MouseEvent,\n type MutableRefObject,\n} from \"react\";\nimport { cn } from \"../../../utils/cn\";\nimport { HeroActions, HeroEyebrow, heroContainer, heroTitleBase } from \"../parts\";\nimport type { HeroLogo, HeroProps } from \"../types\";\n\nconst DEFAULT_DEPTH = 18;\nconst FLOAT_DURATION = 5; // seconds per breathing cycle\nconst FLOAT_STAGGER = 0.7; // seconds of desync between consecutive tiles\n\nfunction setSectionRef(\n node: HTMLElement | null,\n localRef: MutableRefObject<HTMLElement | null>,\n forwarded: ForwardedRef<HTMLElement>,\n) {\n localRef.current = node;\n if (typeof forwarded === \"function\") forwarded(node);\n else if (forwarded) (forwarded as MutableRefObject<HTMLElement | null>).current = node;\n}\n\nfunction tileEntryOffset(logo: HeroLogo) {\n // Push the tile further out from the centre along the line it already sits on,\n // so it appears to fly in from off-screen.\n return { x: (logo.x - 50) * 4, y: (logo.y - 50) * 4 };\n}\n\n/** Parallax — centred copy with floating logo tiles that react to the mouse. */\nexport const HeroParallax = forwardRef<HTMLElement, HeroProps>(\n (\n { className, eyebrow, title, description, primaryAction, secondaryAction, meta, media, logos = [], id, ...props },\n ref,\n ) => {\n const sectionRef = useRef<HTMLElement | null>(null);\n const [offset, setOffset] = useState({ x: 0, y: 0 });\n const [entered, setEntered] = useState(false);\n const [reduceMotion, setReduceMotion] = useState(false);\n\n useEffect(() => {\n if (typeof window.matchMedia !== \"function\") return;\n const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n const onChange = () => setReduceMotion(mq.matches);\n setReduceMotion(mq.matches);\n mq.addEventListener?.(\"change\", onChange);\n return () => mq.removeEventListener?.(\"change\", onChange);\n }, []);\n\n useEffect(() => {\n if (reduceMotion) return;\n const raf = requestAnimationFrame(() => setEntered(true));\n return () => cancelAnimationFrame(raf);\n }, [reduceMotion]);\n\n const handleMouseMove = (e: MouseEvent<HTMLElement>) => {\n if (reduceMotion) return;\n const el = sectionRef.current;\n if (!el) return;\n const rect = el.getBoundingClientRect();\n setOffset({\n x: ((e.clientX - rect.left) / rect.width - 0.5) * 2,\n y: ((e.clientY - rect.top) / rect.height - 0.5) * 2,\n });\n };\n\n const handleMouseLeave = () => setOffset({ x: 0, y: 0 });\n\n const showResting = entered || reduceMotion;\n\n return (\n <section\n ref={(node) => setSectionRef(node, sectionRef, ref)}\n id={id}\n onMouseMove={handleMouseMove}\n onMouseLeave={handleMouseLeave}\n className={cn(\"relative w-full overflow-hidden border-b border-border bg-background\", className)}\n {...props}\n >\n {logos.map((logo, i) => {\n const depth = logo.depth ?? DEFAULT_DEPTH;\n const entry = tileEntryOffset(logo);\n const entryTransform = `translate(calc(-50% + ${entry.x}px), calc(-50% + ${entry.y}px)) scale(0.6)`;\n const restingTransform = \"translate(-50%, -50%)\";\n const parallaxTransform = reduceMotion\n ? \"translate(0px, 0px)\"\n : `translate(${offset.x * depth}px, ${offset.y * depth}px)`;\n\n return (\n <div\n key={logo.label}\n aria-hidden\n data-logo={logo.label}\n className=\"pointer-events-none absolute z-10 hidden transition-[transform,opacity] duration-700 ease-out md:block\"\n style={{\n left: `${logo.x}%`,\n top: `${logo.y}%`,\n transform: showResting ? restingTransform : entryTransform,\n opacity: showResting ? 1 : 0,\n transitionDelay: `${i * 90}ms`,\n }}\n >\n <div\n className=\"transition-transform duration-200 ease-out\"\n style={{ transform: parallaxTransform }}\n >\n <div\n className=\"flex h-16 w-16 items-center justify-center rounded-2xl border border-border bg-surface shadow-raised\"\n style={\n reduceMotion\n ? undefined\n : {\n animation: `hero-float ${FLOAT_DURATION}s ease-in-out infinite`,\n animationDelay: `-${i * FLOAT_STAGGER}s`,\n }\n }\n >\n {logo.icon}\n </div>\n </div>\n </div>\n );\n })}\n\n <div className={cn(heroContainer, \"relative z-20 pt-24 pb-20 text-center sm:pt-32 sm:pb-24\")}>\n <HeroEyebrow eyebrow={eyebrow} />\n <h1 className={cn(heroTitleBase, \"mt-7 max-w-4xl text-5xl leading-[1.04] sm:text-6xl lg:text-7xl\")}>\n {title}\n </h1>\n {description ? (\n <p className=\"mx-auto mt-7 max-w-xl text-pretty leading-relaxed text-muted-foreground sm:text-lg\">\n {description}\n </p>\n ) : null}\n {primaryAction || secondaryAction ? (\n <div className=\"mt-10 justify-center\">\n <HeroActions primaryAction={primaryAction} secondaryAction={secondaryAction} />\n </div>\n ) : null}\n {meta ? (\n <ul className=\"mt-14 flex flex-wrap items-center justify-center gap-x-9 gap-y-3\">\n {meta.map((m, i) => (\n <li\n key={m.label}\n className={cn(\n \"flex items-center gap-2 text-sm text-muted-foreground\",\n i > 0 && \"sm:border-l sm:border-border sm:pl-9\",\n )}\n >\n {m.icon ? <span className=\"text-foreground/35\">{m.icon}</span> : null}\n {m.label}\n </li>\n ))}\n </ul>\n ) : null}\n {media ? <div className=\"mt-16 w-full\">{media}</div> : null}\n </div>\n </section>\n );\n },\n);\nHeroParallax.displayName = \"HeroParallax\";\n"],"names":["DEFAULT_DEPTH","FLOAT_DURATION","FLOAT_STAGGER","setSectionRef","node","localRef","forwarded","tileEntryOffset","logo","HeroParallax","forwardRef","className","eyebrow","title","description","primaryAction","secondaryAction","meta","media","logos","id","props","ref","sectionRef","useRef","offset","setOffset","useState","entered","setEntered","reduceMotion","setReduceMotion","useEffect","mq","onChange","_a","raf","handleMouseMove","el","rect","handleMouseLeave","showResting","jsxs","cn","i","depth","entry","entryTransform","restingTransform","parallaxTransform","jsx","heroContainer","HeroEyebrow","heroTitleBase","HeroActions","m"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -328,6 +328,22 @@ export declare const Globe: (props: IconProps) => JSX_2.Element;
|
|
|
328
328
|
|
|
329
329
|
export declare const Hero: ForwardRefExoticComponent<HeroProps & RefAttributes<HTMLElement>>;
|
|
330
330
|
|
|
331
|
+
/** A floating logo tile for the `parallax` hero variant. */
|
|
332
|
+
export declare interface HeroLogo {
|
|
333
|
+
/** Accessible name for the tile (used for the label/aria). */
|
|
334
|
+
label: string;
|
|
335
|
+
/** Logo/icon node rendered inside the tile. */
|
|
336
|
+
icon: ReactNode;
|
|
337
|
+
/** Horizontal position as a percentage (0–100) of the hero width. */
|
|
338
|
+
x: number;
|
|
339
|
+
/** Vertical position as a percentage (0–100) of the hero height. */
|
|
340
|
+
y: number;
|
|
341
|
+
/** Parallax travel in px per unit of mouse offset. Higher moves more. */
|
|
342
|
+
depth?: number;
|
|
343
|
+
/** Optional icon accent color. */
|
|
344
|
+
accent?: string;
|
|
345
|
+
}
|
|
346
|
+
|
|
331
347
|
export declare interface HeroMeta {
|
|
332
348
|
label: string;
|
|
333
349
|
icon?: ReactNode;
|
|
@@ -343,9 +359,11 @@ export declare interface HeroProps extends Omit<HTMLAttributes<HTMLElement>, "ti
|
|
|
343
359
|
media?: ReactNode;
|
|
344
360
|
/** Small trust/status row shown under the copy or CTAs. */
|
|
345
361
|
meta?: HeroMeta[];
|
|
362
|
+
/** Floating logo tiles with a mouse parallax effect (`parallax` variant). */
|
|
363
|
+
logos?: HeroLogo[];
|
|
346
364
|
}
|
|
347
365
|
|
|
348
|
-
declare type HeroVariant = "split" | "centered" | "statement";
|
|
366
|
+
declare type HeroVariant = "split" | "centered" | "statement" | "parallax";
|
|
349
367
|
|
|
350
368
|
/**
|
|
351
369
|
* Minimal 1.5px stroke icon set (Lucide/Heroicons inspired).
|
package/dist/styles.css
CHANGED
|
@@ -63,7 +63,8 @@
|
|
|
63
63
|
--radius-lg: 0.875rem;
|
|
64
64
|
--radius-xl: 1.125rem;
|
|
65
65
|
--radius-2xl: 1.5rem;
|
|
66
|
-
--
|
|
66
|
+
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
|
67
|
+
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
|
67
68
|
--blur-md: 12px;
|
|
68
69
|
--blur-2xl: 40px;
|
|
69
70
|
--default-transition-duration: 150ms;
|
|
@@ -90,9 +91,6 @@
|
|
|
90
91
|
--color-danger: rgb(var(--color-danger));
|
|
91
92
|
--color-danger-soft: rgb(var(--color-danger-soft));
|
|
92
93
|
--font-display: "Space Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
93
|
-
--shadow-soft: 0 1px 2px rgb(17 17 17 / 0.04), 0 2px 8px rgb(17 17 17 / 0.04);
|
|
94
|
-
--shadow-raised: 0 2px 4px rgb(17 17 17 / 0.05), 0 12px 32px rgb(17 17 17 / 0.08);
|
|
95
|
-
--shadow-overlay: 0 24px 64px -12px rgb(17 17 17 / 0.18);
|
|
96
94
|
--animate-marquee: marquee 38s linear infinite;
|
|
97
95
|
}
|
|
98
96
|
}
|
|
@@ -319,6 +317,12 @@
|
|
|
319
317
|
.left-7 {
|
|
320
318
|
left: calc(var(--spacing) * 7);
|
|
321
319
|
}
|
|
320
|
+
.z-10 {
|
|
321
|
+
z-index: 10;
|
|
322
|
+
}
|
|
323
|
+
.z-20 {
|
|
324
|
+
z-index: 20;
|
|
325
|
+
}
|
|
322
326
|
.z-50 {
|
|
323
327
|
z-index: 50;
|
|
324
328
|
}
|
|
@@ -1797,6 +1801,9 @@
|
|
|
1797
1801
|
.w-12 {
|
|
1798
1802
|
width: calc(var(--spacing) * 12);
|
|
1799
1803
|
}
|
|
1804
|
+
.w-16 {
|
|
1805
|
+
width: calc(var(--spacing) * 16);
|
|
1806
|
+
}
|
|
1800
1807
|
.w-28 {
|
|
1801
1808
|
width: calc(var(--spacing) * 28);
|
|
1802
1809
|
}
|
|
@@ -2227,12 +2234,6 @@
|
|
|
2227
2234
|
background-color: color-mix(in oklab, rgb(var(--color-primary-soft)) 60%, transparent);
|
|
2228
2235
|
}
|
|
2229
2236
|
}
|
|
2230
|
-
.bg-primary\/50 {
|
|
2231
|
-
background-color: color-mix(in srgb, rgb(rgb(var(--color-primary))) 50%, transparent);
|
|
2232
|
-
@supports (color: color-mix(in lab, red, red)) {
|
|
2233
|
-
background-color: color-mix(in oklab, rgb(var(--color-primary)) 50%, transparent);
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
2237
|
.bg-secondary {
|
|
2237
2238
|
background-color: rgb(var(--color-secondary));
|
|
2238
2239
|
}
|
|
@@ -2878,6 +2879,11 @@
|
|
|
2878
2879
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
|
2879
2880
|
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
|
2880
2881
|
}
|
|
2882
|
+
.transition-\[transform\,opacity\] {
|
|
2883
|
+
transition-property: transform,opacity;
|
|
2884
|
+
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
|
2885
|
+
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
|
2886
|
+
}
|
|
2881
2887
|
.transition-all {
|
|
2882
2888
|
transition-property: all;
|
|
2883
2889
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
|
@@ -2905,6 +2911,18 @@
|
|
|
2905
2911
|
--tw-duration: 300ms;
|
|
2906
2912
|
transition-duration: 300ms;
|
|
2907
2913
|
}
|
|
2914
|
+
.duration-700 {
|
|
2915
|
+
--tw-duration: 700ms;
|
|
2916
|
+
transition-duration: 700ms;
|
|
2917
|
+
}
|
|
2918
|
+
.ease-in-out {
|
|
2919
|
+
--tw-ease: var(--ease-in-out);
|
|
2920
|
+
transition-timing-function: var(--ease-in-out);
|
|
2921
|
+
}
|
|
2922
|
+
.ease-out {
|
|
2923
|
+
--tw-ease: var(--ease-out);
|
|
2924
|
+
transition-timing-function: var(--ease-out);
|
|
2925
|
+
}
|
|
2908
2926
|
.prose-neutral {
|
|
2909
2927
|
--tw-prose-body: oklch(37.1% 0 none);
|
|
2910
2928
|
--tw-prose-headings: oklch(20.5% 0 none);
|
|
@@ -3317,6 +3335,9 @@
|
|
|
3317
3335
|
}
|
|
3318
3336
|
}
|
|
3319
3337
|
@media (width >= 48rem) {
|
|
3338
|
+
.md\:block {
|
|
3339
|
+
display: block;
|
|
3340
|
+
}
|
|
3320
3341
|
.md\:flex {
|
|
3321
3342
|
display: flex;
|
|
3322
3343
|
}
|
|
@@ -3529,6 +3550,14 @@
|
|
|
3529
3550
|
transform: translateX(-50%);
|
|
3530
3551
|
}
|
|
3531
3552
|
}
|
|
3553
|
+
@keyframes hero-float {
|
|
3554
|
+
0%, 100% {
|
|
3555
|
+
transform: translateY(0);
|
|
3556
|
+
}
|
|
3557
|
+
50% {
|
|
3558
|
+
transform: translateY(-7px);
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3532
3561
|
.modal-anim {
|
|
3533
3562
|
transition: opacity 150ms ease, transform 150ms ease, overlay 150ms ease allow-discrete, display 150ms ease allow-discrete;
|
|
3534
3563
|
}
|
|
@@ -3817,6 +3846,10 @@
|
|
|
3817
3846
|
syntax: "*";
|
|
3818
3847
|
inherits: false;
|
|
3819
3848
|
}
|
|
3849
|
+
@property --tw-ease {
|
|
3850
|
+
syntax: "*";
|
|
3851
|
+
inherits: false;
|
|
3852
|
+
}
|
|
3820
3853
|
@layer properties {
|
|
3821
3854
|
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
|
3822
3855
|
*, ::before, ::after, ::backdrop {
|
|
@@ -3877,6 +3910,7 @@
|
|
|
3877
3910
|
--tw-backdrop-saturate: initial;
|
|
3878
3911
|
--tw-backdrop-sepia: initial;
|
|
3879
3912
|
--tw-duration: initial;
|
|
3913
|
+
--tw-ease: initial;
|
|
3880
3914
|
}
|
|
3881
3915
|
}
|
|
3882
3916
|
}
|
package/dist/theme.css
CHANGED
|
@@ -199,6 +199,17 @@
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
/* Gentle vertical "breathing" drift for floating hero logo tiles. */
|
|
203
|
+
@keyframes hero-float {
|
|
204
|
+
0%,
|
|
205
|
+
100% {
|
|
206
|
+
transform: translateY(0);
|
|
207
|
+
}
|
|
208
|
+
50% {
|
|
209
|
+
transform: translateY(-7px);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
202
213
|
/* ── Popover-based Modal (dialog) ────────────────────────── */
|
|
203
214
|
|
|
204
215
|
.modal-anim {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ugurdemirel/landcraft",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"test": "vitest run",
|
|
39
39
|
"test:watch": "vitest",
|
|
40
40
|
"clean": "rimraf dist",
|
|
41
|
+
"prepack": "cp ../../README.md ./README.md",
|
|
41
42
|
"prepublishOnly": "pnpm run build"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|