hero with pretty numbers

This commit is contained in:
Tom Elliott 2025-05-29 18:28:22 +12:00
parent 36f3ccebf8
commit 16510e0087
24 changed files with 295 additions and 33 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -3,6 +3,12 @@ import withPayload from "@payloadcms/next/withPayload";
const nextConfig: NextConfig = {
/* config options here */
images: {
remotePatterns: [
new URL("http://localhost:3000/**"),
new URL("https://inzight.co.nz/**"),
],
},
};
export default withPayload(nextConfig);

View File

@ -16,6 +16,7 @@
"@tailwindcss/postcss": "^4.1.4",
"clsx": "^2.1.1",
"graphql": "^16.10.0",
"motion": "^12.15.0",
"next": "15.3.1",
"payload": "^3.35.1",
"pg": "8.11.3",

View File

@ -93,10 +93,12 @@ export interface Config {
defaultIDType: number;
};
globals: {
general: General;
homeHero: HomeHero;
homeProjects: HomeProject;
};
globalsSelect: {
general: GeneralSelect<false> | GeneralSelect<true>;
homeHero: HomeHeroSelect<false> | HomeHeroSelect<true>;
homeProjects: HomeProjectsSelect<false> | HomeProjectsSelect<true>;
};
@ -549,6 +551,16 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "general".
*/
export interface General {
id: number;
logo: number | Image;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "homeHero".
@ -618,6 +630,16 @@ export interface HomeProject {
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "general_select".
*/
export interface GeneralSelect<T extends boolean = true> {
logo?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "homeHero_select".

View File

@ -1,16 +1,20 @@
import sharp from 'sharp'
import { FixedToolbarFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
import { postgresAdapter } from '@payloadcms/db-postgres'
import { buildConfig } from 'payload'
import sharp from "sharp";
import {
FixedToolbarFeature,
lexicalEditor,
} from "@payloadcms/richtext-lexical";
import { postgresAdapter } from "@payloadcms/db-postgres";
import { buildConfig } from "payload";
import { HomeHero } from '@/globals/Home/Hero'
import { HomeHero } from "@/globals/Home/Hero";
import { News } from './src/collections/News'
import { Projects } from '@/collections/Projects'
import { Images } from '@/collections/media/Images'
import { Documents } from '@/collections/media/Documents'
import { Data } from '@/collections/media/Data'
import { HomeProjects } from '@/globals/Home/Projects'
import { News } from "./src/collections/News";
import { Projects } from "@/collections/Projects";
import { Images } from "@/collections/media/Images";
import { Documents } from "@/collections/media/Documents";
import { Data } from "@/collections/media/Data";
import { HomeProjects } from "@/globals/Home/Projects";
import { General } from "@/globals/General";
export default buildConfig({
// If you'd like to use Rich Text, pass your editor here
@ -18,28 +22,28 @@ export default buildConfig({
features: ({ defaultFeatures }) => [
...defaultFeatures,
FixedToolbarFeature(),
]
],
}),
serverURL: process.env.SERVER_URL || 'http://localhost:3000',
serverURL: process.env.SERVER_URL || "http://localhost:3000",
globals: [HomeHero, HomeProjects],
globals: [General, HomeHero, HomeProjects],
// Define and configure your collections in this array
collections: [Projects, News, Images, Documents, Data],
// Your Payload secret - should be a complex and secure string, unguessable
secret: process.env.PAYLOAD_SECRET || '',
secret: process.env.PAYLOAD_SECRET || "",
// Whichever Database Adapter you're using should go here
// Mongoose is shown as an example, but you can also use Postgres
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URI,
}
connectionString: process.env.DATABASE_URI,
},
}),
// If you want to resize images, crop, set focal point, etc.
// make sure to install it and pass it to the config.
// This is optional - if you don't need to do these things,
// you don't need it!
sharp,
})
});

60
pnpm-lock.yaml generated
View File

