Spaces:
Running
Running
initial commit
Browse files- Dockerfile +24 -0
- README.md +12 -36
- app/actions.ts +8 -0
- app/layout.tsx +27 -13
- app/page.tsx +25 -103
- components/animate/card.tsx +67 -0
- components/shuffler/button.tsx +13 -0
- components/shuffler/index.tsx +54 -0
- components/space/index.tsx +34 -0
- components/space/sub/author.tsx +22 -0
- components/space/sub/header.tsx +26 -0
- components/theme-provider.tsx +9 -0
- components/toggle-mode.tsx +12 -0
- images/cards.svg +3 -0
- next.config.mjs +10 -1
- package-lock.json +53 -0
- package.json +9 -5
- {app → style}/globals.css +0 -0
- tailwind.config.ts +38 -1
- utils/network.ts +41 -0
- utils/types.ts +25 -0
Dockerfile
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Dockerfile
|
2 |
+
# Use an official Node.js runtime as the base image
|
3 |
+
FROM node:18
|
4 |
+
|
5 |
+
# Set the working directory in the container
|
6 |
+
WORKDIR /usr/src/app
|
7 |
+
|
8 |
+
# Copy package.json and package-lock.json to the container
|
9 |
+
COPY package.json package-lock.json ./
|
10 |
+
|
11 |
+
# Install dependencies
|
12 |
+
RUN npm install
|
13 |
+
|
14 |
+
# Copy the rest of the application files to the container
|
15 |
+
COPY . .
|
16 |
+
|
17 |
+
# Build the Next.js application for production
|
18 |
+
RUN npm run build
|
19 |
+
|
20 |
+
# Expose the application port (assuming your app runs on port 3000)
|
21 |
+
EXPOSE 3000
|
22 |
+
|
23 |
+
# Start the application
|
24 |
+
CMD ["npm", "start"]
|
README.md
CHANGED
@@ -1,36 +1,12 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
# or
|
14 |
-
bun dev
|
15 |
-
```
|
16 |
-
|
17 |
-
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
18 |
-
|
19 |
-
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
20 |
-
|
21 |
-
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
22 |
-
|
23 |
-
## Learn More
|
24 |
-
|
25 |
-
To learn more about Next.js, take a look at the following resources:
|
26 |
-
|
27 |
-
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
28 |
-
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
29 |
-
|
30 |
-
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
31 |
-
|
32 |
-
## Deploy on Vercel
|
33 |
-
|
34 |
-
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.
|
35 |
-
|
36 |
-
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
|
|
1 |
+
---
|
2 |
+
title: Space Shuffler
|
3 |
+
emoji: 🔍♦️
|
4 |
+
colorFrom: blue
|
5 |
+
colorTo: pink
|
6 |
+
sdk: docker
|
7 |
+
pinned: false
|
8 |
+
short_description: Find hidden gems from all HF Spaces
|
9 |
+
license: mit
|
10 |
+
---
|
11 |
+
|
12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/actions.ts
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use server"
|
2 |
+
|
3 |
+
import { fetchSpaceRandomly } from "@/utils/network"
|
4 |
+
|
5 |
+
export const getSpace = async () => {
|
6 |
+
const space = await fetchSpaceRandomly();
|
7 |
+
return space;
|
8 |
+
}
|
app/layout.tsx
CHANGED
@@ -1,22 +1,36 @@
|
|
1 |
-
import
|
2 |
-
import { Inter } from "next/font/google";
|
3 |
-
import "./globals.css";
|
4 |
|
5 |
-
|
|
|
6 |
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
11 |
|
12 |
export default function RootLayout({
|
13 |
children,
|
14 |
-
}:
|
15 |
children: React.ReactNode;
|
16 |
-
}
|
17 |
return (
|
18 |
-
|
19 |
-
<
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
);
|
22 |
}
|
|
|
1 |
+
import { Inter, Montserrat } from "next/font/google";
|
|
|
|
|
2 |
|
3 |
+
import { ThemeProvider } from "@/components/theme-provider";
|
4 |
+
import "@/style/globals.css";
|
5 |
|
6 |
+
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
|
7 |
+
const montserrat = Montserrat({
|
8 |
+
subsets: ["latin"],
|
9 |
+
variable: "--font-montserrat",
|
10 |
+
});
|
11 |
|
12 |
export default function RootLayout({
|
13 |
children,
|
14 |
+
}: {
|
15 |
children: React.ReactNode;
|
16 |
+
}) {
|
17 |
return (
|
18 |
+
<>
|
19 |
+
<html lang="en" suppressHydrationWarning>
|
20 |
+
<head />
|
21 |
+
<body
|
22 |
+
className={`${inter.variable} ${montserrat.variable} bg-grid-small-white/5 font-sans`}
|
23 |
+
>
|
24 |
+
<ThemeProvider
|
25 |
+
attribute="class"
|
26 |
+
defaultTheme="system"
|
27 |
+
enableSystem
|
28 |
+
disableTransitionOnChange
|
29 |
+
>
|
30 |
+
{children}
|
31 |
+
</ThemeProvider>
|
32 |
+
</body>
|
33 |
+
</html>
|
34 |
+
</>
|
35 |
);
|
36 |
}
|
app/page.tsx
CHANGED
@@ -1,112 +1,34 @@
|
|
1 |
import Image from "next/image";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
-
export default function Home() {
|
4 |
return (
|
5 |
-
<main className="flex min-h-screen flex-col items-center justify-
|
6 |
-
<div className="
|
7 |
-
<
|
8 |
-
|
9 |
-
|
10 |
-
</p>
|
11 |
-
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:size-auto lg:bg-none">
|
12 |
-
<a
|
13 |
-
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
14 |
-
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
15 |
-
target="_blank"
|
16 |
-
rel="noopener noreferrer"
|
17 |
-
>
|
18 |
-
By{" "}
|
19 |
<Image
|
20 |
-
src=
|
21 |
-
alt="
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
priority
|
26 |
/>
|
27 |
-
</
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
<div className="relative z-[-1] flex place-items-center before:absolute before:h-[300px] before:w-full before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 sm:before:w-[480px] sm:after:w-[240px] before:lg:h-[360px]">
|
32 |
-
<Image
|
33 |
-
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
|
34 |
-
src="/next.svg"
|
35 |
-
alt="Next.js Logo"
|
36 |
-
width={180}
|
37 |
-
height={37}
|
38 |
-
priority
|
39 |
-
/>
|
40 |
-
</div>
|
41 |
-
|
42 |
-
<div className="mb-32 grid text-center lg:mb-0 lg:w-full lg:max-w-5xl lg:grid-cols-4 lg:text-left">
|
43 |
-
<a
|
44 |
-
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
45 |
-
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
46 |
-
target="_blank"
|
47 |
-
rel="noopener noreferrer"
|
48 |
-
>
|
49 |
-
<h2 className="mb-3 text-2xl font-semibold">
|
50 |
-
Docs{" "}
|
51 |
-
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
52 |
-
->
|
53 |
-
</span>
|
54 |
-
</h2>
|
55 |
-
<p className="m-0 max-w-[30ch] text-sm opacity-50">
|
56 |
-
Find in-depth information about Next.js features and API.
|
57 |
-
</p>
|
58 |
-
</a>
|
59 |
-
|
60 |
-
<a
|
61 |
-
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
62 |
-
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
63 |
-
target="_blank"
|
64 |
-
rel="noopener noreferrer"
|
65 |
-
>
|
66 |
-
<h2 className="mb-3 text-2xl font-semibold">
|
67 |
-
Learn{" "}
|
68 |
-
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
69 |
-
->
|
70 |
-
</span>
|
71 |
-
</h2>
|
72 |
-
<p className="m-0 max-w-[30ch] text-sm opacity-50">
|
73 |
-
Learn about Next.js in an interactive course with quizzes!
|
74 |
-
</p>
|
75 |
-
</a>
|
76 |
-
|
77 |
-
<a
|
78 |
-
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
79 |
-
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
80 |
-
target="_blank"
|
81 |
-
rel="noopener noreferrer"
|
82 |
-
>
|
83 |
-
<h2 className="mb-3 text-2xl font-semibold">
|
84 |
-
Templates{" "}
|
85 |
-
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
86 |
-
->
|
87 |
-
</span>
|
88 |
-
</h2>
|
89 |
-
<p className="m-0 max-w-[30ch] text-sm opacity-50">
|
90 |
-
Explore starter templates for Next.js.
|
91 |
-
</p>
|
92 |
-
</a>
|
93 |
-
|
94 |
-
<a
|
95 |
-
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
96 |
-
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
97 |
-
target="_blank"
|
98 |
-
rel="noopener noreferrer"
|
99 |
-
>
|
100 |
-
<h2 className="mb-3 text-2xl font-semibold">
|
101 |
-
Deploy{" "}
|
102 |
-
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
103 |
-
->
|
104 |
-
</span>
|
105 |
</h2>
|
106 |
-
|
107 |
-
|
108 |
-
</p>
|
109 |
-
</a>
|
110 |
</div>
|
111 |
</main>
|
112 |
);
|
|
|
1 |
import Image from "next/image";
|
2 |
+
import { Shuffle } from "lucide-react";
|
3 |
+
|
4 |
+
import CardsSvg from "@/images/cards.svg";
|
5 |
+
import { Space } from "@/components/space";
|
6 |
+
import { fetchSpaceRandomly } from "@/utils/network";
|
7 |
+
import { Shuffler } from "@/components/shuffler";
|
8 |
+
|
9 |
+
export default async function Home() {
|
10 |
+
const space = await fetchSpaceRandomly();
|
11 |
+
const nextSpace = await fetchSpaceRandomly();
|
12 |
|
|
|
13 |
return (
|
14 |
+
<main className="flex min-h-screen flex-col items-center justify-center p-8 lg:p-24">
|
15 |
+
<div className="w-full max-w-xl mx-auto grid gap-16 lg:gap-24 grid-cols-1">
|
16 |
+
<header className="grid grid-cols-1 gap-5">
|
17 |
+
<h1 className="relative font-sans text-center text-5xl lg:text-7xl font-extrabold max-w-max mx-auto text-transparent bg-clip-text bg-flashy">
|
18 |
+
Space Shuffler
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
<Image
|
20 |
+
src={CardsSvg}
|
21 |
+
alt="Cards"
|
22 |
+
width={70}
|
23 |
+
height={70}
|
24 |
+
className="w-10 lg:w-14 absolute -right-9 lg:-right-12 -top-5 lg:-top-6 -rotate-6"
|
|
|
25 |
/>
|
26 |
+
</h1>
|
27 |
+
<h2 className="font-serif text-white/60 text-xl lg:text-2xl text-center">
|
28 |
+
Find hidden gems from 180k Spaces
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
</h2>
|
30 |
+
</header>
|
31 |
+
<Shuffler space={space} nextSpace={nextSpace} />
|
|
|
|
|
32 |
</div>
|
33 |
</main>
|
34 |
);
|
components/animate/card.tsx
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
motion,
|
3 |
+
useMotionValue,
|
4 |
+
useTransform,
|
5 |
+
AnimatePresence,
|
6 |
+
} from "framer-motion";
|
7 |
+
import { useState } from "react";
|
8 |
+
|
9 |
+
export const Card = ({ children, drag, index, setIndex, front }: any) => {
|
10 |
+
const [exitX, setExitX] = useState(0);
|
11 |
+
|
12 |
+
const x = useMotionValue(0);
|
13 |
+
const scale = useTransform(x, [-150, 0, 150], [0.5, 1, 0.5]);
|
14 |
+
const rotate = useTransform(x, [-150, 0, 150], [-45, 0, 45], {
|
15 |
+
clamp: false,
|
16 |
+
});
|
17 |
+
|
18 |
+
const variantsFrontCard = {
|
19 |
+
animate: { scale: 1, y: 0, opacity: 1 },
|
20 |
+
exit: (custom: number) => ({
|
21 |
+
x: custom,
|
22 |
+
opacity: 0,
|
23 |
+
scale: 0.5,
|
24 |
+
transition: { duration: 0.2 },
|
25 |
+
}),
|
26 |
+
};
|
27 |
+
const variantsBackCard = {
|
28 |
+
initial: { scale: 0, y: 105, opacity: 0 },
|
29 |
+
animate: { scale: 0.75, y: 30, opacity: 0 },
|
30 |
+
};
|
31 |
+
|
32 |
+
function handleDragEnd(_: any, info: any) {
|
33 |
+
if (info.offset.x < -100) {
|
34 |
+
setExitX(-250);
|
35 |
+
setIndex(index + 1);
|
36 |
+
}
|
37 |
+
if (info.offset.x > 100) {
|
38 |
+
setExitX(250);
|
39 |
+
setIndex(index + 1);
|
40 |
+
}
|
41 |
+
}
|
42 |
+
|
43 |
+
return (
|
44 |
+
<motion.div
|
45 |
+
style={{
|
46 |
+
position: "absolute",
|
47 |
+
width: "100%",
|
48 |
+
height: "100%",
|
49 |
+
top: 0,
|
50 |
+
x,
|
51 |
+
rotate,
|
52 |
+
}}
|
53 |
+
variants={front ? variantsFrontCard : variantsBackCard}
|
54 |
+
initial="initial"
|
55 |
+
animate="animate"
|
56 |
+
exit="exit"
|
57 |
+
custom={exitX}
|
58 |
+
transition={
|
59 |
+
front
|
60 |
+
? { type: "spring", stiffness: 300, damping: 20 }
|
61 |
+
: { scale: { duration: 0.2 }, opacity: { duration: 0.4 } }
|
62 |
+
}
|
63 |
+
>
|
64 |
+
{children}
|
65 |
+
</motion.div>
|
66 |
+
);
|
67 |
+
};
|
components/shuffler/button.tsx
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Shuffle } from "lucide-react";
|
2 |
+
|
3 |
+
export const ButtonShuffler = ({ onClick }: { onClick: () => void }) => {
|
4 |
+
return (
|
5 |
+
<button
|
6 |
+
className="bg-white/90 hover:bg-white transition-all duration-200 rounded-full text-xl flex items-center justify-center gap-3 text-black px-8 py-5 font-bold"
|
7 |
+
onClick={onClick}
|
8 |
+
>
|
9 |
+
<Shuffle size={28} />
|
10 |
+
Shuffle
|
11 |
+
</button>
|
12 |
+
);
|
13 |
+
};
|
components/shuffler/index.tsx
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
|
3 |
+
import { useState } from "react";
|
4 |
+
import { motion, AnimatePresence } from "framer-motion";
|
5 |
+
|
6 |
+
import { getSpace } from "@/app/actions";
|
7 |
+
import { Space as SpaceProps } from "@/utils/types";
|
8 |
+
import { Space } from "@/components/space";
|
9 |
+
|
10 |
+
import { ButtonShuffler } from "./button";
|
11 |
+
import { Card } from "@/components/animate/card";
|
12 |
+
|
13 |
+
export const Shuffler = ({
|
14 |
+
space: initialSpace,
|
15 |
+
nextSpace: initialNextSpace,
|
16 |
+
}: {
|
17 |
+
space: SpaceProps;
|
18 |
+
nextSpace: SpaceProps;
|
19 |
+
}) => {
|
20 |
+
const [index, setIndex] = useState(0);
|
21 |
+
const [space, setSpace] = useState<SpaceProps>(
|
22 |
+
JSON.parse(JSON.stringify(initialSpace))
|
23 |
+
);
|
24 |
+
const [nextSpace, setNextSpace] = useState<SpaceProps>(
|
25 |
+
JSON.parse(JSON.stringify(initialNextSpace))
|
26 |
+
);
|
27 |
+
|
28 |
+
return (
|
29 |
+
<motion.div className="grid grid-cols-1 gap-10 relative">
|
30 |
+
<div className="relative w-full h-[350px]">
|
31 |
+
<AnimatePresence initial={false}>
|
32 |
+
<Card key={index + 1} front={false}>
|
33 |
+
<Space space={nextSpace} />
|
34 |
+
</Card>
|
35 |
+
<Card key={index} front={true} index={index} setIndex={setIndex}>
|
36 |
+
<Space space={space} />
|
37 |
+
</Card>
|
38 |
+
</AnimatePresence>
|
39 |
+
</div>
|
40 |
+
<div className="w-4 h-[1px] bg-white/50 mx-auto" />
|
41 |
+
<footer className="flex items-center justify-center">
|
42 |
+
<ButtonShuffler
|
43 |
+
onClick={() => {
|
44 |
+
getSpace().then((newSpace) => {
|
45 |
+
setSpace(nextSpace);
|
46 |
+
setNextSpace(newSpace);
|
47 |
+
setIndex(index + 1);
|
48 |
+
});
|
49 |
+
}}
|
50 |
+
/>
|
51 |
+
</footer>
|
52 |
+
</motion.div>
|
53 |
+
);
|
54 |
+
};
|
components/space/index.tsx
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Link from "next/link";
|
2 |
+
import { ExternalLink } from "lucide-react";
|
3 |
+
|
4 |
+
import { Space as SpaceProps } from "@/utils/types";
|
5 |
+
import { SpaceAuthor } from "./sub/author";
|
6 |
+
import { SpaceHeader } from "./sub/header";
|
7 |
+
|
8 |
+
export const Space: React.FC<{ space: SpaceProps }> = ({ space }) => {
|
9 |
+
return (
|
10 |
+
<main className="w-full rounded-xl grid grid-cols-1 gap-3">
|
11 |
+
<SpaceHeader space={space} />
|
12 |
+
<div className="flex items-start justify-between">
|
13 |
+
<p className="text-[1.95rem] leading-[2rem] text-white/80 font-light">
|
14 |
+
{space?.title}
|
15 |
+
</p>
|
16 |
+
<Link
|
17 |
+
href={`https://huggingface.co/spaces/${space.id}`}
|
18 |
+
target="_blank"
|
19 |
+
>
|
20 |
+
<ExternalLink
|
21 |
+
size={24}
|
22 |
+
className="text-white/70 hover:text-blue-500"
|
23 |
+
/>
|
24 |
+
</Link>
|
25 |
+
</div>
|
26 |
+
<SpaceAuthor author={space?.authorData} />
|
27 |
+
{space?.shortDescription && (
|
28 |
+
<p className="text-xl font-serif text-white/60 flex items-center justify-start gap-1.5 line-clamp-1">
|
29 |
+
Explore thousands of community trained LoRAs.
|
30 |
+
</p>
|
31 |
+
)}
|
32 |
+
</main>
|
33 |
+
);
|
34 |
+
};
|
components/space/sub/author.tsx
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { SpaceAuthorData } from "@/utils/types";
|
2 |
+
import Image from "next/image";
|
3 |
+
|
4 |
+
export const SpaceAuthor = ({ author }: { author: SpaceAuthorData }) => {
|
5 |
+
return (
|
6 |
+
<p className="text-xl font-serif text-white/60 flex items-center justify-start gap-1.5">
|
7 |
+
by
|
8 |
+
<Image
|
9 |
+
src={
|
10 |
+
author.avatarUrl?.startsWith("http")
|
11 |
+
? author.avatarUrl
|
12 |
+
: `https://huggingface.co${author.avatarUrl}`
|
13 |
+
}
|
14 |
+
width={44}
|
15 |
+
height={44}
|
16 |
+
alt={`${author.fullname}'s avatar from Hugging Face`}
|
17 |
+
className="rounded-full w-6 h-6"
|
18 |
+
/>
|
19 |
+
{author.name}
|
20 |
+
</p>
|
21 |
+
);
|
22 |
+
};
|
components/space/sub/header.tsx
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Heart } from "lucide-react";
|
2 |
+
|
3 |
+
import { Space } from "@/utils/types";
|
4 |
+
|
5 |
+
export const SpaceHeader = ({ space }: { space: Space }) => {
|
6 |
+
return (
|
7 |
+
<figure className="relative z-[1] h-[200px] px-6 py-4 mb-4">
|
8 |
+
<div
|
9 |
+
className={`bg-gradient-to-br ${space.colorFrom} ${space.colorTo} absolute w-full top-0 left-0 h-full rounded-2xl -z-[1]`}
|
10 |
+
/>
|
11 |
+
<div
|
12 |
+
className={`bg-gradient-to-br ${space.colorFrom} ${space.colorTo} -z-[1] blur-lg opacity-50 h-full w-full absolute top-0 left-0`}
|
13 |
+
/>
|
14 |
+
<div className="flex items-center justify-between w-full relative">
|
15 |
+
<p>tags</p>
|
16 |
+
<p className="flex items-center justify-end gap-1 text-sm text-white">
|
17 |
+
<Heart size={14} />
|
18 |
+
{space.likes}
|
19 |
+
</p>
|
20 |
+
</div>
|
21 |
+
<p className="absolute top-0 left-0 h-full w-full flex items-center justify-center opacity-90 text-7xl text-center whitespace-nowrap truncate">
|
22 |
+
{space.emoji}
|
23 |
+
</p>
|
24 |
+
</figure>
|
25 |
+
);
|
26 |
+
};
|
components/theme-provider.tsx
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
|
3 |
+
import * as React from "react";
|
4 |
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
5 |
+
import { type ThemeProviderProps } from "next-themes/dist/types";
|
6 |
+
|
7 |
+
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
8 |
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
9 |
+
}
|
components/toggle-mode.tsx
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useTheme } from "next-themes";
|
2 |
+
|
3 |
+
export const ToggleMode = () => {
|
4 |
+
const { setTheme } = useTheme();
|
5 |
+
|
6 |
+
return (
|
7 |
+
<div>
|
8 |
+
<button onClick={() => setTheme("light")}>Light Mode</button>
|
9 |
+
<button onClick={() => setTheme("dark")}>Dark Mode</button>
|
10 |
+
</div>
|
11 |
+
);
|
12 |
+
};
|
images/cards.svg
ADDED
next.config.mjs
CHANGED
@@ -1,4 +1,13 @@
|
|
1 |
/** @type {import('next').NextConfig} */
|
2 |
-
const nextConfig = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
export default nextConfig;
|
|
|
1 |
/** @type {import('next').NextConfig} */
|
2 |
+
const nextConfig = {
|
3 |
+
images: {
|
4 |
+
remotePatterns: [
|
5 |
+
{
|
6 |
+
protocol: "https",
|
7 |
+
hostname: "**"
|
8 |
+
}
|
9 |
+
],
|
10 |
+
},
|
11 |
+
};
|
12 |
|
13 |
export default nextConfig;
|
package-lock.json
CHANGED
@@ -8,7 +8,11 @@
|
|
8 |
"name": "spaces-randomizer",
|
9 |
"version": "0.1.0",
|
10 |
"dependencies": {
|
|
|
|
|
|
|
11 |
"next": "14.2.6",
|
|
|
12 |
"react": "^18",
|
13 |
"react-dom": "^18"
|
14 |
},
|
@@ -2082,6 +2086,30 @@
|
|
2082 |
"url": "https://github.com/sponsors/isaacs"
|
2083 |
}
|
2084 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2085 |
"node_modules/fs.realpath": {
|
2086 |
"version": "1.0.0",
|
2087 |
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
@@ -3057,6 +3085,14 @@
|
|
3057 |
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
3058 |
"dev": true
|
3059 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3060 |
"node_modules/merge2": {
|
3061 |
"version": "1.4.1",
|
3062 |
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
@@ -3079,6 +3115,14 @@
|
|
3079 |
"node": ">=8.6"
|
3080 |
}
|
3081 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3082 |
"node_modules/minimatch": {
|
3083 |
"version": "3.1.2",
|
3084 |
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
@@ -3198,6 +3242,15 @@
|
|
3198 |
}
|
3199 |
}
|
3200 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3201 |
"node_modules/next/node_modules/postcss": {
|
3202 |
"version": "8.4.31",
|
3203 |
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
|
|
8 |
"name": "spaces-randomizer",
|
9 |
"version": "0.1.0",
|
10 |
"dependencies": {
|
11 |
+
"framer-motion": "^11.3.29",
|
12 |
+
"lucide-react": "^0.429.0",
|
13 |
+
"mini-svg-data-uri": "^1.4.4",
|
14 |
"next": "14.2.6",
|
15 |
+
"next-themes": "^0.3.0",
|
16 |
"react": "^18",
|
17 |
"react-dom": "^18"
|
18 |
},
|
|
|
2086 |
"url": "https://github.com/sponsors/isaacs"
|
2087 |
}
|
2088 |
},
|
2089 |
+
"node_modules/framer-motion": {
|
2090 |
+
"version": "11.3.29",
|
2091 |
+
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.29.tgz",
|
2092 |
+
"integrity": "sha512-uyDuUOeOElJEA3kbkbyoTNEf75Jih1EUg0ouLKYMlGDdt/LaJPmO+FyOGAGxM2HwKhHcAoKFNveR5A8peb7yhw==",
|
2093 |
+
"dependencies": {
|
2094 |
+
"tslib": "^2.4.0"
|
2095 |
+
},
|
2096 |
+
"peerDependencies": {
|
2097 |
+
"@emotion/is-prop-valid": "*",
|
2098 |
+
"react": "^18.0.0",
|
2099 |
+
"react-dom": "^18.0.0"
|
2100 |
+
},
|
2101 |
+
"peerDependenciesMeta": {
|
2102 |
+
"@emotion/is-prop-valid": {
|
2103 |
+
"optional": true
|
2104 |
+
},
|
2105 |
+
"react": {
|
2106 |
+
"optional": true
|
2107 |
+
},
|
2108 |
+
"react-dom": {
|
2109 |
+
"optional": true
|
2110 |
+
}
|
2111 |
+
}
|
2112 |
+
},
|
2113 |
"node_modules/fs.realpath": {
|
2114 |
"version": "1.0.0",
|
2115 |
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
|
|
3085 |
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
3086 |
"dev": true
|
3087 |
},
|
3088 |
+
"node_modules/lucide-react": {
|
3089 |
+
"version": "0.429.0",
|
3090 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.429.0.tgz",
|
3091 |
+
"integrity": "sha512-548DahFy7Ey+0OPlOyZ6ipnbGJzewv8gq2DxDrnAzWZ4RN4+3XyIwQXhD4AQvuREFjS1EnbAgOMaYB0VQyaK1g==",
|
3092 |
+
"peerDependencies": {
|
3093 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
3094 |
+
}
|
3095 |
+
},
|
3096 |
"node_modules/merge2": {
|
3097 |
"version": "1.4.1",
|
3098 |
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
|
|
3115 |
"node": ">=8.6"
|
3116 |
}
|
3117 |
},
|
3118 |
+
"node_modules/mini-svg-data-uri": {
|
3119 |
+
"version": "1.4.4",
|
3120 |
+
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
3121 |
+
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
|
3122 |
+
"bin": {
|
3123 |
+
"mini-svg-data-uri": "cli.js"
|
3124 |
+
}
|
3125 |
+
},
|
3126 |
"node_modules/minimatch": {
|
3127 |
"version": "3.1.2",
|
3128 |
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
|
|
3242 |
}
|
3243 |
}
|
3244 |
},
|
3245 |
+
"node_modules/next-themes": {
|
3246 |
+
"version": "0.3.0",
|
3247 |
+
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz",
|
3248 |
+
"integrity": "sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==",
|
3249 |
+
"peerDependencies": {
|
3250 |
+
"react": "^16.8 || ^17 || ^18",
|
3251 |
+
"react-dom": "^16.8 || ^17 || ^18"
|
3252 |
+
}
|
3253 |
+
},
|
3254 |
"node_modules/next/node_modules/postcss": {
|
3255 |
"version": "8.4.31",
|
3256 |
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
package.json
CHANGED
@@ -9,18 +9,22 @@
|
|
9 |
"lint": "next lint"
|
10 |
},
|
11 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
12 |
"react": "^18",
|
13 |
-
"react-dom": "^18"
|
14 |
-
"next": "14.2.6"
|
15 |
},
|
16 |
"devDependencies": {
|
17 |
-
"typescript": "^5",
|
18 |
"@types/node": "^20",
|
19 |
"@types/react": "^18",
|
20 |
"@types/react-dom": "^18",
|
|
|
|
|
21 |
"postcss": "^8",
|
22 |
"tailwindcss": "^3.4.1",
|
23 |
-
"
|
24 |
-
"eslint-config-next": "14.2.6"
|
25 |
}
|
26 |
}
|
|
|
9 |
"lint": "next lint"
|
10 |
},
|
11 |
"dependencies": {
|
12 |
+
"framer-motion": "^11.3.29",
|
13 |
+
"lucide-react": "^0.429.0",
|
14 |
+
"mini-svg-data-uri": "^1.4.4",
|
15 |
+
"next": "14.2.6",
|
16 |
+
"next-themes": "^0.3.0",
|
17 |
"react": "^18",
|
18 |
+
"react-dom": "^18"
|
|
|
19 |
},
|
20 |
"devDependencies": {
|
|
|
21 |
"@types/node": "^20",
|
22 |
"@types/react": "^18",
|
23 |
"@types/react-dom": "^18",
|
24 |
+
"eslint": "^8",
|
25 |
+
"eslint-config-next": "14.2.6",
|
26 |
"postcss": "^8",
|
27 |
"tailwindcss": "^3.4.1",
|
28 |
+
"typescript": "^5"
|
|
|
29 |
}
|
30 |
}
|
{app → style}/globals.css
RENAMED
File without changes
|
tailwind.config.ts
CHANGED
@@ -1,10 +1,16 @@
|
|
1 |
import type { Config } from "tailwindcss";
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
const config: Config = {
|
4 |
content: [
|
5 |
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
6 |
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
7 |
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
|
8 |
],
|
9 |
theme: {
|
10 |
extend: {
|
@@ -12,9 +18,40 @@ const config: Config = {
|
|
12 |
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
13 |
"gradient-conic":
|
14 |
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
|
|
15 |
},
|
|
|
|
|
|
|
|
|
16 |
},
|
17 |
},
|
18 |
-
plugins: [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
export default config;
|
|
|
1 |
import type { Config } from "tailwindcss";
|
2 |
+
const svgToDataUri = require("mini-svg-data-uri");
|
3 |
+
|
4 |
+
const {
|
5 |
+
default: flattenColorPalette,
|
6 |
+
} = require("tailwindcss/lib/util/flattenColorPalette");
|
7 |
|
8 |
const config: Config = {
|
9 |
content: [
|
10 |
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
11 |
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
12 |
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
13 |
+
"./utils/**/*.{js,ts,jsx,tsx,mdx}",
|
14 |
],
|
15 |
theme: {
|
16 |
extend: {
|
|
|
18 |
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
19 |
"gradient-conic":
|
20 |
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
21 |
+
"flashy": "linear-gradient(90deg, #2B90FF 0%, #861FFF 55.81%, #FF3270 93.33%, #FFD702 100%)",
|
22 |
},
|
23 |
+
fontFamily: {
|
24 |
+
sans: ['var(--font-inter)'],
|
25 |
+
serif: ['var(--font-montserrat)'],
|
26 |
+
}
|
27 |
},
|
28 |
},
|
29 |
+
plugins: [
|
30 |
+
addVariablesForColors,
|
31 |
+
function ({ matchUtilities, theme }: any) {
|
32 |
+
matchUtilities(
|
33 |
+
{
|
34 |
+
"bg-grid-small": (value: string) => ({
|
35 |
+
backgroundImage: `url("${svgToDataUri(
|
36 |
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="44" height="44" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`
|
37 |
+
)}")`,
|
38 |
+
}),
|
39 |
+
},
|
40 |
+
{ values: flattenColorPalette(theme("backgroundColor")), type: "color" }
|
41 |
+
);
|
42 |
+
},
|
43 |
+
],
|
44 |
};
|
45 |
+
|
46 |
+
function addVariablesForColors({ addBase, theme }: any ) {
|
47 |
+
let allColors = flattenColorPalette(theme("colors"));
|
48 |
+
let newVars = Object.fromEntries(
|
49 |
+
Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
|
50 |
+
);
|
51 |
+
|
52 |
+
addBase({
|
53 |
+
":root": newVars,
|
54 |
+
});
|
55 |
+
}
|
56 |
+
|
57 |
export default config;
|
utils/network.ts
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const COLORS = {
|
2 |
+
from: {
|
3 |
+
purple: "from-purple-500",
|
4 |
+
pink: "from-pink-500",
|
5 |
+
gray: "from-gray-700",
|
6 |
+
blue: "from-blue-500",
|
7 |
+
green: "from-green-500",
|
8 |
+
yellow: "from-yellow-500",
|
9 |
+
indigo: "from-indigo-500",
|
10 |
+
},
|
11 |
+
to: {
|
12 |
+
purple: "to-purple-500",
|
13 |
+
pink: "to-pink-500",
|
14 |
+
gray: "to-gray-700",
|
15 |
+
blue: "to-blue-500",
|
16 |
+
green: "to-green-500",
|
17 |
+
yellow: "to-yellow-500",
|
18 |
+
indigo: "to-indigo-500",
|
19 |
+
},
|
20 |
+
};
|
21 |
+
|
22 |
+
export const fetchSpaceRandomly = async () => {
|
23 |
+
const randomPage = Math.floor(Math.random() * 100) + 1;
|
24 |
+
const url = `https://huggingface.co/spaces-json?p=${randomPage}&runtime.stage=RUNNING&sort=trending`;
|
25 |
+
const response = await fetch(url);
|
26 |
+
const json = await response.json();
|
27 |
+
const spaces = json?.spaces;
|
28 |
+
|
29 |
+
if (!spaces) {
|
30 |
+
return null;
|
31 |
+
}
|
32 |
+
|
33 |
+
const randomIndex = Math.floor(Math.random() * spaces.length);
|
34 |
+
|
35 |
+
const space = {
|
36 |
+
...spaces[randomIndex],
|
37 |
+
colorFrom: COLORS.from[spaces[randomIndex].colorFrom as keyof typeof COLORS.from] || COLORS.from.purple,
|
38 |
+
colorTo: COLORS.to[spaces[randomIndex].colorTo as keyof typeof COLORS.from] || COLORS.to.pink,
|
39 |
+
}
|
40 |
+
return space;
|
41 |
+
}
|
utils/types.ts
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export interface Space {
|
2 |
+
id: string;
|
3 |
+
name: string;
|
4 |
+
description: string;
|
5 |
+
likes: number;
|
6 |
+
author: string;
|
7 |
+
title: string,
|
8 |
+
emoji: string,
|
9 |
+
colorFrom: string,
|
10 |
+
colorTo: string,
|
11 |
+
pinned: boolean;
|
12 |
+
shortDescription?: string;
|
13 |
+
runtime: SpaceRuntime;
|
14 |
+
authorData: SpaceAuthorData;
|
15 |
+
}
|
16 |
+
|
17 |
+
export interface SpaceAuthorData {
|
18 |
+
avatarUrl: string;
|
19 |
+
fullname: string
|
20 |
+
name: string;
|
21 |
+
}
|
22 |
+
|
23 |
+
export interface SpaceRuntime {
|
24 |
+
stage: string;
|
25 |
+
}
|