BBo09 commited on
Commit
c75641e
·
verified ·
1 Parent(s): 853af0e

Upload 22 files

Browse files
app/_actions/generate.ts ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server";
2
+
3
+ import { HfInference } from "@huggingface/inference";
4
+ import fs from "fs/promises";
5
+
6
+ import { Form } from "@/_types";
7
+ import prisma from "@/_utils/prisma";
8
+
9
+ export async function generate({ brand_name, industry, description }: Form) {
10
+ if (!process.env.PUBLIC_FILE_UPLOAD_DIR) {
11
+ throw new Error("PUBLIC_FILE_UPLOAD_DIR is not set");
12
+ }
13
+ const inference = new HfInference(process.env.HF_ACCESS_TOKEN, {
14
+ use_cache: false,
15
+ });
16
+
17
+ const prompt: any = await inference
18
+ .chatCompletion({
19
+ model: "meta-llama/Meta-Llama-3.1-70B-Instruct",
20
+ messages: [
21
+ { role: "user", content: "lee, a noodle restaurant" },
22
+ {
23
+ role: "assistant",
24
+ content:
25
+ 'logo,Minimalist,A pair of chopsticks and a bowl of rice with the word "Lee",',
26
+ },
27
+ { role: "user", content: "cat shop" },
28
+ { role: "assistant", content: "wablogo,Minimalist,Leaf and cat,logo," },
29
+ { role: "user", content: "Ato, real estate company" },
30
+ {
31
+ role: "assistant",
32
+ content:
33
+ 'logo,Minimalist,A man stands in front of a door,his shadow forming the word "A",',
34
+ },
35
+ { role: "user", content: `${brand_name}, ${description}, ${industry}` },
36
+ ],
37
+ temperature: 0.5,
38
+ max_tokens: 1024,
39
+ top_p: 0.7,
40
+ })
41
+ .then((res) => res)
42
+ .catch((err) => {
43
+ return { error: err.message };
44
+ });
45
+
46
+ if (prompt?.error) {
47
+ return {
48
+ error: prompt.error,
49
+ };
50
+ }
51
+
52
+ if (prompt?.choices[0]?.message?.content) {
53
+ const hfRequest = await inference.textToImage({
54
+ inputs: prompt.choices[0].message.content,
55
+ model: "Shakker-Labs/FLUX.1-dev-LoRA-Logo-Design",
56
+ parameters: {
57
+ num_inference_steps: 24,
58
+ guidance_scale: 3.5,
59
+ },
60
+ });
61
+
62
+ const buffer = await hfRequest.arrayBuffer();
63
+ const array = new Uint8Array(buffer);
64
+
65
+ const newImage = await prisma.logo.create({
66
+ data: {
67
+ name: prompt.choices[0].message.content,
68
+ },
69
+ });
70
+
71
+ const indexFile = newImage.id;
72
+
73
+ const dir = await fs
74
+ .opendir(process.env.PUBLIC_FILE_UPLOAD_DIR)
75
+ .catch(() => null);
76
+ if (!dir) await fs.mkdir(process.env.PUBLIC_FILE_UPLOAD_DIR);
77
+ await fs.writeFile(
78
+ `${process.env.PUBLIC_FILE_UPLOAD_DIR}/${indexFile}.png`,
79
+ array
80
+ );
81
+
82
+ return { data: indexFile };
83
+ }
84
+
85
+ return {
86
+ error: "Failed to generate logo",
87
+ };
88
+ }
app/_actions/logos.ts ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server";
2
+
3
+ import prisma from "@/_utils/prisma";
4
+
5
+ export const getLastLogos = async () => {
6
+ const images = await prisma.logo.findMany({
7
+ select: {
8
+ id: true,
9
+ },
10
+ take: 24,
11
+ orderBy: {
12
+ id: "desc",
13
+ },
14
+ });
15
+ return images.map((image) => image.id);
16
+ };
17
+
18
+ const ITEMS_PER_PAGE = 24;
19
+
20
+ export const getLogos = async (page: number = 0) => {
21
+ const images = await prisma.logo.findMany({
22
+ select: {
23
+ id: true,
24
+ },
25
+ skip: page * ITEMS_PER_PAGE,
26
+ take: ITEMS_PER_PAGE,
27
+ orderBy: {
28
+ id: "desc",
29
+ },
30
+ });
31
+
32
+ const total = await prisma.logo.count();
33
+ const hasMore = total > (page + 1) * ITEMS_PER_PAGE;
34
+
35
+ return {
36
+ logos: images.map((image) => image.id),
37
+ hasMore,
38
+ };
39
+ };
app/_components/gallery/index.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Image from "next/image";
2
+ import Link from "next/link";
3
+
4
+ export const Gallery = ({ logos }: { logos: Array<number> }) => {
5
+ return (
6
+ <section id="gallery" className="w-full py-10 lg:py-16">
7
+ <div className="mx-auto bg-amber-500/10 border border-amber-500/15 text-amber-500 px-3 py-1.5 rounded-full flex items-center gap-1 justify-center max-w-max text-xs mb-4">
8
+ <span className="text-xs">⚡</span>
9
+ Increase your creativity
10
+ </div>
11
+ <h3 className="max-w-4xl text-2xl lg:text-3xl text-[#aaaaaa] font-semibold mb-12 text-center mx-auto">
12
+ See our <span className="text-white">last designs</span>.
13
+ </h3>
14
+ <div className="max-lg:grid max-lg:grid-cols-2 lg:flex lg:items-start lg:justify-center gap-6 flex-wrap">
15
+ {logos.map((index) => (
16
+ <Image
17
+ key={index}
18
+ src={`/api/images/${index}`}
19
+ alt="Generated logo"
20
+ width={500}
21
+ height={500}
22
+ className="rounded-2xl w-full lg:size-72 object-cover"
23
+ />
24
+ ))}
25
+ </div>
26
+ <div className="mt-12 flex items-center justify-center">
27
+ <Link
28
+ href="/gallery"
29
+ className="rounded-full text-zinc-300 bg-zinc-900 font-medium text-base px-6 py-3 hover:bg-opacity-80 transition-all duration-150 text-center max-lg:w-full"
30
+ >
31
+ View all examples
32
+ </Link>
33
+ </div>
34
+ </section>
35
+ );
36
+ };
app/_components/gallery/list.tsx ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import InfiniteScroll from "react-infinite-scroll-component";
4
+ import Image from "next/image";
5
+ import { useState } from "react";
6
+ import { getLogos } from "@/app/_actions/logos";
7
+
8
+ export const InfiniteGallery = ({
9
+ logos: initialLogos,
10
+ hasMore: initialHasMore,
11
+ }: {
12
+ logos: Array<number>;
13
+ hasMore: boolean;
14
+ }) => {
15
+ const [page, setPage] = useState(0);
16
+ const [logos, setLogos] = useState([...initialLogos]);
17
+ const [hasMore, setHasMore] = useState(initialHasMore);
18
+
19
+ const fetchMoreData = async () => {
20
+ const newLogos = await getLogos(page + 1);
21
+ setLogos([...logos, ...newLogos.logos]);
22
+ setHasMore(newLogos.hasMore);
23
+ setPage(page + 1);
24
+ };
25
+
26
+ return (
27
+ <InfiniteScroll
28
+ scrollableTarget="content-wrapper"
29
+ dataLength={logos.length} //This is important field to render the next data
30
+ next={fetchMoreData}
31
+ hasMore={hasMore}
32
+ loader={
33
+ <div className="w-full max-lg:col-span-2 text-center">Loading...</div>
34
+ }
35
+ className="max-lg:grid max-lg:grid-cols-2 lg:flex lg:items-start lg:justify-center gap-6 flex-wrap"
36
+ endMessage={
37
+ <div className="w-full max-lg:col-span-2 text-zinc-400 text-center">
38
+ Yay! You have seen it all
39
+ </div>
40
+ }
41
+ >
42
+ {logos.map((index) => (
43
+ <Image
44
+ key={index}
45
+ src={`/api/images/${index}`}
46
+ alt="Generated logo"
47
+ width={500}
48
+ height={500}
49
+ className="rounded-2xl w-full lg:size-72 object-cover"
50
+ />
51
+ ))}
52
+ </InfiniteScroll>
53
+ );
54
+ };
app/_components/generation/index.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+
5
+ import { Form } from "@/_types";
6
+ import { generate } from "@/app/_actions/generate";
7
+
8
+ import { Brand } from "./step/brand";
9
+ import { Steps } from "./step/list";
10
+ import { Industry } from "./step/industry";
11
+ import { Description } from "./step/description";
12
+ import classNames from "classnames";
13
+ import { toast } from "react-toastify";
14
+ import Image from "next/image";
15
+
16
+ export const Generation = () => {
17
+ const [form, setForm] = useState<Form>({
18
+ brand_name: "",
19
+ display_name: false,
20
+ description: "",
21
+ industry: "",
22
+ style: "",
23
+ });
24
+
25
+ const [loading, setLoading] = useState<boolean>(false);
26
+ const [result, setResult] = useState<number | undefined>(undefined);
27
+
28
+ const handleGenerate = async () => {
29
+ if (loading) return;
30
+ setLoading(true);
31
+ try {
32
+ const response = await generate(form);
33
+ setResult(response.data);
34
+ } catch (err) {
35
+ toast.error("An error occurred. Please try again later.");
36
+ } finally {
37
+ setLoading(false);
38
+ }
39
+ };
40
+
41
+ return (
42
+ <main id="generation" className="w-full py-10 lg:py-20">
43
+ <h3 className="max-w-4xl text-2xl lg:text-3xl text-[#aaaaaa] font-semibold mb-12 text-center mx-auto">
44
+ Start your <span className="text-white">generation</span> here.
45
+ </h3>
46
+ <Steps form={form} />
47
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8 lg:gap-20">
48
+ <Brand form={form} setForm={setForm} />
49
+ <Description form={form} setForm={setForm} />
50
+ <Industry form={form} setForm={setForm} />
51
+ <div className="lg:col-span-3 flex items-center justify-center">
52
+ <button
53
+ className={classNames(
54
+ "max-lg:w-full rounded-full bg-white text-zinc-950 font-medium text-sm px-6 py-3 hover:bg-opacity-80 transition-all duration-150 disabled:bg-zinc-500 disabled:text-zinc-700",
55
+ {
56
+ "animate-pulse": loading,
57
+ }
58
+ )}
59
+ disabled={!form.brand_name || !form.description || !form.industry}
60
+ onClick={handleGenerate}
61
+ >
62
+ {loading ? "Generating..." : "Generate my Logo"}
63
+ </button>
64
+ </div>
65
+ {result && (
66
+ <div className="lg:col-span-3 flex items-center justify-center rounded-3xl">
67
+ <Image
68
+ src={`/api/images/${result}`}
69
+ alt="Generated logo"
70
+ className="h-[300px]"
71
+ width={400}
72
+ height={400}
73
+ />
74
+ </div>
75
+ )}
76
+ </div>
77
+ </main>
78
+ );
79
+ };
app/_components/generation/step/brand.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Form } from "@/_types";
2
+
3
+ export const Brand = ({
4
+ form,
5
+ setForm,
6
+ }: {
7
+ form: Form;
8
+ setForm: React.Dispatch<React.SetStateAction<Form>>;
9
+ }) => {
10
+ return (
11
+ <div className="w-full">
12
+ <label htmlFor="brand_name" className="text-zinc-300 mb-1 block text-sm">
13
+ Brand name
14
+ </label>
15
+ <input
16
+ type="text"
17
+ id="brand_name"
18
+ placeholder="Hugging Face"
19
+ value={form.brand_name}
20
+ className="border bg-zinc-900 border-zinc-800 rounded-lg py-2 px-4 text-gray-200 outline-none w-full placeholder:text-gray-600"
21
+ onChange={(e) => setForm({ ...form, brand_name: e.target.value })}
22
+ />
23
+ </div>
24
+ );
25
+ };
app/_components/generation/step/description.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Form } from "@/_types";
2
+
3
+ export const Description = ({
4
+ form,
5
+ setForm,
6
+ }: {
7
+ form: Form;
8
+ setForm: React.Dispatch<React.SetStateAction<Form>>;
9
+ }) => {
10
+ return (
11
+ <div className="w-full">
12
+ <label htmlFor="description" className="text-zinc-300 mb-1 block text-sm">
13
+ Short Description
14
+ </label>
15
+ <input
16
+ type="text"
17
+ id="description"
18
+ placeholder="A platform for building and sharing models"
19
+ value={form.description}
20
+ className="border bg-zinc-900 border-zinc-800 rounded-lg py-2 px-4 text-gray-200 outline-none w-full placeholder:text-gray-600"
21
+ onChange={(e) => setForm({ ...form, description: e.target.value })}
22
+ />
23
+ </div>
24
+ );
25
+ };
app/_components/generation/step/industry.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Form } from "@/_types";
2
+
3
+ import { INDUSTRIES } from "@/_utils";
4
+
5
+ export const Industry = ({
6
+ form,
7
+ setForm,
8
+ }: {
9
+ form: Form;
10
+ setForm: React.Dispatch<React.SetStateAction<Form>>;
11
+ }) => {
12
+ return (
13
+ <div className="">
14
+ <label htmlFor="industry" className="text-zinc-300 mb-1 block text-sm">
15
+ Industry
16
+ </label>
17
+ <select
18
+ id="industry"
19
+ className="border bg-zinc-900 border-zinc-800 rounded-lg py-3 px-4 text-gray-200 outline-none w-full"
20
+ onChange={(e) => setForm({ ...form, industry: e.target.value })}
21
+ >
22
+ <option value="">Select an industry</option>
23
+ {INDUSTRIES.map((industry, i) => (
24
+ <option key={i} value={industry.name}>
25
+ {/* <industry.icon className="mr-2 inline-block" /> */}
26
+ {industry.name}
27
+ </option>
28
+ ))}
29
+ </select>
30
+ </div>
31
+ );
32
+ };
app/_components/generation/step/list.tsx ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { TiLightbulb } from "react-icons/ti";
2
+ import { MdWorkOutline } from "react-icons/md";
3
+ import { IoMdCheckmark } from "react-icons/io";
4
+ import { MdOutlinePermIdentity } from "react-icons/md";
5
+ import classNames from "classnames";
6
+
7
+ import { Form } from "@/_types";
8
+
9
+ const STEPS = [
10
+ {
11
+ title: "Brand",
12
+ description: "Tell us about your brand.",
13
+ icon: MdOutlinePermIdentity,
14
+ active: "bg-amber-500 !border-amber-500",
15
+ key: "brand_name",
16
+ },
17
+ {
18
+ title: "Concept",
19
+ description: "What's your brand about?",
20
+ icon: TiLightbulb,
21
+ active: "bg-violet-500 !border-violet-500",
22
+ key: "description",
23
+ },
24
+ {
25
+ title: "Industry",
26
+ description: "What industry are you in?",
27
+ icon: MdWorkOutline,
28
+ active: "bg-emerald-500 !border-emerald-500",
29
+ key: "industry",
30
+ },
31
+ ];
32
+
33
+ export const Steps = ({ form }: { form: Form }) => {
34
+ return (
35
+ <div className="max-lg:hidden w-full flex items-center justify-center gap-2 mb-12">
36
+ {STEPS.map((s, i) => (
37
+ <>
38
+ <div
39
+ key={i}
40
+ className={classNames(
41
+ "text-center flex flex-col items-center min-w-60 cursor-pointer",
42
+ {
43
+ "opacity-40": form[s.key as keyof typeof form] === "",
44
+ }
45
+ )}
46
+ >
47
+ <div
48
+ className={classNames(
49
+ "size-7 border border-white text-white flex items-center justify-center rounded-2xl mb-3",
50
+ {
51
+ [s.active]: form[s.key as keyof typeof form],
52
+ }
53
+ )}
54
+ >
55
+ {form[s.key as keyof typeof form] ? (
56
+ <IoMdCheckmark className="text-xl" />
57
+ ) : (
58
+ <s.icon className="text-base" />
59
+ )}
60
+ </div>
61
+ <p className="text-white text-xl font-semibold">{s.title}</p>
62
+ <p className="text-white text-sm font-mono">{s.description}</p>
63
+ </div>
64
+ {i !== STEPS.length - 1 && (
65
+ <div
66
+ key={"linear_" + i}
67
+ className="h-0 flex-1 border-gray-100/20 border-dashed border-b"
68
+ />
69
+ )}
70
+ </>
71
+ ))}
72
+ </div>
73
+ );
74
+ };
app/_components/hero-header.tsx ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import classNames from "classnames";
4
+ import { useState } from "react";
5
+ import { useInterval } from "react-use";
6
+
7
+ export const HeroHeader = () => {
8
+ const [selectedWord, setSelectedWord] = useState("Think.");
9
+
10
+ useInterval(() => {
11
+ setSelectedWord((prev) => {
12
+ switch (prev) {
13
+ case "Think.":
14
+ return "Customize.";
15
+ case "Customize.":
16
+ return "Generate.";
17
+ case "Generate.":
18
+ return "Think.";
19
+ default:
20
+ return "Think.";
21
+ }
22
+ });
23
+ }, 2000);
24
+
25
+ return (
26
+ <header className="py-14 lg:py-20 grid grid-cols-1 gap-5">
27
+ <h1 className="text-4xl lg:text-5xl font-semibold text-[#aaaaaa] max-w-max mx-auto text-center">
28
+ <span
29
+ className={classNames("transition-all duration-300 text-opacity-0", {
30
+ "text-white !text-opacity-100": selectedWord === "Think.",
31
+ })}
32
+ >
33
+ Think.
34
+ </span>{" "}
35
+ <span
36
+ className={classNames("transition-all duration-300 text-opacity-0", {
37
+ "text-white !text-opacity-100": selectedWord === "Customize.",
38
+ })}
39
+ >
40
+ Customize.
41
+ </span>{" "}
42
+ <br />
43
+ and{" "}
44
+ <span
45
+ className={classNames("transition-all duration-300 text-opacity-0", {
46
+ "text-white !text-opacity-100": selectedWord === "Generate.",
47
+ })}
48
+ >
49
+ Generate.
50
+ </span>
51
+ </h1>
52
+ <h2 className="text-lg font-light text-center text-[#898989] max-w-sm mx-auto">
53
+ An AI powered tool that helps you create a logo for your brand.
54
+ </h2>
55
+ <div className="max-lg:max-w-xs w-full max-lg:mx-auto max-lg:grid lg:flex lg:items-center lg:justify-center gap-6 mt-3 ">
56
+ <a
57
+ href="#generation"
58
+ className="rounded-full bg-white text-zinc-950 font-medium text-base px-6 py-3 hover:bg-opacity-80 transition-all duration-150 text-center max-lg:w-full"
59
+ >
60
+ Start generation
61
+ </a>
62
+ <a
63
+ href="#gallery"
64
+ className="rounded-full text-zinc-300 bg-zinc-900 font-medium text-base px-6 py-3 hover:bg-opacity-80 transition-all duration-150 text-center max-lg:w-full"
65
+ >
66
+ View examples
67
+ </a>
68
+ </div>
69
+ </header>
70
+ );
71
+ };
app/_fonts/GeistMonoVF.woff ADDED
Binary file (67.9 kB). View file
 
