Initial Montech monorepo: Nuxt landing, Vue admin, Nest API.

Scaffold SSR site with CMS/blog/gallery/SEO admin and deploy configs for montech.ir.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-30 18:42:28 +03:30
co-authored by Cursor
commit 7e8e1b1f1b
96 changed files with 19336 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
<template>
<section id="top" class="relative flex min-h-svh flex-col overflow-hidden">
<div class="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(200,255,46,0.08),transparent_55%)]" />
<div class="pointer-events-none absolute inset-0 bg-gradient-to-b from-ink/40 via-transparent to-ink" />
<div class="relative flex flex-1 flex-col justify-end px-6 pb-12 pt-36 md:px-10 md:pb-16 md:pt-44">
<div
class="mb-10 flex items-center justify-between font-mono text-[10px] tracking-[0.3em] text-ash animate-fade-up md:mb-16 md:text-[11px]"
style="animation-delay: 0.2s"
>
<span class="flex items-center gap-3">
<span class="h-1.5 w-1.5 animate-blink rounded-full bg-volt" />
{{ data.eyebrow }}
</span>
<span class="hidden md:block">{{ data.location }}</span>
</div>
<div class="flex flex-col gap-12 lg:flex-row lg:items-end lg:justify-between">
<h1 class="font-display text-[clamp(3rem,12.5vw,11.5rem)] font-extrabold uppercase leading-[0.92] tracking-[-0.03em]">
<span
v-for="(line, i) in data.lines"
:key="i"
class="block overflow-hidden pb-[0.08em] -mb-[0.08em] animate-fade-up"
:style="{ animationDelay: `${0.15 + i * 0.12}s` }"
>
<template v-if="line.includes(data.highlightWord)">
<span
v-html="
line.replace(
data.highlightWord,
`<span class='font-serif normal-case italic tracking-normal text-volt'>${data.highlightWord}</span>`,
)
"
/>
</template>
<template v-else>
{{ line }}<span v-if="i === data.lines.length - 1" class="text-volt">.</span>
</template>
</span>
</h1>
<div class="flex shrink-0 flex-col items-start gap-10 animate-fade-up lg:items-end lg:pb-3" style="animation-delay: 0.55s">
<p class="max-w-sm text-sm leading-relaxed text-ash md:text-base lg:text-right">
{{ data.body }}
</p>
<a
href="#work"
class="group flex items-center gap-4 font-mono text-[11px] tracking-[0.3em] text-bone transition-colors hover:text-volt"
>
{{ data.cta }}
<span class="flex h-10 w-10 items-center justify-center rounded-full border border-white/20 transition-all group-hover:border-volt group-hover:bg-volt group-hover:text-ink">
↓
</span>
</a>
</div>
</div>
<div
class="mt-14 flex items-end justify-between border-t border-white/[0.07] pt-6 font-mono text-[10px] tracking-[0.3em] text-ash animate-fade-up md:mt-20"
style="animation-delay: 0.8s"
>
<span>SCROLL</span>
<span class="hidden sm:block">{{ data.coords }}</span>
<span class="hidden sm:block">{{ data.projectsLabel }}</span>
</div>
</div>
</section>
</template>
<script setup lang="ts">
defineProps<{
data: {
eyebrow: string;
location: string;
lines: string[];
highlightWord: string;
body: string;
cta: string;
coords: string;
projectsLabel: string;
};
}>();
</script>
@@ -0,0 +1,29 @@
<template>
<section id="manifesto" class="px-6 py-28 md:px-10 md:py-40">
<div class="mx-auto max-w-[1500px]">
<SectionTag index="01" :title="data.tag" right="The short version" />
<p class="mt-14 max-w-5xl font-display text-[clamp(2rem,6vw,5.5rem)] font-extrabold uppercase leading-[1.05] tracking-[-0.02em]">
<span
v-for="(w, i) in data.words"
:key="i"
class="mr-[0.28em] inline-block"
:class="{
'text-outline': w.outline,
'text-volt': w.volt,
}"
>{{ w.text }}</span>
</p>
<p class="mt-10 max-w-xl text-sm leading-relaxed text-ash md:text-base">{{ data.body }}</p>
</div>
</section>
</template>
<script setup lang="ts">
defineProps<{
data: {
tag: string;
words: Array<{ text: string; outline?: boolean; volt?: boolean }>;
body: string;
};
}>();
</script>
@@ -0,0 +1,23 @@
<template>
<section id="process" class="px-6 py-28 md:px-10 md:py-40">
<div class="mx-auto max-w-[1500px]">
<SectionTag index="04" :title="data.tag" right="12 weeks, typical" />
<div class="mt-16 grid gap-8 md:grid-cols-2 lg:grid-cols-4">
<div v-for="step in data.steps" :key="step.n" class="border-t border-white/[0.1] pt-6">
<p class="font-mono text-[11px] tracking-[0.25em] text-volt">{{ step.n }}</p>
<h3 class="mt-4 font-display text-2xl font-bold uppercase tracking-[-0.02em]">{{ step.title }}</h3>
<p class="mt-3 text-sm leading-relaxed text-ash">{{ step.desc }}</p>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
defineProps<{
data: {
tag: string;
steps: Array<{ n: string; title: string; desc: string }>;
};
}>();
</script>
+13
View File
@@ -0,0 +1,13 @@
<template>
<div class="flex items-end justify-between gap-6 border-b border-white/[0.07] pb-4 font-mono text-[11px] tracking-[0.28em] text-ash">
<div class="flex items-center gap-3">
<span class="text-volt">{{ index }}</span>
<span class="text-bone">{{ title }}</span>
</div>
<span v-if="right" class="hidden sm:block">{{ right }}</span>
</div>
</template>
<script setup lang="ts">
defineProps<{ index: string; title: string; right?: string }>();
</script>
@@ -0,0 +1,45 @@
<template>
<section id="services" class="px-6 py-28 md:px-10 md:py-40">
<div class="mx-auto max-w-[1500px]">
<SectionTag index="02" :title="data.tag" :right="`${String(data.items.length).padStart(2, '0')} Capabilities`" />
<ul class="mt-16 divide-y divide-white/[0.07] border-y border-white/[0.07]">
<li
v-for="(s, i) in data.items"
:key="s.n"
class="group cursor-pointer py-8 md:py-10"
@click="open = open === i ? -1 : i"
>
<div class="flex items-start justify-between gap-6">
<div class="flex flex-1 flex-col gap-3 md:flex-row md:items-baseline md:gap-10">
<span class="font-mono text-[11px] tracking-[0.25em] text-ash">{{ s.n }}</span>
<h3 class="font-display text-2xl font-bold uppercase tracking-[-0.02em] md:text-4xl group-hover:text-volt transition-colors">
{{ s.title }}
</h3>
</div>
<span class="font-mono text-volt">{{ open === i ? "—" : "+" }}</span>
</div>
<div v-if="open === i" class="mt-6 max-w-3xl pl-0 md:pl-16">
<p class="text-sm leading-relaxed text-ash md:text-base">{{ s.desc }}</p>
<div class="mt-4 flex flex-wrap gap-2">
<span
v-for="t in s.tags"
:key="t"
class="border border-white/10 px-3 py-1 font-mono text-[10px] tracking-[0.18em] text-ash"
>{{ t }}</span>
</div>
</div>
</li>
</ul>
</div>
</section>
</template>
<script setup lang="ts">
defineProps<{
data: {
tag: string;
items: Array<{ n: string; title: string; desc: string; tags: string[] }>;
};
}>();
const open = ref(0);
</script>
+63
View File
@@ -0,0 +1,63 @@
<template>
<footer id="contact" class="relative overflow-hidden border-t border-white/[0.07]">
<div class="border-b border-white/[0.07] py-8 md:py-10">
<SiteMarquee
:items="data.marquee"
:duration="20"
class-name="font-display text-xl font-bold uppercase tracking-[-0.01em] md:text-3xl"
/>
</div>
<div class="mx-auto max-w-[1500px] px-6 py-20 md:px-10 md:py-28">
<SectionTag index="06" :title="data.tag" right="Response within 24h" />
<div class="mt-14 grid gap-12 lg:grid-cols-[1.4fr_1fr]">
<div>
<h2 class="font-display text-[clamp(2.5rem,7vw,6rem)] font-extrabold uppercase leading-[0.95] tracking-[-0.03em]">
{{ data.cta }}
</h2>
<a
:href="`mailto:${data.email}`"
class="mt-8 inline-flex items-center gap-3 border border-volt bg-volt px-6 py-3 font-mono text-[11px] tracking-[0.22em] text-ink transition hover:bg-transparent hover:text-volt"
>
{{ data.email }} ↗
</a>
</div>
<div class="grid gap-8 sm:grid-cols-3 lg:grid-cols-1">
<div>
<p class="font-mono text-[10px] tracking-[0.25em] text-ash">NEW BUSINESS</p>
<p class="mt-2 text-sm">{{ data.email }}</p>
<p class="text-sm text-ash">{{ data.phone }}</p>
</div>
<div>
<p class="font-mono text-[10px] tracking-[0.25em] text-ash">STUDIO</p>
<p v-for="line in data.address" :key="line" class="mt-1 text-sm text-ash">{{ line }}</p>
</div>
<div>
<p class="font-mono text-[10px] tracking-[0.25em] text-ash">SOCIALS</p>
<p v-for="s in data.socials" :key="s" class="mt-1 text-sm text-ash">{{ s }}</p>
</div>
</div>
</div>
<div class="mt-20 flex flex-wrap items-center justify-between gap-4 border-t border-white/[0.07] pt-6 font-mono text-[10px] tracking-[0.2em] text-ash">
<span>© {{ year }} MONTECH SOFTWARE GROUP</span>
<span>{{ brand }}®</span>
</div>
</div>
</footer>
</template>
<script setup lang="ts">
defineProps<{
brand?: string;
data: {
tag: string;
marquee: string[];
cta: string;
email: string;
phone: string;
address: string[];
socials: string[];
};
}>();
const year = new Date().getFullYear();
</script>
+32
View File
@@ -0,0 +1,32 @@
<template>
<div class="overflow-hidden" :class="className">
<div class="animate-marquee flex w-max" :style="{ '--marquee-duration': `${duration}s` } as any">
<div v-for="dup in 2" :key="dup" class="flex shrink-0 items-center">
<span v-for="(item, i) in items" :key="`${dup}-${i}`" class="flex items-center">
<span class="mx-5 whitespace-nowrap md:mx-8" :class="itemClassName">{{ item }}</span>
<span class="text-volt/70" :class="separatorClassName">{{ separator }}</span>
</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
items: string[];
duration?: number;
className?: string;
itemClassName?: string;
separatorClassName?: string;
separator?: string;
}>(),
{
duration: 24,
className: "",
itemClassName: "",
separatorClassName: "",
separator: "✦",
},
);
</script>
+40
View File
@@ -0,0 +1,40 @@
<template>
<header class="fixed inset-x-0 top-0 z-50 border-b border-white/[0.06] bg-ink/70 backdrop-blur-md">
<div class="mx-auto flex max-w-[1500px] items-center justify-between px-6 py-4 md:px-10">
<NuxtLink to="/" class="font-display text-lg font-extrabold tracking-tight">
{{ brand }}<span class="align-super text-[0.55em] text-volt">®</span>
</NuxtLink>
<nav class="hidden items-center gap-8 font-mono text-[11px] tracking-[0.22em] text-ash md:flex">
<a href="#services" class="hover:text-volt">SERVICES</a>
<a href="#work" class="hover:text-volt">WORK</a>
<a href="#team" class="hover:text-volt">TEAM</a>
<NuxtLink to="/blog" class="hover:text-volt">BLOG</NuxtLink>
<NuxtLink to="/gallery" class="hover:text-volt">GALLERY</NuxtLink>
<a href="#contact" class="text-bone hover:text-volt">CONTACT</a>
</nav>
<span class="font-mono text-[10px] tracking-[0.2em] text-ash tabular-nums">{{ time }}</span>
</div>
</header>
</template>
<script setup lang="ts">
const props = defineProps<{ brand?: string; timezone?: string }>();
const brand = computed(() => props.brand || "Montech");
const time = ref("");
onMounted(() => {
const tz = props.timezone || "Asia/Tehran";
const tick = () => {
time.value = new Intl.DateTimeFormat("en-GB", {
timeZone: tz,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}).format(new Date());
};
tick();
const id = setInterval(tick, 1000);
onUnmounted(() => clearInterval(id));
});
</script>
+20
View File
@@ -0,0 +1,20 @@
<template>
<section class="border-y border-white/[0.07] bg-coal px-6 py-20 md:px-10 md:py-28">
<div class="mx-auto grid max-w-[1500px] gap-10 sm:grid-cols-2 lg:grid-cols-4">
<div v-for="(s, i) in data.items" :key="i">
<p class="font-display text-5xl font-extrabold tracking-[-0.03em] text-volt md:text-6xl">
{{ s.value }}{{ s.suffix }}
</p>
<p class="mt-3 font-mono text-[11px] tracking-[0.22em] text-ash">{{ s.label }}</p>
</div>
</div>
</section>
</template>
<script setup lang="ts">
defineProps<{
data: {
items: Array<{ value: number; suffix: string; label: string }>;
};
}>();
</script>
+38
View File
@@ -0,0 +1,38 @@
<template>
<section id="team" class="px-6 py-28 md:px-10 md:py-40">
<div class="mx-auto max-w-[1500px]">
<SectionTag index="05" :title="data.tag" right="Leadership" />
<div class="mb-16 mt-14 flex flex-col justify-between gap-8 md:flex-row md:items-end">
<h2 class="font-display text-[clamp(2.6rem,8vw,7.5rem)] font-extrabold uppercase leading-[0.92] tracking-[-0.02em]">
{{ data.headline.split(" ").slice(0, -1).join(" ") }}
<span class="font-serif normal-case italic tracking-normal text-volt">
{{ data.headline.split(" ").slice(-1)[0] }}
</span>
</h2>
<p class="max-w-xs text-sm leading-relaxed text-ash md:text-right">{{ data.body }}</p>
</div>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
<figure v-for="m in data.members" :key="m.name" class="group">
<div class="aspect-[3/4] bg-gradient-to-br from-smoke to-ink" />
<figcaption class="mt-4">
<p class="font-display text-xl font-bold uppercase tracking-[-0.02em] group-hover:text-volt transition-colors">
{{ m.name }}
</p>
<p class="mt-1 font-mono text-[10px] tracking-[0.22em] text-ash">{{ m.role }}</p>
</figcaption>
</figure>
</div>
</div>
</section>
</template>
<script setup lang="ts">
defineProps<{
data: {
tag: string;
headline: string;
body: string;
members: Array<{ name: string; role: string }>;
};
}>();
</script>
+35
View File
@@ -0,0 +1,35 @@
<template>
<section id="work" class="px-6 py-28 md:px-10 md:py-40">
<div class="mx-auto max-w-[1500px]">
<SectionTag index="03" :title="data.tag" right="2023 — 2025" />
<div class="mt-16 grid gap-6 md:grid-cols-2">
<article
v-for="p in data.items"
:key="p.n"
class="group relative overflow-hidden border border-white/[0.07] bg-coal p-6 md:p-8"
>
<div class="mb-16 aspect-[16/10] bg-gradient-to-br from-smoke to-ink" />
<div class="flex items-end justify-between gap-4">
<div>
<p class="font-mono text-[10px] tracking-[0.25em] text-ash">{{ p.n }} / {{ p.year }}</p>
<h3 class="mt-2 font-display text-3xl font-extrabold uppercase tracking-[-0.02em] group-hover:text-volt transition-colors">
{{ p.title }}
</h3>
<p class="mt-2 text-sm text-ash">{{ p.cat }}</p>
</div>
<span class="text-volt">↗</span>
</div>
</article>
</div>
</div>
</section>
</template>
<script setup lang="ts">
defineProps<{
data: {
tag: string;
items: Array<{ n: string; title: string; cat: string; year: string }>;
};
}>();
</script>