Compare commits

...

8 Commits

Author SHA1 Message Date
fa71f53cb9 Add note: I was in fact here - June 21, 2025 2025-06-21 04:29:34 +02:00
613ffe8940 Remove temporary note - restore original README 2025-06-21 04:28:48 +02:00
ff823fd72e Update README.md - I was here June 21, 2025 2025-06-21 04:28:03 +02:00
ef420a777b Update docker-compose.yml 2025-06-20 00:17:18 +02:00
27a2ce6c40 Update docker-compose.yml 2025-06-20 00:15:50 +02:00
v0
da55b8b7dd fix: remove invalid <config> element in app/page.tsx
Replace incorrect React component with valid one.

#VERCEL_SKIP

Co-authored-by: Anders Lehmann Pier <3219386+AndersPier@users.noreply.github.com>
2025-06-19 22:07:32 +00:00
2a4654083b Update docker-compose.yml 2025-06-19 23:54:28 +02:00
v0
8e89f35e92 fix: resolve Docker build failures and configuration issues
Add missing dependencies, fix Next.js config, update Docker Compose, and configure Tailwind CSS.

#VERCEL_SKIP

Co-authored-by: Anders Lehmann Pier <3219386+AndersPier@users.noreply.github.com>
2025-06-19 21:53:56 +00:00
15 changed files with 5534 additions and 772 deletions

View File

@@ -28,3 +28,6 @@ Continue building your app on:
2. Deploy your chats from the v0 interface
3. Changes are automatically pushed to this repository
4. Vercel deploys the latest version from this repository
I was in fact here - June 21, 2025

View File

@@ -2,85 +2,50 @@
@tailwind components;
@tailwind utilities;
body {
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96%;
--secondary-foreground: 222.2 84% 4.9%;
--muted: 210 40% 96%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96%;
--accent-foreground: 222.2 84% 4.9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 84% 4.9%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 94.1%;
}
}
@@ -92,3 +57,61 @@ body {
@apply bg-background text-foreground;
}
}
/* Custom prose styles for markdown preview */
.prose {
@apply max-w-none;
}
.prose h1 {
@apply text-3xl font-bold mb-4;
}
.prose h2 {
@apply text-2xl font-semibold mb-3;
}
.prose h3 {
@apply text-xl font-semibold mb-2;
}
.prose p {
@apply mb-4 leading-relaxed;
}
.prose ul {
@apply mb-4;
}
.prose ol {
@apply mb-4;
}
.prose li {
@apply mb-2;
}
.prose blockquote {
@apply border-l-4 pl-4 py-2 my-4 italic;
}
.prose code {
@apply px-1 py-0.5 rounded text-sm font-mono;
}
.prose pre {
@apply p-4 rounded-lg overflow-x-auto my-4;
}
.prose table {
@apply w-full border-collapse my-4;
}
.prose th,
.prose td {
@apply border px-4 py-2 text-left;
}
.prose th {
@apply font-semibold;
}

View File

@@ -1,59 +1,9 @@
"use client"
import { MarkdownEditor } from "@/components/markdown-editor"
const defaultMarkdown = `# 🚀 Welcome to Markdown Editor
This is a **modern** markdown editor with *real-time* preview and a **colorful** interface!
## ✨ Features
- 🎨 Beautiful split-screen editing
- ⚡ Live preview with instant updates
- 🛠️ Rich formatting toolbar
- 🌈 Modern, colorful UI
### 💻 Code Example
\`\`\`javascript
function createAwesome() {
console.log("Building something amazing! 🎉");
return "success";
}
\`\`\`
### 📝 Lists & More
1. **First priority** - Get things done
2. **Second priority** - Make it beautiful
3. **Third priority** - Share with the world
- 🎯 Bullet points with style
- 🚀 Another awesome point
- 💡 Nested brilliance
- ⭐ Even more nested goodness
### 🔗 Links and Media
[🌟 Visit our GitHub](https://github.com) for more amazing projects!
> 💬 This is a beautiful blockquote with some **bold** text and *italic* styling.
---
### 📊 Data Table
| Feature | Status | Priority |
|---------|--------|----------|
| Editor | ✅ Complete | High |
| Preview | ✅ Complete | High |
| Export | 🚧 In Progress | Medium |
Happy writing! ✨🎉`
export default function Home() {
// app/page.tsx
export default function HomePage() {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50">
<MarkdownEditor initialContent={defaultMarkdown} />
</div>
<main className="flex min-h-screen flex-col items-center justify-center gap-4">
<h1 className="text-3xl font-bold">Markdown Editor</h1>
<p className="text-muted-foreground">Your application is up and running. Start editing!</p>
</main>
)
}

