Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
/.next/
/out/

# nuxt
.nuxt
.nitro
.output

# production
/build

Expand All @@ -47,3 +52,17 @@ next-env.d.ts
# wrangler files
.wrangler
.dev.vars

# IDE
.vscode/
.idea/
*.swp
*.swo

# Logs
logs
*.log

# Cache
.cache
.parcel-cache
30 changes: 23 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,33 @@ Try it online: [cloudmark.site](https://cloudmark.site)

### Prerequisites

- Node.js 15+ and pnpm
- Node.js 18+ and npm/yarn
- Cloudflare account (for preview and deployment)

### Install Dependencies

```bash
pnpm install
npm install
```

### Development Mode

```bash
pnpm dev
npm run dev
```

Visit [http://localhost:3000](http://localhost:3000) to see the result.

### Local Preview with Cloudflare Pages

```bash
pnpm preview
npm run preview
```

### Build and Deploy

```bash
pnpm deploy
npm run deploy
```

## Cloudflare Configuration
Expand All @@ -85,11 +85,27 @@ Cloudmark uses Cloudflare KV to store bookmark data. You need to:

## Technology Stack

- [Next.js](https://nextjs.org/) - React framework
- [Nuxt 3](https://nuxt.com/) - Vue.js framework
- [Vue 3](https://vuejs.org/) - Progressive JavaScript framework
- [Cloudflare Pages](https://pages.cloudflare.com/) - Hosting and serverless functions
- [Cloudflare KV](https://developers.cloudflare.com/workers/runtime-apis/kv/) - Data storage
- [Tailwind CSS](https://tailwindcss.com/) - Styling
- [Next-Intl](https://next-intl-docs.vercel.app/) - Internationalization
- [Vue i18n](https://vue-i18n.intlify.dev/) - Internationalization
- [TypeScript](https://www.typescriptlang.org/) - Type safety

## Migration from Next.js

This project has been migrated from Next.js + React to Nuxt 3 + Vue 3. Key changes include:

- **Framework**: Next.js β†’ Nuxt 3
- **UI Library**: React β†’ Vue 3 with Composition API
- **Routing**: Next.js Router β†’ Vue Router (via Nuxt)
- **State Management**: React hooks β†’ Vue Composition API
- **Server**: Next.js API Routes β†’ Nuxt 3 Server API
- **Internationalization**: next-intl β†’ Vue i18n
- **Build**: Next.js build β†’ Nitro (Nuxt's build engine)

All functionality has been preserved while modernizing the tech stack.

## License

Expand Down
209 changes: 209 additions & 0 deletions assets/css/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--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: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.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%;
--radius: 0.5rem;
}

.dark {
--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: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--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: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
--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%;
}
}

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

/* Custom animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

@keyframes fadeUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}

@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}

.animate-fadeIn {
animation: fadeIn 0.6s ease-out forwards;
}

.animate-fadeUp {
animation: fadeUp 0.6s ease-out forwards;
}

.animate-scaleIn {
animation: scaleIn 0.6s ease-out forwards;
}

.animate-slideIn {
animation: slideIn 0.6s ease-out forwards;
}

/* Animation delays */
.animation-delay-200 {
animation-delay: 200ms;
}

.animation-delay-400 {
animation-delay: 400ms;
}

.animation-delay-600 {
animation-delay: 600ms;
}

.animation-delay-700 {
animation-delay: 700ms;
}

.animation-delay-800 {
animation-delay: 800ms;
}

.animation-delay-900 {
animation-delay: 900ms;
}

/* Stagger animations */
.stagger-container {
--stagger-delay: 100ms;
}

.stagger-item {
opacity: 0;
animation: fadeIn 0.6s ease-out forwards;
}

.stagger-item:nth-child(1) { animation-delay: calc(1 * var(--stagger-delay)); }
.stagger-item:nth-child(2) { animation-delay: calc(2 * var(--stagger-delay)); }
.stagger-item:nth-child(3) { animation-delay: calc(3 * var(--stagger-delay)); }
.stagger-item:nth-child(4) { animation-delay: calc(4 * var(--stagger-delay)); }
.stagger-item:nth-child(5) { animation-delay: calc(5 * var(--stagger-delay)); }
.stagger-item:nth-child(6) { animation-delay: calc(6 * var(--stagger-delay)); }
.stagger-item:nth-child(7) { animation-delay: calc(7 * var(--stagger-delay)); }
.stagger-item:nth-child(8) { animation-delay: calc(8 * var(--stagger-delay)); }
.stagger-item:nth-child(9) { animation-delay: calc(9 * var(--stagger-delay)); }

/* Interactive elements */
.hover-scale {
transition: transform 0.2s ease-in-out;
}

.hover-scale:hover {
transform: scale(1.05);
}

.active-scale:active {
transform: scale(0.95);
}

/* Feature card animations */
.feature-card {
transition: all 0.3s ease;
}

.feature-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}

/* Delay classes for animations */
.delay-100 { animation-delay: 100ms; }
.delay-200 { animation-delay: 200ms; }
.delay-300 { animation-delay: 300ms; }
.delay-400 { animation-delay: 400ms; }
.delay-500 { animation-delay: 500ms; }
.delay-600 { animation-delay: 600ms; }
.delay-700 { animation-delay: 700ms; }
.delay-800 { animation-delay: 800ms; }
.delay-900 { animation-delay: 900ms; }
24 changes: 24 additions & 0 deletions components/AppFooter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<template>
<footer class="border-t border-border/40 mt-auto">
<div class="container flex flex-col gap-2 py-6 text-center">
<p class="text-xs text-muted-foreground">
Released under the AGPL License.
</p>
<p class="text-xs text-muted-foreground">
Copyright Β© {{ currentYear }}
<NuxtLink
to="https://github.com/wesleyel"
class="hover:text-primary"
target="_blank"
external
>
Wesley Yang
</NuxtLink>
</p>
</div>
</footer>
</template>

<script setup lang="ts">
const currentYear = new Date().getFullYear()
</script>
52 changes: 52 additions & 0 deletions components/AppHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<header class="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div class="container flex h-14 items-center">
<!-- Logo -->
<NuxtLink
to="/"
class="flex items-center space-x-2 transition-transform duration-200 hover:scale-105"
>
<img
src="/icon1.svg"
alt="Cloudmark logo"
width="24"
height="24"
class="h-6 w-6 text-primary"
/>
<span class="font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-500">
Cloudmark
</span>
</NuxtLink>

<!-- Navigation -->
<nav class="ml-auto flex gap-2 sm:gap-4 items-center">
<NuxtLink
to="/doc"
class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground hover:text-primary hover:bg-primary/5 transition-colors duration-200"
prefetch
>
<Icon name="lucide:file-text" class="h-4 w-4" />
<span class="hidden sm:inline">{{ $t('Navigation.quickstart') }}</span>
</NuxtLink>

<NuxtLink
to="https://github.com/wesleyel/cloudmark"
external
target="_blank"
class="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground hover:text-primary hover:bg-primary/5 transition-colors duration-200"
>
<Icon name="lucide:github" class="h-4 w-4" />
<span class="hidden sm:inline">{{ $t('Navigation.github') }}</span>
</NuxtLink>

<div class="pl-1 border-border/40">
<LanguageSwitcher />
</div>
</nav>
</div>
</header>
</template>

<script setup lang="ts">
const { t } = useI18n()
</script>
Loading