mirror of
https://github.com/mmahdium/portfolio.git
synced 2026-08-17 05:24:30 +03:30
Update README and localization files to enhance project description, add error messages, and improve Persian translations.
This commit is contained in:
@@ -2,96 +2,165 @@
|
|||||||
|
|
||||||
# Nuxt UI Portfolio · Ali Arghyani
|
# Nuxt UI Portfolio · Ali Arghyani
|
||||||
|
|
||||||
A bilingual portfolio powered by Nuxt 4, Nuxt UI, Tailwind4, and TypeScript. The site highlights projects, experience, and recommendations with a focus on performance, accessibility, and developer experience. Dark mode is the default theme, with automatic RTL support for Persian content.
|
A modern, bilingual portfolio built with Nuxt 4, Nuxt UI, Tailwind CSS 4, and TypeScript. Features English and Persian (RTL) support with dark mode as default.
|
||||||
|
|
||||||
## Features
|
🔗 **Live Demo**: [aliarghyani.vercel.app](https://aliarghyani.vercel.app)
|
||||||
- Nuxt 4 application using the `app/` source directory structure
|
|
||||||
- Nuxt UI component library with custom theme tokens and chip variants
|
|
||||||
- Tailwind CSS 4 utility pipeline with custom variants and shared utilities
|
|
||||||
- Nuxt Image integration for hero and project thumbnails with responsive formats
|
|
||||||
- `@nuxtjs/i18n` for English (`en`) and Persian (`fa`) locales, including RTL switching
|
|
||||||
- `@nuxtjs/color-mode` for light/dark themes (dark is the default)
|
|
||||||
- VueUse utilities for scroll observation, reduced motion checks, and lazy mounting
|
|
||||||
- Local Roobert font preloads delivered from `public/fonts`
|
|
||||||
- Ready for deployment to Vercel (Nitro preset) or any static/edge host
|
|
||||||
|
|
||||||
## Tech Stack
|
## ✨ Features
|
||||||
- **Framework**: Nuxt 4 (Vue 3 + Vite)
|
|
||||||
- **UI Library**: Nuxt UI 4
|
- **Nuxt 4** - Latest framework with `app/` directory structure
|
||||||
- **Styling**: Tailwind CSS 4, custom CSS utilities
|
- **Nuxt UI 4** - Modern component library with custom theming
|
||||||
- **Language**: TypeScript
|
- **Tailwind CSS 4** - Utility-first styling with custom variants
|
||||||
- **Internationalization**: `@nuxtjs/i18n`
|
- **TypeScript** - Full type safety
|
||||||
- **Theme & Color Mode**: `@nuxtjs/color-mode`
|
- **i18n** - English & Persian with automatic RTL switching
|
||||||
- **Images**: `@nuxt/image`
|
- **Dark Mode** - Default theme with light mode option
|
||||||
- **Composables**: VueUse
|
- **Responsive Images** - Optimized with `@nuxt/image`
|
||||||
|
- **Performance** - Lazy loading, font preloading, reduced motion support
|
||||||
|
|
||||||
|
## � Quick StaTrt
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
```bash
|
```bash
|
||||||
|
# Install dependencies
|
||||||
pnpm install
|
pnpm install
|
||||||
|
|
||||||
|
# Start development server
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
- Node.js 18.20.0 or newer (up to 22.x) is required.
|
|
||||||
- Nuxt dev server runs on http://localhost:3000 by default.
|
|
||||||
|
|
||||||
### Project Scripts
|
Visit: http://localhost:3000
|
||||||
- `pnpm dev` – Start the Nuxt development server with HMR.
|
|
||||||
- `pnpm build` – Create a production build.
|
|
||||||
- `pnpm preview` – Preview the production build locally.
|
|
||||||
- `pnpm generate` – Generate a fully static build (optional).
|
|
||||||
- `pnpm typecheck` – Run Vue TSC for type analysis.
|
|
||||||
- `pnpm format` – Check formatting with Prettier.
|
|
||||||
- `pnpm format:write` – Apply Prettier formatting fixes.
|
|
||||||
|
|
||||||
## Project Structure (excerpt)
|
### Requirements
|
||||||
```
|
- Node.js 18.20.0 or newer (up to 22.x)
|
||||||
app/
|
- pnpm (recommended) or npm
|
||||||
app.vue # Root shell, head configuration, font preloads
|
|
||||||
app.config.ts # Nuxt UI tokens, component defaults
|
|
||||||
assets/css/main.css # Tailwind entrypoint, base styles, chip utilities
|
|
||||||
components/ # UI components (TopNav, Hero, Projects, etc.)
|
|
||||||
composables/ # Reusable logic (useSectionObserver, useSocialText)
|
|
||||||
data/ # Portfolio data (localized TypeScript modules)
|
|
||||||
pages/ # Route components (index, blog placeholder)
|
|
||||||
utils/ # chipTones and other helpers
|
|
||||||
|
|
||||||
public/
|
### Available Scripts
|
||||||
fonts/ # Roobert font files served directly
|
|
||||||
img/ # Static imagery consumed by Nuxt Image
|
|
||||||
|
|
||||||
i18n/
|
```bash
|
||||||
locales/ # en.json and fa.json dictionaries
|
pnpm dev # Start dev server
|
||||||
|
pnpm build # Build for production
|
||||||
|
pnpm preview # Preview production build
|
||||||
|
pnpm generate # Generate static site
|
||||||
|
pnpm typecheck # Run TypeScript checks
|
||||||
|
pnpm format # Check code formatting
|
||||||
|
pnpm format:write # Fix code formatting
|
||||||
```
|
```
|
||||||
|
|
||||||
## Internationalization
|
## 📁 Project Structure
|
||||||
- `@nuxtjs/i18n` is configured with `strategy: 'prefix_except_default'`.
|
|
||||||
- Locale metadata (language, dir, name) is defined in `nuxt.config.ts`.
|
|
||||||
- Dynamic `lang` and `dir` attributes are applied in `app/app.vue`, so toggling locales updates both SSR and client output.
|
|
||||||
- Portfolio content lives in typed modules (`app/data/portfolio.ts` for EN, `app/data/portfolio.fa.ts` for FA).
|
|
||||||
|
|
||||||
## Styling & Theming
|
```
|
||||||
- Tailwind CSS is initialized through `app/assets/css/main.css` with a reusable chip utility, hover states, and dark mode variant.
|
app/ # Source directory (srcDir)
|
||||||
- Nuxt UI theme tokens (`app/app.config.ts`) unify colors, typography, chip variants, and button defaults.
|
├── app.vue # Root component
|
||||||
- Local Roobert fonts are preloaded and combined with Fraunces, Inter, and Outfit using `@nuxt/fonts`.
|
├── app.config.ts # Nuxt UI theme tokens
|
||||||
- Dark mode is the default preference; color mode state is stored under `nuxt-color-mode`.
|
├── error.vue # Error page (404, 500)
|
||||||
|
├── assets/css/ # Tailwind & global styles
|
||||||
|
├── components/ # Auto-imported components
|
||||||
|
│ ├── common/ # Shared UI (TopNav, Footer)
|
||||||
|
│ └── portfolio/ # Portfolio components
|
||||||
|
├── composables/ # Auto-imported composables
|
||||||
|
├── data/ # Static content (EN/FA)
|
||||||
|
├── layouts/ # Layout components
|
||||||
|
├── middleware/ # Route middleware
|
||||||
|
├── pages/ # File-based routing
|
||||||
|
├── plugins/ # Nuxt plugins
|
||||||
|
├── types/ # TypeScript definitions
|
||||||
|
└── utils/ # Helper functions
|
||||||
|
|
||||||
## Performance Notes
|
server/ # Server-side code
|
||||||
- Hero and project images use `<NuxtImg>` with responsive sizes and modern formats.
|
└── api/ # API endpoints
|
||||||
- Recommendations carousel mounts lazily once visible and respects `prefers-reduced-motion`.
|
└── health.get.ts # Health check (/api/health)
|
||||||
- Fonts are preloaded in `app/app.vue` to stabilize LCP, and assets are served from `public/fonts`.
|
|
||||||
- Additional caching, route rules, or SWR headers can be added via `nuxt.config.ts` when deploying.
|
|
||||||
|
|
||||||
## Deployment
|
public/ # Static assets
|
||||||
### Vercel
|
├── favicon/ # Favicon files
|
||||||
- Set the build command to `pnpm build`.
|
├── fonts/ # Local fonts (Roobert, Vazirmatn)
|
||||||
- Output directory remains Nuxt's default (`.output`).
|
└── img/ # Images
|
||||||
- Optional Plausible analytics is controlled by `runtimeConfig.public.loadPlausible`.
|
|
||||||
|
i18n/ # Internationalization
|
||||||
|
└── locales/ # Translation files (en.json, fa.json)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 Internationalization
|
||||||
|
|
||||||
|
- **Strategy**: `prefix_except_default` (English is default, Persian uses `/fa` prefix)
|
||||||
|
- **Locales**: English (`en`) and Persian (`fa`) with RTL support
|
||||||
|
- **Content**: Separate data files for each language in `app/data/`
|
||||||
|
- **Switching**: Automatic `lang` and `dir` attributes update on locale change
|
||||||
|
|
||||||
|
## 🎨 Styling & Theming
|
||||||
|
|
||||||
|
- **Tailwind CSS 4** with custom utilities and variants
|
||||||
|
- **Nuxt UI tokens** in `app.config.ts` for consistent theming
|
||||||
|
- **Dark mode** as default with `@nuxtjs/color-mode`
|
||||||
|
- **Local fonts**: Roobert (EN) and Vazirmatn (FA) preloaded for performance
|
||||||
|
- **Custom chip utility** for tags and badges
|
||||||
|
|
||||||
|
## 🚀 Deployment
|
||||||
|
|
||||||
|
### Vercel (Recommended)
|
||||||
|
```bash
|
||||||
|
# Install Vercel CLI
|
||||||
|
npm i -g vercel
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
vercel
|
||||||
|
```
|
||||||
|
|
||||||
|
Or connect your GitHub repository to Vercel for automatic deployments.
|
||||||
|
|
||||||
### Static Hosting
|
### Static Hosting
|
||||||
- Run `pnpm generate`.
|
```bash
|
||||||
- Serve the contents of `.output/public`.
|
# Generate static files
|
||||||
|
pnpm generate
|
||||||
|
|
||||||
## Contributing
|
# Deploy .output/public to any static host
|
||||||
Issues and pull requests are welcome. Please run `pnpm typecheck` and `pnpm format` before submitting changes to keep the project consistent.
|
```
|
||||||
|
|
||||||
## License
|
### Environment Variables
|
||||||
This project is open source under the MIT License. See [`LICENSE`](./LICENSE) for details.
|
- `NUXT_PUBLIC_LOAD_PLAUSIBLE` - Enable/disable Plausible analytics (optional)
|
||||||
|
|
||||||
|
## 🧪 Testing the Structure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Homepage
|
||||||
|
http://localhost:3000
|
||||||
|
|
||||||
|
# Persian version
|
||||||
|
http://localhost:3000/fa
|
||||||
|
|
||||||
|
# Health check API
|
||||||
|
http://localhost:3000/api/health
|
||||||
|
|
||||||
|
# 404 error page
|
||||||
|
http://localhost:3000/non-existent-page
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ Tech Stack
|
||||||
|
|
||||||
|
- **Framework**: Nuxt 4 (Vue 3 + Vite)
|
||||||
|
- **UI Library**: Nuxt UI 4
|
||||||
|
- **Styling**: Tailwind CSS 4
|
||||||
|
- **Language**: TypeScript
|
||||||
|
- **i18n**: @nuxtjs/i18n
|
||||||
|
- **Theme**: @nuxtjs/color-mode
|
||||||
|
- **Images**: @nuxt/image
|
||||||
|
- **Utilities**: VueUse
|
||||||
|
|
||||||
|
## 📝 Auto-imports
|
||||||
|
|
||||||
|
Nuxt automatically imports:
|
||||||
|
- Components from `app/components/`
|
||||||
|
- Composables from `app/composables/`
|
||||||
|
- Utils from `app/utils/`
|
||||||
|
|
||||||
|
No explicit imports needed!
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Issues and pull requests are welcome. Please run `pnpm typecheck` and `pnpm format` before submitting changes.
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT License - See [LICENSE](./LICENSE) for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Made with ❤️ by Ali Arghyani**
|
||||||
|
|||||||
+323
@@ -0,0 +1,323 @@
|
|||||||
|
<template>
|
||||||
|
<UApp>
|
||||||
|
<div class="error-page">
|
||||||
|
<UContainer>
|
||||||
|
<div class="error-content">
|
||||||
|
<!-- Animated Icon -->
|
||||||
|
<div class="error-icon-wrapper">
|
||||||
|
<div class="error-icon-bg"></div>
|
||||||
|
<UIcon :name="errorIcon" class="error-icon" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status Code -->
|
||||||
|
<h1 class="error-code">
|
||||||
|
{{ statusCode }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<h2 class="error-title">
|
||||||
|
{{ errorTitle }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<!-- Message -->
|
||||||
|
<p class="error-message">
|
||||||
|
{{ message }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="error-actions">
|
||||||
|
<UButton to="/" size="lg" color="primary" icon="i-heroicons-home" class="error-button">
|
||||||
|
{{ $t('common.backToHome') }}
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<UButton @click="handleGoBack" size="lg" color="gray" variant="ghost" icon="i-heroicons-arrow-left"
|
||||||
|
class="error-button">
|
||||||
|
{{ $t('common.goBack') }}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Additional Help -->
|
||||||
|
<UCard v-if="statusCode === 404" class="error-help">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UIcon name="i-heroicons-light-bulb" class="text-primary" />
|
||||||
|
<span class="font-semibold">{{ $t('error.helpTitle') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ul class="error-suggestions">
|
||||||
|
<li>{{ $t('error.suggestion1') }}</li>
|
||||||
|
<li>{{ $t('error.suggestion2') }}</li>
|
||||||
|
<li>{{ $t('error.suggestion3') }}</li>
|
||||||
|
</ul>
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
</UContainer>
|
||||||
|
</div>
|
||||||
|
</UApp>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* Error Page
|
||||||
|
*
|
||||||
|
* This page is displayed when an error occurs during rendering.
|
||||||
|
* It handles both 404 (Not Found) and 500 (Server Error) cases.
|
||||||
|
*
|
||||||
|
* Learn more: https://nuxt.com/docs/getting-started/error-handling
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface ErrorProps {
|
||||||
|
error: {
|
||||||
|
statusCode: number
|
||||||
|
statusMessage?: string
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<ErrorProps>()
|
||||||
|
const { t } = useI18n()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const statusCode = computed(() => props.error?.statusCode || 500)
|
||||||
|
|
||||||
|
const errorIcon = computed(() => {
|
||||||
|
switch (statusCode.value) {
|
||||||
|
case 404:
|
||||||
|
return 'i-heroicons-magnifying-glass'
|
||||||
|
case 403:
|
||||||
|
return 'i-heroicons-lock-closed'
|
||||||
|
case 500:
|
||||||
|
return 'i-heroicons-exclamation-triangle'
|
||||||
|
default:
|
||||||
|
return 'i-heroicons-x-circle'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const errorTitle = computed(() => {
|
||||||
|
switch (statusCode.value) {
|
||||||
|
case 404:
|
||||||
|
return t('error.notFoundTitle')
|
||||||
|
case 403:
|
||||||
|
return t('error.forbiddenTitle')
|
||||||
|
case 500:
|
||||||
|
return t('error.serverErrorTitle')
|
||||||
|
default:
|
||||||
|
return t('error.defaultTitle')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const message = computed(() => {
|
||||||
|
if (props.error?.message) {
|
||||||
|
return props.error.message
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (statusCode.value) {
|
||||||
|
case 404:
|
||||||
|
return t('error.notFoundMessage')
|
||||||
|
case 403:
|
||||||
|
return t('error.forbiddenMessage')
|
||||||
|
case 500:
|
||||||
|
return t('error.serverErrorMessage')
|
||||||
|
default:
|
||||||
|
return t('error.defaultMessage')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleGoBack = () => {
|
||||||
|
if (window.history.length > 1) {
|
||||||
|
router.back()
|
||||||
|
} else {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
background: linear-gradient(135deg, rgb(var(--color-gray-50)) 0%, rgb(var(--color-gray-100)) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .error-page {
|
||||||
|
background: linear-gradient(135deg, rgb(var(--color-gray-950)) 0%, rgb(var(--color-gray-900)) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
margin: auto !important;
|
||||||
|
padding: 0 1rem;
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon-bg {
|
||||||
|
position: absolute;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
background: rgb(var(--color-primary-500) / 0.1);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon {
|
||||||
|
position: relative;
|
||||||
|
font-size: 4rem;
|
||||||
|
color: rgb(var(--color-primary-500));
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-code {
|
||||||
|
font-size: 6rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: linear-gradient(135deg, rgb(var(--color-primary-500)), rgb(var(--color-primary-600)));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
font-size: 2rem !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
color: rgb(var(--color-gray-900));
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .error-title {
|
||||||
|
color: rgb(var(--color-gray-100));
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1.75;
|
||||||
|
margin-bottom: 2.5rem !important;
|
||||||
|
color: rgb(var(--color-gray-600));
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .error-message {
|
||||||
|
color: rgb(var(--color-gray-400));
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem !important;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-button {
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-help {
|
||||||
|
margin-top: 0;
|
||||||
|
text-align: start;
|
||||||
|
animation: fadeIn 0.8s ease-out 0.3s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-suggestions {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-suggestions li {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 2.5rem !important;
|
||||||
|
padding-right: 1rem !important;
|
||||||
|
color: rgb(var(--color-gray-700));
|
||||||
|
line-height: 1.7;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .error-suggestions li {
|
||||||
|
color: rgb(var(--color-gray-300));
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-suggestions li::before {
|
||||||
|
content: "→";
|
||||||
|
position: absolute;
|
||||||
|
left: 0.75rem;
|
||||||
|
top: 0;
|
||||||
|
color: rgb(var(--color-primary-500));
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.error-code {
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div class="layout-default">
|
||||||
|
<!-- Default layout wrapper -->
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* Default Layout
|
||||||
|
*
|
||||||
|
* This is a minimal example layout demonstrating Nuxt's layout system.
|
||||||
|
* To use this layout in a page, add: definePageMeta({ layout: 'default' })
|
||||||
|
*
|
||||||
|
* Learn more: https://nuxt.com/docs/guide/directory-structure/layouts
|
||||||
|
*/
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.layout-default {
|
||||||
|
/* Add default layout styles here */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<div class="layout-marketing">
|
||||||
|
<!-- Marketing layout with centered content -->
|
||||||
|
<div class="marketing-container">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* Marketing Layout
|
||||||
|
*
|
||||||
|
* Example layout for marketing pages with centered content.
|
||||||
|
* To use this layout in a page, add: definePageMeta({ layout: 'marketing' })
|
||||||
|
*
|
||||||
|
* Learn more: https://nuxt.com/docs/guide/directory-structure/layouts
|
||||||
|
*/
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.layout-marketing {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marketing-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Demo Route Middleware
|
||||||
|
*
|
||||||
|
* This is a non-global route middleware example that demonstrates
|
||||||
|
* Nuxt's middleware system without affecting actual navigation.
|
||||||
|
*
|
||||||
|
* To use this middleware on a specific page, add:
|
||||||
|
* definePageMeta({ middleware: 'demo' })
|
||||||
|
*
|
||||||
|
* For global middleware, create a file with .global.ts extension.
|
||||||
|
*
|
||||||
|
* Learn more: https://nuxt.com/docs/guide/directory-structure/middleware
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default defineNuxtRouteMiddleware((to, from) => {
|
||||||
|
// Example: Log navigation (no-op for demo purposes)
|
||||||
|
if (import.meta.dev) {
|
||||||
|
console.log('[Demo Middleware] Navigating from:', from.path, 'to:', to.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: Conditional redirect (commented out to avoid side effects)
|
||||||
|
// if (to.path === '/restricted') {
|
||||||
|
// return navigateTo('/')
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Allow navigation to continue
|
||||||
|
})
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# ⚠️ Deprecated Directory
|
||||||
|
|
||||||
|
**This directory is deprecated and should not be used for new assets.**
|
||||||
|
|
||||||
|
## Why?
|
||||||
|
|
||||||
|
With Nuxt's `srcDir: 'app'` configuration, static assets must be placed in the **root `public/` directory**, not `app/public/`.
|
||||||
|
|
||||||
|
## Migration
|
||||||
|
|
||||||
|
All assets from this directory have been consolidated into the root `public/` folder. Please use:
|
||||||
|
|
||||||
|
```
|
||||||
|
/public/
|
||||||
|
├── favicon/
|
||||||
|
├── fonts/
|
||||||
|
└── img/
|
||||||
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Nuxt 4.x Public Directory Documentation](https://nuxt.com/docs/4.x/guide/directory-structure/public)
|
||||||
|
- Assets are served from `/` (e.g., `/img/logo.png` resolves to `public/img/logo.png`)
|
||||||
+17
-1
@@ -107,7 +107,23 @@
|
|||||||
"readMore": "Read article"
|
"readMore": "Read article"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"present": "Present"
|
"present": "Present",
|
||||||
|
"backToHome": "Back to Home",
|
||||||
|
"goBack": "Go Back"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"notFoundTitle": "Page Not Found",
|
||||||
|
"notFoundMessage": "The page you're looking for doesn't exist or has been moved.",
|
||||||
|
"forbiddenTitle": "Access Denied",
|
||||||
|
"forbiddenMessage": "You don't have permission to access this resource.",
|
||||||
|
"serverErrorTitle": "Server Error",
|
||||||
|
"serverErrorMessage": "Something went wrong on our end. Please try again later.",
|
||||||
|
"defaultTitle": "Oops! Something Went Wrong",
|
||||||
|
"defaultMessage": "An unexpected error occurred. Please try again.",
|
||||||
|
"helpTitle": "Need Help?",
|
||||||
|
"suggestion1": "Check the URL for typos",
|
||||||
|
"suggestion2": "Use the navigation menu to find what you're looking for",
|
||||||
|
"suggestion3": "Return to the homepage and start fresh"
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"portfolioTitleSuffix": "Portfolio"
|
"portfolioTitleSuffix": "Portfolio"
|
||||||
|
|||||||
+29
-13
@@ -3,8 +3,8 @@
|
|||||||
"work": "سوابق کاری",
|
"work": "سوابق کاری",
|
||||||
"education": "تحصیلات",
|
"education": "تحصیلات",
|
||||||
"recommendations": "توصیهنامهها",
|
"recommendations": "توصیهنامهها",
|
||||||
"projects": " پروژه ها ",
|
"projects": "پروژهها",
|
||||||
"blog": "بلاگ",
|
"blog": "وبلاگ",
|
||||||
"contact": "ارتباط",
|
"contact": "ارتباط",
|
||||||
"mainTools": "ابزارهای اصلی",
|
"mainTools": "ابزارهای اصلی",
|
||||||
"roles": "نقشها",
|
"roles": "نقشها",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"expert": "حرفهای",
|
"expert": "حرفهای",
|
||||||
"proficient": "مسلط",
|
"proficient": "مسلط",
|
||||||
"usedBefore": "تجربه قبلی",
|
"usedBefore": "تجربه قبلی",
|
||||||
"aiStack": "پشته هوش مصنوعی"
|
"aiStack": "استک هوش مصنوعی"
|
||||||
},
|
},
|
||||||
"softSkills": {
|
"softSkills": {
|
||||||
"problemSolving": {
|
"problemSolving": {
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"blog": "نوشتهها",
|
"blog": "مقالات",
|
||||||
"contact": "ارتباط",
|
"contact": "ارتباط",
|
||||||
"website": "وبسایت",
|
"website": "وبسایت",
|
||||||
"switchToEnglish": "English",
|
"switchToEnglish": "English",
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"nav": {
|
"nav": {
|
||||||
"home": "خانه",
|
"home": "خانه",
|
||||||
"skills": "مهارتها",
|
"skills": "مهارتها",
|
||||||
"blog": "بلاگ",
|
"blog": "وبلاگ",
|
||||||
"contact": "ارتباط",
|
"contact": "ارتباط",
|
||||||
"theme": "تم"
|
"theme": "تم"
|
||||||
},
|
},
|
||||||
@@ -77,13 +77,29 @@
|
|||||||
"readMore": "مطالعه"
|
"readMore": "مطالعه"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"present": "اکنون"
|
"present": "اکنون",
|
||||||
|
"backToHome": "بازگشت به خانه",
|
||||||
|
"goBack": "بازگشت"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"notFoundTitle": "صفحه یافت نشد",
|
||||||
|
"notFoundMessage": "صفحهای که به دنبال آن هستید وجود ندارد یا منتقل شده است.",
|
||||||
|
"forbiddenTitle": "دسترسی رد شد",
|
||||||
|
"forbiddenMessage": "شما اجازه دسترسی به این منبع را ندارید.",
|
||||||
|
"serverErrorTitle": "خطای سرور",
|
||||||
|
"serverErrorMessage": "مشکلی از سمت ما پیش آمده است. لطفاً بعداً دوباره تلاش کنید.",
|
||||||
|
"defaultTitle": "اوه! مشکلی پیش آمد",
|
||||||
|
"defaultMessage": "یک خطای غیرمنتظره رخ داد. لطفاً دوباره تلاش کنید.",
|
||||||
|
"helpTitle": "نیاز به کمک دارید؟",
|
||||||
|
"suggestion1": "آدرس URL را برای اشتباه تایپی بررسی کنید",
|
||||||
|
"suggestion2": "از منوی ناوبری برای یافتن آنچه میخواهید استفاده کنید",
|
||||||
|
"suggestion3": "به صفحه اصلی بازگردید و از نو شروع کنید"
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"portfolioTitleSuffix": "پورتفولیو"
|
"portfolioTitleSuffix": "نمونهکارها"
|
||||||
},
|
},
|
||||||
"projectLabels": {
|
"projectLabels": {
|
||||||
"openSource": "متنباز"
|
"openSource": "اوپنسورس"
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"customizer": "سفارشیسازی تم",
|
"customizer": "سفارشیسازی تم",
|
||||||
@@ -132,16 +148,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ai_stack": {
|
"ai_stack": {
|
||||||
"title": "پشته هوش مصنوعی",
|
"title": "استک هوش مصنوعی",
|
||||||
"subtitle": "محیطهای توسعه، پروتکلها، مفاهیم و رویکردهایی که جریان کاری هوش مصنوعی من را توانمند میکنند",
|
"subtitle": "محیطهای توسعه، پروتکلها، مفاهیم و رویکردهایی که جریان کاری هوش مصنوعی من را توانمند میکنند",
|
||||||
"filter": {
|
"filter": {
|
||||||
"all": "همه",
|
"all": "همه",
|
||||||
"methods": "روشها",
|
"methods": "روشها",
|
||||||
"ides": "IDEها",
|
"ides": "محیطهای توسعه",
|
||||||
"assistants": "دستیارها",
|
"assistants": "دستیارها",
|
||||||
"rules": "قوانین",
|
"rules": "قوانین",
|
||||||
"mcps": "MCPها",
|
"mcps": "پروتکلهای MCP",
|
||||||
"extensions": "افزونهها",
|
"extensions": "اکستنشنها",
|
||||||
"infra": "زیرساخت",
|
"infra": "زیرساخت",
|
||||||
"evaluation": "ارزیابی"
|
"evaluation": "ارزیابی"
|
||||||
},
|
},
|
||||||
@@ -164,6 +180,6 @@
|
|||||||
"projectCategories": {
|
"projectCategories": {
|
||||||
"current": "جاری",
|
"current": "جاری",
|
||||||
"freelance": "پروژههای فریلنسری",
|
"freelance": "پروژههای فریلنسری",
|
||||||
"public": "مخازن عمومی"
|
"public": "پروژههای عمومی"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user