View File

@@ -1,98 +0,0 @@
"use client"
import { useState, useCallback } from "react"
import { MarkdownToolbar } from "./markdown-toolbar"
import { MarkdownInput } from "./markdown-input"
import { MarkdownPreview } from "./markdown-preview"
import { FileText, Eye, Edit3, Sparkles } from "lucide-react"
interface MarkdownEditorProps {
initialContent?: string
}
export function MarkdownEditor({ initialContent = "" }: MarkdownEditorProps) {
const [content, setContent] = useState(initialContent)
const [textareaRef, setTextareaRef] = useState<HTMLTextAreaElement | null>(null)
const insertText = useCallback(
(before: string, after = "", placeholder = "") => {
if (!textareaRef) return
const start = textareaRef.selectionStart
const end = textareaRef.selectionEnd
const selectedText = content.substring(start, end)
const textToInsert = selectedText || placeholder
const newText = content.substring(0, start) + before + textToInsert + after + content.substring(end)
setContent(newText)
// Set cursor position after insertion
setTimeout(() => {
const newCursorPos = start + before.length + textToInsert.length
textareaRef.setSelectionRange(newCursorPos, newCursorPos)
textareaRef.focus()
}, 0)
},
[content, textareaRef],
)
return (
<div className="flex flex-col h-screen">
{/* Header */}
<header className="border-b bg-white/80 backdrop-blur-xl supports-[backdrop-filter]:bg-white/60 shadow-sm">
<div className="flex h-16 items-center px-6">
<div className="flex items-center gap-3">
<div className="relative">
<FileText className="h-7 w-7 text-blue-600" />
<Sparkles className="h-3 w-3 text-yellow-500 absolute -top-1 -right-1" />
</div>
<div>
<h1 className="text-xl font-bold bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent">
Markdown Editor
</h1>
<p className="text-xs text-muted-foreground">Write Preview Create</p>
</div>
</div>
</div>
</header>
{/* Toolbar */}
<div className="border-b bg-gradient-to-r from-blue-50/50 via-purple-50/50 to-pink-50/50 shadow-sm">
<MarkdownToolbar onInsertText={insertText} />
</div>
{/* Editor */}
<div className="flex flex-1 overflow-hidden">
{/* Input Panel */}
<div className="flex-1 flex flex-col border-r border-gray-200">
<div className="flex items-center gap-3 px-4 py-3 bg-gradient-to-r from-green-50 to-emerald-50 border-b">
<div className="p-1 rounded-full bg-green-100">
<Edit3 className="h-4 w-4 text-green-600" />
</div>
<span className="text-sm font-semibold text-green-700">Editor</span>
<div className="ml-auto flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-green-400 animate-pulse"></div>
<span className="text-xs text-green-600">Live</span>
</div>
</div>
<MarkdownInput content={content} onChange={setContent} onTextareaRef={setTextareaRef} />
</div>
{/* Preview Panel */}
<div className="flex-1 flex flex-col">
<div className="flex items-center gap-3 px-4 py-3 bg-gradient-to-r from-blue-50 to-indigo-50 border-b">
<div className="p-1 rounded-full bg-blue-100">
<Eye className="h-4 w-4 text-blue-600" />
</div>
<span className="text-sm font-semibold text-blue-700">Preview</span>
<div className="ml-auto flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-blue-400 animate-pulse"></div>
<span className="text-xs text-blue-600">Real-time</span>
</div>
</div>
<MarkdownPreview content={content} />
</div>
</div>
</div>
)
}

View File

