mirror of
https://github.com/D4M13N-D3V/neroshitron.git
synced 2025-03-14 10:05:04 +00:00
feat:gallery
This commit is contained in:
parent
91ea64d20f
commit
1b5e1da64d
39
app/api/galleries/[id]/images/route.ts
Normal file
39
app/api/galleries/[id]/images/route.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
const galleryId = params.id;
|
||||
const supabase = createClient();
|
||||
|
||||
// List all files in the galleryId path
|
||||
let { data: files, error } = await supabase.storage.from('galleries').list(galleryId);
|
||||
|
||||
if (files==null || error) {
|
||||
console.error('Error listing files:', error);
|
||||
return NextResponse.error();
|
||||
}
|
||||
|
||||
const urls = [];
|
||||
|
||||
// Loop through each file, download it, convert it to base64, and add the data URL to the array
|
||||
for (const file of files) {
|
||||
let { data: blobdata, error } = await supabase.storage.from('galleries').download(galleryId+"/"+file.name);
|
||||
|
||||
if (error || blobdata==null) {
|
||||
console.error('Error downloading file:', error);
|
||||
continue;
|
||||
}
|
||||
|
||||
const base64 = Buffer.from(await blobdata.arrayBuffer()).toString('base64');
|
||||
const contentType = file.name.endsWith('.png') ? 'image/png' : 'image/jpeg';
|
||||
const dataUrl = `data:${contentType};base64,${base64}`;
|
||||
|
||||
urls.push(dataUrl);
|
||||
}
|
||||
|
||||
// Return a JSON response with the array of URLs
|
||||
return new Response(JSON.stringify(urls), { headers: { 'content-type': 'application/json' } });
|
||||
}
|
29
app/api/galleries/[id]/thumbnail/route.ts
Normal file
29
app/api/galleries/[id]/thumbnail/route.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
const galleryId= params.id // 312
|
||||
const supabase = createClient();
|
||||
|
||||
// Extract galleryId from the route value
|
||||
var blob = null;
|
||||
var contentType = "image/jpeg"
|
||||
let { data: blobdata, error } = await supabase.storage.from('galleries').download(galleryId+'/1.jpeg')
|
||||
blob = blobdata;
|
||||
console.log(error)
|
||||
if (error) {
|
||||
contentType = "image/png"
|
||||
let { data: blobdata, error } = await supabase.storage.from('galleries').download(galleryId+'/1.png')
|
||||
console.log(error)
|
||||
blob = blobdata;
|
||||
}
|
||||
if(blob != null){
|
||||
const base64 = Buffer.from(await blob.arrayBuffer()).toString('base64');
|
||||
const dataUrl = `data:${contentType};base64,${base64}`;
|
||||
return new Response(dataUrl);
|
||||
}
|
||||
return NextResponse.error();
|
||||
}
|
12
app/api/galleries/route.ts
Normal file
12
app/api/galleries/route.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const supabase = createClient();
|
||||
let { data: galleries, error } = await supabase
|
||||
.from('galleries')
|
||||
.select('*')
|
||||
return NextResponse.json(galleries)
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function Gallery() {
|
||||
const supabase = createClient();
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
return (
|
||||
<div className="flex-1 w-full flex flex-col gap-20 items-center animate-in">
|
||||
<div className="absolute w-full h-full">
|
||||
<img src="gallery_girl.png" className="float-right object-cover h-screen w-3/6 animate-fade-up animate-duration-[3000ms]" alt="Background" />
|
||||
</div>
|
||||
<h1 className="animate-jump animate-duration-1000 animate-ease-linear z-10 pt-20 text-7xl text-center text-white text-shadow-purple-grey-glow absolute">Neroshi's Gallery</h1>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4 z-50 pt-60 ">
|
||||
<a href="#" className="animate-fade animate-duration-1000 animate-delay-[2000ms] animate-ease-linear block w-64 h-64 max-w-sm p-6 bg-opacity-50 backdrop-blur rounded-lg shadow hover:bg-gray-100 hover:bg-opacity-15">
|
||||
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white text-shadow-purple-grey-glow">Gallery Name</h5>
|
||||
</a>
|
||||
<a href="#" className="animate-fade animate-duration-1000 animate-delay-[2000ms] animate-ease-linear block w-64 h-64 max-w-sm p-6 bg-opacity-50 backdrop-blur rounded-lg shadow hover:bg-gray-100 hover:bg-opacity-15">
|
||||
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white text-shadow-purple-grey-glow">Gallery Name</h5>
|
||||
</a>
|
||||
<a href="#" className="animate-fade animate-duration-1000 animate-delay-[2000ms] animate-ease-linear block w-64 h-64 max-w-sm p-6 bg-opacity-50 backdrop-blur rounded-lg shadow hover:bg-gray-100 hover:bg-opacity-15">
|
||||
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white text-shadow-purple-grey-glow">Gallery Name</h5>
|
||||
</a>
|
||||
<a href="#" className="animate-fade animate-duration-1000 animate-delay-[2000ms] animate-ease-linear block w-64 h-64 max-w-sm p-6 bg-opacity-50 backdrop-blur rounded-lg shadow hover:bg-gray-100 hover:bg-opacity-15">
|
||||
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white text-shadow-purple-grey-glow">Gallery Name</h5>
|
||||
</a>
|
||||
<a href="#" className="animate-fade animate-duration-1000 animate-delay-[2000ms] animate-ease-linear block w-64 h-64 max-w-sm p-6 bg-opacity-50 backdrop-blur rounded-lg shadow hover:bg-gray-100 hover:bg-opacity-15">
|
||||
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white text-shadow-purple-grey-glow">Gallery Name</h5>
|
||||
</a>
|
||||
<a href="#" className="animate-fade animate-duration-1000 animate-delay-[2000ms] animate-ease-linear block w-64 h-64 max-w-sm p-6 bg-opacity-50 backdrop-blur rounded-lg shadow hover:bg-gray-100 hover:bg-opacity-15">
|
||||
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white text-shadow-purple-grey-glow">Gallery Name</h5>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
74
app/gallery/page.tsx
Normal file
74
app/gallery/page.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Vortex } from "@/components/ui/vortex";
|
||||
import GalleryThumbnail from "@/components/ui/gallery_thumbnail";
|
||||
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { User } from "@supabase/supabase-js";
|
||||
import Gallery from "@/components/ui/gallery";
|
||||
|
||||
function PageComponent() {
|
||||
|
||||
const supabase = createClient();
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [galleries, setGalleries] = useState<any[]>([]); // replace any with your gallery type
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [selectedGallery, setSelectedGallery] = useState<number | null>(null);
|
||||
|
||||
|
||||
const selectGallery = (gallery:number) => {
|
||||
setSelectedGallery(gallery);
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const getData = async () => {
|
||||
const galleriesResponse = await fetch('/api/galleries');
|
||||
const galleriesData = await galleriesResponse.json();
|
||||
let { data: { user } } = await supabase.auth.getUser();
|
||||
setGalleries(galleriesData);
|
||||
setUser(user);
|
||||
setLoading(false);
|
||||
}
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, [selectedGallery,isOpen]);
|
||||
return ( ( user ? (
|
||||
<div className="w-full h-full flex justify-center">
|
||||
<div className="flex-1 w-full h-full flex flex-col gap-20">
|
||||
<div className="absolute w-full h-full overflow-hidden z-2 animate-jump-in animate-ease-out">
|
||||
<img src="gallery_girl.png" className="float-right object-cover h-screen w-3/6" alt="Background" />
|
||||
</div>
|
||||
<div className="absolute items-center w-3/5 h-full ml-10 z-2 overflow-hidden">
|
||||
<div className="grid grid-cols-3 gap-x-10 h-full overflow-y-auto no-scrollbar pt-36">
|
||||
{galleries.map((gallery, index) => (
|
||||
<GalleryThumbnail key={index} id={gallery.id} onSelect={() => selectGallery(gallery.id)}></GalleryThumbnail>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(isOpen ? (
|
||||
<>
|
||||
|
||||
|
||||
<div className={`fixed inset-0 transition-opacity${isOpen ? 'animate-in' : 'fade-out'}`} aria-hidden="true">
|
||||
<div className="absolute inset-0 bg-neroshi-blue-900 opacity-70" onClick={()=>setIsOpen(false)} >
|
||||
</div>
|
||||
<div className="absolute inset-0 overflow-y-auto overflow-x-hidden no-scrollbar pt-20 w-full p-20">
|
||||
<Gallery id={selectedGallery as number} closeMenu={() => setIsOpen(false)}></Gallery>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</>
|
||||
): null)}
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<h1>loading</h1>
|
||||
)));
|
||||
}
|
||||
|
||||
export default PageComponent;
|
@ -2,6 +2,40 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.my-masonry-grid {
|
||||
display: -webkit-box; /* Not needed if autoprefixing */
|
||||
display: -ms-flexbox; /* Not needed if autoprefixing */
|
||||
display: flex;
|
||||
margin-left: -30px; /* gutter size offset */
|
||||
width: auto;
|
||||
}
|
||||
.my-masonry-grid_column {
|
||||
padding-left: 30px; /* gutter size */
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
/* Style your items */
|
||||
.my-masonry-grid_column > div { /* change div to reference your elements you put in <Masonry> */
|
||||
background: grey;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
@keyframes fadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
.fade-out {
|
||||
animation: fadeOut 0.5s forwards;
|
||||
}
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 200 20% 98%;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { GeistSans } from "geist/font/sans";
|
||||
import "./globals.css";
|
||||
import NavigationBar from "@/components/NavigationBar";
|
||||
import { Vortex } from "@/components/ui/vortex";
|
||||
|
||||
const defaultUrl = process.env.VERCEL_URL
|
||||
? `https://${process.env.VERCEL_URL}`
|
||||
@ -23,7 +24,7 @@ export default function RootLayout({
|
||||
<div className="w-full absolute z-20">
|
||||
<NavigationBar/>
|
||||
</div>
|
||||
<main className="min-h-screen flex flex-col items-center">
|
||||
<main className="min-h-screen flex flex-col items-center bg-gradient-to-r from-neroshi-blue-900 to-neroshi-blue-950">
|
||||
{children}
|
||||
</main>
|
||||
</body>
|
||||
|
33
app/livestream/page.tsx
Normal file
33
app/livestream/page.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
function PageComponent() {
|
||||
|
||||
const getData = async () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-40 h-full w-full animate-in"> {/* This adds padding top of 20px */}
|
||||
<div className="flex">
|
||||
<iframe
|
||||
className="flex-grow"
|
||||
style={{flexBasis: '90%'}} // Video takes up 90% of the width
|
||||
src="http://localhost:8080/embed/video"
|
||||
title="Owncast"
|
||||
height={720}
|
||||
referrerPolicy="origin"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
<iframe
|
||||
className="flex-2"
|
||||
style={{flexBasis: '10%'}} // Chat takes up 10% of the width
|
||||
src="http://localhost:8080/embed/chat/readwrite"
|
||||
title="Owncast"
|
||||
referrerPolicy="origin"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageComponent;
|
@ -25,7 +25,7 @@ export default function Login({
|
||||
return redirect("/login?message=Could not authenticate user");
|
||||
}
|
||||
|
||||
return redirect("/protected");
|
||||
return redirect("/gallery");
|
||||
};
|
||||
|
||||
const signUp = async (formData: FormData) => {
|
||||
@ -59,7 +59,7 @@ export default function Login({
|
||||
|
||||
<Link
|
||||
href="/"
|
||||
className="absolute left-1 top-44 py-2 px-4 rounded-md no-underline text-foreground bg-btn-background hover:bg-btn-background-hover flex items-center group text-sm"
|
||||
className="absolute left-1 top-44 py-2 px-4 rounded-md no-underline text-foreground flex items-center group text-sm"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -93,21 +93,21 @@ export default function Login({
|
||||
<div className="flex">
|
||||
<SubmitButton
|
||||
formAction={signIn}
|
||||
className="bg-green-700 rounded-md px-4 py-2 text-foreground mb-2 mx-1 w-1/2"
|
||||
className="bg-neroshi-blue-500 hover:bg-neroshi-blue-400 rounded-md px-4 py-2 text-foreground mb-2 mx-1 w-1/2"
|
||||
pendingText="Signing In..."
|
||||
>
|
||||
Sign In
|
||||
</SubmitButton>
|
||||
<SubmitButton
|
||||
formAction={signUp}
|
||||
className="border border-foreground/20 rounded-md px-4 py-2 text-foreground mb-2 mx-1 w-1/2"
|
||||
className="bg-neroshi-blue-300 hover:bg-neroshi-blue-200 border border-foreground/20 rounded-md px-4 py-2 text-foreground mb-2 mx-1 w-1/2"
|
||||
pendingText="Signing Up..."
|
||||
>
|
||||
Sign Up
|
||||
</SubmitButton>
|
||||
</div>
|
||||
{searchParams?.message && (
|
||||
<p className="mt-4 bg-foreground/10 mt-12 p-2 text-foreground text-center">
|
||||
<p className="mt-4 bg-foreground/10 mt-14 p-2 text-foreground text-center">
|
||||
{searchParams.message}
|
||||
</p>
|
||||
)}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function Index() {
|
||||
const canInitSupabaseClient = () => {
|
||||
@ -14,11 +15,5 @@ export default async function Index() {
|
||||
|
||||
const isSupabaseConnected = canInitSupabaseClient();
|
||||
|
||||
return (
|
||||
<div className="flex-1 w-full flex flex-col gap-20 items-center animate-in">
|
||||
<h1>This is unprotected.</h1>
|
||||
</div>
|
||||
// <div className="flex-1 w-full flex flex-col gap-20 items-center animate-in">
|
||||
// </div>
|
||||
);
|
||||
return redirect("/gallery")
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
export default function DeployButton() {
|
||||
return (
|
||||
<a
|
||||
className="py-2 px-3 flex rounded-md no-underline hover:bg-btn-background-hover border"
|
||||
href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-supabase&project-name=nextjs-with-supabase&repository-name=nextjs-with-supabase&demo-title=nextjs-with-supabase&demo-description=This%20starter%20configures%20Supabase%20Auth%20to%20use%20cookies%2C%20making%20the%20user's%20session%20available%20throughout%20the%20entire%20Next.js%20app%20-%20Client%20Components%2C%20Server%20Components%2C%20Route%20Handlers%2C%20Server%20Actions%20and%20Middleware.&demo-url=https%3A%2F%2Fdemo-nextjs-with-supabase.vercel.app%2F&external-id=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-supabase&demo-image=https%3A%2F%2Fdemo-nextjs-with-supabase.vercel.app%2Fopengraph-image.png&integration-ids=oac_VqOgBHqhEoFTPzGkPd7L0iH6"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
aria-label="Vercel logomark"
|
||||
role="img"
|
||||
viewBox="0 0 74 64"
|
||||
className="h-4 w-4 mr-2"
|
||||
>
|
||||
<path
|
||||
d="M37.5896 0.25L74.5396 64.25H0.639648L37.5896 0.25Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
Deploy to Vercel
|
||||
</a>
|
||||
);
|
||||
}
|
@ -19,85 +19,103 @@ export default async function AuthButton() {
|
||||
return redirect("/login");
|
||||
};
|
||||
|
||||
|
||||
|
||||
if(user){
|
||||
let email = user.email;
|
||||
if(email != null){
|
||||
const emailHash = crypto.createHash('md5').update(email.trim().toLowerCase()).digest('hex');
|
||||
const gravatarUrl = `https://www.gravatar.com/avatar/${emailHash}`;
|
||||
return(
|
||||
<nav className="w-full flex justify-center h-16">
|
||||
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
|
||||
<div className="flex items-center gap-2 ">
|
||||
<Link
|
||||
href="/gallery"
|
||||
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
|
||||
>
|
||||
Gallery
|
||||
</Link>
|
||||
<Link
|
||||
href="/commissions"
|
||||
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
|
||||
>
|
||||
Commissions
|
||||
</Link>
|
||||
<Link
|
||||
href="/subscriptions"
|
||||
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
|
||||
>
|
||||
Subscription
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<form action={signOut}>
|
||||
<button className="py-2 px-4 rounded-md no-underline bg-btn-background hover:bg-btn-background-hover">
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
<img src={gravatarUrl} alt="Profile" className="w-10 h-10 object-cover rounded-full cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
</nav>)
|
||||
<div className="flex justify-center items-center pt-2 ">
|
||||
<nav className="w-1/3 bg-neroshi-blue-300 bg-opacity-10 flex justify-center h-16 animate-in rounded-3xl" style={{ backdropFilter: 'blur(10px)' }}>
|
||||
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
|
||||
<div className="flex items-center gap-2 ">
|
||||
<Link
|
||||
href="/gallery"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Gallery
|
||||
</Link>
|
||||
<Link
|
||||
href="/livestream"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Stream
|
||||
</Link>
|
||||
<Link
|
||||
href="/commissions"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Commissions
|
||||
</Link>
|
||||
<Link
|
||||
href="/subscriptions"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Subscription
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<form action={signOut}>
|
||||
<button className="py-2 px-4 rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800">
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
<img src={gravatarUrl} alt="Profile" className="w-10 h-10 object-cover rounded-full cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
else{
|
||||
return(
|
||||
<nav className="w-full flex justify-center h-16">
|
||||
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
|
||||
return( <div className="flex justify-center items-center pt-2 ">
|
||||
<nav className="w-1/3 bg-neroshi-blue-300 bg-opacity-10 flex justify-center h-16 animate-in rounded-3xl" style={{ backdropFilter: 'blur(10px)' }}>
|
||||
|
||||
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
|
||||
<div className="flex items-center gap-2 ">
|
||||
<Link
|
||||
href="/gallery"
|
||||
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
|
||||
>
|
||||
Gallery
|
||||
</Link>
|
||||
<Link
|
||||
href="/commissions"
|
||||
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
|
||||
>
|
||||
Commissions
|
||||
</Link>
|
||||
<Link
|
||||
href="/subscriptions"
|
||||
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
|
||||
>
|
||||
Subscription
|
||||
</Link>
|
||||
<Link
|
||||
href="/gallery"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Gallery
|
||||
</Link>
|
||||
<Link
|
||||
href="/livestream"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Stream
|
||||
</Link>
|
||||
<Link
|
||||
href="/commissions"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Commissions
|
||||
</Link>
|
||||
<Link
|
||||
href="/subscriptions"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Subscription
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
href="/login"
|
||||
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
<Link
|
||||
href="/login"
|
||||
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
|
||||
className="py-2 px-3 flex rounded-3xl no-underline bg-neroshi-blue-900 hover:bg-neroshi-blue-800"
|
||||
>
|
||||
Signup
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>)
|
||||
</nav>
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
|
20
components/ui/example.tsx
Normal file
20
components/ui/example.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { use, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
interface GalleryThumbnailProps {
|
||||
}
|
||||
|
||||
const GalleryThumbnail = ({ }: GalleryThumbnailProps) => {
|
||||
const getData = async () => {
|
||||
}
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default GalleryThumbnail;
|
129
components/ui/gallery.tsx
Normal file
129
components/ui/gallery.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import { use, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import Masonry from 'react-masonry-css';
|
||||
|
||||
interface GalleryProps {
|
||||
id: number;
|
||||
closeMenu: () => void;
|
||||
}
|
||||
|
||||
const Gallery = ({ id, closeMenu }: GalleryProps) => {
|
||||
|
||||
const [isSingle, setIsSingle] = useState<boolean>(false);
|
||||
const [loaded, setLoaded] = useState({})
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||
const [images, setImages] = useState<string[]>([]);
|
||||
const [galleryId, setGalleryId] = useState(id as number);
|
||||
console.log(id)
|
||||
const getData = async () => {
|
||||
const thumbnailResponse = await fetch('/api/galleries/'+String(galleryId)+'/images');
|
||||
const thumbnailUrl = await thumbnailResponse.json() as string[];
|
||||
setImages(thumbnailUrl);
|
||||
}
|
||||
const handleDownload = (image: string) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = image;
|
||||
link.download = 'image.jpg'; // or any other filename
|
||||
link.style.display = 'none';
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
useEffect(() => {
|
||||
getData();
|
||||
if (images.length === 1) {
|
||||
setIsSingle(true);
|
||||
setSelectedImage(images[0]);
|
||||
}
|
||||
}, [selectedImage]);
|
||||
|
||||
const handleClick = (image: string) => {
|
||||
setSelectedImage(image);
|
||||
};
|
||||
|
||||
const breakpointColumnsObj = {
|
||||
default: 3
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="fixed bg-purple-800 left-10 bottom-5 animate-shake mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50"
|
||||
onClick={()=> closeMenu()}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="mr-2 h-4 w-4 transition-transform group-hover:-translate-x-1"
|
||||
>
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>{" "}
|
||||
Back
|
||||
</button>
|
||||
|
||||
|
||||
<div className='z-10 pt-10' style={{ display: selectedImage ? 'flex' : 'block', alignItems: 'flex-start' }}>
|
||||
{isSingle ? (
|
||||
<div className='w-full h-full flex items-center'>
|
||||
{selectedImage &&
|
||||
<img
|
||||
src={selectedImage}
|
||||
style={{ objectFit: 'contain' }}
|
||||
className="cursor-pointer animate-in w-full h-auto"
|
||||
onClick={() => setSelectedImage(null)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{selectedImage &&
|
||||
<>
|
||||
|
||||
<img
|
||||
src={selectedImage}
|
||||
style={{ objectFit: 'contain' }}
|
||||
className="cursor-pointer animate-in w-4/6 pr-20 h-auto"
|
||||
onClick={() => setSelectedImage(null)}
|
||||
/>
|
||||
<button
|
||||
className="fixed bg-neroshi-blue-800 left-40 bottom-5 animate-pulse mb-4 py-2 px-4 rounded-lg no-underline flex items-center z-50"
|
||||
onClick={() => handleDownload(selectedImage)}
|
||||
>
|
||||
<svg className='mr-3' xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
Download Current Image
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
<Masonry
|
||||
breakpointCols={selectedImage==null ? 4 : 2}
|
||||
className="my-masonry-grid"
|
||||
style={{ width: selectedImage ? '50%' : '100%' }}
|
||||
>
|
||||
{images.filter(img => img !== selectedImage).map((image, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={image}
|
||||
onClick={() => handleClick(image)}
|
||||
className={`animate-in hover:scale-105 p-2 cursor-pointer my-2 transition-all opacity-100 duration-500 ease-in-out transform`}
|
||||
/>
|
||||
))}
|
||||
</Masonry>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Gallery;
|
41
components/ui/gallery_thumbnail.tsx
Normal file
41
components/ui/gallery_thumbnail.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { use, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
interface GalleryThumbnailProps {
|
||||
id: number;
|
||||
onSelect: (id:number) => void;
|
||||
}
|
||||
|
||||
const GalleryThumbnail = ({ id, onSelect }: GalleryThumbnailProps) => {
|
||||
const [galleryId, setGalleryId] = useState(id as number);
|
||||
const [thumbnailUrl, setThumbnailUrl] = useState('' as string);
|
||||
const toggleModal = () => {
|
||||
onSelect(galleryId);
|
||||
};
|
||||
|
||||
|
||||
const getData = async () => {
|
||||
const thumbnailResponse = await fetch('/api/galleries/'+galleryId+'/thumbnail');
|
||||
const thumbnailUrl = await thumbnailResponse.text();
|
||||
setThumbnailUrl(thumbnailUrl);
|
||||
}
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="py-3 sm:max-w-xl sm:mx-auto animate-in flex-3">
|
||||
<div className="h-48 overflow-visible w-full relative hover:scale-105 shadow-lg">
|
||||
<img
|
||||
className={`aspect-content rounded-3xl`}
|
||||
src={thumbnailUrl}
|
||||
alt=""
|
||||
onClick={toggleModal}
|
||||
style={{ width: '20rem', height: '20rem', objectFit: 'cover' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GalleryThumbnail;
|
255
components/ui/vortex.tsx
Normal file
255
components/ui/vortex.tsx
Normal file
@ -0,0 +1,255 @@
|
||||
import { cn } from "@/utils/cn";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { createNoise3D } from "simplex-noise";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface VortexProps {
|
||||
children?: any;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
particleCount?: number;
|
||||
rangeY?: number;
|
||||
baseHue?: number;
|
||||
baseSpeed?: number;
|
||||
rangeSpeed?: number;
|
||||
baseRadius?: number;
|
||||
rangeRadius?: number;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
export const Vortex = (props: VortexProps) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const containerRef = useRef(null);
|
||||
const particleCount = props.particleCount || 700;
|
||||
const particlePropCount = 9;
|
||||
const particlePropsLength = particleCount * particlePropCount;
|
||||
const rangeY = props.rangeY || 100;
|
||||
const baseTTL = 50;
|
||||
const rangeTTL = 150;
|
||||
const baseSpeed = props.baseSpeed || 0.0;
|
||||
const rangeSpeed = props.rangeSpeed || 1.5;
|
||||
const baseRadius = props.baseRadius || 1;
|
||||
const rangeRadius = props.rangeRadius || 2;
|
||||
const baseHue = props.baseHue || 220;
|
||||
const rangeHue = 100;
|
||||
const noiseSteps = 3;
|
||||
const xOff = 0.00125;
|
||||
const yOff = 0.00125;
|
||||
const zOff = 0.0005;
|
||||
const backgroundColor = props.backgroundColor || "#000000";
|
||||
let tick = 0;
|
||||
const noise3D = createNoise3D();
|
||||
let particleProps = new Float32Array(particlePropsLength);
|
||||
let center: [number, number] = [0, 0];
|
||||
|
||||
const HALF_PI: number = 0.5 * Math.PI;
|
||||
const TAU: number = 2 * Math.PI;
|
||||
const TO_RAD: number = Math.PI / 180;
|
||||
const rand = (n: number): number => n * Math.random();
|
||||
const randRange = (n: number): number => n - rand(2 * n);
|
||||
const fadeInOut = (t: number, m: number): number => {
|
||||
let hm = 0.5 * m;
|
||||
return Math.abs(((t + hm) % m) - hm) / hm;
|
||||
};
|
||||
const lerp = (n1: number, n2: number, speed: number): number =>
|
||||
(1 - speed) * n1 + speed * n2;
|
||||
|
||||
const setup = () => {
|
||||
const canvas = canvasRef.current;
|
||||
const container = containerRef.current;
|
||||
if (canvas && container) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (ctx) {
|
||||
resize(canvas, ctx);
|
||||
initParticles();
|
||||
draw(canvas, ctx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const initParticles = () => {
|
||||
tick = 0;
|
||||
// simplex = new SimplexNoise();
|
||||
particleProps = new Float32Array(particlePropsLength);
|
||||
|
||||
for (let i = 0; i < particlePropsLength; i += particlePropCount) {
|
||||
initParticle(i);
|
||||
}
|
||||
};
|
||||
|
||||
const initParticle = (i: number) => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
let x, y, vx, vy, life, ttl, speed, radius, hue;
|
||||
|
||||
x = rand(canvas.width);
|
||||
y = center[1] + randRange(rangeY);
|
||||
vx = 0;
|
||||
vy = 0;
|
||||
life = 0;
|
||||
ttl = baseTTL + rand(rangeTTL);
|
||||
speed = baseSpeed + rand(rangeSpeed);
|
||||
radius = baseRadius + rand(rangeRadius);
|
||||
hue = baseHue + rand(rangeHue);
|
||||
|
||||
particleProps.set([x, y, vx, vy, life, ttl, speed, radius, hue], i);
|
||||
};
|
||||
|
||||
const draw = (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => {
|
||||
tick++;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.fillStyle = backgroundColor;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
drawParticles(ctx);
|
||||
renderGlow(canvas, ctx);
|
||||
renderToScreen(canvas, ctx);
|
||||
|
||||
window.requestAnimationFrame(() => draw(canvas, ctx));
|
||||
};
|
||||
|
||||
const drawParticles = (ctx: CanvasRenderingContext2D) => {
|
||||
for (let i = 0; i < particlePropsLength; i += particlePropCount) {
|
||||
updateParticle(i, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
const updateParticle = (i: number, ctx: CanvasRenderingContext2D) => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
let i2 = 1 + i,
|
||||
i3 = 2 + i,
|
||||
i4 = 3 + i,
|
||||
i5 = 4 + i,
|
||||
i6 = 5 + i,
|
||||
i7 = 6 + i,
|
||||
i8 = 7 + i,
|
||||
i9 = 8 + i;
|
||||
let n, x, y, vx, vy, life, ttl, speed, x2, y2, radius, hue;
|
||||
|
||||
x = particleProps[i];
|
||||
y = particleProps[i2];
|
||||
n = noise3D(x * xOff, y * yOff, tick * zOff) * noiseSteps * TAU;
|
||||
vx = lerp(particleProps[i3], Math.cos(n), 0.5);
|
||||
vy = lerp(particleProps[i4], Math.sin(n), 0.5);
|
||||
life = particleProps[i5];
|
||||
ttl = particleProps[i6];
|
||||
speed = particleProps[i7];
|
||||
x2 = x + vx * speed;
|
||||
y2 = y + vy * speed;
|
||||
radius = particleProps[i8];
|
||||
hue = particleProps[i9];
|
||||
|
||||
drawParticle(x, y, x2, y2, life, ttl, radius, hue, ctx);
|
||||
|
||||
life++;
|
||||
|
||||
particleProps[i] = x2;
|
||||
particleProps[i2] = y2;
|
||||
particleProps[i3] = vx;
|
||||
particleProps[i4] = vy;
|
||||
particleProps[i5] = life;
|
||||
|
||||
(checkBounds(x, y, canvas) || life > ttl) && initParticle(i);
|
||||
};
|
||||
|
||||
const drawParticle = (
|
||||
x: number,
|
||||
y: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
life: number,
|
||||
ttl: number,
|
||||
radius: number,
|
||||
hue: number,
|
||||
ctx: CanvasRenderingContext2D
|
||||
) => {
|
||||
ctx.save();
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineWidth = radius;
|
||||
ctx.strokeStyle = `hsla(${hue},100%,60%,${fadeInOut(life, ttl)})`;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y);
|
||||
ctx.lineTo(x2, y2);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
const checkBounds = (x: number, y: number, canvas: HTMLCanvasElement) => {
|
||||
return x > canvas.width || x < 0 || y > canvas.height || y < 0;
|
||||
};
|
||||
|
||||
const resize = (
|
||||
canvas: HTMLCanvasElement,
|
||||
ctx?: CanvasRenderingContext2D
|
||||
) => {
|
||||
const { innerWidth, innerHeight } = window;
|
||||
|
||||
canvas.width = innerWidth;
|
||||
canvas.height = innerHeight;
|
||||
|
||||
center[0] = 0.5 * canvas.width;
|
||||
center[1] = 0.5 * canvas.height;
|
||||
};
|
||||
|
||||
const renderGlow = (
|
||||
canvas: HTMLCanvasElement,
|
||||
ctx: CanvasRenderingContext2D
|
||||
) => {
|
||||
ctx.save();
|
||||
ctx.filter = "blur(8px) brightness(200%)";
|
||||
ctx.globalCompositeOperation = "lighter";
|
||||
ctx.drawImage(canvas, 0, 0);
|
||||
ctx.restore();
|
||||
|
||||
ctx.save();
|
||||
ctx.filter = "blur(4px) brightness(200%)";
|
||||
ctx.globalCompositeOperation = "lighter";
|
||||
ctx.drawImage(canvas, 0, 0);
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
const renderToScreen = (
|
||||
canvas: HTMLCanvasElement,
|
||||
ctx: CanvasRenderingContext2D
|
||||
) => {
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = "lighter";
|
||||
ctx.drawImage(canvas, 0, 0);
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setup();
|
||||
window.addEventListener("resize", () => {
|
||||
const canvas = canvasRef.current;
|
||||
const ctx = canvas?.getContext("2d");
|
||||
if (canvas && ctx) {
|
||||
resize(canvas, ctx);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={cn("relative h-full w-full", props.containerClassName)}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
ref={containerRef}
|
||||
className="absolute h-full w-full inset-0 z-0 bg-transparent flex items-center justify-center"
|
||||
>
|
||||
<canvas ref={canvasRef}></canvas>
|
||||
</motion.div>
|
||||
|
||||
<div className={cn("relative z-10", props.className)}>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -20,6 +20,7 @@ services:
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 1935:1935
|
||||
volumes:
|
||||
- ./data:/owncast/data
|
||||
|
||||
|
109
package-lock.json
generated
109
package-lock.json
generated
@ -7,13 +7,21 @@
|
||||
"dependencies": {
|
||||
"@supabase/ssr": "latest",
|
||||
"@supabase/supabase-js": "latest",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"autoprefixer": "10.4.17",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.2.6",
|
||||
"geist": "^1.2.1",
|
||||
"md5": "^2.3.0",
|
||||
"next": "latest",
|
||||
"postcss": "8.4.33",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-masonry-css": "^1.0.16",
|
||||
"react-responsive-masonry": "^2.2.0",
|
||||
"simplex-noise": "^4.0.1",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss": "3.4.1",
|
||||
"tailwindcss-animated": "^1.0.1",
|
||||
"tailwindcss-textshadow": "^2.1.3",
|
||||
@ -37,6 +45,17 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz",
|
||||
"integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullhuman/postcss-purgecss": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.3.0.tgz",
|
||||
@ -463,6 +482,14 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/aspect-ratio": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz",
|
||||
"integrity": "sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz",
|
||||
@ -813,6 +840,14 @@
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
|
||||
@ -1070,6 +1105,30 @@
|
||||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "11.2.6",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.2.6.tgz",
|
||||
"integrity": "sha512-XUrjjBt57e5YoHQtjwc3eNchFBuHvIgN/cS8SC4oIaAn2J/0+bLanUxXizidJKZVeHJam/JrmMnPRjYMglVn5g==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/is-prop-valid": "*",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/is-prop-valid": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
@ -1925,6 +1984,16 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/purgecss": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/purgecss/-/purgecss-2.3.0.tgz",
|
||||
@ -2121,6 +2190,24 @@
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-masonry-css": {
|
||||
"version": "1.0.16",
|
||||
"resolved": "https://registry.npmjs.org/react-masonry-css/-/react-masonry-css-1.0.16.tgz",
|
||||
"integrity": "sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-responsive-masonry": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-responsive-masonry/-/react-responsive-masonry-2.2.0.tgz",
|
||||
"integrity": "sha512-IYbnfe2tWCZ3pvyTLyBWPj7uv5ZmNOULYMcAZi5a47ZLhSotOck1vkkISq6gP2qiyWdMvPfeMhjvYzUYGw9BOQ=="
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@ -2154,6 +2241,11 @@
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
|
||||
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
@ -2253,6 +2345,11 @@
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simplex-noise": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simplex-noise/-/simplex-noise-4.0.1.tgz",
|
||||
"integrity": "sha512-zl/+bdSqW7HJOQ0oDbxrNYaF4F5ik0i7M6YOYmEoIJNtg16NpvWaTTM1Y7oV/7T0jFljawLgYPS81Uu2rsfo1A=="
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@ -2430,6 +2527,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.3.0.tgz",
|
||||
"integrity": "sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
|
||||
|
@ -8,13 +8,21 @@
|
||||
"dependencies": {
|
||||
"@supabase/ssr": "latest",
|
||||
"@supabase/supabase-js": "latest",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"autoprefixer": "10.4.17",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.2.6",
|
||||
"geist": "^1.2.1",
|
||||
"md5": "^2.3.0",
|
||||
"next": "latest",
|
||||
"postcss": "8.4.33",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-masonry-css": "^1.0.16",
|
||||
"react-responsive-masonry": "^2.2.0",
|
||||
"simplex-noise": "^4.0.1",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss": "3.4.1",
|
||||
"tailwindcss-animated": "^1.0.1",
|
||||
"tailwindcss-textshadow": "^2.1.3",
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
supabase/volumes/db/data/base/5/17986_fsm
Normal file
BIN
supabase/volumes/db/data/base/5/17986_fsm
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user