@ -26,6 +26,9 @@ importers:
graphql:
specifier: ^16.10.0
version: 16.10.0
motion:
specifier: ^12.15.0
version: 12.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next:
specifier: 15.3.1
version: 15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4)
@ -2053,6 +2056,20 @@ packages:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
framer-motion@12.15.0:
resolution: {integrity: sha512-XKg/LnKExdLGugZrDILV7jZjI599785lDIJZLxMiiIFidCsy0a4R2ZEf+Izm67zyOuJgQYTHOmodi7igQsw3vg==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -2619,6 +2636,26 @@ packages:
monaco-editor@0.52.2:
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
motion-dom@12.15.0:
resolution: {integrity: sha512-D2ldJgor+2vdcrDtKJw48k3OddXiZN1dDLLWrS8kiHzQdYVruh0IoTwbJBslrnTXIPgFED7PBN2Zbwl7rNqnhA==}
motion-utils@12.12.1:
resolution: {integrity: sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==}
motion@12.15.0:
resolution: {integrity: sha512-HLouXyIb1uQFiZgJTYGrtEzbatPc6vK+HP+Qt6afLQjaudiGiLLVsoy71CwzD/Stlh06FUd5OpyiXqn6XvqjqQ==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@ -5544,6 +5581,15 @@ snapshots:
dependencies:
is-callable: 1.2.7
framer-motion@12.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
motion-dom: 12.15.0
motion-utils: 12.12.1
tslib: 2.8.1
optionalDependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
fsevents@2.3.3:
optional: true
@ -6212,6 +6258,20 @@ snapshots:
monaco-editor@0.52.2: {}
motion-dom@12.15.0:
dependencies:
motion-utils: 12.12.1
motion-utils@12.12.1: {}
motion@12.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
framer-motion: 12.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
tslib: 2.8.1
optionalDependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
ms@2.1.3: {}
nanoid@3.3.11: {}

View File

@ -1,5 +1,6 @@
import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { FixedToolbarFeatureClient as FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
@ -25,6 +26,7 @@ import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0
export const importMap = {
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/client#FixedToolbarFeatureClient": FixedToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,

View File

@ -12,7 +12,7 @@ const BUTTON_VARIANTS: Parameters<typeof Button>[0]["variant"][] = [
export default function Page() {
return (
<div className="flex justify-center bg-gray-50 min-h-screen">
<div className="flex justify-center bg-gray-50 min-h-screen dark:bg-black dark:text-gray-50">
<div className="container px-4 py-8 flex flex-col gap-8">
<div className="border-b">
<h1 className="text-4xl mb-4">iNZight Analytics Brand</h1>
@ -20,9 +20,9 @@ export default function Page() {
<div className="">
<h2 className="text-2xl mb-2">Colours</h2>
<div className="h-32 flex gap-4 bg-black p-4">
<div className="w-3/5"></div>
<div className="w-3/10 bg-white"></div>
<div className="h-32 flex gap-1 bg-gray-500 p-4">
<div className="w-6/10 bg-white dark:bg-black"></div>
<div className="w-3/10 bg-black dark:bg-white"></div>
<div className="w-1/10 bg-accent-500"></div>
</div>
<div className="p-4 space-y-2">

View File

@ -22,16 +22,16 @@ export default function Button({
variant === "filled" ? "" : "border",
type === "primary" &&
(variant === "filled"
? "bg-accent-800 text-accent-100"
: "border-accent-800 text-accent-800 hover:bg-accent-800 hover:text-accent-100"),
? "bg-accent-600 text-accent-50 hover:bg-accent-700"
: "border-accent-600 text-accent-600 hover:bg-accent-600 hover:text-accent-50"),
type === "secondary" &&
(variant === "filled"
? "bg-secondary-800 text-secondary-100"
: "border-secondary-800 text-secondary-800 hover:bg-secondary-800 hover:text-secondary-100"),
? "bg-secondary-600 text-secondary-50 hover:bg-secondary-700"
: "border-secondary-600 text-secondary-600 hover:bg-secondary-600 hover:text-secondary-50"),
type === "alternate" &&
(variant === "filled"
? "bg-black text-white"
: "border-black text-black hover:bg-black hover:text-white")
? "bg-black text-white dark:bg-white dark:text-black"
: "border-black text-black hover:bg-black hover:text-white dark:border-white dark:text-white dark:hover:bg-white dark:hover:text-black")
)}
{...props}
>

View File

@ -0,0 +1,38 @@
import { getPayload } from "payload";
import config from "@payload-config";
import Link from "next/link";
import Image from "next/image";
import { Image as PImage } from "@payload-types";
const isValidLogo = (image: number | PImage): image is PImage => {
return typeof image !== "number";
};
export default async function Header() {
const payload = await getPayload({ config });
const { logo } = await payload.findGlobal({
slug: "general",
});
if (typeof logo === "number") throw "Bad image";
return (
<div className="text-white bg-black px-8 py-4 flex justify-between items-center absolute z-10 w-full h-[var(--header-height)]">
{/* Left hand side - logo */}
<Link href="https://inzight.co.nz">
LOGO
{/* {logo.url && logo.alt && logo.width && logo.height && (
<Image
src={logo.url}
alt={logo.alt}
width={logo.width}
height={100}
/>
)} */}
</Link>
{/* Right hand size - nav */}
<nav>NAV</nav>
</div>
);
}

View File

@ -0,0 +1,44 @@
"use client";
import { motion } from "motion/react";
import { letters } from "./letters";
export default function ScrollingNumbers({ numbers }: { numbers: string[][] }) {
return (
<div className="absolute top-0 h-full w-full z-0 opacity-15 overflow-clip">
<div className="h-full w-full flex gap-4 skew-24 scale-150">
{numbers.map((col, i) => (
<NumberCol col={col} key={i} />
))}
<div className="absolute w-full h-full mask-b-from-20% bg-black skew"></div>
</div>
</div>
);
}
const NumberCol = ({ col }: { col: string[] }) => {
const range = [0, -100];
const speed = Math.log(letters.indexOf(col[0]) + 10) * 100;
return (
<motion.div
initial={{
translateY: range[0] + "vh",
}}
animate={{ translateY: range[1] + "vh" }}
transition={{
type: "tween",
duration: speed + 10,
repeat: Infinity,
repeatType: "reverse",
}}
className="flex-1 flex flex-col items-center"
>
{col.map((num, j) => (
<div key={j} className="text-accent-300 text-3xl">
{num}
</div>
))}
</motion.div>
);
};

View File

@ -0,0 +1,38 @@
export const letters = [
"a",
"b",
"c",
"d",
"e",
"f",
// "g",
// "h",
// "i",
// "j",
// "k",
// "l",
// "m",
// "n",
// "o",
// "p",
// "q",
// "r",
// "s",
// "t",
// "u",
// "v",
// "w",
// "x",
// "y",
// "z",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
];

View File

@ -31,4 +31,7 @@
--color-secondary-800: #344735;
--color-secondary-900: #2c3b2d;
--color-secondary-950: #141f16;
/* Other vars */
--header-height: 100px;
}

View File

@ -1,9 +1,10 @@
import type { Metadata } from "next";
import "./globals.css";
import Header from "./components/Header";
export const metadata: Metadata = {
title: "Create Next App",
title: "iNZight Analytics Ltd",
description: "Generated by create next app",
};
@ -14,7 +15,10 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={``}>{children}</body>
<body className={`bg-black`}>
<Header />
{children}
</body>
</html>
);
}

View File

@ -1,3 +1,27 @@
export default function Home() {
return <div className="bg-green-300">hello</div>;
import { getPayload } from "payload";
import config from "@payload-config";
import ScrollingNumbers from "./components/Hero/ScrollingNumbers";
import { letters } from "./components/Hero/letters";
const randomNumbers = Array.from({ length: 50 }).map((i) =>
Array.from({ length: 200 }).map(
(j) => letters[Math.floor(Math.random() * letters.length)]
)
);
export default async function Home() {
const payload = await getPayload({ config });
const { titleGroup } = await payload.findGlobal({
slug: "homeHero",
});
return (
<div className="">
<div className="h-screen pt-[var(--header-height)] flex flex-col items-center justify-end text-white pb-[10vh] relative">
<ScrollingNumbers numbers={randomNumbers} />
<h1 className="text-8xl max-w-6xl">{titleGroup.title}</h1>
</div>
<div className="h-screen bg-accent-700">hello</div>
</div>
);
}

15
src/globals/General.ts Normal file
View File

@ -0,0 +1,15 @@
import { GlobalConfig } from "payload";
export const General: GlobalConfig = {
slug: "general",
label: "General",
fields: [
{
name: "logo",
required: true,
label: "Logo",
type: "upload",
relationTo: "images",
},
],
};

View File

@ -20,7 +20,8 @@
],
"paths": {
"@/*": ["./src/*"],
"@payload-config": ["./payload.config.ts"]
"@payload-config": ["./payload.config.ts"],
"@payload-types": ["./payload-types.ts"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],