@@ -1,65 +0,0 @@
"use client"
import type React from "react"
import { useEffect, useRef } from "react"
interface MarkdownInputProps {
content: string
onChange: (content: string) => void
onTextareaRef: (ref: HTMLTextAreaElement | null) => void
}
export function MarkdownInput({ content, onChange, onTextareaRef }: MarkdownInputProps) {
const textareaRef = useRef<HTMLTextAreaElement>(null)
useEffect(() => {
onTextareaRef(textareaRef.current)
}, [onTextareaRef])
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Tab") {
e.preventDefault()
const start = e.currentTarget.selectionStart
const end = e.currentTarget.selectionEnd
const newContent = content.substring(0, start) + " " + content.substring(end)
onChange(newContent)
setTimeout(() => {
if (textareaRef.current) {
textareaRef.current.setSelectionRange(start + 2, start + 2)
}
}, 0)
}
}
return (
<div className="flex-1 relative bg-gradient-to-br from-white to-gray-50/50">
<textarea
ref={textareaRef}
value={content}
onChange={(e) => onChange(e.target.value)}
onKeyDown={handleKeyDown}
className="w-full h-full p-6 resize-none border-0 outline-none bg-transparent font-mono text-sm leading-relaxed
placeholder:text-gray-400 focus:placeholder:text-gray-300 transition-all duration-200
selection:bg-blue-100 selection:text-blue-900"
placeholder="✨ Start writing your markdown here...
Try typing:
# My Amazing Title
**Bold text** and *italic text*
- List items
> Blockquotes
```code blocks```"
spellCheck={false}
/>
{/* Subtle grid pattern overlay */}
<div
className="absolute inset-0 pointer-events-none opacity-[0.02]"
style={{
backgroundImage: `radial-gradient(circle at 1px 1px, rgba(0,0,0,0.15) 1px, transparent 0)`,
backgroundSize: "20px 20px",
}}
/>
</div>
)
}

View File

@@ -1,129 +0,0 @@
"use client"
import ReactMarkdown from "react-markdown"
import remarkGfm from "remark-gfm"
interface MarkdownPreviewProps {
content: string
}
export function MarkdownPreview({ content }: MarkdownPreviewProps) {
return (
<div className="flex-1 overflow-auto bg-gradient-to-br from-white to-blue-50/30">
<div className="p-6 prose prose-slate dark:prose-invert max-w-none">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
h1: ({ children }) => (
<h1 className="text-3xl font-bold bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent mb-4">
{children}
</h1>
),
h2: ({ children }) => (
<h2 className="text-2xl font-semibold text-gray-800 mb-3 pb-2 border-b-2 border-gradient-to-r from-blue-200 to-purple-200">
{children}
</h2>
),
h3: ({ children }) => <h3 className="text-xl font-semibold text-gray-700 mb-2">{children}</h3>,
p: ({ children }) => <p className="text-gray-700 leading-relaxed mb-4">{children}</p>,
strong: ({ children }) => <strong className="font-bold text-blue-700">{children}</strong>,
em: ({ children }) => <em className="italic text-purple-600">{children}</em>,
code({ inline, className, children, ...props }) {
if (inline) {
return (
<code
className="bg-gradient-to-r from-pink-100 to-purple-100 text-purple-800 px-2 py-1 rounded-md text-sm font-mono border border-purple-200"
{...props}
>
{children}
</code>
)
}
// block code
return (
<div className="relative my-6 rounded-xl overflow-hidden shadow-lg">
<div className="bg-gradient-to-r from-gray-800 to-gray-900 px-4 py-2 flex items-center gap-2">
<div className="flex gap-1">
<div className="w-3 h-3 rounded-full bg-red-400"></div>
<div className="w-3 h-3 rounded-full bg-yellow-400"></div>
<div className="w-3 h-3 rounded-full bg-green-400"></div>
</div>
<span className="text-gray-300 text-xs font-mono ml-2">code</span>
</div>
<pre className="bg-gradient-to-br from-gray-900 to-black p-4 overflow-x-auto text-sm text-gray-100 font-mono">
<code {...props}>{children}</code>
</pre>
</div>
)
},
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-gradient-to-b from-blue-400 to-purple-400 bg-gradient-to-r from-blue-50 to-purple-50 pl-4 py-2 my-4 rounded-r-lg">
{children}
</blockquote>
),
ul: ({ children }) => <ul className="list-none space-y-2 my-4">{children}</ul>,
ol: ({ children }) => <ol className="list-none space-y-2 my-4 counter-reset-list">{children}</ol>,
li: ({ children, ...props }) => {
const isOrdered = props.node?.parent?.tagName === "ol"
return (
<li className={`flex items-start gap-3 ${isOrdered ? "counter-increment-list" : ""}`}>
{isOrdered ? (
<span className="flex-shrink-0 w-6 h-6 bg-gradient-to-r from-blue-500 to-purple-500 text-white text-xs font-bold rounded-full flex items-center justify-center counter-content">
{/* Counter will be handled by CSS */}
</span>
) : (
<span className="flex-shrink-0 w-2 h-2 bg-gradient-to-r from-pink-400 to-purple-400 rounded-full mt-2"></span>
)}
<div className="flex-1">{children}</div>
</li>
)
},
a: ({ children, href }) => (
<a
href={href}
className="text-blue-600 hover:text-purple-600 underline decoration-2 underline-offset-2 hover:decoration-purple-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
{children}
</a>
),
table({ children }) {
return (
<div className="overflow-x-auto my-6 rounded-lg shadow-lg">
<table className="min-w-full border-collapse bg-white">{children}</table>
</div>
)
},
th({ children }) {
return (
<th className="border border-gray-200 px-4 py-3 bg-gradient-to-r from-blue-50 to-purple-50 font-semibold text-left text-gray-800">
{children}
</th>
)
},
td({ children }) {
return (
<td className="border border-gray-200 px-4 py-3 text-gray-700 hover:bg-gray-50 transition-colors duration-150">
{children}
</td>
)
},
hr: () => (
<hr className="my-8 border-0 h-1 bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 rounded-full" />
),
}}
>
{content || (
<div className="text-center py-12 text-gray-400">
<div className="text-4xl mb-4"></div>
<p className="text-lg">Start typing to see the magic happen...</p>
<p className="text-sm mt-2">Your markdown will appear here in real-time!</p>
</div>
)}
</ReactMarkdown>
</div>
</div>
)
}

