Initial commit: Nuxt site
@@ -0,0 +1 @@
|
||||
NUXT_PUBLIC_CMS_API_URL=http://localhost:4000
|
||||
@@ -0,0 +1,24 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
@@ -0,0 +1,7 @@
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default withNuxt({
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off'
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import { defineNuxtConfig } from 'nuxt/config'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2026-05-27',
|
||||
srcDir: 'src/',
|
||||
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],
|
||||
css: ['~/assets/css/fonts.css'],
|
||||
tailwindcss: {
|
||||
cssPath: '~/assets/css/tailwind.css',
|
||||
configPath: 'tailwind.config.ts',
|
||||
viewer: false
|
||||
},
|
||||
app: {
|
||||
head: {
|
||||
htmlAttrs: {
|
||||
lang: 'fa',
|
||||
dir: 'rtl'
|
||||
},
|
||||
title: 'استودیو نورو | Noro Studio',
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{
|
||||
name: 'description',
|
||||
content:
|
||||
'استودیو نورو — طراحی هویت بصری، UI/UX، سوشال مدیا و محتوای دیجیتال'
|
||||
},
|
||||
{ name: 'color-scheme', content: 'dark light' }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/png', href: '/favicon.png' }
|
||||
]
|
||||
}
|
||||
},
|
||||
typescript: {
|
||||
strict: true
|
||||
},
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
cmsApiUrl: process.env.NUXT_PUBLIC_CMS_API_URL || 'http://localhost:4000'
|
||||
}
|
||||
},
|
||||
devtools: { enabled: true }
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "nuxt-tailwind-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "nuxt dev",
|
||||
"build": "nuxt build",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"nuxt": "3.12.4",
|
||||
"@nuxtjs/tailwindcss": "^6.12.0",
|
||||
"@pinia/nuxt": "^0.5.5",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint-config": "^0.5.4",
|
||||
"@types/node": "^22.0.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"typescript": "^5.5.0"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
@@ -0,0 +1,9 @@
|
||||
const themeInitScript = `(function(){try{var k='noro-theme';var t=localStorage.getItem(k);var d=t==='dark'||(t!=='light'&&window.matchMedia('(prefers-color-scheme: dark)').matches);document.documentElement.classList.toggle('dark',d);document.documentElement.style.colorScheme=d?'dark':'light'}catch(e){}})();`
|
||||
|
||||
export default defineNitroPlugin((nitroApp) => {
|
||||
nitroApp.hooks.hook('render:html', (htmlContext) => {
|
||||
htmlContext.head.unshift(
|
||||
`<script id="noro-theme-init">${themeInitScript}</script>`
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,145 @@
|
||||
/* Peyda — متن و بدنه */
|
||||
@font-face {
|
||||
font-family: Peyda;
|
||||
src: url('../fonts/peyda/PeydaWeb-Thin.woff2') format('woff2');
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Peyda;
|
||||
src: url('../fonts/peyda/peydaWeb-extralight.woff2') format('woff2');
|
||||
font-weight: 200;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Peyda;
|
||||
src: url('../fonts/peyda/peydaWeb-light.woff2') format('woff2');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Peyda;
|
||||
src: url('../fonts/peyda/PeydaWeb-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Peyda;
|
||||
src: url('../fonts/peyda/PeydaWeb-Medium.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Peyda;
|
||||
src: url('../fonts/peyda/PeydaWeb-SemiBold.woff2') format('woff2');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Peyda;
|
||||
src: url('../fonts/peyda/PeydaWeb-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Peyda;
|
||||
src: url('../fonts/peyda/PeydaWeb-ExtraBold.woff2') format('woff2');
|
||||
font-weight: 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Peyda;
|
||||
src: url('../fonts/peyda/PeydaWeb-Black.woff2') format('woff2');
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Rokh Pro — عناوین اصلی */
|
||||
@font-face {
|
||||
font-family: Rokh;
|
||||
src: url('../fonts/rokh/Rokh-Thin.woff2') format('woff2');
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Rokh;
|
||||
src: url('../fonts/rokh/Rokh-ExtraLight.woff2') format('woff2');
|
||||
font-weight: 200;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Rokh;
|
||||
src: url('../fonts/rokh/Rokh-Light.woff2') format('woff2');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Rokh;
|
||||
src: url('../fonts/rokh/Rokh-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Rokh;
|
||||
src: url('../fonts/rokh/Rokh-Medium.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Rokh;
|
||||
src: url('../fonts/rokh/Rokh-SemiBold.woff2') format('woff2');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Rokh;
|
||||
src: url('../fonts/rokh/Rokh-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Rokh;
|
||||
src: url('../fonts/rokh/Rokh-ExtraBold.woff2') format('woff2');
|
||||
font-weight: 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Rokh;
|
||||
src: url('../fonts/rokh/Rokh-Black.woff2') format('woff2');
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
@apply scroll-smooth antialiased;
|
||||
}
|
||||
|
||||
/* Light — مینیمال و تمیز */
|
||||
html:not(.dark) {
|
||||
--noro-bg: 250 252 252;
|
||||
--noro-surface: 255 255 255;
|
||||
--noro-border: 226 232 232;
|
||||
--noro-text: 15 20 20;
|
||||
--noro-muted: 100 116 116;
|
||||
--noro-brand: 57 192 196;
|
||||
--noro-brand-hover: 46 179 183;
|
||||
--noro-brand-light: 147 224 226;
|
||||
--noro-glow-primary: 57 192 196;
|
||||
--noro-glow-secondary: 93 206 209;
|
||||
--noro-hero-gradient-from: 57 192 196;
|
||||
--noro-hero-gradient-via: 93 206 209;
|
||||
--noro-hero-gradient-to: 46 179 183;
|
||||
--noro-btn-primary-bg: 57 192 196;
|
||||
--noro-btn-primary-text: 255 255 255;
|
||||
--noro-btn-secondary-bg: 255 255 255;
|
||||
--noro-btn-secondary-text: 15 20 20;
|
||||
--noro-btn-secondary-border: 226 232 232;
|
||||
--noro-accent-btn-bg: 57 192 196;
|
||||
--noro-accent-btn-hover: 46 179 183;
|
||||
--noro-header-bg: 250 252 252;
|
||||
--noro-glass-bg: 255 255 255;
|
||||
--noro-glass-opacity: 0.58;
|
||||
--noro-glass-border: 255 255 255;
|
||||
--noro-glass-border-opacity: 0.72;
|
||||
--noro-glass-blur: 24px;
|
||||
}
|
||||
|
||||
/* Dark — futuristic & premium */
|
||||
html.dark {
|
||||
--noro-bg: 5 8 8;
|
||||
--noro-surface: 10 14 14;
|
||||
--noro-border: 30 41 41;
|
||||
--noro-text: 250 252 252;
|
||||
--noro-muted: 148 163 163;
|
||||
--noro-brand: 57 192 196;
|
||||
--noro-brand-hover: 93 206 209;
|
||||
--noro-brand-light: 147 224 226;
|
||||
--noro-glow-primary: 57 192 196;
|
||||
--noro-glow-secondary: 46 179 183;
|
||||
--noro-hero-gradient-from: 57 192 196;
|
||||
--noro-hero-gradient-via: 147 224 226;
|
||||
--noro-hero-gradient-to: 93 206 209;
|
||||
--noro-btn-primary-bg: 57 192 196;
|
||||
--noro-btn-primary-text: 5 8 8;
|
||||
--noro-btn-secondary-bg: 255 255 255;
|
||||
--noro-btn-secondary-text: 5 8 8;
|
||||
--noro-btn-secondary-border: 255 255 255;
|
||||
--noro-accent-btn-bg: 57 192 196;
|
||||
--noro-accent-btn-hover: 93 206 209;
|
||||
--noro-header-bg: 5 8 8;
|
||||
--noro-glass-bg: 255 255 255;
|
||||
--noro-glass-opacity: 0.05;
|
||||
--noro-glass-border: 255 255 255;
|
||||
--noro-glass-border-opacity: 0.09;
|
||||
--noro-glass-blur: 24px;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-noro-bg font-sans text-noro-text transition-[background-color,color] duration-500;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: Rokh, Peyda, Tahoma, system-ui, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.font-heading {
|
||||
font-family: Rokh, Peyda, Tahoma, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.noro-container {
|
||||
@apply mx-auto max-w-6xl px-4 sm:px-6;
|
||||
}
|
||||
|
||||
.noro-glass {
|
||||
border-color: rgb(var(--noro-glass-border) / var(--noro-glass-border-opacity));
|
||||
background-color: rgb(var(--noro-glass-bg) / var(--noro-glass-opacity));
|
||||
-webkit-backdrop-filter: blur(var(--noro-glass-blur)) saturate(180%);
|
||||
backdrop-filter: blur(var(--noro-glass-blur)) saturate(180%);
|
||||
}
|
||||
|
||||
html:not(.dark) .noro-glass {
|
||||
box-shadow:
|
||||
inset 0 1px 0 0 rgb(255 255 255 / 0.85),
|
||||
0 1px 2px rgb(15 20 20 / 0.04);
|
||||
}
|
||||
|
||||
html.dark .noro-glass {
|
||||
box-shadow: inset 0 1px 0 0 rgb(255 255 255 / 0.07);
|
||||
}
|
||||
|
||||
.noro-glass-header {
|
||||
@apply noro-glass sticky top-0 z-50 border-b;
|
||||
}
|
||||
|
||||
.noro-section {
|
||||
@apply py-20 sm:py-28;
|
||||
}
|
||||
|
||||
.noro-glow {
|
||||
@apply pointer-events-none absolute rounded-full blur-[120px];
|
||||
}
|
||||
|
||||
.noro-service-card {
|
||||
@apply relative overflow-hidden rounded-2xl border border-noro-border bg-transparent transition duration-300 hover:-translate-y-1 hover:shadow-lg;
|
||||
}
|
||||
|
||||
.noro-service-card:hover {
|
||||
border-color: rgb(var(--noro-brand) / 0.3);
|
||||
}
|
||||
|
||||
html.dark .noro-service-card:hover {
|
||||
box-shadow:
|
||||
0 10px 15px -3px rgb(0 0 0 / 0.1),
|
||||
0 4px 6px -4px rgb(var(--noro-brand) / 0.1);
|
||||
}
|
||||
|
||||
.noro-card {
|
||||
@apply rounded-2xl border border-noro-border bg-noro-surface transition duration-300;
|
||||
}
|
||||
|
||||
.noro-card-hover {
|
||||
@apply noro-card hover:-translate-y-1 hover:shadow-lg;
|
||||
}
|
||||
|
||||
.noro-card-hover:hover {
|
||||
border-color: rgb(var(--noro-brand) / 0.3);
|
||||
}
|
||||
|
||||
html.dark .noro-card-hover:hover {
|
||||
box-shadow:
|
||||
0 10px 15px -3px rgb(0 0 0 / 0.1),
|
||||
0 4px 6px -4px rgb(var(--noro-brand) / 0.1);
|
||||
}
|
||||
|
||||
.noro-gradient-text {
|
||||
@apply bg-gradient-to-l from-[rgb(var(--noro-hero-gradient-from))] via-[rgb(var(--noro-hero-gradient-via))] to-[rgb(var(--noro-hero-gradient-to))] bg-clip-text text-transparent;
|
||||
}
|
||||
|
||||
.text-noro-brand {
|
||||
color: rgb(var(--noro-brand));
|
||||
}
|
||||
|
||||
.bg-noro-brand {
|
||||
background-color: rgb(var(--noro-brand));
|
||||
}
|
||||
|
||||
.border-noro-brand {
|
||||
border-color: rgb(var(--noro-brand));
|
||||
}
|
||||
|
||||
.noro-btn-primary {
|
||||
@apply inline-flex items-center justify-center rounded-xl bg-[rgb(var(--noro-btn-primary-bg))] px-6 py-3 text-sm font-semibold text-[rgb(var(--noro-btn-primary-text))] transition hover:bg-[rgb(var(--noro-brand-hover))];
|
||||
}
|
||||
|
||||
.noro-btn-secondary {
|
||||
@apply inline-flex items-center justify-center rounded-xl border border-[rgb(var(--noro-btn-secondary-border))] bg-[rgb(var(--noro-btn-secondary-bg))] px-6 py-3 text-sm font-semibold text-[rgb(var(--noro-btn-secondary-text))] transition hover:opacity-90;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="2" y="2" width="20" height="20" rx="5" stroke="url(#ig-gradient)" stroke-width="2"/>
|
||||
<circle cx="12" cy="12" r="4.5" stroke="url(#ig-gradient)" stroke-width="2"/>
|
||||
<circle cx="17.5" cy="6.5" r="1.25" fill="url(#ig-gradient)"/>
|
||||
<defs>
|
||||
<linearGradient id="ig-gradient" x1="2" y1="22" x2="22" y2="2" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F58529"/>
|
||||
<stop offset="0.5" stop-color="#DD2A7B"/>
|
||||
<stop offset="1" stop-color="#8134AF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 590 B |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
const { navLinks, site, messengersLabel } = useNoroData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="border-t border-noro-border bg-noro-surface">
|
||||
<div class="noro-container py-12">
|
||||
<div class="grid gap-10 md:grid-cols-3">
|
||||
<div>
|
||||
<NoroLogo variant="horizontal-text" class="h-[101px] max-h-[60px] w-auto" />
|
||||
<p class="mt-4 text-sm leading-7 text-noro-muted">
|
||||
{{ site.description }}
|
||||
</p>
|
||||
<a
|
||||
:href="`https://${site.website}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="mt-3 inline-block text-sm text-noro-brand transition hover:text-[rgb(var(--noro-brand-hover))]"
|
||||
>
|
||||
{{ site.website }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="mb-4 text-sm font-semibold text-noro-text">صفحات</p>
|
||||
<nav class="flex flex-col gap-2">
|
||||
<NuxtLink
|
||||
v-for="link in navLinks"
|
||||
:key="link.to"
|
||||
:to="link.to"
|
||||
class="text-sm text-noro-muted transition hover:text-noro-brand"
|
||||
>
|
||||
{{ link.label }}
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="mb-4 text-sm font-semibold text-noro-text">ارتباط</p>
|
||||
<ul class="space-y-2 text-sm text-noro-muted">
|
||||
<li>
|
||||
<a
|
||||
:href="`mailto:${site.email}`"
|
||||
class="transition hover:text-noro-brand"
|
||||
>
|
||||
{{ site.email }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
:href="`tel:${site.phoneTel}`"
|
||||
class="transition hover:text-noro-brand"
|
||||
dir="ltr"
|
||||
>
|
||||
{{ site.phone }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="mb-2 block text-noro-muted">{{ messengersLabel }}</span>
|
||||
<MessengerLinks
|
||||
:messengers="site.messengers"
|
||||
:handle="site.messengerHandle"
|
||||
/>
|
||||
</li>
|
||||
<li>{{ site.location }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-10 flex flex-col items-center justify-between gap-4 border-t border-noro-border pt-8 sm:flex-row"
|
||||
>
|
||||
<p class="text-sm text-noro-muted">
|
||||
© {{ new Date().getFullYear() }} {{ site.name }}. تمامی حقوق محفوظ است.
|
||||
</p>
|
||||
<NoroLogo variant="icon" class="h-6 w-auto opacity-80" />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
const { navLinks } = useNoroData()
|
||||
|
||||
const mobileOpen = ref(false)
|
||||
const route = useRoute()
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
mobileOpen.value = false
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="noro-glass-header">
|
||||
<div class="noro-container flex h-16 items-center justify-between">
|
||||
<NuxtLink to="/" class="group flex shrink-0 transition opacity-90 hover:opacity-100">
|
||||
<NoroLogo variant="horizontal" class="h-[29px] w-auto sm:h-8" />
|
||||
</NuxtLink>
|
||||
|
||||
<nav class="hidden items-center gap-8 md:flex">
|
||||
<NuxtLink
|
||||
v-for="link in navLinks"
|
||||
:key="link.to"
|
||||
:to="link.to"
|
||||
class="text-sm text-noro-muted transition hover:text-noro-text"
|
||||
active-class="!text-noro-brand font-medium"
|
||||
>
|
||||
{{ link.label }}
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-2 sm:gap-3">
|
||||
<ThemeToggle />
|
||||
|
||||
<UiButton to="/contact" size="sm" variant="accent" class="hidden sm:inline-flex">
|
||||
شروع همکاری
|
||||
</UiButton>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-white/15 p-2 text-noro-muted transition hover:bg-white/10 hover:text-noro-text dark:border-white/10 md:hidden"
|
||||
aria-label="منو"
|
||||
@click="mobileOpen = !mobileOpen"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
v-if="!mobileOpen"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
<path
|
||||
v-else
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="mobileOpen"
|
||||
class="noro-glass border-t px-4 py-4 md:hidden"
|
||||
>
|
||||
<nav class="flex flex-col gap-3">
|
||||
<NuxtLink
|
||||
v-for="link in navLinks"
|
||||
:key="link.to"
|
||||
:to="link.to"
|
||||
class="rounded-lg px-3 py-2 text-sm text-noro-muted transition hover:bg-white/10 hover:text-noro-text dark:hover:bg-white/5"
|
||||
active-class="!bg-noro-brand/10 !text-noro-brand"
|
||||
>
|
||||
{{ link.label }}
|
||||
</NuxtLink>
|
||||
<UiButton to="/contact" size="sm" variant="accent" class="mt-2 w-full justify-center">
|
||||
شروع همکاری
|
||||
</UiButton>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
@@ -0,0 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import logoChehelsotoun from '~/assets/images/clients/chehelsotoun-souleh.png'
|
||||
import logoMontzar from '~/assets/images/clients/montzar.png'
|
||||
import logoMadar from '~/assets/images/clients/madar.png'
|
||||
import logoAzadKhorasgan from '~/assets/images/clients/azad-khorasgan.png'
|
||||
import logoIsfahan from '~/assets/images/clients/isfahan.png'
|
||||
import logoMontech from '~/assets/images/clients/montech.png'
|
||||
import logoFarshchin from '~/assets/images/clients/farshchin.png'
|
||||
import logoRouyesh from '~/assets/images/clients/rouyesh.png'
|
||||
|
||||
const { clients } = useNoroData()
|
||||
|
||||
const logos: Record<string, string> = {
|
||||
'chehelsotoun-souleh': logoChehelsotoun,
|
||||
montzar: logoMontzar,
|
||||
madar: logoMadar,
|
||||
'azad-khorasgan': logoAzadKhorasgan,
|
||||
isfahan: logoIsfahan,
|
||||
montech: logoMontech,
|
||||
farshchin: logoFarshchin,
|
||||
rouyesh: logoRouyesh
|
||||
}
|
||||
|
||||
function clientLogo(client: { id: string; logoUrl?: string }) {
|
||||
return client.logoUrl || logos[client.id]
|
||||
}
|
||||
|
||||
const sequence = computed(() => Array.from({ length: 4 }, () => clients.value).flat())
|
||||
|
||||
const trackRef = ref<HTMLElement | null>(null)
|
||||
const innerRef = ref<HTMLElement | null>(null)
|
||||
|
||||
let offset = 0
|
||||
let rafId = 0
|
||||
let halfWidth = 0
|
||||
let prefersReducedMotion = false
|
||||
|
||||
function measure() {
|
||||
if (!innerRef.value) return
|
||||
halfWidth = innerRef.value.scrollWidth / 2
|
||||
}
|
||||
|
||||
function tick() {
|
||||
if (!innerRef.value || halfWidth <= 0) {
|
||||
rafId = requestAnimationFrame(tick)
|
||||
return
|
||||
}
|
||||
|
||||
if (!prefersReducedMotion) {
|
||||
offset -= 0.6
|
||||
if (Math.abs(offset) >= halfWidth) {
|
||||
offset += halfWidth
|
||||
}
|
||||
innerRef.value.style.transform = `translate3d(${offset}px, 0, 0)`
|
||||
}
|
||||
|
||||
rafId = requestAnimationFrame(tick)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
await nextTick()
|
||||
measure()
|
||||
setTimeout(measure, 150)
|
||||
window.addEventListener('resize', measure)
|
||||
rafId = requestAnimationFrame(tick)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cancelAnimationFrame(rafId)
|
||||
window.removeEventListener('resize', measure)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="noro-section border-t border-noro-border">
|
||||
<div class="noro-container">
|
||||
<SectionHeading
|
||||
eyebrow="مشتریان ما"
|
||||
title="برندهایی که با ما همکاری کردهاند"
|
||||
description="افتخار همکاری با مجموعههای معتبر در حوزههای مختلف را داریم."
|
||||
align="center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="trackRef"
|
||||
class="marquee-root mt-12 min-h-11 overflow-hidden sm:min-h-12"
|
||||
aria-label="لوگوی مشتریان"
|
||||
>
|
||||
<div
|
||||
ref="innerRef"
|
||||
dir="ltr"
|
||||
class="marquee-inner flex w-max items-center will-change-transform"
|
||||
>
|
||||
<template v-for="copy in 2" :key="copy">
|
||||
<div
|
||||
v-for="(client, index) in sequence"
|
||||
:key="`${copy}-${client.id}-${index}`"
|
||||
class="flex h-10 w-24 shrink-0 items-center justify-center sm:h-11 sm:w-28"
|
||||
:aria-hidden="copy === 2 ? true : undefined"
|
||||
>
|
||||
<img
|
||||
:src="clientLogo(client)"
|
||||
:alt="copy === 1 && index < clients.length ? client.name : undefined"
|
||||
:title="client.name"
|
||||
width="96"
|
||||
height="36"
|
||||
class="max-h-8 max-w-[88px] object-contain opacity-60 transition duration-300 hover:opacity-100 sm:max-h-9 sm:max-w-[96px]"
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
@load="measure"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.marquee-root {
|
||||
-webkit-mask-image: linear-gradient(to right, transparent, black 5%, black 95%, transparent);
|
||||
mask-image: linear-gradient(to right, transparent, black 5%, black 95%, transparent);
|
||||
}
|
||||
|
||||
.marquee-inner {
|
||||
gap: 2.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.marquee-inner {
|
||||
gap: 3.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div class="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
|
||||
<div
|
||||
class="noro-glow left-1/2 top-1/3 h-[520px] w-[720px] -translate-x-1/2 -translate-y-1/2 bg-gradient-to-r from-[#39c0c4]/25 via-[#39c0c4]/15 to-[#2eb3b7]/20 opacity-60 dark:opacity-100"
|
||||
/>
|
||||
<div
|
||||
class="noro-glow -left-32 top-0 h-96 w-96 bg-[rgb(var(--noro-glow-primary)/0.12)] dark:bg-[rgb(var(--noro-glow-primary)/0.3)]"
|
||||
/>
|
||||
<div
|
||||
class="noro-glow -right-32 top-1/3 h-80 w-80 bg-[rgb(var(--noro-glow-secondary)/0.08)] dark:bg-[rgb(var(--noro-glow-secondary)/0.2)]"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-0 bg-[radial-gradient(ellipse_80%_50%_at_50%_0%,rgb(var(--noro-glow-primary)/0.1),transparent_70%)] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_0%,rgb(var(--noro-glow-primary)/0.2),transparent_70%)]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import heroBackgroundDark from '~/assets/images/hero/studio-background.png'
|
||||
import heroBackgroundLight from '~/assets/images/hero/studio-background-light.png'
|
||||
|
||||
const { site, stats, heroDarkImage, heroLightImage } = useNoroData()
|
||||
|
||||
const heroLight = computed(() => heroLightImage.value || heroBackgroundLight)
|
||||
const heroDark = computed(() => heroDarkImage.value || heroBackgroundDark)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="relative -mt-16 min-h-[92vh] overflow-hidden pt-16">
|
||||
<div class="absolute inset-0" aria-hidden="true">
|
||||
<img
|
||||
:src="heroLight"
|
||||
alt=""
|
||||
class="block h-full w-full object-cover object-center dark:hidden"
|
||||
decoding="async"
|
||||
>
|
||||
<img
|
||||
:src="heroDark"
|
||||
alt=""
|
||||
class="hidden h-full w-full object-cover object-center dark:block"
|
||||
decoding="async"
|
||||
>
|
||||
<div class="absolute inset-0 bg-white/65 dark:bg-black/55" />
|
||||
<div
|
||||
class="absolute inset-0 bg-[radial-gradient(ellipse_60%_50%_at_50%_38%,rgb(255_255_255/0.55),transparent_80%)] dark:bg-gradient-to-b dark:from-black/30 dark:via-black/45 dark:to-black/80"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-[rgb(var(--noro-bg)/0.85)] dark:to-black/80"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-0 hidden dark:block dark:bg-[radial-gradient(ellipse_70%_55%_at_50%_35%,rgb(var(--noro-brand)/0.12),transparent_70%)]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="noro-container relative pb-24 pt-20 sm:pb-32 sm:pt-28">
|
||||
<div class="mx-auto flex max-w-3xl flex-col items-center text-center">
|
||||
<div
|
||||
class="animate-fade-up mb-12 sm:mb-14"
|
||||
style="animation-delay: 0.1s"
|
||||
>
|
||||
<NoroLogo variant="vertical" class="h-32 w-auto sm:h-40" />
|
||||
</div>
|
||||
|
||||
<p
|
||||
class="animate-fade-up mb-5 inline-flex rounded-full border border-noro-brand/25 bg-noro-brand/10 px-4 py-1.5 text-sm font-medium text-noro-brand dark:border-noro-brand/40 dark:bg-noro-brand/15 sm:mb-6"
|
||||
style="animation-delay: 0.15s"
|
||||
>
|
||||
{{ site.tagline }}
|
||||
</p>
|
||||
|
||||
<h1
|
||||
class="animate-fade-up text-4xl font-bold leading-tight tracking-tight text-noro-text drop-shadow-[0_1px_12px_rgb(255_255_255/0.9)] dark:text-white dark:drop-shadow-none sm:text-5xl md:text-6xl"
|
||||
style="animation-delay: 0.2s"
|
||||
>
|
||||
طراحی دیجیتال
|
||||
<span class="mt-2 block noro-gradient-text">
|
||||
با هویت متمایز
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="animate-fade-up mx-auto mt-7 max-w-xl text-base leading-relaxed text-noro-muted drop-shadow-[0_1px_8px_rgb(255_255_255/0.85)] dark:text-white/75 dark:drop-shadow-none sm:mt-8 sm:text-lg"
|
||||
style="animation-delay: 0.3s"
|
||||
>
|
||||
{{ site.description }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="animate-fade-up mt-10 flex flex-col items-center justify-center gap-3 sm:flex-row sm:gap-4"
|
||||
style="animation-delay: 0.4s"
|
||||
>
|
||||
<NuxtLink to="/contact" class="noro-btn-primary">
|
||||
شروع همکاری
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/portfolio"
|
||||
class="inline-flex items-center justify-center rounded-xl border border-noro-border bg-noro-surface px-6 py-3 text-sm font-semibold text-noro-text transition hover:bg-noro-bg dark:border-white/25 dark:bg-white/10 dark:text-white dark:backdrop-blur-sm dark:hover:bg-white/20"
|
||||
>
|
||||
مشاهده نمونهکارها
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="animate-fade-up mt-16 grid w-full grid-cols-3 gap-4 border-t border-noro-border pt-10 dark:border-white/15 sm:gap-8"
|
||||
style="animation-delay: 0.5s"
|
||||
>
|
||||
<div v-for="item in stats" :key="item.label">
|
||||
<p class="text-2xl font-bold text-noro-text dark:text-white sm:text-3xl">
|
||||
{{ item.value }}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-noro-muted dark:text-white/60 sm:text-sm">
|
||||
{{ item.label }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import type { Messenger } from '~/data/noro'
|
||||
import iconTelegram from '~/assets/images/messengers/telegram.png'
|
||||
import iconBale from '~/assets/images/messengers/bale.png'
|
||||
import iconRubika from '~/assets/images/messengers/rubika.png'
|
||||
import iconInstagram from '~/assets/images/messengers/instagram.svg'
|
||||
|
||||
defineProps<{
|
||||
messengers: Array<Messenger & { iconUrl?: string }>
|
||||
handle: string
|
||||
showHandle?: boolean
|
||||
}>()
|
||||
|
||||
const icons: Record<Messenger['id'], string> = {
|
||||
telegram: iconTelegram,
|
||||
bale: iconBale,
|
||||
rubika: iconRubika,
|
||||
instagram: iconInstagram
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
v-for="messenger in messengers"
|
||||
:key="messenger.id"
|
||||
:href="messenger.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:aria-label="`${messenger.name} — ${handle}`"
|
||||
class="flex h-9 w-9 items-center justify-center overflow-hidden rounded-lg border border-noro-border bg-white/80 transition hover:border-noro-brand/40 hover:shadow-sm dark:bg-noro-surface/80"
|
||||
>
|
||||
<img
|
||||
:src="messenger.iconUrl || icons[messenger.id]"
|
||||
:alt="messenger.name"
|
||||
class="h-6 w-6 object-contain"
|
||||
decoding="async"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<span
|
||||
v-if="showHandle !== false"
|
||||
class="text-sm font-medium text-noro-brand"
|
||||
dir="ltr"
|
||||
>
|
||||
{{ handle }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
}>(),
|
||||
{
|
||||
size: 'md'
|
||||
}
|
||||
)
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'h-6 w-10',
|
||||
md: 'h-8 w-14',
|
||||
lg: 'h-12 w-20'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:class="sizeClasses[size]"
|
||||
viewBox="0 0 56 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect
|
||||
x="1"
|
||||
y="1"
|
||||
width="54"
|
||||
height="30"
|
||||
rx="15"
|
||||
class="stroke-noro-text"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
/>
|
||||
<circle cx="42" cy="16" r="7" fill="#39c0c4" />
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,107 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import logoHorizontalFullDark from '~/assets/images/logo/logo-horizontal-full.png'
|
||||
|
||||
import logoHorizontalTextDark from '~/assets/images/logo/logo-horizontal-text.png'
|
||||
|
||||
import logoVerticalFullDark from '~/assets/images/logo/logo-vertical-full.png'
|
||||
|
||||
import logoStackedDark from '~/assets/images/logo/logo-stacked.png'
|
||||
|
||||
import logoIconDark from '~/assets/images/logo/logo-icon.png'
|
||||
|
||||
|
||||
|
||||
import logoHorizontalFullLight from '~/assets/images/logo/logo-horizontal-full-light.png'
|
||||
|
||||
import logoHorizontalTextLight from '~/assets/images/logo/logo-horizontal-text-light.png'
|
||||
|
||||
import logoVerticalFullLight from '~/assets/images/logo/logo-vertical-full-light.png'
|
||||
|
||||
import logoStackedLight from '~/assets/images/logo/logo-stacked-light.png'
|
||||
|
||||
import logoIconLight from '~/assets/images/logo/logo-icon-light.png'
|
||||
|
||||
|
||||
|
||||
type LogoVariant = 'horizontal' | 'horizontal-text' | 'vertical' | 'stacked' | 'icon'
|
||||
|
||||
|
||||
|
||||
const props = withDefaults(
|
||||
|
||||
defineProps<{
|
||||
|
||||
variant?: LogoVariant
|
||||
|
||||
class?: string
|
||||
|
||||
}>(),
|
||||
|
||||
{
|
||||
|
||||
variant: 'horizontal'
|
||||
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
const darkSources: Record<LogoVariant, string> = {
|
||||
|
||||
horizontal: logoHorizontalFullDark,
|
||||
|
||||
'horizontal-text': logoHorizontalTextDark,
|
||||
|
||||
vertical: logoVerticalFullDark,
|
||||
|
||||
stacked: logoStackedDark,
|
||||
|
||||
icon: logoIconDark
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const lightSources: Record<LogoVariant, string> = {
|
||||
|
||||
horizontal: logoHorizontalFullLight,
|
||||
|
||||
'horizontal-text': logoHorizontalTextLight,
|
||||
|
||||
vertical: logoVerticalFullLight,
|
||||
|
||||
stacked: logoStackedLight,
|
||||
|
||||
icon: logoIconLight
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const altText = 'استودیو نورو — Noro Studio'
|
||||
|
||||
const imgClass = computed(() => ['select-none', props.class])
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<template>
|
||||
<span class="inline-flex items-center justify-center">
|
||||
<img
|
||||
:src="darkSources[variant]"
|
||||
:alt="altText"
|
||||
:class="[...imgClass, 'hidden dark:flex dark:justify-center dark:items-center dark:gap-[44px] dark:pl-1 dark:grid-cols-11 dark:grid-rows-5 dark:flex-wrap dark:overflow-visible']"
|
||||
decoding="async"
|
||||
>
|
||||
<img
|
||||
:src="lightSources[variant]"
|
||||
:alt="altText"
|
||||
:class="[...imgClass, 'block dark:hidden']"
|
||||
decoding="async"
|
||||
>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import type { PortfolioProject } from '~/data/noro'
|
||||
|
||||
defineProps<{
|
||||
project: PortfolioProject
|
||||
}>()
|
||||
|
||||
const { portfolioCategories } = useNoroData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink
|
||||
:to="`/portfolio/${project.id}`"
|
||||
class="group noro-card-hover block cursor-pointer overflow-hidden"
|
||||
>
|
||||
<div class="relative aspect-[4/3] overflow-hidden bg-noro-surface">
|
||||
<img
|
||||
:src="project.image"
|
||||
:alt="project.title"
|
||||
class="h-full w-full object-cover transition duration-500 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
>
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-t from-black/50 via-black/10 to-transparent transition duration-300 group-hover:from-black/60"
|
||||
/>
|
||||
<span
|
||||
class="absolute bottom-4 right-4 rounded-full bg-white/20 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm"
|
||||
>
|
||||
{{ portfolioCategories[project.category].label }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<h3 class="font-semibold text-noro-text transition group-hover:text-noro-brand">
|
||||
{{ project.title }}
|
||||
</h3>
|
||||
<span class="shrink-0 text-xs text-noro-muted">{{ project.year }}</span>
|
||||
</div>
|
||||
<p class="mt-2 line-clamp-2 text-sm leading-6 text-noro-muted">
|
||||
{{ project.description }}
|
||||
</p>
|
||||
<p class="mt-3 text-xs font-medium text-noro-brand opacity-0 transition group-hover:opacity-100">
|
||||
مشاهده جزئیات ←
|
||||
</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
projectTitle: string
|
||||
}>()
|
||||
|
||||
const rating = ref(0)
|
||||
const hoveredRating = ref(0)
|
||||
const comment = ref('')
|
||||
const submitted = ref(false)
|
||||
|
||||
const displayRating = computed(() => hoveredRating.value || rating.value)
|
||||
|
||||
function setRating(value: number) {
|
||||
rating.value = value
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (rating.value === 0) return
|
||||
submitted.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="noro-card p-6 sm:p-8">
|
||||
<template v-if="!submitted">
|
||||
<h3 class="text-xl font-bold text-noro-text">
|
||||
نظر شما درباره این پروژه
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-noro-muted">
|
||||
امتیاز و نظر خود را درباره «{{ projectTitle }}» با ما به اشتراک بگذارید.
|
||||
</p>
|
||||
|
||||
<div class="mt-6">
|
||||
<p class="mb-3 text-sm text-noro-muted">امتیاز شما</p>
|
||||
<div class="flex gap-1" role="radiogroup" aria-label="امتیاز پروژه">
|
||||
<button
|
||||
v-for="star in 5"
|
||||
:key="star"
|
||||
type="button"
|
||||
class="rounded-lg p-1 transition hover:scale-110 focus-visible:outline focus-visible:outline-2 focus-visible:outline-noro-brand"
|
||||
:aria-label="`${star} از ۵`"
|
||||
@click="setRating(star)"
|
||||
@mouseenter="hoveredRating = star"
|
||||
@mouseleave="hoveredRating = 0"
|
||||
>
|
||||
<svg
|
||||
class="h-8 w-8 transition"
|
||||
:class="star <= displayRating ? 'text-amber-400' : 'text-noro-border'"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="mt-6 block">
|
||||
<span class="mb-2 block text-sm text-noro-muted">نظر شما (اختیاری)</span>
|
||||
<textarea
|
||||
v-model="comment"
|
||||
rows="4"
|
||||
placeholder="نظر یا پیشنهاد خود را بنویسید..."
|
||||
class="w-full resize-none rounded-xl border border-noro-border bg-noro-bg px-4 py-3 text-sm text-noro-text outline-none transition focus:border-noro-brand"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<UiButton
|
||||
type="button"
|
||||
size="lg"
|
||||
class="mt-6"
|
||||
:class="{ 'opacity-50 pointer-events-none': rating === 0 }"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
ثبت نظر
|
||||
</UiButton>
|
||||
</template>
|
||||
|
||||
<div v-else class="text-center">
|
||||
<p class="text-4xl">★</p>
|
||||
<h3 class="mt-4 text-xl font-bold text-noro-text">
|
||||
ممنون از نظر شما!
|
||||
</h3>
|
||||
<p class="mt-2 text-noro-muted">
|
||||
بازخورد شما برای بهبود کیفیت کارهای ما ارزشمند است.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||