ServicesSection.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. <template>
  2. <section id="services" class="py-24 bg-card relative overflow-hidden">
  3. <div class="absolute inset-0 grid-pattern opacity-30" />
  4. <div v-if="isLoading" class="py-24 flex items-center justify-center">
  5. <Loader2 class="w-8 h-8 animate-spin text-primary" />
  6. </div>
  7. <div v-else class="container mx-auto px-4 relative z-10">
  8. <div class="text-center mb-16">
  9. <span class="text-primary font-display text-sm tracking-widest uppercase">{{ t("services.badge") }}</span>
  10. <h2 class="font-display text-3xl sm:text-4xl lg:text-5xl font-bold mt-4 mb-6">
  11. {{ t("services.title") }} <span class="text-gradient">{{ t("services.titleGradient") }}</span>
  12. </h2>
  13. <p class="text-muted-foreground max-w-2xl mx-auto">{{ t("services.description") }}</p>
  14. </div>
  15. <!-- Services Grid -->
  16. <div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-24">
  17. <div
  18. v-for="(service, index) in services"
  19. :key="service.id"
  20. class="group p-6 bg-gradient-card rounded-xl border border-border/50 hover:border-primary/50 transition-all duration-300 hover:shadow-hover"
  21. :style="{ animationDelay: `${index * 100}ms` }"
  22. >
  23. <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">
  24. <component :is="iconMap[service.tech_type] || iconMap.DEFAULT" class="w-7 h-7" />
  25. </div>
  26. <h3 class="font-display text-xl font-semibold mb-3">{{ t(service.name_key) }}</h3>
  27. <p class="text-muted-foreground leading-relaxed">{{ t(service.description_key) }}</p>
  28. </div>
  29. </div>
  30. <!-- Materials -->
  31. <div id="materials" class="mt-20">
  32. <div class="flex items-center gap-3 mb-10">
  33. <div class="w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center">
  34. <Sparkles class="w-5 h-5 text-primary" />
  35. </div>
  36. <h3 class="font-display text-2xl font-bold">{{ t("pricing.materials") }}</h3>
  37. </div>
  38. <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
  39. <div
  40. v-for="(material, idx) in materials"
  41. :key="material.id"
  42. class="p-5 bg-background border border-border/40 rounded-2xl hover:border-primary/30 transition-colors group animate-fade-in"
  43. :style="{ animationDelay: `${idx * 50}ms` }"
  44. >
  45. <div class="text-lg font-display font-bold text-foreground mb-1 group-hover:text-primary transition-colors uppercase">
  46. {{ material[`name_${locale}`] || material.name_en }}
  47. </div>
  48. <p class="text-xs text-muted-foreground line-clamp-2 leading-relaxed">{{ material[`desc_${locale}`] || material.desc_en }}</p>
  49. </div>
  50. </div>
  51. </div>
  52. </div>
  53. </section>
  54. </template>
  55. <script setup lang="ts">
  56. import { ref, onMounted } from "vue";
  57. import { useI18n } from "vue-i18n";
  58. import { Layers, Zap, Loader2, Sparkles, Box, Cpu } from "lucide-vue-next";
  59. import { getServices, getMaterials } from "@/lib/api";
  60. const { t, locale } = useI18n();
  61. interface Service { id: number; name_key: string; description_key: string; tech_type: string }
  62. interface Material {
  63. id: number;
  64. name_en: string; name_ru: string; name_me: string;
  65. desc_en: string; desc_ru: string; desc_me: string;
  66. price_per_cm3: number
  67. }
  68. const iconMap: Record<string, any> = { FDM: Layers, SLA: Zap, DEFAULT: Box };
  69. const services = ref<Service[]>([]);
  70. const materials = ref<Material[]>([]);
  71. const isLoading = ref(true);
  72. onMounted(async () => {
  73. try {
  74. [services.value, materials.value] = await Promise.all([getServices(), getMaterials()]);
  75. } catch (e) {
  76. console.error("Failed to fetch services/materials:", e);
  77. } finally {
  78. isLoading.value = false;
  79. }
  80. });
  81. </script>