View File

@@ -1,102 +0,0 @@
"use client"
import { Button } from "@/components/ui/button"
import { Separator } from "@/components/ui/separator"
import {
Bold,
Italic,
Strikethrough,
Code,
Link,
ImageIcon,
List,
ListOrdered,
Quote,
Heading1,
Heading2,
Heading3,
Minus,
Table,
} from "lucide-react"
interface MarkdownToolbarProps {
onInsertText: (before: string, after?: string, placeholder?: string) => void
}
export function MarkdownToolbar({ onInsertText }: MarkdownToolbarProps) {
const toolbarItems = [
{
group: "text",
color: "from-red-500 to-pink-500",
items: [
{ icon: Bold, label: "Bold", action: () => onInsertText("**", "**", "bold text") },
{ icon: Italic, label: "Italic", action: () => onInsertText("*", "*", "italic text") },
{ icon: Strikethrough, label: "Strikethrough", action: () => onInsertText("~~", "~~", "strikethrough text") },
{ icon: Code, label: "Inline Code", action: () => onInsertText("`", "`", "code") },
],
},
{
group: "headings",
color: "from-blue-500 to-cyan-500",
items: [
{ icon: Heading1, label: "Heading 1", action: () => onInsertText("# ", "", "Heading 1") },
{ icon: Heading2, label: "Heading 2", action: () => onInsertText("## ", "", "Heading 2") },
{ icon: Heading3, label: "Heading 3", action: () => onInsertText("### ", "", "Heading 3") },
],
},
{
group: "lists",
color: "from-green-500 to-emerald-500",
items: [
{ icon: List, label: "Bullet List", action: () => onInsertText("- ", "", "List item") },
{ icon: ListOrdered, label: "Numbered List", action: () => onInsertText("1. ", "", "List item") },
{ icon: Quote, label: "Quote", action: () => onInsertText("> ", "", "Quote text") },
],
},
{
group: "media",
color: "from-purple-500 to-indigo-500",
items: [
{ icon: Link, label: "Link", action: () => onInsertText("[", "](url)", "link text") },
{ icon: ImageIcon, label: "Image", action: () => onInsertText("![", "](image-url)", "alt text") },
{ icon: Minus, label: "Horizontal Rule", action: () => onInsertText("\n---\n") },
{
icon: Table,
label: "Table",
action: () => onInsertText("\n| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1 | Cell 2 |\n"),
},
],
},
]
return (
<div className="flex items-center gap-2 p-3 flex-wrap">
{toolbarItems.map((group, groupIndex) => (
<div key={group.group} className="flex items-center gap-1">
{group.items.map((item, itemIndex) => (
<Button
key={item.label}
variant="ghost"
size="sm"
onClick={item.action}
className={`h-9 w-9 p-0 rounded-xl transition-all duration-200 hover:scale-105 hover:shadow-lg group relative overflow-hidden
${itemIndex === 0 ? `hover:bg-gradient-to-r hover:${group.color} hover:text-white` : "hover:bg-gray-100"}
`}
title={item.label}
>
<item.icon className="h-4 w-4 relative z-10 transition-transform group-hover:scale-110" />
{itemIndex === 0 && (
<div
className={`absolute inset-0 bg-gradient-to-r ${group.color} opacity-0 group-hover:opacity-100 transition-opacity duration-200`}
/>
)}
</Button>
))}
{groupIndex < toolbarItems.length - 1 && (
<Separator orientation="vertical" className="h-6 mx-2 bg-gradient-to-b from-gray-200 to-gray-300" />
)}
</div>
))}
</div>
)
}

