Compare commits
15
Commits
5d52e12bd9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe129bc92e | ||
|
|
9e734f29b8 | ||
|
|
b8f7168d64 | ||
|
|
d8eb5d7504 | ||
|
|
61a87b1f9a | ||
|
|
747fa89322 | ||
|
|
b3248dcd1f | ||
|
|
8f44c57e7c | ||
|
|
418031c6ae | ||
|
|
831a8e88f4 | ||
|
|
65297587ab | ||
|
|
2fda722b26 | ||
|
|
0a1c75fbdf | ||
|
|
6379ba973f | ||
|
|
c7caed0c36 |
@@ -0,0 +1,34 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 12
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 26
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- run: pnpm build
|
||||||
|
|
||||||
|
- run: pnpm exec wrangler deploy
|
||||||
|
env:
|
||||||
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
@@ -39,3 +39,10 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
|
||||||
|
#Ignore vscode AI rules
|
||||||
|
.github/instructions/codacy.instructions.md
|
||||||
|
|
||||||
|
# wrangler
|
||||||
|
.wrangler
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
22.14.0
|
||||||
@@ -1,36 +1,44 @@
|
|||||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
# Woiken Website
|
||||||
|
|
||||||
## Getting Started
|
The landing page for [woiken.com](https://woiken.com), a coming-soon page for Woiken.
|
||||||
|
|
||||||
First, run the development server:
|
It's a single page built with Next.js 16 and exported as static files. Scrolling opens a
|
||||||
|
framed sky photo up to full screen, where the "A new cloud, coming soon." tagline fades in.
|
||||||
|
|
||||||
```bash
|
## Commands
|
||||||
npm run dev
|
|
||||||
# or
|
| Command | Action |
|
||||||
yarn dev
|
| :------------------------- | :-------------------------------------------------- |
|
||||||
# or
|
| `pnpm install` | Install dependencies |
|
||||||
pnpm dev
|
| `pnpm dev` | Start the dev server at `localhost:3000` |
|
||||||
# or
|
| `pnpm build` | Build the static site to `./out/` |
|
||||||
bun dev
|
| `pnpm lint` | Lint with ESLint |
|
||||||
|
| `pnpm exec wrangler dev` | Serve `./out/` locally the way Cloudflare will |
|
||||||
|
|
||||||
|
Use pnpm 12. `pnpm start` doesn't work, because the site is a static export (`output: "export"`).
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── app/
|
||||||
|
│ ├── page.tsx # The landing page
|
||||||
|
│ ├── layout.tsx
|
||||||
|
│ └── globals.css # Tailwind + photo darkening
|
||||||
|
├── components/
|
||||||
|
│ └── ScrollExpand/ # Scroll-to-expand hero (from React Bits)
|
||||||
|
├── public/
|
||||||
|
│ └── grass-field.jpg # Hero photo
|
||||||
|
├── wrangler.jsonc # Cloudflare Worker config
|
||||||
|
└── .gitea/workflows/deploy.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
## Deployment
|
||||||
|
|
||||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
Every push to `main` deploys the site via `.gitea/workflows/deploy.yml`. The workflow builds
|
||||||
|
the site and runs `wrangler deploy`, which uploads `./out/` as static assets to the
|
||||||
|
`woiken-website` Worker on the `woiken.com` custom domain.
|
||||||
|
## Credits
|
||||||
|
|
||||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
Hero photo: [grass field at daytime](https://unsplash.com/photos/grass-field-at-daytime-PEm_sLmJT-w)
|
||||||
|
on Unsplash.
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
|
||||||
|
|||||||
@@ -2,3 +2,8 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
|
||||||
|
/* Tone down the bright sky so the white type reads on top of it */
|
||||||
|
.woiken-hero .scroll-expand__media {
|
||||||
|
filter: brightness(0.72) saturate(1.1);
|
||||||
|
}
|
||||||
|
|||||||
+8
-10
@@ -1,10 +1,9 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import {ThemeProvider} from "next-themes";
|
import { ThemeProvider } from "next-themes";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -14,8 +13,11 @@ const geistMono = Geist_Mono({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Woiken",
|
||||||
description: "Generated by create next app",
|
description: "Woiken coming soon",
|
||||||
|
icons: {
|
||||||
|
icon: "/woiken-icon.svg",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -25,12 +27,8 @@ export default function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body
|
<body className={`${geistSans.className} ${geistMono.variable} antialiased`}>
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
<ThemeProvider defaultTheme="dark">{children}</ThemeProvider>
|
||||||
>
|
|
||||||
<ThemeProvider defaultTheme="dark">
|
|
||||||
{children}
|
|
||||||
</ThemeProvider>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
+29
-27
@@ -1,29 +1,31 @@
|
|||||||
"use client";
|
import ScrollExpand from "@/components/ScrollExpand/ScrollExpand";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
export default function Home() {
|
||||||
import React from "react";
|
return (
|
||||||
import { AuroraBackground } from "@/components/ui/aurora-background";
|
<div className="min-h-screen bg-[#0b0b08] text-white">
|
||||||
|
<ScrollExpand
|
||||||
|
className="woiken-hero"
|
||||||
|
src="/grass-field.jpg"
|
||||||
|
alt="Clouds drifting over a green field under a blue sky"
|
||||||
|
title="Woiken"
|
||||||
|
scrollHint="Scroll"
|
||||||
|
startWidth={46}
|
||||||
|
startHeight={56}
|
||||||
|
overlayScrim={0.55}
|
||||||
|
useWindowScroll
|
||||||
|
>
|
||||||
|
<p className="mb-4 text-xs font-medium uppercase tracking-[0.3em] text-white/70 md:text-sm">
|
||||||
|
Woiken
|
||||||
|
</p>
|
||||||
|
<h1 className="text-5xl font-semibold leading-[1.05] tracking-tight md:text-7xl lg:text-8xl">
|
||||||
|
A new cloud
|
||||||
|
</h1>
|
||||||
|
<p className="mt-5 text-lg text-white/80 md:text-2xl">coming soon.</p>
|
||||||
|
</ScrollExpand>
|
||||||
|
|
||||||
export default function AuroraBackgroundDemo() {
|
<footer className="px-6 py-10 text-center text-sm text-white/40">
|
||||||
return (
|
© {new Date().getFullYear()} Woiken
|
||||||
<AuroraBackground>
|
</footer>
|
||||||
<motion.div
|
</div>
|
||||||
initial={{ opacity: 0.0, y: 40 }}
|
);
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
}
|
||||||
transition={{
|
|
||||||
delay: 0.3,
|
|
||||||
duration: 0.8,
|
|
||||||
ease: "easeInOut",
|
|
||||||
}}
|
|
||||||
className="relative flex flex-col gap-4 items-center justify-center px-4"
|
|
||||||
>
|
|
||||||
<div className="text-3xl md:text-7xl font-bold dark:text-white text-center">
|
|
||||||
woiken.cloud
|
|
||||||
</div>
|
|
||||||
<div className="font-extralight text-base md:text-4xl dark:text-neutral-200 py-4">
|
|
||||||
Coming soon.
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</AuroraBackground>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
.scroll-expand {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-expand--scroller {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-expand--scroller::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-expand__track {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-expand__stage {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
--se-title-size: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-expand__frame {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
clip-path: inset(21% 29% 21% 29% round 24px);
|
||||||
|
will-change: clip-path;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-expand__media {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
will-change: transform;
|
||||||
|
transform-origin: center;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-expand__scrim {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.1) 45%, rgba(0, 0, 0, 0.35));
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-expand__overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 6%;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-expand__title {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 6%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--se-title-size);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
line-height: 1;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 2px 24px rgba(0, 0, 0, 0.45);
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-expand__hint {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 1.25rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: rgba(255, 255, 255, 0.55);
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
|
||||||
|
|
||||||
|
export interface ScrollExpandProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
||||||
|
src?: string;
|
||||||
|
mediaType?: "image" | "video";
|
||||||
|
poster?: string;
|
||||||
|
alt?: string;
|
||||||
|
title?: string;
|
||||||
|
scrollHint?: string;
|
||||||
|
startWidth?: number;
|
||||||
|
startHeight?: number;
|
||||||
|
startRadius?: number;
|
||||||
|
endRadius?: number;
|
||||||
|
mediaZoom?: number;
|
||||||
|
scrollDistance?: number;
|
||||||
|
holdDistance?: number;
|
||||||
|
smoothing?: number;
|
||||||
|
overlayScrim?: number;
|
||||||
|
useWindowScroll?: boolean;
|
||||||
|
enabled?: boolean;
|
||||||
|
children?: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
style?: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const ScrollExpand: (props: ScrollExpandProps) => JSX.Element;
|
||||||
|
|
||||||
|
export default ScrollExpand;
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
import './ScrollExpand.css';
|
||||||
|
|
||||||
|
const clamp = (v, a, b) => (v < a ? a : v > b ? b : v);
|
||||||
|
|
||||||
|
const smoothstep = (edge0, edge1, x) => {
|
||||||
|
const t = clamp((x - edge0) / (edge1 - edge0 || 1e-6), 0, 1);
|
||||||
|
return t * t * (3 - 2 * t);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ScrollExpand = ({
|
||||||
|
src = '',
|
||||||
|
mediaType = 'image',
|
||||||
|
poster = '',
|
||||||
|
alt = '',
|
||||||
|
title = '',
|
||||||
|
scrollHint = '',
|
||||||
|
startWidth = 42,
|
||||||
|
startHeight = 58,
|
||||||
|
startRadius = 24,
|
||||||
|
endRadius = 0,
|
||||||
|
mediaZoom = 1.35,
|
||||||
|
scrollDistance = 1.2,
|
||||||
|
holdDistance = 0.35,
|
||||||
|
smoothing = 0.1,
|
||||||
|
overlayScrim = 0.45,
|
||||||
|
useWindowScroll = false,
|
||||||
|
enabled = true,
|
||||||
|
children,
|
||||||
|
className = '',
|
||||||
|
style,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const rootRef = useRef(null);
|
||||||
|
const trackRef = useRef(null);
|
||||||
|
const stageRef = useRef(null);
|
||||||
|
const frameRef = useRef(null);
|
||||||
|
const mediaRef = useRef(null);
|
||||||
|
const titleRef = useRef(null);
|
||||||
|
const overlayRef = useRef(null);
|
||||||
|
const scrimRef = useRef(null);
|
||||||
|
const hintRef = useRef(null);
|
||||||
|
|
||||||
|
const propsRef = useRef({});
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
propsRef.current = {
|
||||||
|
startWidth,
|
||||||
|
startHeight,
|
||||||
|
startRadius,
|
||||||
|
endRadius,
|
||||||
|
mediaZoom,
|
||||||
|
scrollDistance,
|
||||||
|
holdDistance,
|
||||||
|
smoothing,
|
||||||
|
overlayScrim,
|
||||||
|
useWindowScroll,
|
||||||
|
enabled
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const applyProgress = useCallback(p => {
|
||||||
|
const frame = frameRef.current;
|
||||||
|
const media = mediaRef.current;
|
||||||
|
if (!frame || !media) return;
|
||||||
|
const c = propsRef.current;
|
||||||
|
|
||||||
|
const e = smoothstep(0, 1, p);
|
||||||
|
|
||||||
|
const w = c.startWidth + (100 - c.startWidth) * e;
|
||||||
|
const h = c.startHeight + (100 - c.startHeight) * e;
|
||||||
|
const ix = Math.max(0, (100 - w) / 2);
|
||||||
|
const iy = Math.max(0, (100 - h) / 2);
|
||||||
|
const r = c.startRadius + (c.endRadius - c.startRadius) * e;
|
||||||
|
frame.style.clipPath = `inset(${iy}% ${ix}% ${iy}% ${ix}% round ${r}px)`;
|
||||||
|
|
||||||
|
media.style.transform = `scale(${c.mediaZoom + (1 - c.mediaZoom) * e})`;
|
||||||
|
|
||||||
|
if (scrimRef.current) scrimRef.current.style.opacity = `${c.overlayScrim * e}`;
|
||||||
|
|
||||||
|
if (titleRef.current) {
|
||||||
|
const out = smoothstep(0.4, 0.88, p);
|
||||||
|
titleRef.current.style.opacity = `${1 - out}`;
|
||||||
|
titleRef.current.style.transform = `translate3d(0, ${-28 * out}px, 0) scale(${1 + 0.06 * out})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hintRef.current) {
|
||||||
|
const gone = smoothstep(0, 0.12, p);
|
||||||
|
hintRef.current.style.opacity = `${1 - gone}`;
|
||||||
|
hintRef.current.style.transform = `translate3d(0, ${8 * gone}px, 0)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlayRef.current) {
|
||||||
|
const inn = smoothstep(0.68, 1, p);
|
||||||
|
overlayRef.current.style.opacity = `${inn}`;
|
||||||
|
overlayRef.current.style.transform = `translate3d(0, ${18 * (1 - inn)}px, 0)`;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = rootRef.current;
|
||||||
|
const track = trackRef.current;
|
||||||
|
const stage = stageRef.current;
|
||||||
|
if (!root || !track || !stage) return;
|
||||||
|
|
||||||
|
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
|
let raf = 0;
|
||||||
|
let current = 0;
|
||||||
|
let target = 0;
|
||||||
|
let stageH = 0;
|
||||||
|
let running = false;
|
||||||
|
|
||||||
|
const measure = () => {
|
||||||
|
const c = propsRef.current;
|
||||||
|
stageH = c.useWindowScroll ? window.innerHeight : root.clientHeight;
|
||||||
|
if (stageH <= 0) return;
|
||||||
|
stage.style.height = `${stageH}px`;
|
||||||
|
track.style.height = `${stageH * (1 + Math.max(0, c.scrollDistance) + Math.max(0, c.holdDistance))}px`;
|
||||||
|
|
||||||
|
const w = root.clientWidth || stageH;
|
||||||
|
stage.style.setProperty('--se-title-size', `${clamp(w * 0.075, 20, 84)}px`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const readProgress = () => {
|
||||||
|
const c = propsRef.current;
|
||||||
|
if (!c.enabled) return 1;
|
||||||
|
const span = stageH * Math.max(0.01, c.scrollDistance);
|
||||||
|
if (c.useWindowScroll) {
|
||||||
|
const top = track.getBoundingClientRect().top;
|
||||||
|
return clamp(-top / span, 0, 1);
|
||||||
|
}
|
||||||
|
return clamp(root.scrollTop / span, 0, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tick = () => {
|
||||||
|
const c = propsRef.current;
|
||||||
|
const k = c.smoothing <= 0 ? 1 : 1 - Math.exp(-1 / (60 * c.smoothing));
|
||||||
|
current += (target - current) * k;
|
||||||
|
if (Math.abs(target - current) < 0.0004) {
|
||||||
|
current = target;
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
applyProgress(current);
|
||||||
|
raf = running ? requestAnimationFrame(tick) : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const kick = () => {
|
||||||
|
if (running) return;
|
||||||
|
running = true;
|
||||||
|
if (!raf) raf = requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onScroll = () => {
|
||||||
|
target = readProgress();
|
||||||
|
if (propsRef.current.smoothing <= 0 || reduceMotion) {
|
||||||
|
current = target;
|
||||||
|
applyProgress(current);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
kick();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResize = () => {
|
||||||
|
measure();
|
||||||
|
target = readProgress();
|
||||||
|
current = target;
|
||||||
|
applyProgress(current);
|
||||||
|
};
|
||||||
|
|
||||||
|
measure();
|
||||||
|
target = readProgress();
|
||||||
|
current = target;
|
||||||
|
applyProgress(current);
|
||||||
|
|
||||||
|
const scroller = useWindowScroll ? window : root;
|
||||||
|
scroller.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
window.addEventListener('resize', onResize);
|
||||||
|
const ro = new ResizeObserver(onResize);
|
||||||
|
ro.observe(root);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (raf) cancelAnimationFrame(raf);
|
||||||
|
scroller.removeEventListener('scroll', onScroll);
|
||||||
|
window.removeEventListener('resize', onResize);
|
||||||
|
ro.disconnect();
|
||||||
|
};
|
||||||
|
}, [applyProgress, useWindowScroll]);
|
||||||
|
|
||||||
|
const media =
|
||||||
|
mediaType === 'video' ? (
|
||||||
|
<video
|
||||||
|
ref={mediaRef}
|
||||||
|
className="scroll-expand__media"
|
||||||
|
src={src}
|
||||||
|
poster={poster}
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
loop
|
||||||
|
playsInline
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
|
<img ref={mediaRef} className="scroll-expand__media" src={src} alt={alt} draggable={false} />
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={rootRef}
|
||||||
|
className={`scroll-expand ${useWindowScroll ? '' : 'scroll-expand--scroller'} ${className}`.trim()}
|
||||||
|
style={style}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<div ref={trackRef} className="scroll-expand__track">
|
||||||
|
<div ref={stageRef} className="scroll-expand__stage">
|
||||||
|
<div ref={frameRef} className="scroll-expand__frame">
|
||||||
|
{media}
|
||||||
|
<div ref={scrimRef} className="scroll-expand__scrim" />
|
||||||
|
{children ? (
|
||||||
|
<div ref={overlayRef} className="scroll-expand__overlay">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{title ? (
|
||||||
|
<div ref={titleRef} className="scroll-expand__title">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{scrollHint ? (
|
||||||
|
<div ref={hintRef} className="scroll-expand__hint">
|
||||||
|
{scrollHint}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScrollExpand;
|
||||||
+8
-13
@@ -1,16 +1,11 @@
|
|||||||
import { dirname } from "path";
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
import { fileURLToPath } from "url";
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||||
import { FlatCompat } from "@eslint/eslintrc";
|
import nextTs from "eslint-config-next/typescript";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const eslintConfig = defineConfig([
|
||||||
const __dirname = dirname(__filename);
|
...nextVitals,
|
||||||
|
...nextTs,
|
||||||
const compat = new FlatCompat({
|
globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts", "tailwind.config.ts"]),
|
||||||
baseDirectory: __dirname,
|
]);
|
||||||
});
|
|
||||||
|
|
||||||
const eslintConfig = [
|
|
||||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
|
||||||
];
|
|
||||||
|
|
||||||
export default eslintConfig;
|
export default eslintConfig;
|
||||||
|
|||||||
+12
-13
@@ -3,33 +3,32 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
"motion": "^12.4.7",
|
"motion": "^12.4.7",
|
||||||
"next": "15.1.7",
|
"next": "16.3.6",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.3.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.3.0",
|
||||||
"tailwind-merge": "^3.0.2",
|
"tailwind-merge": "^3.0.2",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19.3.0",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19.3.0",
|
||||||
"eslint": "^9",
|
"eslint": "^9.39.5",
|
||||||
"eslint-config-next": "15.1.7",
|
"eslint-config-next": "16.3.6",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "^5"
|
"typescript": "^5",
|
||||||
},
|
"wrangler": "^4.142.0"
|
||||||
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+5404
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
|||||||
|
allowBuilds:
|
||||||
|
esbuild: true
|
||||||
|
sharp: false
|
||||||
|
workerd: true
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
@@ -0,0 +1,21 @@
|
|||||||
|
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Woiken Icon</title>
|
||||||
|
<desc id="desc">A minimalist dark rounded-square icon with a cloud outline and stylized W center mark.</desc>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bg" x1="64" y1="64" x2="448" y2="448" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#141412"/>
|
||||||
|
<stop offset="1" stop-color="#0A0A09"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="accent" x1="152" y1="156" x2="360" y2="356" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#FFFFFF"/>
|
||||||
|
<stop offset="1" stop-color="#CFCFCB"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect x="24" y="24" width="464" height="464" rx="112" fill="url(#bg)"/>
|
||||||
|
|
||||||
|
<path d="M161 318C131.7 318 108 294.3 108 265C108 240.5 124.7 219.8 147.3 213.7C152.8 170.8 189.4 138 234 138C266.6 138 295.2 155.3 311.2 181.3C316 180.1 321 179.5 326 179.5C361.6 179.5 390.5 208.4 390.5 244C390.5 279.6 361.6 308.5 326 308.5H161Z" stroke="url(#accent)" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
|
||||||
|
<path d="M183 230L214 306L256 230L298 306L329 230" stroke="url(#accent)" stroke-width="24" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
+19
-5
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -11,7 +15,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -19,9 +23,19 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "woiken-website",
|
||||||
|
"compatibility_date": "2026-09-28",
|
||||||
|
"assets": {
|
||||||
|
"directory": "./out",
|
||||||
|
"not_found_handling": "404-page"
|
||||||
|
},
|
||||||
|
"routes": [{ "pattern": "woiken.com", "custom_domain": true }]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user