|
|
@@ -1,12 +1,11 @@
|
|
|
<script setup>
|
|
|
import PageHero from "../components/PageHero.vue";
|
|
|
-import solutionsBgUrl from "../assets/images/bg.jpg?url";
|
|
|
-import Reveal from "../components/motion/Reveal.vue";
|
|
|
import { Icon } from "@iconify/vue";
|
|
|
import { onBeforeUnmount, onMounted, ref } from "vue";
|
|
|
-import platformCover from "../assets/images/p1.png?url";
|
|
|
-import equipmentCover from "../assets/images/sence3.png?url";
|
|
|
+import platformCover from "../assets/images/pbg.png?url";
|
|
|
+import equipmentCover from "../assets/images/case2.png?url";
|
|
|
import productionCover from "../assets/images/sence2.jpg?url";
|
|
|
+import qhseCover from "../assets/images/sence3.png?url";
|
|
|
|
|
|
const activeTab = ref("platform");
|
|
|
const tabBarRef = ref(null);
|
|
|
@@ -19,86 +18,91 @@ const pillars = [
|
|
|
{
|
|
|
key: "platform",
|
|
|
title: "设备升级改造能力",
|
|
|
- lead: "油气专属底座,打通“数采—数据—系统—AI”,为全场景应用落地提供稳定支撑",
|
|
|
+ lead: "实现设备升级改造与生产管理全生命周期的闭环管理",
|
|
|
icon: "lucide:layers-3",
|
|
|
cover: platformCover,
|
|
|
items: [
|
|
|
{
|
|
|
title: "全景设备升级 适配平台接入",
|
|
|
- desc: "依托工业互联网平台底座,提供油气领域全类型设备升级改造服务,针对老旧设备、传统设备,完成仪器仪表升级、PLC 控制系统改造或加装,确保改造后设备可无缝接入平台,打通设备与平台的数据联通通道,为后续智能管控奠定基础",
|
|
|
+ desc: "依托工业互联网平台底座,提供油气领域全类型设备升级改造服务,针对老旧设备、传统设备,完成仪器仪表升级、PLC控制系统改造或加装,确保改造后设备可无缝接入平台,打通设备与平台的数据联通通道,为后续智能管控奠定基础。",
|
|
|
+ image: new URL("../assets/images/l.png", import.meta.url).href,
|
|
|
},
|
|
|
{
|
|
|
title: "实时数采远程管控 提升作业便捷度",
|
|
|
desc: "通过设备升级改造,结合平台数采能力,实现现场设备运行数据实时采集、同步上传,打破现场操作局限,支持远程对设备进行控制、参数调试,无需现场值守,降低人工成本,提升设备运维与操作的便捷性、高效性。",
|
|
|
+ image: new URL("../assets/images/n.png", import.meta.url).href,
|
|
|
},
|
|
|
{
|
|
|
- title: "AI 大模型赋能 智能动态调参",
|
|
|
- desc: "升级后的设备联动工业互联网平台 AI 大模型算法,基于实时采集的设备运行数据,进行智能分析、精准研判,自动输出动态调参建议或完成自动调参,优化设备运行状态,确保设备始终处于最佳运行工况,提升运行效率。",
|
|
|
+ title: "AI大模型赋能 智能动态调参",
|
|
|
+ desc: "升级后的设备联动工业互联网平台AI大模型算法,基于实时采集的设备运行数据,进行智能分析、精准研判,自动输出动态调参建议或完成自动调参,优化设备运行状态,确保设备始终处于最佳运行工况,提升运行效率。",
|
|
|
+ image: new URL("../assets/images/p.png", import.meta.url).href,
|
|
|
},
|
|
|
{
|
|
|
title: "设备专项赋能 预测性维护",
|
|
|
desc: "针对油气领域部分核心设备,在升级改造基础上,结合平台数据处理与大模型能力,实时监测运行参数,精准预判设备潜在故障、损耗情况,提前触发维护提醒,实现预测性维护,减少停机损失,延长设备使用寿命。",
|
|
|
+ image: new URL("../assets/images/w.png", import.meta.url).href,
|
|
|
},
|
|
|
],
|
|
|
},
|
|
|
{
|
|
|
key: "equipment",
|
|
|
title: "智慧注气",
|
|
|
- lead: "围绕设备全生命周期,实现台账、巡检维保、预测维护与实时监控预警的闭环管理",
|
|
|
+ lead: "实现智能注气、调度、管理,提升生产效率与安全",
|
|
|
icon: "lucide:cpu",
|
|
|
cover: equipmentCover,
|
|
|
items: [
|
|
|
{
|
|
|
title: "老旧设备升级 筑牢无人值守基础",
|
|
|
desc: "依托工业互联网平台底座,针对油气注气领域老旧设备,开展专项升级改造,加装远程控制器、智能阀门等核心组件,完成设备智能化升级,打破传统人工操作局限,为注气现场无人值守搭建硬件支撑,确保设备可无缝接入平台实现协同管控。",
|
|
|
+ image: new URL("../assets/images/o.png", import.meta.url).href,
|
|
|
},
|
|
|
{
|
|
|
title: "实时数据采集 精准把控运行状态",
|
|
|
desc: "升级后的注气设备联动平台数采能力,实时采集现场压力、温度、流量等核心运行参数,同步上传至工业互联网平台,实现参数实时可视化监控,全面掌握注气全流程运行状态,为无人值守下的智能调控提供精准数据支撑。",
|
|
|
+ image: new URL("../assets/images/v.png", import.meta.url).href,
|
|
|
},
|
|
|
{
|
|
|
title: "智能动态调参 实现无人管控",
|
|
|
desc: "依托工业互联网平台算法能力,基于实时采集的运行数据,自动分析注气工况,动态调整注入参数,无需人工现场操作,真正实现注气现场无人少人值守,大幅减少人工干预,确保注气过程稳定、高效、精准。",
|
|
|
+ image: new URL("../assets/images/z.png", import.meta.url).href,
|
|
|
},
|
|
|
{
|
|
|
title: "降本增效保安全 赋能场景升级",
|
|
|
- desc: "通过无人值守模式,大幅减少现场值守人员,降低人力成本;智能动态调参优化注气效率,提升作业产能;减少人员现场作业频次,规避现场安全风险,实现降本、增效、保安全三重价值,推动注气场景数字化、智能化转型。",
|
|
|
+ desc: "通过无人值守模式,大幅减少现场值守人员,降低人力成本;智能动态调参优化注气效率,提升作业产能;减少人员现场作业频次,规避现场安全风险,实现“降本、增效、保安全”三重价值,推动注气场景数字化、智能化转型。",
|
|
|
+ image: new URL("../assets/images/s.png", import.meta.url).href,
|
|
|
},
|
|
|
],
|
|
|
},
|
|
|
{
|
|
|
key: "production",
|
|
|
title: "智慧钻井",
|
|
|
- lead: "面向油气生产项目,贯通任务、计划、数据与报表,提升协同效率与过程可控性",
|
|
|
+ lead: "提升生产效率与安全",
|
|
|
icon: "lucide:clipboard-check",
|
|
|
cover: productionCover,
|
|
|
items: [
|
|
|
{
|
|
|
title: "数据精准采集 筑牢钻井分析基础",
|
|
|
- desc: "依托工业互联网平台数采能力,深度联动录井仪设备,实时采集钻井全流程工况数据,全面记录钻井过程中的各项核心参数,实现数据精准捕捉、实时上传、规范存储,为后续 AI 分析、钻井优化及完井报告生成,提供完整、可靠的数据支撑。",
|
|
|
+ desc: "依托工业互联网平台数采能力,深度联动录井仪设备,实时采集钻井全流程工况数据,全面记录钻井过程中的各项核心参数,实现数据精准捕捉、实时上传、规范存储,为后续AI分析、钻井优化及完井报告生成,提供完整、可靠的数据支撑。",
|
|
|
+ image: new URL("../assets/images/ok.png", import.meta.url).href,
|
|
|
},
|
|
|
{
|
|
|
- title: "AI 大模型深度赋能 精准分析钻井工况",
|
|
|
- desc: "基于工业互联网平台 AI 大模型能力,对采集的录井数据进行深度挖掘、智能分析,精准识别钻井过程中的工况异常、参数偏差等问题,快速研判钻井态势,为钻井作业优化提供科学、精准的数据分析支撑,规避盲目操作风险。",
|
|
|
+ title: "AI大模型深度赋能 精准分析钻井工况",
|
|
|
+ desc: "基于工业互联网平台AI大模型能力,对采集的录井数据进行深度挖掘、智能分析,精准识别钻井过程中的工况异常、参数偏差等问题,快速研判钻井态势,为钻井作业优化提供科学、精准的数据分析支撑,规避盲目操作风险。",
|
|
|
+ image: new URL("../assets/images/ll.png", import.meta.url).href,
|
|
|
},
|
|
|
{
|
|
|
title: "钻井参数智能优化 提升钻井作业交通",
|
|
|
- desc: "结合 AI 大模型的工况分析结果,联动工业互联网平台管控能力,为钻井现场提供精准的参数优化建议,指导现场调整钻井参数,优化钻井工艺,有效提升钻速、降低钻井损耗,助力钻井作业提质增效,契合油气勘探开发高效化需求。",
|
|
|
+ desc: "结合AI大模型的工况分析结果,联动工业互联网平台管控能力,为钻井现场提供精准的参数优化建议,指导现场调整钻井参数,优化钻井工艺,有效提升钻速、降低钻井损耗,助力钻井作业提质增效,契合油气勘探开发高效化需求。",
|
|
|
+ image: new URL("../assets/images/mk.png", import.meta.url).href,
|
|
|
},
|
|
|
{
|
|
|
title: "智能生成完井报告 提升报告编制效率",
|
|
|
- desc: "依托平台数据沉淀与 AI 处理能力,自动汇总钻井全流程录井数据、工况分析结果及优化记录,一键生成标准化完井报告,无需人工繁琐录入,大幅缩短报告编制周期,确保报告数据精准、内容规范,为钻井成果复盘、后续作业提供有力支撑。",
|
|
|
+ desc: "依托平台数据沉淀与AI处理能力,自动汇总钻井全流程录井数据、工况分析结果及优化记录,一键生成标准化完井报告,无需人工繁琐录入,大幅缩短报告编制周期,确保报告数据精准、内容规范,为钻井成果复盘、后续作业提供有力支撑。",
|
|
|
+ image: new URL("../assets/images/pp.png", import.meta.url).href,
|
|
|
},
|
|
|
],
|
|
|
},
|
|
|
];
|
|
|
-
|
|
|
-const deliver = [
|
|
|
- { title: "SaaS", desc: "开箱即用,按需订阅,快速试点与复制。" },
|
|
|
- { title: "私有化", desc: "数据本地化部署,适配企业安全与合规要求。" },
|
|
|
- { title: "混合云", desc: "核心数据本地,弹性算力上云,兼顾成本与性能。" },
|
|
|
-];
|
|
|
-
|
|
|
const approach = [
|
|
|
{ step: "01", title: "数据采集", desc: "实时采集设备、生产等现场数据" },
|
|
|
{ step: "02", title: "数据传输", desc: "通过边缘网关传输至平台" },
|
|
|
@@ -128,6 +132,83 @@ function scrollToTab(key) {
|
|
|
}
|
|
|
|
|
|
let observer = null;
|
|
|
+
|
|
|
+const currentImageIndex = ref({
|
|
|
+ platform: 0,
|
|
|
+ equipment: 0,
|
|
|
+ production: 0,
|
|
|
+});
|
|
|
+
|
|
|
+const imageTimers = ref({});
|
|
|
+
|
|
|
+const getActiveIndex = (key, totalItems) => {
|
|
|
+ const rawIndex = currentImageIndex.value[key];
|
|
|
+ const index = Number.isFinite(rawIndex) ? rawIndex : 0;
|
|
|
+ if (!totalItems || totalItems <= 0) return 0;
|
|
|
+ return ((index % totalItems) + totalItems) % totalItems;
|
|
|
+};
|
|
|
+
|
|
|
+const getActiveItem = (pillar) => {
|
|
|
+ const totalItems = pillar?.items?.length || 0;
|
|
|
+ const index = getActiveIndex(pillar?.key, totalItems);
|
|
|
+ return pillar?.items?.[index] || null;
|
|
|
+};
|
|
|
+
|
|
|
+const getActiveImageSrc = (pillar) =>
|
|
|
+ getActiveItem(pillar)?.image || pillar.cover;
|
|
|
+const getActiveImageAlt = (pillar) =>
|
|
|
+ getActiveItem(pillar)?.title || pillar.title;
|
|
|
+
|
|
|
+const startImageCarousel = (key, totalItems) => {
|
|
|
+ if (prefersReducedMotion()) return;
|
|
|
+ if (!totalItems || totalItems < 2) return;
|
|
|
+
|
|
|
+ stopImageCarousel(key);
|
|
|
+
|
|
|
+ imageTimers.value[key] = setInterval(() => {
|
|
|
+ currentImageIndex.value[key] =
|
|
|
+ (currentImageIndex.value[key] + 1) % totalItems;
|
|
|
+ }, 4000);
|
|
|
+};
|
|
|
+
|
|
|
+const stopImageCarousel = (key) => {
|
|
|
+ if (imageTimers.value[key]) {
|
|
|
+ clearInterval(imageTimers.value[key]);
|
|
|
+ imageTimers.value[key] = null;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const goToImageSlide = (key, index, totalItems) => {
|
|
|
+ currentImageIndex.value[key] = index;
|
|
|
+ startImageCarousel(key, totalItems);
|
|
|
+};
|
|
|
+
|
|
|
+function updateActiveTab() {
|
|
|
+ const sections = pillars
|
|
|
+ .map((p) => document.getElementById(`product-${p.key}`))
|
|
|
+ .filter(Boolean);
|
|
|
+ const offset = headerOffsetPx();
|
|
|
+
|
|
|
+ const scrollPos = window.scrollY + offset;
|
|
|
+
|
|
|
+ let currentSection = null;
|
|
|
+ for (const section of sections) {
|
|
|
+ const rect = section.getBoundingClientRect();
|
|
|
+ if (rect.top <= 100) {
|
|
|
+ currentSection = section;
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (currentSection) {
|
|
|
+ const key = currentSection.id.replace("product-", "");
|
|
|
+ if (activeTab.value !== key) {
|
|
|
+ activeTab.value = key;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
const offset = headerOffsetPx();
|
|
|
observer = new IntersectionObserver(
|
|
|
@@ -142,8 +223,8 @@ onMounted(() => {
|
|
|
},
|
|
|
{
|
|
|
root: null,
|
|
|
- threshold: [0.25, 0.45, 0.6],
|
|
|
- rootMargin: `-${offset}px 0px -55% 0px`,
|
|
|
+ threshold: [0.3, 0.5, 0.7],
|
|
|
+ rootMargin: `-${offset}px 0px -40% 0px`,
|
|
|
},
|
|
|
);
|
|
|
|
|
|
@@ -151,11 +232,26 @@ onMounted(() => {
|
|
|
const el = document.getElementById(`product-${p.key}`);
|
|
|
if (el) observer.observe(el);
|
|
|
}
|
|
|
+
|
|
|
+ for (const p of pillars) {
|
|
|
+ if (currentImageIndex.value[p.key] === undefined)
|
|
|
+ currentImageIndex.value[p.key] = 0;
|
|
|
+ startImageCarousel(p.key, p.items?.length || 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ window.addEventListener("scroll", updateActiveTab, { passive: true });
|
|
|
+ updateActiveTab();
|
|
|
});
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
observer?.disconnect?.();
|
|
|
observer = null;
|
|
|
+
|
|
|
+ for (const key of Object.keys(imageTimers.value || {})) {
|
|
|
+ stopImageCarousel(key);
|
|
|
+ }
|
|
|
+
|
|
|
+ window.removeEventListener("scroll", updateActiveTab);
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
@@ -203,16 +299,12 @@ onBeforeUnmount(() => {
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
- <div class="productStac">
|
|
|
+ <div class="productStac container">
|
|
|
<section
|
|
|
v-for="p in pillars"
|
|
|
:id="`product-${p.key}`"
|
|
|
:key="p.key"
|
|
|
- :class="{
|
|
|
- 'section--soft': p.title === '智慧注气',
|
|
|
- productSection: true,
|
|
|
- }"
|
|
|
- style="padding: 0 100px"
|
|
|
+ class="productSection"
|
|
|
>
|
|
|
<header class="productHead">
|
|
|
<div class="productHeadTop">
|
|
|
@@ -221,18 +313,59 @@ onBeforeUnmount(() => {
|
|
|
<p class="productLead">{{ p.lead }}</p>
|
|
|
</header>
|
|
|
|
|
|
- <div v-if="p.key === 'platform'" class="platformLayout">
|
|
|
- <div class="platformImage">
|
|
|
- <img :src="p.cover" :alt="p.title" />
|
|
|
+ <div class="tabContentLayout">
|
|
|
+ <div class="tabContentImage">
|
|
|
+ <div class="imageCarouselWrapper">
|
|
|
+ <div class="imageCarouselViewport">
|
|
|
+ <Transition name="image-carousel-slide" mode="out-in">
|
|
|
+ <div
|
|
|
+ v-if="p.items?.length"
|
|
|
+ :key="`${p.key}-${getActiveIndex(p.key, p.items.length)}`"
|
|
|
+ class="imageCarouselSlide"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ :src="getActiveImageSrc(p)"
|
|
|
+ :alt="getActiveImageAlt(p)"
|
|
|
+ loading="lazy"
|
|
|
+ decoding="async"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="carouselIndicators">
|
|
|
+ <button
|
|
|
+ v-for="(item, idx) in p.items"
|
|
|
+ :key="idx"
|
|
|
+ :class="[
|
|
|
+ 'indicator',
|
|
|
+ { 'is-active': idx === currentImageIndex[p.key] },
|
|
|
+ ]"
|
|
|
+ @click="goToImageSlide(p.key, idx, p.items.length)"
|
|
|
+ @mouseenter="stopImageCarousel(p.key)"
|
|
|
+ @mouseleave="startImageCarousel(p.key, p.items.length)"
|
|
|
+ :aria-label="`第${idx + 1}张图片`"
|
|
|
+ ></button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div class="platformFeatures">
|
|
|
+ <div class="tabContentFeatures">
|
|
|
<div
|
|
|
- v-for="it in p.items"
|
|
|
+ v-for="(it, idx) in p.items"
|
|
|
:key="it.title"
|
|
|
- class="platformFeature"
|
|
|
+ class="tabFeatureRow"
|
|
|
+ :class="{
|
|
|
+ 'is-highlighted': idx === currentImageIndex[p.key],
|
|
|
+ }"
|
|
|
+ @mouseenter="
|
|
|
+ () => {
|
|
|
+ stopImageCarousel(p.key);
|
|
|
+ currentImageIndex[p.key] = idx;
|
|
|
+ }
|
|
|
+ "
|
|
|
+ @mouseleave="() => startImageCarousel(p.key, p.items.length)"
|
|
|
>
|
|
|
- <div class="featureDot" aria-hidden="true"></div>
|
|
|
- <div class="featureMain">
|
|
|
+ <div class="tabFeatureMain">
|
|
|
<div class="featureTitle">{{ it.title }}</div>
|
|
|
<div class="muted featureDesc">{{ it.desc }}</div>
|
|
|
</div>
|
|
|
@@ -240,15 +373,22 @@ onBeforeUnmount(() => {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div v-else class="tabContentLayout" style="padding-bottom: 20px">
|
|
|
- <div class="tabContentImage">
|
|
|
- <img :src="p.cover" :alt="p.title" />
|
|
|
- </div>
|
|
|
+ <!-- <div class="tabContentLayout" v-else>
|
|
|
<div class="tabContentFeatures">
|
|
|
<div
|
|
|
v-for="(it, idx) in p.items"
|
|
|
:key="it.title"
|
|
|
class="tabFeatureRow"
|
|
|
+ :class="{
|
|
|
+ 'is-highlighted': idx === currentImageIndex[p.key],
|
|
|
+ }"
|
|
|
+ @mouseenter="
|
|
|
+ () => {
|
|
|
+ stopImageCarousel(p.key);
|
|
|
+ currentImageIndex[p.key] = idx;
|
|
|
+ }
|
|
|
+ "
|
|
|
+ @mouseleave="() => startImageCarousel(p.key, p.items.length)"
|
|
|
>
|
|
|
<div class="tabFeatureMain">
|
|
|
<div class="featureTitle">{{ it.title }}</div>
|
|
|
@@ -256,7 +396,42 @@ onBeforeUnmount(() => {
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ <div class="tabContentImage">
|
|
|
+ <div class="imageCarouselWrapper">
|
|
|
+ <div class="imageCarouselViewport">
|
|
|
+ <Transition name="image-carousel-slide" mode="out-in">
|
|
|
+ <div
|
|
|
+ v-if="p.items?.length"
|
|
|
+ :key="`${p.key}-${getActiveIndex(p.key, p.items.length)}`"
|
|
|
+ class="imageCarouselSlide"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ :src="getActiveImageSrc(p)"
|
|
|
+ :alt="getActiveImageAlt(p)"
|
|
|
+ loading="lazy"
|
|
|
+ decoding="async"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="carouselIndicators">
|
|
|
+ <button
|
|
|
+ v-for="(item, idx) in p.items"
|
|
|
+ :key="idx"
|
|
|
+ :class="[
|
|
|
+ 'indicator',
|
|
|
+ { 'is-active': idx === currentImageIndex[p.key] },
|
|
|
+ ]"
|
|
|
+ @click="goToImageSlide(p.key, idx, p.items.length)"
|
|
|
+ @mouseenter="stopImageCarousel(p.key)"
|
|
|
+ @mouseleave="startImageCarousel(p.key, p.items.length)"
|
|
|
+ :aria-label="`第${idx + 1}张图片`"
|
|
|
+ ></button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div> -->
|
|
|
</section>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -731,4 +906,455 @@ onBeforeUnmount(() => {
|
|
|
:deep(.pageHero__subtitle) {
|
|
|
color: #4f5055;
|
|
|
}
|
|
|
+
|
|
|
+.productSection {
|
|
|
+ scroll-margin-top: calc(var(--header-h) + 84px);
|
|
|
+ border: 0;
|
|
|
+ border-radius: 0;
|
|
|
+ background: transparent;
|
|
|
+ box-shadow: none;
|
|
|
+ margin-top: 50px;
|
|
|
+}
|
|
|
+
|
|
|
+.productHead {
|
|
|
+ padding: 18px 18px 14px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.productHeadTop {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.productTitle {
|
|
|
+ letter-spacing: -0.02em;
|
|
|
+ color: var(--ink);
|
|
|
+ font-size: 30px;
|
|
|
+ line-height: 1.25;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.productLead {
|
|
|
+ margin-top: 10px;
|
|
|
+ max-width: 92ch;
|
|
|
+ text-align: center;
|
|
|
+ margin-left: auto;
|
|
|
+ margin-right: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.platformLayout {
|
|
|
+ margin-top: 40px;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 50px;
|
|
|
+ align-items: start;
|
|
|
+}
|
|
|
+
|
|
|
+.platformImage {
|
|
|
+ /* border-radius: 16px; */
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 12px 40px rgba(2, 6, 23, 0.12);
|
|
|
+}
|
|
|
+
|
|
|
+.platformImage img {
|
|
|
+ width: 100%;
|
|
|
+ height: auto;
|
|
|
+ object-fit: contain;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.platformFeatures {
|
|
|
+ display: grid;
|
|
|
+ gap: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.platformFeature {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 22px 1fr;
|
|
|
+ align-items: start;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 22px;
|
|
|
+ padding-bottom: 0;
|
|
|
+ background: #fff;
|
|
|
+ /* border-radius: 16px; */
|
|
|
+ box-shadow: 0 8px 24px rgba(2, 6, 23, 0.08);
|
|
|
+ min-height: 160px;
|
|
|
+ transition:
|
|
|
+ transform 180ms ease,
|
|
|
+ box-shadow 180ms ease;
|
|
|
+}
|
|
|
+
|
|
|
+.platformFeature:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 12px 32px rgba(2, 6, 23, 0.12);
|
|
|
+}
|
|
|
+
|
|
|
+.tabContentLayout {
|
|
|
+ margin-top: 40px;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1.2fr;
|
|
|
+ gap: 50px;
|
|
|
+ align-items: start;
|
|
|
+}
|
|
|
+
|
|
|
+.tabContentImage {
|
|
|
+ /* border-radius: 16px; */
|
|
|
+ --carousel-h: 500px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 12px 40px rgba(2, 6, 23, 0.12);
|
|
|
+ position: sticky;
|
|
|
+ top: calc(var(--header-h) + 100px);
|
|
|
+}
|
|
|
+
|
|
|
+.tabContentImage img {
|
|
|
+ width: 100%;
|
|
|
+ height: var(--carousel-h);
|
|
|
+ object-fit: cover;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.tabContentFeatures {
|
|
|
+ display: grid;
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.tabFeatureRow {
|
|
|
+ padding: 28px;
|
|
|
+ background: #fff;
|
|
|
+ /* border-radius: 16px; */
|
|
|
+ box-shadow: 0 8px 24px rgba(2, 6, 23, 0.08);
|
|
|
+ min-height: 140px;
|
|
|
+ transition:
|
|
|
+ transform 180ms ease,
|
|
|
+ box-shadow 180ms ease;
|
|
|
+}
|
|
|
+
|
|
|
+.tabFeatureRow:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 12px 32px rgba(2, 6, 23, 0.12);
|
|
|
+}
|
|
|
+
|
|
|
+.featureDot {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 999px;
|
|
|
+ background: linear-gradient(180deg, #0766cd, #0d4a9e);
|
|
|
+ box-shadow:
|
|
|
+ 0 10px 22px rgba(10, 71, 156, 0.25),
|
|
|
+ inset 0 0 0 2px rgba(255, 255, 255, 0.85);
|
|
|
+ margin-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.featureMain,
|
|
|
+.tabFeatureMain {
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.featureTitle {
|
|
|
+ letter-spacing: -0.01em;
|
|
|
+ color: rgba(2, 6, 23, 0.9);
|
|
|
+ font-size: 22px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1.35;
|
|
|
+}
|
|
|
+
|
|
|
+.featureDesc {
|
|
|
+ margin-top: 10px;
|
|
|
+ line-height: 1.75;
|
|
|
+ color: #666666;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.deliverTitle {
|
|
|
+ /* font-weight: 900; */
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 960px) {
|
|
|
+ .sectionHead {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tabBar {
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+
|
|
|
+ .platformLayout,
|
|
|
+ .tabContentLayout {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ gap: 30px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tabContentImage {
|
|
|
+ position: static;
|
|
|
+ --carousel-h: 300px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 560px) {
|
|
|
+ .platformFeature,
|
|
|
+ .tabFeatureRow {
|
|
|
+ padding: 20px;
|
|
|
+ min-height: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .featureTitle {
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .featureDesc {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (prefers-reduced-motion: reduce) {
|
|
|
+ .tabBtn {
|
|
|
+ transition: none !important;
|
|
|
+ transform: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tabText {
|
|
|
+ transition: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .platformFeature,
|
|
|
+ .tabFeatureRow {
|
|
|
+ transition: none !important;
|
|
|
+ transform: none !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.imageCarouselWrapper {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.imageCarouselViewport {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+ height: var(--carousel-h);
|
|
|
+}
|
|
|
+
|
|
|
+.imageCarouselSlide {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.imageCarouselSlide img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.imageOverlay {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ padding: 20px;
|
|
|
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.overlayText {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1.4;
|
|
|
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+.image-carousel-slide-enter-active,
|
|
|
+.image-carousel-slide-leave-active {
|
|
|
+ transition: all 0.6s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.image-carousel-slide-enter-from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(50px);
|
|
|
+}
|
|
|
+
|
|
|
+.image-carousel-slide-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(-50px);
|
|
|
+}
|
|
|
+
|
|
|
+.carouselBtn {
|
|
|
+ width: 44px;
|
|
|
+ height: 44px;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 2px solid rgba(7, 102, 205, 0.3);
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ color: #0766cd;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ flex-shrink: 0;
|
|
|
+ z-index: 2;
|
|
|
+}
|
|
|
+
|
|
|
+.carouselBtn:hover {
|
|
|
+ background: #0766cd;
|
|
|
+ color: #fff;
|
|
|
+ border-color: #0766cd;
|
|
|
+ transform: scale(1.08);
|
|
|
+}
|
|
|
+
|
|
|
+.carouselBtn:focus-visible {
|
|
|
+ outline: none;
|
|
|
+ box-shadow: 0 0 0 3px rgba(7, 102, 205, 0.24);
|
|
|
+}
|
|
|
+
|
|
|
+.carouselIndicators {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: end;
|
|
|
+ gap: 10px;
|
|
|
+ /* margin-top: 16px; */
|
|
|
+ padding-right: 10px;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ top: -300px;
|
|
|
+}
|
|
|
+
|
|
|
+.indicator {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ /* border-radius: 50%; */
|
|
|
+ border: none;
|
|
|
+ background: rgba(7, 102, 205, 0.25);
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 0;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.indicator:hover {
|
|
|
+ background: rgba(7, 102, 205, 0.6);
|
|
|
+ transform: scale(1.3);
|
|
|
+}
|
|
|
+
|
|
|
+.indicator.is-active {
|
|
|
+ background: #0766cd;
|
|
|
+}
|
|
|
+
|
|
|
+.indicator:focus-visible {
|
|
|
+ outline: none;
|
|
|
+ box-shadow: 0 0 0 3px rgba(7, 102, 205, 0.24);
|
|
|
+}
|
|
|
+
|
|
|
+.tabFeatureRow {
|
|
|
+ padding: 28px;
|
|
|
+ background: #fff;
|
|
|
+ /* border-radius: 16px; */
|
|
|
+ box-shadow: 0 8px 24px rgba(2, 6, 23, 0.08);
|
|
|
+ min-height: 140px;
|
|
|
+ transition:
|
|
|
+ transform 180ms ease,
|
|
|
+ box-shadow 180ms ease,
|
|
|
+ border-left 0.3s ease;
|
|
|
+ border-left: 3px solid transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.tabFeatureRow:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 12px 32px rgba(2, 6, 23, 0.12);
|
|
|
+}
|
|
|
+
|
|
|
+.tabFeatureRow.is-highlighted {
|
|
|
+ border-left: 3px solid #0766cd;
|
|
|
+ box-shadow: 0 8px 24px rgba(7, 102, 205, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+/* ... existing code ... */
|
|
|
+
|
|
|
+.tabContentImage {
|
|
|
+ /* border-radius: 16px; */
|
|
|
+ --carousel-h: 500px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 12px 40px rgba(2, 6, 23, 0.12);
|
|
|
+ position: sticky;
|
|
|
+ top: calc(var(--header-h) + 100px);
|
|
|
+}
|
|
|
+
|
|
|
+.imageCarouselWrapper {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.imageCarouselViewport {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.imageCarouselSlide {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.imageCarouselSlide img {
|
|
|
+ width: 100%;
|
|
|
+ height: 500px;
|
|
|
+ object-fit: cover;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.carouselIndicators {
|
|
|
+ position: absolute;
|
|
|
+ right: 20px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
+.indicator {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: none;
|
|
|
+ background: rgba(255, 255, 255, 0.5);
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 0;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.indicator:hover {
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ transform: scale(1.3);
|
|
|
+}
|
|
|
+
|
|
|
+.indicator.is-active {
|
|
|
+ background: #014198;
|
|
|
+ width: 6px;
|
|
|
+ height: 20px;
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.indicator:focus-visible {
|
|
|
+ outline: none;
|
|
|
+ box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.5);
|
|
|
+}
|
|
|
</style>
|