View File

@@ -1,19 +1,17 @@
version: '3.8'
services:
markdown-editor:
build:
context: .
dockerfile: Dockerfile
container_name: markdown-editor-app
ports:
- "3000:3000"
#ports:
# - "3000:3000"
environment:
- NODE_ENV=production
- NEXT_TELEMETRY_DISABLED=1
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
@@ -27,10 +25,10 @@ services:
container_name: markdown-editor-nginx
ports:
- "80:80"
- "443:443"
#- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
#- ./ssl:/etc/nginx/ssl:ro
depends_on:
- markdown-editor
restart: unless-stopped

View File

@@ -1,8 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
// Remove the invalid outputFileTracingRoot option
experimental: {
outputFileTracingRoot: undefined,
// Add any valid experimental options here if needed
},
// Optimize for Docker
compress: true,

View File

@@ -9,24 +9,65 @@
"start": "next start"
},
"dependencies": {
"@types/react-syntax-highlighter": "^15.5.11",
"@radix-ui/react-accordion": "latest",
"@radix-ui/react-alert-dialog": "latest",
"@radix-ui/react-aspect-ratio": "latest",
"@radix-ui/react-avatar": "latest",
"@radix-ui/react-checkbox": "latest",
"@radix-ui/react-collapsible": "latest",
"@radix-ui/react-context-menu": "latest",
"@radix-ui/react-dialog": "latest",
"@radix-ui/react-dropdown-menu": "latest",
"@radix-ui/react-hover-card": "latest",
"@radix-ui/react-label": "latest",
"@radix-ui/react-menubar": "latest",
"@radix-ui/react-navigation-menu": "latest",
"@radix-ui/react-popover": "latest",
"@radix-ui/react-progress": "latest",
"@radix-ui/react-radio-group": "latest",
"@radix-ui/react-scroll-area": "latest",
"@radix-ui/react-select": "latest",
"@radix-ui/react-separator": "latest",
"@radix-ui/react-slider": "latest",
"@radix-ui/react-slot": "latest",
"@radix-ui/react-switch": "latest",
"@radix-ui/react-tabs": "latest",
"@radix-ui/react-toast": "latest",
"@radix-ui/react-toggle": "latest",
"@radix-ui/react-toggle-group": "latest",
"@radix-ui/react-tooltip": "latest",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "latest",
"embla-carousel-react": "latest",
"input-otp": "latest",
"lucide-react": "^0.454.0",
"next": "15.2.4",
"next-themes": "latest",
"prism-react-renderer": "^2.3.1",
"react": "^19",
"react-day-picker": "latest",
"react-dom": "^19",
"react-markdown": "latest",
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "latest",
"react-hook-form": "latest",
"react-markdown": "^9.0.1",
"react-resizable-panels": "latest",
"recharts": "latest",
"remark-gfm": "^4.0.0",
"sonner": "latest",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"vaul": "latest"
},
"devDependencies": {
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"autoprefixer": "^10.4.14",
"eslint": "^8.0.0",
"eslint-config-next": "15.2.4",
"postcss": "^8.5",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
},
"packageManager": "pnpm@10.12.1"
}

5443
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -1,96 +1,81 @@
import type { Config } from "tailwindcss";
import type { Config } from "tailwindcss"
const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"*.{js,ts,jsx,tsx,mdx}"
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
"*.{js,ts,jsx,tsx,mdx}",
],
prefix: "",
theme: {
extend: {
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
keyframes: {
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
};
export default config;
}
export default config