Compare commits

..
15 Commits
Author SHA1 Message Date
niklas fe129bc92e docs: update README from default nextjs one
Deploy / deploy (push) Successful in 44s
2026-09-28 22:11:48 +02:00
niklas 9e734f29b8 chore: remove packageManager field from package.json
Deploy / deploy (push) Successful in 45s
2026-09-28 22:04:42 +02:00
niklas b8f7168d64 chore: build a workflow to deploy site automatically
Deploy / deploy (push) Failing after 4s
2026-09-28 22:03:55 +02:00
niklas d8eb5d7504 update landing page with grassy cloud fields 2026-09-28 21:57:12 +02:00
niklas 61a87b1f9a feat: migrate to pnpm and nextjs16 2026-09-28 21:57:04 +02:00
Niklas Haiden 747fa89322 Update main heading to emphasize platform launch and remove redundant text 2026-05-16 19:32:44 +02:00
Niklas Haiden b3248dcd1f Refine header text for clarity and focus on the platform's European base 2026-05-16 19:30:13 +02:00
Niklas 8f44c57e7c Merge pull request #2 from NiHaiden/codex/build-coming-soon-website-with-cursor-style
Add Woiken SVG icon and wire it as site icon
2026-02-18 16:52:04 +01:00
Niklas 418031c6ae Merge branch 'main' into codex/build-coming-soon-website-with-cursor-style 2026-02-18 16:50:58 +01:00
Niklas 831a8e88f4 Add Woiken SVG icon and wire it as site icon 2026-02-18 16:50:09 +01:00
Niklas 65297587ab Update header text for clarity and focus 2026-02-18 11:28:28 +01:00
Niklas 2fda722b26 Remove paragraph about fast-moving teams
Removed the paragraph about being built for fast-moving teams.
2026-02-18 11:16:42 +01:00
Niklas 0a1c75fbdf Merge pull request #1 from NiHaiden/codex/build-coming-soon-website-with-cursor-style
Center landing, simplify navbar, apply Geist Sans (Next 16 upgrade attempted)
2026-02-18 11:13:27 +01:00
Niklas 6379ba973f Center landing content and simplify navbar branding 2026-02-18 11:13:06 +01:00
Niklas Haiden c7caed0c36 force nodejs version 2025-02-23 16:49:14 +01:00
19 changed files with 5972 additions and 3132 deletions
+34
View File
@@ -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 }}
+7
View File
@@ -39,3 +39,10 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
#Ignore vscode AI rules
.github/instructions/codacy.instructions.md
# wrangler
.wrangler
+1
View File
@@ -0,0 +1 @@
22.14.0
+37 -29
View File
@@ -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
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
## Commands
| Command | Action |
| :------------------------- | :-------------------------------------------------- |
| `pnpm install` | Install dependencies |
| `pnpm dev` | Start the dev server at `localhost:3000` |
| `pnpm build` | Build the static site to `./out/` |
| `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.
## 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.
Hero photo: [grass field at daytime](https://unsplash.com/photos/grass-field-at-daytime-PEm_sLmJT-w)
on Unsplash.
+5
View File
@@ -2,3 +2,8 @@
@tailwind components;
@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);
}
+7 -9
View File
@@ -4,7 +4,6 @@ import "./globals.css";
import { ThemeProvider } from "next-themes";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
@@ -14,8 +13,11 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Woiken",
description: "Woiken coming soon",
icons: {
icon: "/woiken-icon.svg",
},
};
export default function RootLayout({
@@ -25,12 +27,8 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider defaultTheme="dark">
{children}
</ThemeProvider>
<body className={`${geistSans.className} ${geistMono.variable} antialiased`}>
<ThemeProvider defaultTheme="dark">{children}</ThemeProvider>
</body>
</html>
);
+25 -23
View File
@@ -1,29 +1,31 @@
"use client";
import ScrollExpand from "@/components/ScrollExpand/ScrollExpand";
import { motion } from "framer-motion";
import React from "react";
import { AuroraBackground } from "@/components/ui/aurora-background";
export default function AuroraBackgroundDemo() {
export default function Home() {
return (
<AuroraBackground>
<motion.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="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
>
<div className="text-3xl md:text-7xl font-bold dark:text-white text-center">
woiken.cloud
<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>
<footer className="px-6 py-10 text-center text-sm text-white/40">
© {new Date().getFullYear()} Woiken
</footer>
</div>
<div className="font-extralight text-base md:text-4xl dark:text-neutral-200 py-4">
Coming soon.
</div>
</motion.div>
</AuroraBackground>
);
}
+102
View File
@@ -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
View File
@@ -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;
+243
View File
@@ -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
View File
@@ -1,16 +1,11 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts", "tailwind.config.ts"]),
]);
export default eslintConfig;
+12 -13
View File
@@ -3,33 +3,32 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "eslint ."
},
"dependencies": {
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.475.0",
"motion": "^12.4.7",
"next": "15.1.7",
"next": "16.3.6",
"next-themes": "^0.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"tailwind-merge": "^3.0.2",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.1.7",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"eslint": "^9.39.5",
"eslint-config-next": "16.3.6",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
},
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
"typescript": "^5",
"wrangler": "^4.142.0"
}
}
+5404
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
allowBuilds:
esbuild: true
sharp: false
workerd: true
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

+21
View File
@@ -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
View File
@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -11,7 +15,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
@@ -19,9 +23,19 @@
}
],
"paths": {
"@/*": ["./*"]
"@/*": [
"./*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
+10
View File
@@ -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 }]
}
-3035
View File
File diff suppressed because it is too large Load Diff