app/_fonts/GeistVF.woff ADDED
Binary file (66.3 kB). View file
 
app/_fonts/nohemi/bold.woff ADDED
Binary file (24.9 kB). View file
 
app/_fonts/nohemi/extrabold.woff ADDED
Binary file (24.7 kB). View file
 
app/_fonts/nohemi/light.woff ADDED
Binary file (32.9 kB). View file
 
app/_fonts/nohemi/regular.woff ADDED
Binary file (25.6 kB). View file
 
app/_fonts/nohemi/semibold.woff ADDED
Binary file (25.4 kB). View file
 
app/api/images/[id]/route.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest } from "next/server";
2
+ import fs from "fs/promises";
3
+
4
+ import prisma from "@/_utils/prisma";
5
+
6
+ export async function GET(
7
+ req: NextRequest,
8
+ { params }: { params: { id: string } }
9
+ ) {
10
+ const { id } = params;
11
+
12
+ const image = await prisma.logo.findUnique({
13
+ where: { id: Number(id) },
14
+ });
15
+
16
+ if (!image) {
17
+ return new Response(null, { status: 404 });
18
+ }
19
+
20
+ const buffer = await fs.readFile(
21
+ `${process.env.PUBLIC_FILE_UPLOAD_DIR}/${image.id}.png`
22
+ );
23
+
24
+ if (!buffer) {
25
+ return new Response(null, { status: 404 });
26
+ }
27
+
28
+ return new Response(buffer, {
29
+ headers: {
30
+ "Content-Type": "image/png",
31
+ },
32
+ });
33
+ }
app/favicon.ico ADDED
app/gallery/page.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { InfiniteGallery } from "@/app/_components/gallery/list";
2
+ import { getLogos } from "@/app/_actions/logos";
3
+
4
+ async function lastLogos() {
5
+ const logos = await getLogos();
6
+ return logos;
7
+ }
8
+ export const revalidate = 0;
9
+
10
+ export default async function Gallery() {
11
+ const { hasMore, logos } = await lastLogos();
12
+ return (
13
+ <section className="w-full py-10 lg:py-16">
14
+ <div className="max-lg:grid max-lg:grid-cols-2 lg:flex lg:items-start lg:justify-center gap-6 flex-wrap">
15
+ <InfiniteGallery logos={logos} hasMore={hasMore} />
16
+ </div>
17
+ </section>
18
+ );
19
+ }
app/layout.tsx ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import localFont from "next/font/local";
3
+ import { ToastContainer } from "react-toastify";
4
+ import "react-toastify/dist/ReactToastify.css";
5
+
6
+ import "@/assets/globals.css";
7
+ import { Navigation } from "@/components/_navigation";
8
+
9
+ const nohemiRegular = localFont({
10
+ src: [
11
+ {
12
+ path: "./_fonts/nohemi/light.woff",
13
+ weight: "300",
14
+ },
15
+ {
16
+ path: "./_fonts/nohemi/regular.woff",
17
+ weight: "400",
18
+ },
19
+ {
20
+ path: "./_fonts/nohemi/semibold.woff",
21
+ weight: "600",
22
+ },
23
+ {
24
+ path: "./_fonts/nohemi/bold.woff",
25
+ weight: "700",
26
+ },
27
+ {
28
+ path: "./_fonts/nohemi/extrabold.woff",
29
+ weight: "900",
30
+ },
31
+ ],
32
+ variable: "--font-nohemi-sans",
33
+ });
34
+
35
+ const geistMono = localFont({
36
+ src: "./_fonts/GeistMonoVF.woff",
37
+ variable: "--font-geist-mono",
38
+ weight: "100 900",
39
+ });
40
+
41
+ export const metadata: Metadata = {
42
+ title: "Create Next App",
43
+ description: "Generated by create next app",
44
+ };
45
+
46
+ export default function RootLayout({
47
+ children,
48
+ }: Readonly<{
49
+ children: React.ReactNode;
50
+ }>) {
51
+ return (
52
+ <html lang="en">
53
+ <body
54
+ className={`${nohemiRegular.variable} ${geistMono.variable} antialiased`}
55
+ >
56
+ <div
57
+ id="content-wrapper"
58
+ className="h-screen w-full overflow-auto font-[family-name:var(--font-nohemi-sans)] p-6 scroll-smooth"
59
+ >
60
+ <Navigation />
61
+ {children}
62
+ <footer className="mt-4 w-full max-w-4xl mx-auto border-t border-zinc-800 pt-8 pb-3 text-center">
63
+ <p className="text-sm text-zinc-400">
64
+ Powered by{" "}
65
+ <a
66
+ href="https://github.com/huggingface/huggingface.js"
67
+ target="_blank"
68
+ className="font-mono text-amber-500 hover:text-amber-400"
69
+ >
70
+ huggingface.js
71
+ </a>{" "}
72
+ and{" "}
73
+ <a
74
+ href="https://huggingface.co/Shakker-Labs/FLUX.1-dev-LoRA-Logo-Design"
75
+ target="_blank"
76
+ className="font-mono text-zinc-100 hover:text-white"
77
+ >
78
+ Shakker-Labs/FLUX.1-dev-LoRA-Logo-Design
79
+ </a>
80
+ </p>
81
+ </footer>
82
+ </div>
83
+ <ToastContainer />
84
+ </body>
85
+ </html>
86
+ );
87
+ }
app/page.tsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getLastLogos } from "./_actions/logos";
2
+ import { Gallery } from "./_components/gallery";
3
+ import { Generation } from "./_components/generation";
4
+ import { HeroHeader } from "./_components/hero-header";
5
+
6
+ async function lastLogos() {
7
+ const logos = await getLastLogos();
8
+ return logos;
9
+ }
10
+ export const revalidate = 0;
11
+
12
+ export default async function Home() {
13
+ const logos = await lastLogos();
14
+ return (
15
+ <section>
16
+ <div className="max-w-4xl mx-auto">
17
+ <HeroHeader />
18
+ <Generation />
19
+ </div>
20
+ <Gallery logos={logos} />
21
+ </section>
22
+ );
23
+ }