├── .example.env
├── .gitignore
├── LICENSE
├── README.md
├── app
├── api
│ ├── generateCode
│ │ └── route.ts
│ └── s3-upload
│ │ └── route.ts
├── favicon.ico
├── fonts
│ ├── GeistMonoVF.woff
│ └── GeistVF.woff
├── globals.css
├── icon.png
├── layout.tsx
└── page.tsx
├── components.json
├── components
├── code-viewer.css
├── code-viewer.tsx
├── loading-dots.module.css
├── loading-dots.tsx
└── ui
│ ├── button.tsx
│ ├── select.tsx
│ ├── shimmerbutton.tsx
│ └── tooltip.tsx
├── lib
├── shadcn-docs
│ ├── avatar.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── checkbox.tsx
│ ├── index.ts
│ ├── input.tsx
│ ├── label.tsx
│ ├── radio-group.tsx
│ ├── select.tsx
│ └── textarea.tsx
├── shadcn.ts
└── utils.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
├── biglogo.svg
├── hero-2.svg
├── hero-3.svg
├── hero.svg
├── logo-2.svg
├── logos.svg
├── meta.svg
└── og-image.png
├── tailwind.config.ts
└── tsconfig.json
/.example.env:
--------------------------------------------------------------------------------
1 | S3_UPLOAD_KEY=
2 | S3_UPLOAD_SECRET=
3 | S3_UPLOAD_BUCKET=
4 | S3_UPLOAD_REGION=
5 | TOGETHER_API_KEY=
6 |
7 | # Optional
8 | HELICONE_API_KEY=
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2024 Scott Chacon and others
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Napkins.dev
4 |
5 |
6 |
7 | An open source wireframe to app generator. Powered by Llama 4 & Together.ai.
8 |
9 |
10 | ## Tech stack
11 |
12 | - [Llama 4](https://togetherai.link/) from Meta for the LLM
13 | - [Together AI](https://togetherai.link/) for LLM inference
14 | - [Sandpack](https://sandpack.codesandbox.io/) for the code sandbox
15 | - [S3](https://aws.amazon.com/s3/) for image storage
16 | - Next.js app router with Tailwind
17 | - Helicone for observability
18 | - Plausible for website analytics
19 |
20 | ## Cloning & running
21 |
22 | 1. Clone the repo: `git clone https://github.com/Nutlope/napkins`
23 | 2. Create a `.env` file and add your [Together AI API key](https://togetherai.link/llama3.2vision/?utm_source=example-app&utm_medium=napkins&utm_campaign=napkins-app-signup): `TOGETHER_API_KEY=`
24 | 3. Create an S3 bucket and add the credentials to your `.env` file. Follow [this guide](https://next-s3-upload.codingvalue.com/setup) to set them up. All required values are in the `.env.example` file.
25 | 4. Run `npm install` and `npm run dev` to install dependencies and run locally
26 |
27 | ## Future Tasks
28 |
29 | - [ ] Make sure it looks nicer and less cluttered on mobile
30 | - [ ] On the sidebar, show the output from the vision model
31 | - [ ] Experiment with making it better through few shot prompts
32 | - [ ] Add a shadcn toggle to allow folks to use or not use it
33 | - [ ] Allow folks to edit the generated app with a prompt
34 | - [ ] Allow folks to choose from different themes
35 | - [ ] Show versions as the user edits the app
36 |
--------------------------------------------------------------------------------
/app/api/generateCode/route.ts:
--------------------------------------------------------------------------------
1 | import shadcnDocs from '@/lib/shadcn-docs';
2 | import dedent from 'dedent';
3 | import Together from 'together-ai';
4 | import { z } from 'zod';
5 |
6 | let options: ConstructorParameters[0] = {};
7 |
8 | if (process.env.HELICONE_API_KEY) {
9 | options.baseURL = 'https://together.helicone.ai/v1';
10 | options.defaultHeaders = {
11 | 'Helicone-Auth': `Bearer ${process.env.HELICONE_API_KEY}`,
12 | };
13 | }
14 |
15 | let together = new Together(options);
16 |
17 | export async function POST(req: Request) {
18 | let json = await req.json();
19 | let result = z
20 | .object({
21 | model: z.string(),
22 | imageUrl: z.string(),
23 | shadcn: z.boolean().default(false),
24 | })
25 | .safeParse(json);
26 |
27 | if (result.error) {
28 | return new Response(result.error.message, { status: 422 });
29 | }
30 |
31 | let { model, imageUrl, shadcn } = result.data;
32 | let codingPrompt = getCodingPrompt(shadcn);
33 |
34 | const res = await together.chat.completions.create({
35 | model,
36 | temperature: 0.2,
37 | stream: true,
38 | messages: [
39 | {
40 | role: 'user',
41 | content: [
42 | { type: 'text', text: codingPrompt },
43 | {
44 | type: 'image_url',
45 | image_url: {
46 | url: imageUrl,
47 | },
48 | },
49 | ],
50 | },
51 | ],
52 | });
53 |
54 | let textStream = res
55 | .toReadableStream()
56 | .pipeThrough(new TextDecoderStream())
57 | .pipeThrough(
58 | new TransformStream({
59 | transform(chunk, controller) {
60 | if (chunk) {
61 | try {
62 | let text = JSON.parse(chunk).choices[0].text;
63 | if (text) controller.enqueue(text);
64 | } catch (error) {
65 | console.error(error);
66 | }
67 | }
68 | },
69 | })
70 | )
71 | .pipeThrough(new TextEncoderStream());
72 |
73 | return new Response(textStream, {
74 | headers: new Headers({
75 | 'Cache-Control': 'no-cache',
76 | }),
77 | });
78 | }
79 |
80 | function getCodingPrompt(shadcn: boolean) {
81 | let systemPrompt = `
82 | You are an expert frontend frontend React developer. You will be given a screenshot of a website from the user, and then you will return code for it using React and Tailwind CSS. Follow the instructions carefully, it is very important for my job. I will tip you $1 million if you do a good job:
83 |
84 | - Think carefully step by step about how to recreate the UI described in the prompt.
85 | - Create a React component for whatever the user asked you to create and make sure it can run by itself by using a default export
86 | - Feel free to have multiple components in the file, but make sure to have one main component that uses all the other components
87 | - Make sure the website looks exactly like the screenshot described in the prompt.
88 | - Pay close attention to background color, text color, font size, font family, padding, margin, border, etc. Match the colors and sizes exactly.
89 | - Make sure to code every part of the description including any headers, footers, etc.
90 | - Use the exact text from the description for the UI elements.
91 | - Do not add comments in the code such as "" and "" in place of writing the full code. WRITE THE FULL CODE.
92 | - Repeat elements as needed to match the description. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "" or bad things will happen.
93 | - For all images, please use an svg with a white, gray, or black background and don't try to import them locally or from the internet.
94 | - Make sure the React app is interactive and functional by creating state when needed and having no required props
95 | - If you use any imports from React like useState or useEffect, make sure to import them directly
96 | - Use TypeScript as the language for the React component
97 | - Use Tailwind classes for styling. DO NOT USE ARBITRARY VALUES (e.g. \`h-[600px]\`). Make sure to use a consistent color palette.
98 | - Use margin and padding to style the components and ensure the components are spaced out nicely
99 | - Please ONLY return the full React code starting with the imports, nothing else. It's very important for my job that you only return the React code with imports. DO NOT START WITH \`\`\`typescript or \`\`\`javascript or \`\`\`tsx or \`\`\`.
100 | - ONLY IF the user asks for a dashboard, graph or chart, the recharts library is available to be imported, e.g. \`import { LineChart, XAxis, ... } from "recharts"\` & \` ...\`. Please only use this when needed.
101 | - If you need an icon, please create an SVG for it and use it in the code. DO NOT IMPORT AN ICON FROM A LIBRARY.
102 | - Make the design look nice and don't have borders around the entire website even if that's described
103 | `;
104 |
105 | if (shadcn) {
106 | systemPrompt += `
107 | There are some prestyled components available for use. Please use your best judgement to use any of these components if the app calls for one.
108 |
109 | Here are the components that are available, along with how to import them, and how to use them:
110 |
111 | ${shadcnDocs
112 | .map(
113 | (component) => `
114 |
115 |
116 | ${component.name}
117 |
118 |
119 | ${component.importDocs}
120 |
121 |
122 | ${component.usageDocs}
123 |
124 |
125 | `
126 | )
127 | .join('\n')}
128 | `;
129 | }
130 |
131 | systemPrompt += `
132 | NO OTHER LIBRARIES (e.g. zod, hookform) ARE INSTALLED OR ABLE TO BE IMPORTED.
133 | `;
134 |
135 | systemPrompt += `
136 | Here are some examples of good outputs:
137 |
138 |
139 | ${examples
140 | .map(
141 | (example) => `
142 |
143 |
144 | ${example.input}
145 |
146 |
147 | ${example.output}
148 |
149 |
150 | `
151 | )
152 | .join('\n')}
153 | `;
154 |
155 | return dedent(systemPrompt);
156 | }
157 |
158 | export const runtime = 'edge';
159 |
160 | let examples = [
161 | {
162 | input: `A landing page screenshot`,
163 | output: `
164 | import { Button } from "@/components/ui/button"
165 |
166 | export default function LandingPage() {
167 | return (
168 |
169 |
183 |
184 |
185 |
186 |
187 |
188 | Used by 100+ companies
189 |
190 |
191 | Welcome to your all-in-one AI tool
192 |
193 |
194 | Check out all the new features in the 13.2 update in the demo below
195 |
196 |
197 | Get Started
198 |
199 |
200 |
201 | IMAGE PLACEHOLDER
202 |
203 |
204 |
205 |
206 | )
207 | }
208 | `,
209 | },
210 | ];
211 |
--------------------------------------------------------------------------------
/app/api/s3-upload/route.ts:
--------------------------------------------------------------------------------
1 | // app/api/s3-upload/route.js
2 | export { POST } from "next-s3-upload/route";
3 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nutlope/napkins/22f3e630e1a0847fc465ff9b12393a2aeffb1fe0/app/favicon.ico
--------------------------------------------------------------------------------
/app/fonts/GeistMonoVF.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nutlope/napkins/22f3e630e1a0847fc465ff9b12393a2aeffb1fe0/app/fonts/GeistMonoVF.woff
--------------------------------------------------------------------------------
/app/fonts/GeistVF.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nutlope/napkins/22f3e630e1a0847fc465ff9b12393a2aeffb1fe0/app/fonts/GeistVF.woff
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --background: #ffffff;
7 | --foreground: #171717;
8 | }
9 |
10 | body {
11 | color: var(--foreground);
12 | background: var(--background);
13 | font-family: Arial, Helvetica, sans-serif;
14 | }
15 |
16 | @layer utilities {
17 | .text-balance {
18 | text-wrap: balance;
19 | }
20 | }
21 |
22 | @layer base {
23 | :root {
24 | --background: 0 0% 100%;
25 | --foreground: 224 71.4% 4.1%;
26 | --card: 0 0% 100%;
27 | --card-foreground: 224 71.4% 4.1%;
28 | --popover: 0 0% 100%;
29 | --popover-foreground: 224 71.4% 4.1%;
30 | --primary: 220.9 39.3% 11%;
31 | --primary-foreground: 210 20% 98%;
32 | --secondary: 220 14.3% 95.9%;
33 | --secondary-foreground: 220.9 39.3% 11%;
34 | --muted: 220 14.3% 95.9%;
35 | --muted-foreground: 220 8.9% 46.1%;
36 | --accent: 220 14.3% 95.9%;
37 | --accent-foreground: 220.9 39.3% 11%;
38 | --destructive: 0 84.2% 60.2%;
39 | --destructive-foreground: 210 20% 98%;
40 | --border: 220 13% 91%;
41 | --input: 220 13% 91%;
42 | --ring: 224 71.4% 4.1%;
43 | --chart-1: 12 76% 61%;
44 | --chart-2: 173 58% 39%;
45 | --chart-3: 197 37% 24%;
46 | --chart-4: 43 74% 66%;
47 | --chart-5: 27 87% 67%;
48 | --radius: 0.5rem;
49 | }
50 | }
51 |
52 | @layer base {
53 | * {
54 | @apply border-border;
55 | }
56 | body {
57 | @apply bg-background text-foreground;
58 | }
59 | }
60 |
61 | input[type="file"] {
62 | @apply sr-only;
63 | }
64 |
--------------------------------------------------------------------------------
/app/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nutlope/napkins/22f3e630e1a0847fc465ff9b12393a2aeffb1fe0/app/icon.png
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next';
2 | import localFont from 'next/font/local';
3 | import './globals.css';
4 | import Image from 'next/image';
5 | import Link from 'next/link';
6 | import Logo from '@/public/biglogo.svg';
7 | import { Button } from '@/components/ui/button';
8 | import { GitHubLogoIcon, TwitterLogoIcon } from '@radix-ui/react-icons';
9 | import PlausibleProvider from 'next-plausible';
10 |
11 | let title = 'Napkins.dev – Screenshot to code';
12 | let description = 'Generate your next app with a screenshot using Llama 4';
13 | let url = 'https://www.napkins.dev/';
14 | let ogimage = 'https://www.napkins.dev/og-image.png';
15 | let sitename = 'napkins.dev';
16 |
17 | export const metadata: Metadata = {
18 | metadataBase: new URL(url),
19 | title,
20 | description,
21 | icons: {
22 | icon: '/favicon.ico',
23 | },
24 | openGraph: {
25 | images: [ogimage],
26 | title,
27 | description,
28 | url: url,
29 | siteName: sitename,
30 | locale: 'en_US',
31 | type: 'website',
32 | },
33 | twitter: {
34 | card: 'summary_large_image',
35 | images: [ogimage],
36 | title,
37 | description,
38 | },
39 | };
40 |
41 | const geistSans = localFont({
42 | src: './fonts/GeistVF.woff',
43 | variable: '--font-geist-sans',
44 | });
45 | const geistMono = localFont({
46 | src: './fonts/GeistMonoVF.woff',
47 | variable: '--font-geist-mono',
48 | });
49 |
50 | export default function RootLayout({
51 | children,
52 | }: Readonly<{
53 | children: React.ReactNode;
54 | }>) {
55 | return (
56 |
57 |
58 |
59 |
60 |
63 |
80 |
81 | {children}
82 |
83 |
84 |
85 | Powered by{' '}
86 |
91 | Together AI
92 | {' '}
93 | and{' '}
94 |
99 | Llama 4
100 |
101 |
102 |
103 |
104 |
105 |
106 | GitHub
107 |
108 |
109 |
110 |
111 |
112 | Twitter
113 |
114 |
115 |
116 |
117 |
118 |
119 | );
120 | }
121 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | 'use client';
3 |
4 | import { useEffect, useState } from 'react';
5 | import { useS3Upload } from 'next-s3-upload';
6 | import { PhotoIcon, XCircleIcon } from '@heroicons/react/20/solid';
7 | import { FileUploader } from 'react-drag-drop-files';
8 | import {
9 | Select,
10 | SelectContent,
11 | SelectItem,
12 | SelectTrigger,
13 | SelectValue,
14 | } from '@/components/ui/select';
15 | import CodeViewer from '@/components/code-viewer';
16 | import { AnimatePresence, motion } from 'framer-motion';
17 | import ShimmerButton from '@/components/ui/shimmerbutton';
18 | import {
19 | Tooltip,
20 | TooltipContent,
21 | TooltipProvider,
22 | TooltipTrigger,
23 | } from '@/components/ui/tooltip';
24 | import LoadingDots from '@/components/loading-dots';
25 | import { readStream } from '@/lib/utils';
26 |
27 | export default function UploadComponent() {
28 | const [imageUrl, setImageUrl] = useState(undefined);
29 | let [status, setStatus] = useState<
30 | 'initial' | 'uploading' | 'uploaded' | 'creating' | 'created'
31 | >('initial');
32 | let [model, setModel] = useState(
33 | 'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8'
34 | );
35 | const [generatedCode, setGeneratedCode] = useState('');
36 | const [shadcn, setShadcn] = useState(false);
37 | const [buildingMessage, setBuildingMessage] = useState(
38 | 'Building your app...'
39 | );
40 |
41 | let loading = status === 'creating';
42 |
43 | useEffect(() => {
44 | let el = document.querySelector('.cm-scroller');
45 | if (el && loading) {
46 | let end = el.scrollHeight - el.clientHeight;
47 | el.scrollTo({ top: end });
48 | }
49 | }, [loading, generatedCode]);
50 |
51 | const { uploadToS3 } = useS3Upload();
52 |
53 | const handleFileChange = async (file: File) => {
54 | let objectUrl = URL.createObjectURL(file);
55 | setStatus('uploading');
56 | setImageUrl(objectUrl);
57 | const { url } = await uploadToS3(file);
58 | setImageUrl(url);
59 | setStatus('uploaded');
60 | };
61 |
62 | async function createApp() {
63 | setStatus('creating');
64 | setGeneratedCode('');
65 | setBuildingMessage('Building your app...');
66 |
67 | let res = await fetch('/api/generateCode', {
68 | method: 'POST',
69 | headers: {
70 | 'Content-Type': 'application/json',
71 | },
72 | body: JSON.stringify({
73 | model,
74 | shadcn,
75 | imageUrl,
76 | }),
77 | });
78 |
79 | if (!res.ok) throw new Error(res.statusText);
80 | if (!res.body) throw new Error('No response body');
81 |
82 | for await (let chunk of readStream(res.body)) {
83 | setGeneratedCode((prev) => prev + chunk);
84 | }
85 |
86 | setStatus('created');
87 | }
88 |
89 | function handleSampleImage() {
90 | setImageUrl(
91 | 'https://napkinsdev.s3.us-east-1.amazonaws.com/next-s3-uploads/be191fc8-149b-43eb-b434-baf883986c2c/appointment-booking.png'
92 | );
93 | setStatus('uploaded');
94 | }
95 |
96 | return (
97 |
98 | {status === 'initial' ||
99 | status === 'uploading' ||
100 | status === 'uploaded' ? (
101 |
102 |
103 |
104 |
105 | Turn your wireframe into an app
106 |
107 |
108 |
109 | Upload an image of your website design and we’ll build it for
110 | you with React + Tailwind.
111 |
112 |
113 |
114 |
115 | ) : (
116 |
117 |
118 |
119 |
120 |
121 |
122 | {status === 'creating' && (
123 |
135 |
136 | {status === 'creating' && buildingMessage}
137 |
138 |
139 | )}
140 |
141 |
142 | )}
143 |
144 | {imageUrl ? (
145 |
146 |
147 |
152 |
153 |
154 | setImageUrl('')} />
155 |
156 |
157 | ) : (
158 | <>
159 |
168 |
169 |
170 |
174 |
175 |
179 | Upload a screenshot
180 |
181 | or drag and drop
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
193 | Need an example image? Try ours.
194 |
195 |
196 | >
197 | )}
198 |
199 |
200 |
AI Model:
201 |
202 |
203 |
204 |
205 |
206 |
207 |
211 | Llama 4 Scout
212 |
213 |
217 | Llama 4 Maverick
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
235 |
240 | Generate app
241 |
242 |
243 | {loading && (
244 |
245 |
246 |
247 | )}
248 |
249 |
250 |
251 |
252 | {status === 'initial' && (
253 |
254 | Please upload an image first
255 |
256 | )}
257 |
258 |
259 |
260 |
261 | );
262 | }
263 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "gray",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | }
20 | }
--------------------------------------------------------------------------------
/components/code-viewer.css:
--------------------------------------------------------------------------------
1 | .sp-preview-container {
2 | @apply flex h-full w-full grow flex-col justify-center;
3 | }
4 |
5 | .sp-preview-iframe {
6 | @apply grow;
7 | }
8 |
--------------------------------------------------------------------------------
/components/code-viewer.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as shadcnComponents from "@/lib/shadcn";
4 | import { Sandpack } from "@codesandbox/sandpack-react";
5 | import {
6 | SandpackPreview,
7 | SandpackProvider,
8 | } from "@codesandbox/sandpack-react/unstyled";
9 |
10 | import { aquaBlue } from "@codesandbox/sandpack-themes";
11 | // import { githubLight } from "@codesandbox/sandpack-themes";
12 |
13 | import dedent from "dedent";
14 | import "./code-viewer.css";
15 |
16 | export default function CodeViewer({
17 | code,
18 | showEditor = false,
19 | }: {
20 | code: string;
21 | showEditor?: boolean;
22 | }) {
23 | return showEditor ? (
24 |
37 | ) : (
38 |
47 |
52 |
53 | );
54 | }
55 |
56 | const sharedProps = {
57 | template: "react-ts",
58 | theme: aquaBlue,
59 | customSetup: {
60 | dependencies: {
61 | "lucide-react": "latest",
62 | recharts: "2.9.0",
63 | "react-router-dom": "latest",
64 | "@radix-ui/react-accordion": "^1.2.0",
65 | "@radix-ui/react-alert-dialog": "^1.1.1",
66 | "@radix-ui/react-aspect-ratio": "^1.1.0",
67 | "@radix-ui/react-avatar": "^1.1.0",
68 | "@radix-ui/react-checkbox": "^1.1.1",
69 | "@radix-ui/react-collapsible": "^1.1.0",
70 | "@radix-ui/react-dialog": "^1.1.1",
71 | "@radix-ui/react-dropdown-menu": "^2.1.1",
72 | "@radix-ui/react-hover-card": "^1.1.1",
73 | "@radix-ui/react-label": "^2.1.0",
74 | "@radix-ui/react-menubar": "^1.1.1",
75 | "@radix-ui/react-navigation-menu": "^1.2.0",
76 | "@radix-ui/react-popover": "^1.1.1",
77 | "@radix-ui/react-progress": "^1.1.0",
78 | "@radix-ui/react-radio-group": "^1.2.0",
79 | "@radix-ui/react-select": "^2.1.1",
80 | "@radix-ui/react-separator": "^1.1.0",
81 | "@radix-ui/react-slider": "^1.2.0",
82 | "@radix-ui/react-slot": "^1.1.0",
83 | "@radix-ui/react-switch": "^1.1.0",
84 | "@radix-ui/react-tabs": "^1.1.0",
85 | "@radix-ui/react-toast": "^1.2.1",
86 | "@radix-ui/react-toggle": "^1.1.0",
87 | "@radix-ui/react-toggle-group": "^1.1.0",
88 | "@radix-ui/react-tooltip": "^1.1.2",
89 | "class-variance-authority": "^0.7.0",
90 | clsx: "^2.1.1",
91 | "date-fns": "^3.6.0",
92 | "embla-carousel-react": "^8.1.8",
93 | "react-day-picker": "^8.10.1",
94 | "tailwind-merge": "^2.4.0",
95 | "tailwindcss-animate": "^1.0.7",
96 | vaul: "^0.9.1",
97 | },
98 | },
99 | } as const;
100 |
101 | const sharedOptions = {
102 | externalResources: [
103 | "https://unpkg.com/@tailwindcss/ui/dist/tailwind-ui.min.css",
104 | ],
105 | };
106 |
107 | const sharedFiles = {
108 | "/lib/utils.ts": shadcnComponents.utils,
109 | "/components/ui/accordion.tsx": shadcnComponents.accordian,
110 | "/components/ui/alert-dialog.tsx": shadcnComponents.alertDialog,
111 | "/components/ui/alert.tsx": shadcnComponents.alert,
112 | "/components/ui/avatar.tsx": shadcnComponents.avatar,
113 | "/components/ui/badge.tsx": shadcnComponents.badge,
114 | "/components/ui/breadcrumb.tsx": shadcnComponents.breadcrumb,
115 | "/components/ui/button.tsx": shadcnComponents.button,
116 | "/components/ui/calendar.tsx": shadcnComponents.calendar,
117 | "/components/ui/card.tsx": shadcnComponents.card,
118 | "/components/ui/carousel.tsx": shadcnComponents.carousel,
119 | "/components/ui/checkbox.tsx": shadcnComponents.checkbox,
120 | "/components/ui/collapsible.tsx": shadcnComponents.collapsible,
121 | "/components/ui/dialog.tsx": shadcnComponents.dialog,
122 | "/components/ui/drawer.tsx": shadcnComponents.drawer,
123 | "/components/ui/dropdown-menu.tsx": shadcnComponents.dropdownMenu,
124 | "/components/ui/input.tsx": shadcnComponents.input,
125 | "/components/ui/label.tsx": shadcnComponents.label,
126 | "/components/ui/menubar.tsx": shadcnComponents.menuBar,
127 | "/components/ui/navigation-menu.tsx": shadcnComponents.navigationMenu,
128 | "/components/ui/pagination.tsx": shadcnComponents.pagination,
129 | "/components/ui/popover.tsx": shadcnComponents.popover,
130 | "/components/ui/progress.tsx": shadcnComponents.progress,
131 | "/components/ui/radio-group.tsx": shadcnComponents.radioGroup,
132 | "/components/ui/select.tsx": shadcnComponents.select,
133 | "/components/ui/separator.tsx": shadcnComponents.separator,
134 | "/components/ui/skeleton.tsx": shadcnComponents.skeleton,
135 | "/components/ui/slider.tsx": shadcnComponents.slider,
136 | "/components/ui/switch.tsx": shadcnComponents.switchComponent,
137 | "/components/ui/table.tsx": shadcnComponents.table,
138 | "/components/ui/tabs.tsx": shadcnComponents.tabs,
139 | "/components/ui/textarea.tsx": shadcnComponents.textarea,
140 | "/components/ui/toast.tsx": shadcnComponents.toast,
141 | "/components/ui/toaster.tsx": shadcnComponents.toaster,
142 | "/components/ui/toggle-group.tsx": shadcnComponents.toggleGroup,
143 | "/components/ui/toggle.tsx": shadcnComponents.toggle,
144 | "/components/ui/tooltip.tsx": shadcnComponents.tooltip,
145 | "/components/ui/use-toast.tsx": shadcnComponents.useToast,
146 | "/public/index.html": dedent`
147 |
148 |
149 |
150 |
151 |
152 | Document
153 |
154 |
155 |
156 |
157 |
158 |
159 | `,
160 | };
161 |
--------------------------------------------------------------------------------
/components/loading-dots.module.css:
--------------------------------------------------------------------------------
1 | .loading {
2 | display: inline-flex;
3 | align-items: center;
4 | }
5 |
6 | .loading .spacer {
7 | margin-right: 2px;
8 | }
9 |
10 | .loading span {
11 | animation-name: blink;
12 | animation-duration: 1.4s;
13 | animation-iteration-count: infinite;
14 | animation-fill-mode: both;
15 | width: 5px;
16 | height: 5px;
17 | border-radius: 50%;
18 | display: inline-block;
19 | margin: 0 1px;
20 | }
21 |
22 | .loading span:nth-of-type(2) {
23 | animation-delay: 0.2s;
24 | }
25 |
26 | .loading span:nth-of-type(3) {
27 | animation-delay: 0.4s;
28 | }
29 |
30 | .loading2 {
31 | display: inline-flex;
32 | align-items: center;
33 | }
34 |
35 | .loading2 .spacer {
36 | margin-right: 2px;
37 | }
38 |
39 | .loading2 span {
40 | animation-name: blink;
41 | animation-duration: 1.4s;
42 | animation-iteration-count: infinite;
43 | animation-fill-mode: both;
44 | width: 4px;
45 | height: 4px;
46 | border-radius: 50%;
47 | display: inline-block;
48 | margin: 0 1px;
49 | }
50 |
51 | .loading2 span:nth-of-type(2) {
52 | animation-delay: 0.2s;
53 | }
54 |
55 | .loading2 span:nth-of-type(3) {
56 | animation-delay: 0.4s;
57 | }
58 |
59 | @keyframes blink {
60 | 0% {
61 | opacity: 0.2;
62 | }
63 | 20% {
64 | opacity: 1;
65 | }
66 | 100% {
67 | opacity: 0.2;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/components/loading-dots.tsx:
--------------------------------------------------------------------------------
1 | import styles from "./loading-dots.module.css";
2 |
3 | export default function LoadingDots({
4 | color = "#000",
5 | style = "small",
6 | }: {
7 | color: string;
8 | style: string;
9 | }) {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | )
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button"
46 | return (
47 |
52 | )
53 | }
54 | )
55 | Button.displayName = "Button"
56 |
57 | export { Button, buttonVariants }
58 |
--------------------------------------------------------------------------------
/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import {
5 | CaretSortIcon,
6 | CheckIcon,
7 | ChevronDownIcon,
8 | ChevronUpIcon,
9 | } from "@radix-ui/react-icons"
10 | import * as SelectPrimitive from "@radix-ui/react-select"
11 |
12 | import { cn } from "@/lib/utils"
13 |
14 | const Select = SelectPrimitive.Root
15 |
16 | const SelectGroup = SelectPrimitive.Group
17 |
18 | const SelectValue = SelectPrimitive.Value
19 |
20 | const SelectTrigger = React.forwardRef<
21 | React.ElementRef,
22 | React.ComponentPropsWithoutRef
23 | >(({ className, children, ...props }, ref) => (
24 | span]:line-clamp-1",
28 | className
29 | )}
30 | {...props}
31 | >
32 | {children}
33 |
34 |
35 |
36 |
37 | ))
38 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
39 |
40 | const SelectScrollUpButton = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 |
53 |
54 | ))
55 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
56 |
57 | const SelectScrollDownButton = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
69 |
70 |
71 | ))
72 | SelectScrollDownButton.displayName =
73 | SelectPrimitive.ScrollDownButton.displayName
74 |
75 | const SelectContent = React.forwardRef<
76 | React.ElementRef,
77 | React.ComponentPropsWithoutRef
78 | >(({ className, children, position = "popper", ...props }, ref) => (
79 |
80 |
91 |
92 |
99 | {children}
100 |
101 |
102 |
103 |
104 | ))
105 | SelectContent.displayName = SelectPrimitive.Content.displayName
106 |
107 | const SelectLabel = React.forwardRef<
108 | React.ElementRef,
109 | React.ComponentPropsWithoutRef
110 | >(({ className, ...props }, ref) => (
111 |
116 | ))
117 | SelectLabel.displayName = SelectPrimitive.Label.displayName
118 |
119 | const SelectItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | SelectItem.displayName = SelectPrimitive.Item.displayName
140 |
141 | const SelectSeparator = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef
144 | >(({ className, ...props }, ref) => (
145 |
150 | ))
151 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
152 |
153 | export {
154 | Select,
155 | SelectGroup,
156 | SelectValue,
157 | SelectTrigger,
158 | SelectContent,
159 | SelectLabel,
160 | SelectItem,
161 | SelectSeparator,
162 | SelectScrollUpButton,
163 | SelectScrollDownButton,
164 | }
165 |
--------------------------------------------------------------------------------
/components/ui/shimmerbutton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 | import React, { CSSProperties } from "react";
3 |
4 | export interface ShimmerButtonProps
5 | extends React.ButtonHTMLAttributes {
6 | shimmerColor?: string;
7 | shimmerSize?: string;
8 | borderRadius?: string;
9 | shimmerDuration?: string;
10 | background?: string;
11 | className?: string;
12 | children?: React.ReactNode;
13 | }
14 |
15 | const ShimmerButton = React.forwardRef(
16 | (
17 | {
18 | shimmerColor = "#ffffff",
19 | shimmerSize = "0.05em",
20 | shimmerDuration = "3s",
21 | borderRadius = "100px",
22 | background = "rgba(0, 0, 0, 1)",
23 | className,
24 | children,
25 | ...props
26 | },
27 | ref
28 | ) => {
29 | return (
30 |
49 | {/* spark container */}
50 |
56 | {/* spark */}
57 |
58 | {/* spark before */}
59 |
60 |
61 |
62 | {children}
63 |
64 | {/* Highlight */}
65 |
81 |
82 | {/* backdrop */}
83 |
88 |
89 | );
90 | }
91 | );
92 |
93 | ShimmerButton.displayName = "ShimmerButton";
94 |
95 | export default ShimmerButton;
96 |
--------------------------------------------------------------------------------
/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/lib/shadcn-docs/avatar.tsx:
--------------------------------------------------------------------------------
1 | export const name = "Avatar";
2 |
3 | export const importDocs = `
4 | import { Avatar, AvatarFallback, AvatarImage } from "/components/ui/avatar";
5 | `;
6 |
7 | export const usageDocs = `
8 |
9 |
10 | CN
11 |
12 | `;
13 |
--------------------------------------------------------------------------------
/lib/shadcn-docs/button.tsx:
--------------------------------------------------------------------------------
1 | export const name = "Button";
2 |
3 | export const importDocs = `
4 | import { Button } from "/components/ui/button"
5 | `;
6 |
7 | export const usageDocs = `
8 | A normal button
9 | Button
10 | Button
11 | Button
12 | Button
13 | Button
14 | `;
15 |
--------------------------------------------------------------------------------
/lib/shadcn-docs/card.tsx:
--------------------------------------------------------------------------------
1 | export const name = "Card";
2 |
3 | export const importDocs = `
4 | import {
5 | Card,
6 | CardContent,
7 | CardDescription,
8 | CardFooter,
9 | CardHeader,
10 | CardTitle,
11 | } from "/components/ui/card"
12 | `;
13 |
14 | export const usageDocs = `
15 |
16 |
17 | Card Title
18 | Card Description
19 |
20 |
21 | Card Content
22 |
23 |
24 | Card Footer
25 |
26 |
27 | `;
28 |
--------------------------------------------------------------------------------
/lib/shadcn-docs/checkbox.tsx:
--------------------------------------------------------------------------------
1 | export const name = "Checkbox";
2 |
3 | export const importDocs = `
4 | import { Checkbox } from "/components/ui/checkbox"
5 | `;
6 |
7 | export const usageDocs = `
8 |
9 | `;
10 |
--------------------------------------------------------------------------------
/lib/shadcn-docs/index.ts:
--------------------------------------------------------------------------------
1 | import * as Avatar from "./avatar";
2 | import * as Button from "./button";
3 | import * as Card from "./card";
4 | // import * as Checkbox from "./checkbox";
5 | import * as Input from "./input";
6 | import * as Label from "./label";
7 | import * as RadioGroup from "./radio-group";
8 | import * as Select from "./select";
9 | import * as Textarea from "./textarea";
10 |
11 | const shadcnDocs = [
12 | Avatar,
13 | Button,
14 | Card,
15 | // Checkbox,
16 | Input,
17 | Label,
18 | RadioGroup,
19 | Select,
20 | Textarea,
21 | ];
22 |
23 | export default shadcnDocs;
24 |
--------------------------------------------------------------------------------
/lib/shadcn-docs/input.tsx:
--------------------------------------------------------------------------------
1 | export const name = "Input";
2 |
3 | export const importDocs = `
4 | import { Input } from "/components/ui/input"
5 | `;
6 |
7 | export const usageDocs = `
8 |
9 | `;
10 |
--------------------------------------------------------------------------------
/lib/shadcn-docs/label.tsx:
--------------------------------------------------------------------------------
1 | export const name = "Label";
2 |
3 | export const importDocs = `
4 | import { Label } from "/components/ui/label"
5 | `;
6 |
7 | export const usageDocs = `
8 | Your email address
9 | `;
10 |
--------------------------------------------------------------------------------
/lib/shadcn-docs/radio-group.tsx:
--------------------------------------------------------------------------------
1 | export const name = "RadioGroup";
2 |
3 | export const importDocs = `
4 | import { Label } from "/components/ui/label"
5 | import { RadioGroup, RadioGroupItem } from "/components/ui/radio-group"
6 | `;
7 |
8 | export const usageDocs = `
9 |
10 |
11 |
12 | Option One
13 |
14 |
15 |
16 | Option Two
17 |
18 |
19 | `;
20 |
--------------------------------------------------------------------------------
/lib/shadcn-docs/select.tsx:
--------------------------------------------------------------------------------
1 | export const name = "Select";
2 |
3 | export const importDocs = `
4 | import {
5 | Select,
6 | SelectContent,
7 | SelectItem,
8 | SelectTrigger,
9 | SelectValue,
10 | } from "@/components/ui/select"
11 | `;
12 |
13 | export const usageDocs = `
14 |
15 |
16 |
17 |
18 |
19 | Light
20 | Dark
21 | System
22 |
23 |
24 | `;
25 |
--------------------------------------------------------------------------------
/lib/shadcn-docs/textarea.tsx:
--------------------------------------------------------------------------------
1 | export const name = "Textarea";
2 |
3 | export const importDocs = `
4 | import { Textarea } from "@/components/ui/textarea"
5 | `;
6 |
7 | export const usageDocs = `
8 |
9 | `;
10 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | export async function* readStream(response: ReadableStream) {
9 | let reader = response.pipeThrough(new TextDecoderStream()).getReader();
10 | let done = false;
11 |
12 | while (!done) {
13 | let { value, done: streamDone } = await reader.read();
14 | done = streamDone;
15 |
16 | if (value) yield value;
17 | }
18 |
19 | reader.releaseLock();
20 | }
21 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "napkins-dev",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@codesandbox/sandpack-react": "^2.18.2",
13 | "@codesandbox/sandpack-themes": "^2.0.21",
14 | "@heroicons/react": "^2.1.5",
15 | "@radix-ui/react-icons": "^1.3.0",
16 | "@radix-ui/react-select": "^2.1.1",
17 | "@radix-ui/react-slot": "^1.1.0",
18 | "@radix-ui/react-tooltip": "^1.1.2",
19 | "class-variance-authority": "^0.7.0",
20 | "clsx": "^2.1.1",
21 | "dedent": "^1.5.3",
22 | "framer-motion": "^11.5.5",
23 | "lucide-react": "^0.441.0",
24 | "next": "14.2.12",
25 | "next-plausible": "^3.12.2",
26 | "next-s3-upload": "^0.3.4",
27 | "react": "^18",
28 | "react-dom": "^18",
29 | "react-drag-drop-files": "^2.3.10",
30 | "tailwind-merge": "^2.5.2",
31 | "tailwindcss-animate": "^1.0.7",
32 | "together-ai": "^0.14.0",
33 | "zod": "^3.23.8"
34 | },
35 | "devDependencies": {
36 | "@types/node": "^20",
37 | "@types/react": "^18",
38 | "@types/react-dom": "^18",
39 | "eslint": "^8",
40 | "eslint-config-next": "14.2.12",
41 | "postcss": "^8",
42 | "prettier": "^3.3.3",
43 | "prettier-plugin-tailwindcss": "^0.6.7",
44 | "tailwindcss": "^3.4.1",
45 | "typescript": "^5"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/hero-2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/public/hero-3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/public/hero.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/public/logo-2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/public/logos.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/public/meta.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nutlope/napkins/22f3e630e1a0847fc465ff9b12393a2aeffb1fe0/public/og-image.png
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | darkMode: ["class"],
5 | content: [
6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
8 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
9 | ],
10 | theme: {
11 | extend: {
12 | fontFamily: {
13 | sans: ["var(--font-geist-sans)"],
14 | mono: ["var(--font-geist-mono)"],
15 | },
16 | colors: {
17 | background: "hsl(var(--background))",
18 | foreground: "hsl(var(--foreground))",
19 | card: {
20 | DEFAULT: "hsl(var(--card))",
21 | foreground: "hsl(var(--card-foreground))",
22 | },
23 | popover: {
24 | DEFAULT: "hsl(var(--popover))",
25 | foreground: "hsl(var(--popover-foreground))",
26 | },
27 | primary: {
28 | DEFAULT: "hsl(var(--primary))",
29 | foreground: "hsl(var(--primary-foreground))",
30 | },
31 | secondary: {
32 | DEFAULT: "hsl(var(--secondary))",
33 | foreground: "hsl(var(--secondary-foreground))",
34 | },
35 | muted: {
36 | DEFAULT: "hsl(var(--muted))",
37 | foreground: "hsl(var(--muted-foreground))",
38 | },
39 | accent: {
40 | DEFAULT: "hsl(var(--accent))",
41 | foreground: "hsl(var(--accent-foreground))",
42 | },
43 | destructive: {
44 | DEFAULT: "hsl(var(--destructive))",
45 | foreground: "hsl(var(--destructive-foreground))",
46 | },
47 | border: "hsl(var(--border))",
48 | input: "hsl(var(--input))",
49 | ring: "hsl(var(--ring))",
50 | chart: {
51 | "1": "hsl(var(--chart-1))",
52 | "2": "hsl(var(--chart-2))",
53 | "3": "hsl(var(--chart-3))",
54 | "4": "hsl(var(--chart-4))",
55 | "5": "hsl(var(--chart-5))",
56 | },
57 | },
58 | borderRadius: {
59 | lg: "var(--radius)",
60 | md: "calc(var(--radius) - 2px)",
61 | sm: "calc(var(--radius) - 4px)",
62 | },
63 | animation: {
64 | "spin-around": "spin-around calc(var(--speed) * 2) infinite linear",
65 | slide: "slide var(--speed) ease-in-out infinite alternate",
66 | },
67 | keyframes: {
68 | "spin-around": {
69 | "0%": {
70 | transform: "translateZ(0) rotate(0)",
71 | },
72 | "15%, 35%": {
73 | transform: "translateZ(0) rotate(90deg)",
74 | },
75 | "65%, 85%": {
76 | transform: "translateZ(0) rotate(270deg)",
77 | },
78 | "100%": {
79 | transform: "translateZ(0) rotate(360deg)",
80 | },
81 | },
82 | slide: {
83 | to: {
84 | transform: "translate(calc(100cqw - 100%), 0)",
85 | },
86 | },
87 | },
88 | },
89 | },
90 | plugins: [require("tailwindcss-animate")],
91 | };
92 | export default config;
93 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------