yanghao 1 miesiąc temu
rodzic
commit
aceb3a468c

BIN
src/assets/images/a.png


BIN
src/assets/images/b.png


BIN
src/assets/images/bg3.png


BIN
src/assets/images/c.png


BIN
src/assets/images/d.png


BIN
src/assets/images/e.png


BIN
src/assets/images/f.png


BIN
src/assets/images/g.png


BIN
src/assets/images/h.png


BIN
src/assets/images/k.png


BIN
src/assets/images/logo.png


BIN
src/assets/images/m.png


+ 1 - 1
src/components/layout/AppHeader.vue

@@ -70,7 +70,7 @@ const mobileLabel = computed(() =>
           <img
             src="../../assets//images/logo.png"
             alt=""
-            style="width: auto; height: 36px"
+            style="width: auto; height: 26px"
           />
         </RouterLink>
 

+ 134 - 8
src/views/HomeView.vue

@@ -571,7 +571,27 @@ const partners = Array.from({ length: 10 }).map((_, idx) => ({
                 class="capModuleTile"
                 role="listitem"
               >
-                <div class="capModuleIcon" aria-hidden="true">
+                <div
+                  class="capModuleIcon"
+                  v-if="p.title === '连接现场设备'"
+                  aria-hidden="true"
+                >
+                  <Icon :icon="m.icon" width="26" height="26" />
+                </div>
+
+                <div
+                  class="capModuleIcon2"
+                  v-if="p.title === '数据资产治理'"
+                  aria-hidden="true"
+                >
+                  <Icon :icon="m.icon" width="26" height="26" />
+                </div>
+
+                <div
+                  class="capModuleIcon3"
+                  v-if="p.title === '生产运营决策'"
+                  aria-hidden="true"
+                >
                   <Icon :icon="m.icon" width="26" height="26" />
                 </div>
                 <div class="capModuleName">{{ m.title }}</div>
@@ -689,7 +709,68 @@ const partners = Array.from({ length: 10 }).map((_, idx) => ({
                   <div class="solutionsTabDesc">
                     {{ solutions[solutionsTabIndex].desc }}
                   </div>
-                  <RouterLink class="solutionsTabLink" to="/solutions">
+                  <RouterLink
+                    class="solutionsTabLink"
+                    v-if="solutions[solutionsTabIndex].title === '设备管理'"
+                    to="/products"
+                  >
+                    了解更多<Icon
+                      icon="lucide:chevron-right"
+                      width="20"
+                      height="20"
+                    />
+                  </RouterLink>
+
+                  <RouterLink
+                    class="solutionsTabLink"
+                    v-if="solutions[solutionsTabIndex].title === '智能钻修井'"
+                    to="/solutions"
+                  >
+                    了解更多<Icon
+                      icon="lucide:chevron-right"
+                      width="20"
+                      height="20"
+                    />
+                  </RouterLink>
+
+                  <RouterLink
+                    class="solutionsTabLink"
+                    v-if="solutions[solutionsTabIndex].title === '智慧注气'"
+                    to="/solutions"
+                  >
+                    了解更多<Icon
+                      icon="lucide:chevron-right"
+                      width="20"
+                      height="20"
+                    />
+                  </RouterLink>
+
+                  <RouterLink
+                    class="solutionsTabLink"
+                    v-if="solutions[solutionsTabIndex].title === '智慧压裂'"
+                  >
+                    了解更多<Icon
+                      icon="lucide:chevron-right"
+                      width="20"
+                      height="20"
+                    />
+                  </RouterLink>
+
+                  <RouterLink
+                    class="solutionsTabLink"
+                    v-if="solutions[solutionsTabIndex].title === '智慧管网'"
+                  >
+                    了解更多<Icon
+                      icon="lucide:chevron-right"
+                      width="20"
+                      height="20"
+                    />
+                  </RouterLink>
+
+                  <RouterLink
+                    class="solutionsTabLink"
+                    v-if="solutions[solutionsTabIndex].title === '场站管理'"
+                  >
                     了解更多<Icon
                       icon="lucide:chevron-right"
                       width="20"
@@ -720,7 +801,7 @@ const partners = Array.from({ length: 10 }).map((_, idx) => ({
           </div>
         </Reveal>
         <div style="display: flex; justify-content: center; margin-top: 20px">
-          <RouterLink class="btn btn-link" to="/cases"
+          <RouterLink class="btn btn-link" to="/cases/1"
             >查看更多案例
             <Icon icon="lucide:chevron-right" width="24" height="24"
           /></RouterLink>
@@ -813,7 +894,32 @@ const partners = Array.from({ length: 10 }).map((_, idx) => ({
                       </div>
                     </div>
                   </div>
-                  <RouterLink class="valueAccBtn" to="/cases"
+                  <RouterLink v-if="c.title === '采油厂'" class="valueAccBtn"
+                    >了解更多</RouterLink
+                  >
+
+                  <RouterLink v-if="c.title === '集输站'" class="valueAccBtn"
+                    >了解更多</RouterLink
+                  >
+
+                  <RouterLink
+                    v-if="c.title === '注水注气'"
+                    to="/cases/3"
+                    class="valueAccBtn"
+                    >了解更多</RouterLink
+                  >
+
+                  <RouterLink
+                    v-if="c.title === '设备运维'"
+                    to="/products"
+                    class="valueAccBtn"
+                    >了解更多</RouterLink
+                  >
+
+                  <RouterLink
+                    v-if="c.title === 'QHSE'"
+                    to="/products"
+                    class="valueAccBtn"
                     >了解更多</RouterLink
                   >
                 </div>
@@ -1651,13 +1757,33 @@ const partners = Array.from({ length: 10 }).map((_, idx) => ({
   border-radius: 16px;
   display: grid;
   place-items: center;
-  border: 1px solid rgb(var(--cap-accent, 37 99 235) / 0.16);
+
+  color: rgb(var(--cap-accent, 37 99 235) / 1);
+}
+
+.capModuleIcon2 {
+  width: 46px;
+  height: 46px;
+  border-radius: 16px;
+  display: grid;
+  place-items: center;
+
+  color: #00b259;
+}
+
+.capModuleIcon3 {
+  width: 46px;
+  height: 46px;
+  border-radius: 16px;
+  display: grid;
+  place-items: center;
+
   background: radial-gradient(
     circle at 30% 30%,
-    rgb(var(--cap-accent, 37 99 235) / 0.22),
-    rgb(var(--cap-accent, 37 99 235) / 0.06)
+    rgb(250, 170, 27 / 0.22),
+    rgb(250, 170, 27 / 0.06)
   );
-  color: rgb(var(--cap-accent, 37 99 235) / 1);
+  color: #faaa1b;
 }
 
 .capModuleName {

+ 407 - 19
src/views/ProductsView.vue

@@ -25,14 +25,17 @@ const pillars = [
       {
         title: "油气专属底座 · 适配全场景需求",
         desc: "聚焦油气领域专属场景,深度适配数采、大模型相关各类应用需求,可灵活承载勘探、开发等全环节数字化场景,贴合行业业务特性。",
+        image: platformCover,
       },
       {
         title: "高兼容架构 · 打破系统壁垒",
         desc: "采用高兼容技术架构,可无缝对接油气领域各类数采设备、现有系统及大模型应用,实现数据互通、系统联动,降低对接成本。",
+        image: new URL("../assets/images/news3.png", import.meta.url).href,
       },
       {
         title: "灵活可扩展 · 承载多元需求",
         desc: "平台底座具备高度灵活性与可扩展性,可根据油气领域不同场景、不同需求,灵活承载数采、大模型相关各类数字化应用落地。",
+        image: new URL("../assets/images/case7.png", import.meta.url).href,
       },
     ],
   },
@@ -46,18 +49,22 @@ const pillars = [
       {
         title: "全维度设备管理 · 筑牢管控基础",
         desc: "依托平台底座,承载油气领域设备全生命周期管理场景,兼容各类数采设备,实现设备台账、参数信息统一管控,适配不同型号、不同环节油气设备,高效统筹设备资源。",
+        image: equipmentCover,
       },
       {
         title: "智慧维保巡检 · 降低运维成本",
         desc: "联动巡检、维保全流程,支持自定义巡检计划与维保流程,无缝对接现场数采数据,实现维保巡检标准化、规范化,减少人工冗余,提高运维效率。",
+        image: new URL("../assets/images/a.png", import.meta.url).href,
       },
       {
         title: "预测性维护 · 防范设备风险",
         desc: "借助平台大模型与数采技术支撑,实时分析设备运行数据,精准预测潜在故障并提前触发维护提醒,规避停机风险,降低故障损失。",
+        image: new URL("../assets/images/b.png", import.meta.url).href,
       },
       {
         title: "实时监控预警 · 保障作业安全",
         desc: "实时采集运行参数,实现设备状态可视化监控;异常情况快速预警、精准推送,适配油气现场复杂作业环境,筑牢安全生产防线。",
+        image: new URL("../assets/images/c.png", import.meta.url).href,
       },
     ],
   },
@@ -71,18 +78,22 @@ const pillars = [
       {
         title: "项目任务管理 · 联动平台高效统筹",
         desc: "承接油气领域生产项目全流程任务管理,无缝对接平台数据体系,实现任务分配、进度跟踪、闭环管理,统筹项目各环节,确保生产任务有序推进。",
+        image: new URL("../assets/images/d.png", import.meta.url).href,
       },
       {
         title: "计划与实际联动 · 精准把控生产节奏",
         desc: "基于平台数采能力,科学制定生产计划;同步对接生产实际数据,自动匹配计划与实际差异,实时调整生产安排,保障流程高效衔接。",
+        image: new URL("../assets/images/e.png", import.meta.url).href,
       },
       {
         title: "自动报表生成 · 沉淀核心数据资产",
         desc: "自动汇总数采数据、生产进度与任务完成情况,一键生成日报、月报等报表,减少人工录入,实现生产数据规范化保存与资产沉淀。",
+        image: new URL("../assets/images/f.png", import.meta.url).href,
       },
       {
         title: "全流程数智赋能 · 提升生产整体效能",
         desc: "深度联动平台数采与数据管理能力,打造生产计划、任务执行、数据统计全链条,减少人工冗余,提升协同效率,实现数字化、智能化转型。",
+        image: new URL("../assets/images/g.png", import.meta.url).href,
       },
     ],
   },
@@ -96,18 +107,22 @@ const pillars = [
       {
         title: "合规适配 QHSE 体系 · 筑牢安全底线",
         desc: "全面贴合 QHSE 管理体系标准,覆盖风险隐患、作业许可、环境管理等核心模块,确保管理流程合规达标,契合油气行业安全环保监管要求,规避合规风险。",
+        image: new URL("../assets/images/value5.jpg", import.meta.url).href,
       },
       {
         title: "多系统集成联动 · 实现协同管控",
         desc: "深度集成设备管理、生产管理等系统,实现数据互通、流程联动,同步设备状态、作业计划等数据,打破系统壁垒,构建安全 + 生产 + 设备一体化管控体系。",
+        image: new URL("../assets/images/h.png", import.meta.url).href,
       },
       {
         title: "全维度风险防控 · 保障作业安全",
         desc: "整合风险隐患管理、应急与健康管理、作业许可与过程监督等功能,借助数采与数据处理能力,实现风险精准识别、隐患闭环治理、作业全过程可控。",
+        image: new URL("../assets/images/m.png", import.meta.url).href,
       },
       {
         title: "体系化支撑 · 赋能管理升级",
         desc: "涵盖目标管理、培训与体系支持等模块,联动平台数据资产,实现目标拆解、培训落地与体系落地,推动从被动应对到主动防控转型,提升管理效能。",
+        image: new URL("../assets/images/k.png", import.meta.url).href,
       },
     ],
   },
@@ -141,6 +156,58 @@ function scrollToTab(key) {
 }
 
 let observer = null;
+
+const currentImageIndex = ref({
+  platform: 0,
+  equipment: 0,
+  production: 0,
+  qhse: 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);
+};
+
 onMounted(() => {
   const offset = headerOffsetPx();
   observer = new IntersectionObserver(
@@ -164,11 +231,21 @@ 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);
+  }
 });
 
 onBeforeUnmount(() => {
   observer?.disconnect?.();
   observer = null;
+
+  for (const key of Object.keys(imageTimers.value || {})) {
+    stopImageCarousel(key);
+  }
 });
 </script>
 
@@ -230,18 +307,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>
@@ -249,15 +367,22 @@ onBeforeUnmount(() => {
               </div>
             </div>
 
-            <div v-else class="tabContentLayout">
-              <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>
@@ -265,7 +390,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>
@@ -327,7 +487,7 @@ onBeforeUnmount(() => {
   background-repeat: no-repeat, no-repeat;
   background-blend-mode: overlay;
   color: #111;
-  height: 40vh;
+  height: 50vh;
   border-bottom: none;
   padding: 80px 0;
 }
@@ -492,6 +652,7 @@ onBeforeUnmount(() => {
 
 .tabContentImage {
   /* border-radius: 16px; */
+  --carousel-h: 500px;
   overflow: hidden;
   box-shadow: 0 12px 40px rgba(2, 6, 23, 0.12);
   position: sticky;
@@ -500,7 +661,7 @@ onBeforeUnmount(() => {
 
 .tabContentImage img {
   width: 100%;
-  height: 500px;
+  height: var(--carousel-h);
   object-fit: cover;
   display: block;
 }
@@ -581,10 +742,7 @@ onBeforeUnmount(() => {
 
   .tabContentImage {
     position: static;
-  }
-
-  .tabContentImage img {
-    height: 300px;
+    --carousel-h: 300px;
   }
 }
 
@@ -620,4 +778,234 @@ onBeforeUnmount(() => {
     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>