| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 |
- <template>
- <section id="services" class="py-24 bg-card relative overflow-hidden">
- <div class="absolute inset-0 grid-pattern opacity-30" />
- <div v-if="isLoading" class="py-24 flex items-center justify-center">
- <Loader2 class="w-8 h-8 animate-spin text-primary" />
- </div>
- <div v-else class="container mx-auto px-4 relative z-10">
- <div class="text-center mb-16">
- <span class="text-primary font-display text-sm tracking-widest uppercase">{{ t("services.badge") }}</span>
- <h2 class="font-display text-3xl sm:text-4xl lg:text-5xl font-bold mt-4 mb-6">
- {{ t("services.title") }} <span class="text-gradient">{{ t("services.titleGradient") }}</span>
- </h2>
- <p class="text-muted-foreground max-w-2xl mx-auto">{{ t("services.description") }}</p>
- </div>
- <!-- Services Grid -->
- <div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-24">
- <div
- v-for="(service, index) in services"
- :key="service.id"
- class="group p-6 bg-gradient-card rounded-xl border border-border/50 hover:border-primary/50 transition-all duration-300 hover:shadow-hover"
- :style="{ animationDelay: `${index * 100}ms` }"
- >
- <div class="w-14 h-14 bg-primary/10 rounded-xl flex items-center justify-center mb-4 group-hover:bg-primary group-hover:text-primary-foreground transition-all duration-500">
- <component :is="iconMap[service.tech_type] || iconMap.DEFAULT" class="w-7 h-7" />
- </div>
- <h3 class="font-display text-xl font-semibold mb-3">{{ t(service.name_key) }}</h3>
- <p class="text-muted-foreground leading-relaxed">{{ t(service.description_key) }}</p>
- </div>
- </div>
- <!-- Materials -->
- <div id="materials" class="mt-20">
- <div class="flex items-center gap-3 mb-10">
- <div class="w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center">
- <Sparkles class="w-5 h-5 text-primary" />
- </div>
- <h3 class="font-display text-2xl font-bold">{{ t("pricing.materials") }}</h3>
- </div>
- <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
- <div
- v-for="(material, idx) in materials"
- :key="material.id"
- class="p-5 bg-background border border-border/40 rounded-2xl hover:border-primary/30 transition-colors group animate-fade-in"
- :style="{ animationDelay: `${idx * 50}ms` }"
- >
- <div class="text-lg font-display font-bold text-foreground mb-1 group-hover:text-primary transition-colors uppercase">
- {{ material[`name_${locale}`] || material.name_en }}
- </div>
- <p class="text-xs text-muted-foreground line-clamp-2 leading-relaxed">{{ material[`desc_${locale}`] || material.desc_en }}</p>
- </div>
- </div>
- </div>
- </div>
- </section>
- </template>
- <script setup lang="ts">
- import { ref, onMounted } from "vue";
- import { useI18n } from "vue-i18n";
- import { Layers, Zap, Loader2, Sparkles, Box, Cpu } from "lucide-vue-next";
- import { getServices, getMaterials } from "@/lib/api";
- const { t, locale } = useI18n();
- interface Service { id: number; name_key: string; description_key: string; tech_type: string }
- interface Material {
- id: number;
- name_en: string; name_ru: string; name_me: string;
- desc_en: string; desc_ru: string; desc_me: string;
- price_per_cm3: number
- }
- const iconMap: Record<string, any> = { FDM: Layers, SLA: Zap, DEFAULT: Box };
- const services = ref<Service[]>([]);
- const materials = ref<Material[]>([]);
- const isLoading = ref(true);
- onMounted(async () => {
- try {
- [services.value, materials.value] = await Promise.all([getServices(), getMaterials()]);
- } catch (e) {
- console.error("Failed to fetch services/materials:", e);
- } finally {
- isLoading.value = false;
- }
- });
- </script>
|