Compare commits
7 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
1ffc0e9be6 | 6 days ago |
|
|
72632f097b | 3 weeks ago |
|
|
720866e042 | 4 weeks ago |
|
|
106bf38909 | 1 month ago |
|
|
d72526ebee | 1 month ago |
|
|
6a461052f9 | 2 months ago |
|
|
5519bdf676 | 2 months ago |
@ -0,0 +1,935 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Microgrid | Smart Energy System</title>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
||||
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;700;900&family=Montserrat:wght@400;500;700;800;900&family=Inter:wght@400;500;700;800;900&family=JetBrains+Mono:wght@500&display=swap" rel="stylesheet"/>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.min.css" />
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.css" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/vanilla-tilt/1.7.2/vanilla-tilt.min.js"></script>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"primary": "#13eca4",
|
||||
"primary-dark": "#0da670", // Added for contrast on light mode
|
||||
"background-light": "#f8fcfa",
|
||||
"background-dark": "#10221c",
|
||||
"pearlescent": "#f0f7f5",
|
||||
"silver-accent": "#e2e8f0",
|
||||
"accent": "#ff6b00"
|
||||
},
|
||||
fontFamily: {
|
||||
"display": ["Montserrat", "sans-serif"],
|
||||
"sans": ["Montserrat", "sans-serif"],
|
||||
"mono": ["JetBrains Mono", "Montserrat"],
|
||||
"inter": ["Inter", "Montserrat"],
|
||||
},
|
||||
backgroundImage: {
|
||||
'liquid-gradient': 'linear-gradient(135deg, #f8fcfa 0%, #e8f5f1 50%, #dcfce7 100%)',
|
||||
'glass-gradient': 'linear-gradient(180deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.4) 100%)',
|
||||
'aurora': 'linear-gradient(135deg, rgba(19,236,164,0.15) 0%, rgba(13,166,112,0.08) 50%, rgba(248,252,250,0) 100%)',
|
||||
},
|
||||
boxShadow: {
|
||||
'glow': '0 0 20px rgba(19, 236, 164, 0.4)',
|
||||
'bento': '0 20px 40px -15px rgba(0,0,0,0.05), 0 0 0 1px rgba(255,255,255,0.6)',
|
||||
'bento-hover': '0 30px 60px -20px rgba(19,236,164,0.25), 0 0 0 1px rgba(19,236,164,0.5)',
|
||||
'glass': '0 8px 32px 0 rgba(13, 166, 112, 0.1)',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0', transform: 'translateY(10px)' },
|
||||
'100%': { opacity: '1', transform: 'translateY(0)' },
|
||||
},
|
||||
dash: {
|
||||
'0%': { strokeDashoffset: '1000' },
|
||||
'100%': { strokeDashoffset: '0' },
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fadeIn 0.5s ease-out forwards',
|
||||
'dash-flow': 'dash 3s linear infinite',
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-liquid-gradient text-background-dark font-sans antialiased overflow-x-hidden selection:bg-primary selection:text-white min-h-screen">
|
||||
|
||||
<!-- Three.js Canvas Container -->
|
||||
<div id="canvas-container" class="fixed inset-0 z-0 pointer-events-none opacity-80"></div>
|
||||
<div class="fixed inset-0 bg-gradient-to-t from-background-light via-background-light/40 to-transparent z-0 pointer-events-none"></div>
|
||||
|
||||
<main class="relative z-10 mx-auto ">
|
||||
|
||||
<!-- === Hero Section === -->
|
||||
<section id="mod-hero" class="min-h-screen flex flex-col justify-center text-center">
|
||||
<span class="text-primary font-mono text-sm tracking-[0.3em] uppercase mb-6 " data-aos="fade-down">
|
||||
Smart Energy System
|
||||
</span>
|
||||
<h1 class="text-4xl md:text-[2.66rem] leading-tight tracking-tighter text-background-dark mb-8 drop-shadow-sm" data-aos="zoom-in">
|
||||
INTEGRATED INDEPENDENT
|
||||
<span class="text-primary">INTELLIGENT.</span>
|
||||
</h1>
|
||||
<p class="text-[1rem] text-background-dark/70 font-medium max-w-4xl mx-auto leading-relaxed md:text-center" data-aos="fade-up">
|
||||
The Next Generation of Microgrid Solutions Empowering industrial parks and zero-carbon communities with seamless, green, and autonomous energy.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- === 系统拓扑图模块 === -->
|
||||
<section id="mod-topology" class="min-h-screen flex flex-col justify-center items-center text-center py-24 relative z-10 overflow-hidden">
|
||||
|
||||
<!-- 明亮网格背景 -->
|
||||
<div class="absolute inset-0 z-0 opacity-[0.4] pointer-events-none hex-bg"></div>
|
||||
<div class="absolute inset-0 z-0 opacity-[0.5] pointer-events-none" style="background-image: linear-gradient(rgba(19,236,164,0.2) 1px, transparent 1px), linear-gradient(90deg, rgba(19,236,164,0.2) 1px, transparent 1px); background-size: 60px 60px; transform: perspective(800px) rotateX(60deg) scale(2.5) translateY(-50px); transform-origin: top center;"></div>
|
||||
|
||||
<style>
|
||||
.hex-bg { background-image: url("data:image/svg+xml,%3Csvg width='24' height='40' viewBox='0 0 24 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 10L12 3l12 7v14l-12 7-12-7V10z' stroke='rgba(13,166,112,0.1)' fill='none' fill-rule='evenodd'/%3E%3C/svg%3E"); }
|
||||
.cyber-clip { clip-path: polygon(0 15px, 15px 0, 100% 0, 100% calc(100% - 15px), calc(100% - 15px) 100%, 0 100%); }
|
||||
.cyber-hub { clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); }
|
||||
.tilt-card { transform-style: preserve-3d; will-change: transform; transform: perspective(1000px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) scale3d(1, 1, 1); transition: transform 0.1s cubic-bezier(0.25, 0.46, 0.45, 0.94), border-color 0.4s ease; }
|
||||
.tilt-card.reset { transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94), border-color 0.4s ease; }
|
||||
.svg-circuit-line { fill: none; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.flow-packet { stroke-dasharray: 15, 150; animation: dashFlow 3s linear infinite; }
|
||||
@keyframes dashFlow { 0% { stroke-dashoffset: 165; } 100% { stroke-dashoffset: 0; } }
|
||||
.layer-stats { opacity: 1; transition: opacity 0.3s ease, transform 0.3s ease; transform: translateY(0); }
|
||||
.layer-text { opacity: 0; pointer-events: none; transition: opacity 0.3s ease, transform 0.3s ease; transform: translateY(10px); }
|
||||
.tilt-card:hover .layer-stats { opacity: 0; transform: translateY(-10px); pointer-events: none; }
|
||||
.tilt-card:hover .layer-text { opacity: 1; pointer-events: auto; transform: translateY(0); }
|
||||
</style>
|
||||
|
||||
<div class="relative z-30 mb-20" data-aos="fade-down">
|
||||
<h2 class="text-4xl md:text-[2.66rem] mb-4 uppercase tracking-[0.2em] text-background-dark flex items-center justify-center gap-4">
|
||||
CORE ARCHITECTURE OF MICROGRID SYSTEM
|
||||
</h2>
|
||||
<div class="flex items-center justify-center gap-4 text-primary font-mono text-xs tracking-[0.4em] ">
|
||||
<span class="w-8 h-[2px] bg-gradient-to-r from-transparent to-primary-dark"></span>
|
||||
INTELLIGENT MICROGRID NETWORK
|
||||
<span class="w-8 h-[2px] bg-gradient-to-l from-transparent to-primary-dark"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative w-full max-w-[1400px] mx-auto z-30 flex items-center justify-center px-10 lg:px-20 min-h-[500px]">
|
||||
|
||||
<div class="hidden lg:block absolute inset-0 z-0 pointer-events-none">
|
||||
<svg class="w-full h-full drop-shadow-[0_0_8px_rgba(19,236,164,0.4)]" preserveAspectRatio="xMidYMid meet" viewBox="0 0 1400 500">
|
||||
<path d="M 380 130 L 450 130 Q 480 130 480 160 L 480 250 L 550 250" class="svg-circuit-line stroke-primary/30" />
|
||||
<path d="M 380 130 L 450 130 Q 480 130 480 160 L 480 250 L 550 250" class="svg-circuit-line stroke-primary-dark flow-packet" />
|
||||
<path d="M 380 370 L 450 370 Q 480 370 480 340 L 480 250 L 550 250" class="svg-circuit-line stroke-accent/30" />
|
||||
<path d="M 380 370 L 450 370 Q 480 370 480 340 L 480 250 L 550 250" class="svg-circuit-line stroke-accent flow-packet" style="animation-duration: 2.5s; animation-direction: reverse;" />
|
||||
<path d="M 850 250 L 1020 250" class="svg-circuit-line stroke-primary/30" />
|
||||
<path d="M 850 250 L 1020 250" class="svg-circuit-line stroke-primary-dark flow-packet" style="stroke-dasharray: 20, 100; animation-duration: 1.5s;" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-[minmax(300px,1.2fr)_auto_minmax(300px,1.2fr)] gap-8 lg:gap-16 w-full relative z-10 items-center">
|
||||
|
||||
<div class="flex flex-col gap-8 relative z-10">
|
||||
<!-- Card 1 -->
|
||||
<div class="tilt-card relative bg-white/70 backdrop-blur-xl border border-white p-8 cyber-clip cursor-pointer hover:border-primary/50 shadow-glass text-left w-full lg:w-[290px] ml-auto group">
|
||||
<div class="flex items-start justify-between mb-6 relative z-10">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-12 h-12 bg-primary/10 rounded flex items-center justify-center border border-primary/30 text-primary group-hover:bg-primary group-hover:text-white transition-colors duration-300 shadow-sm">
|
||||
<span class="material-symbols-outlined text-2xl"></span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl text-background-dark tracking-wide">Power Gen</h3>
|
||||
<div class="text-[10px] text-primary font-mono mt-1 ">NODE: GEN_01</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative h-[80px] w-full">
|
||||
<div class="layer-stats absolute inset-0 w-full grid grid-cols-2 gap-3 font-mono text-xs">
|
||||
<div class="bg-primary/5 border border-primary/20 p-3 text-primary flex flex-col justify-center rounded-sm">
|
||||
<span class="text-background-dark/50 text-[10px] mb-1 ">VOLTAGE OUT</span>
|
||||
<span class="text-base ">400V DC</span>
|
||||
</div>
|
||||
<div class="bg-primary/5 border border-primary/20 p-3 text-primary flex flex-col justify-center rounded-sm">
|
||||
<span class="text-background-dark/50 text-[10px] mb-1 ">STATUS</span>
|
||||
<span class="text-base animate-pulse">STABLE</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer-text absolute inset-0 w-full overflow-hidden">
|
||||
<p class="decrypt-text text-[13px] text-background-dark/70 font-medium leading-relaxed" data-target="Harnessing renewable and conventional energy for consistent supply.">>_ Awaiting command...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute bottom-0 right-0 w-6 h-6 border-b-2 border-r-2 border-primary/40 group-hover:border-primary-dark transition-colors m-1"></div>
|
||||
</div>
|
||||
|
||||
<!-- Card 2 -->
|
||||
<div class="tilt-card relative bg-white/70 backdrop-blur-xl border border-white p-8 cyber-clip cursor-pointer hover:border-accent/50 shadow-glass text-left w-full lg:w-[290px] ml-auto group">
|
||||
<div class="flex items-start justify-between mb-6 relative z-10">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-12 h-12 bg-accent/10 rounded flex items-center justify-center border border-accent/30 text-accent group-hover:bg-accent group-hover:text-white transition-colors duration-300 shadow-sm">
|
||||
<span class="material-symbols-outlined text-2xl"></span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl text-background-dark tracking-wide">Energy Storage</h3>
|
||||
<div class="text-[10px] text-accent font-mono mt-1 ">NODE: BATT_X</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative h-[80px] w-full">
|
||||
<div class="layer-stats absolute inset-0 w-full grid grid-cols-2 gap-3 font-mono text-xs">
|
||||
<div class="bg-accent/5 border border-accent/20 p-3 text-accent flex flex-col justify-center rounded-sm">
|
||||
<span class="text-background-dark/50 text-[10px] mb-1 ">CAPACITY</span>
|
||||
<span class="text-base ">500 kWh</span>
|
||||
</div>
|
||||
<div class="bg-accent/5 border border-accent/20 p-3 text-accent flex flex-col justify-center rounded-sm">
|
||||
<span class="text-background-dark/50 text-[10px] mb-1 ">MODE</span>
|
||||
<span class="text-base animate-pulse">CHARGE</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer-text absolute inset-0 w-full overflow-hidden">
|
||||
<p class="decrypt-text text-[13px] text-background-dark/70 font-medium leading-relaxed" data-target="Storing surplus electricity to stabilize the grid day and night.">>_ Interfacing...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute bottom-0 right-0 w-6 h-6 border-b-2 border-r-2 border-accent/40 group-hover:border-accent transition-colors m-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Central Hub -->
|
||||
<div class="flex justify-center items-center relative z-20 mx-4 py-8 lg:py-0">
|
||||
<div class="relative flex flex-col items-center justify-center p-12 bg-white/80 backdrop-blur-2xl border-2 border-white cyber-hub hover:border-primary/50 transition-all duration-500 w-[300px] h-[360px] shadow-glass group">
|
||||
<div class="relative w-32 h-32 flex items-center justify-center mb-8">
|
||||
<div class="absolute inset-0 rounded-full border border-dashed border-primary-dark/40 animate-[spin_10s_linear_infinite]"></div>
|
||||
<div class="absolute inset-2 rounded-full border-t-4 border-primary/80 animate-[spin_3s_linear_infinite]"></div>
|
||||
<div class="absolute inset-6 rounded-full bg-white border border-primary/30 flex items-center justify-center overflow-hidden z-10 group-hover:shadow-[0_0_20px_rgba(19,236,164,0.6)] transition-shadow duration-500">
|
||||
<span class="material-symbols-outlined text-primary text-4xl"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-primary/10 border border-primary/30 px-3 py-1 rounded-full text-primary font-mono text-[10px] mb-4 flex gap-2 items-center tracking-widest ">
|
||||
<span class="w-2 h-2 bg-primary rounded-full animate-ping"></span> CONVERTER
|
||||
</div>
|
||||
<h3 class="text-2xl text-background-dark tracking-wide mb-2 z-10 group-hover:text-primary transition-colors">Power Conversion</h3>
|
||||
<p class="text-[12px] text-background-dark/60 font-mono tracking-widest text-center mt-2 ">AC/DC ⇌ DC/AC<br>EFF: <span class="text-primary">99.1%</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 3 -->
|
||||
<div class="flex flex-col justify-center relative z-10">
|
||||
<div class="tilt-card relative bg-white/70 backdrop-blur-xl border border-white p-8 cyber-clip cursor-pointer hover:border-primary-dark/50 shadow-glass text-left w-full lg:w-[290px] group">
|
||||
<div class="flex items-start justify-between mb-6 relative z-10">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-12 h-12 bg-primary-dark/10 rounded flex items-center justify-center border border-primary-dark/30 text-primary group-hover:bg-primary-dark group-hover:text-white transition-colors duration-300 shadow-sm">
|
||||
<span class="material-symbols-outlined text-2xl"></span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl text-background-dark tracking-wide">Demand Mgt</h3>
|
||||
<div class="text-[10px] text-background-dark/50 font-mono mt-1 ">NODE: LOAD_END</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative h-[80px] w-full">
|
||||
<div class="layer-stats absolute inset-0 w-full grid grid-cols-2 gap-3 font-mono text-xs">
|
||||
<div class="bg-gray-100 border border-gray-200 p-3 text-background-dark flex flex-col justify-center rounded-sm">
|
||||
<span class="text-background-dark/50 text-[10px] mb-1 ">REAL POWER</span>
|
||||
<span class="text-base ">120 kW</span>
|
||||
</div>
|
||||
<div class="bg-gray-100 border border-gray-200 p-3 text-background-dark flex flex-col justify-center rounded-sm">
|
||||
<span class="text-background-dark/50 text-[10px] mb-1 ">POWER FACTOR</span>
|
||||
<span class="text-base ">0.98</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer-text absolute inset-0 w-full overflow-hidden">
|
||||
<p class="decrypt-text text-[13px] text-background-dark/70 font-medium leading-relaxed" data-target="Distributing power smartly to meet diverse load requirements.">>_ Connecting...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute bottom-0 right-0 w-6 h-6 border-b-2 border-r-2 border-primary-dark/40 group-hover:border-primary-dark transition-colors m-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Sections (Glassmorphism & Bright Theme) -->
|
||||
<section id="mod-security" class="min-h-screen flex items-center">
|
||||
<div class="max-w-xl bg-white/80 backdrop-blur-xl p-10 border-l-4 border-accent rounded-r-3xl shadow-glass">
|
||||
<span class="material-symbols-outlined text-5xl text-accent mb-6"></span>
|
||||
<h2 class="text-4xl mb-4 text-background-dark">Safe &<br>Reliable</h2>
|
||||
<p class="text-lg text-background-dark/70 leading-relaxed font-medium">Sustains island operation without disconnection, backed by multi-power redundancy to guarantee zero downtime for critical loads.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="mod-efficiency" class="min-h-screen flex items-center justify-end text-right">
|
||||
<div class="max-w-xl bg-white/80 backdrop-blur-xl p-10 border-r-4 border-primary rounded-l-3xl shadow-glass">
|
||||
<span class="material-symbols-outlined text-5xl text-primary mb-6"></span>
|
||||
<h2 class="text-4xl mb-4 text-background-dark">Smart &<br>Efficient</h2>
|
||||
<p class="text-lg text-background-dark/70 leading-relaxed font-medium">Intelligent optimization and dispatch enable peak shaving and valley filling, effectively reducing electricity costs and energy consumption.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="mod-netzero" class="min-h-screen flex items-center">
|
||||
<div class="max-w-xl bg-white/80 backdrop-blur-xl p-10 border-l-4 border-primary-dark rounded-r-3xl shadow-glass">
|
||||
<span class="material-symbols-outlined text-5xl text-primary mb-6"></span>
|
||||
<h2 class="text-4xl mb-4 text-background-dark">Green &<br>Low-Carbon</h2>
|
||||
<p class="text-lg text-background-dark/70 leading-relaxed font-medium">Efficiently absorbs renewable energy and reduces carbon emissions, facilitating the realization of dual-carbon goals.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="mod-autonomous" class="min-h-screen flex items-center justify-end text-right">
|
||||
<div class="max-w-xl bg-white/80 backdrop-blur-xl p-10 border-r-4 border-accent rounded-l-3xl shadow-glass">
|
||||
<span class="material-symbols-outlined text-5xl text-accent mb-6"></span>
|
||||
<h2 class="text-4xl mb-4 text-background-dark">Autonomous &<br>Controllable</h2>
|
||||
<p class="text-lg text-background-dark/70 leading-relaxed font-medium">Operates independently of the external grid, significantly enhancing energy autonomy and risk resilience.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- === Scenarios === -->
|
||||
<section id="mod-scenarios" class="min-h-[80vh] flex flex-col justify-center items-center text-center pb-20 relative z-10" style="perspective: 2000px;">
|
||||
<h2 class="text-4xl md:text-[2.66rem] mb-12 uppercase tracking-widest text-background-dark mt-10 relative z-30">Applicable Scenarios</h2>
|
||||
|
||||
<div class="flex flex-wrap justify-center gap-4 font-mono text-sm max-w-4xl relative z-30 " id="scenario-buttons">
|
||||
<button class="scenario-btn px-6 py-2 border border-primary/20 bg-white/80 hover:border-primary-dark hover:text-primary transition-all duration-300 rounded-full shadow-sm text-background-dark/70">Industrial Parks</button>
|
||||
<button class="scenario-btn px-6 py-2 border border-primary/20 bg-white/80 hover:border-primary-dark hover:text-primary transition-all duration-300 rounded-full shadow-sm text-background-dark/70">Data Centers</button>
|
||||
<button class="scenario-btn px-6 py-2 border border-primary/20 bg-white/80 hover:border-primary-dark hover:text-primary transition-all duration-300 rounded-full shadow-sm text-background-dark/70">Commercial Complexes</button>
|
||||
<button class="scenario-btn px-6 py-2 border border-primary/20 bg-white/80 hover:border-primary-dark hover:text-primary transition-all duration-300 rounded-full shadow-sm text-background-dark/70">Hospitals & Schools</button>
|
||||
<button class="scenario-btn px-6 py-2 border border-primary/20 bg-white/80 hover:border-primary-dark hover:text-primary transition-all duration-300 rounded-full shadow-sm text-background-dark/70">Remote Regions</button>
|
||||
<button class="scenario-btn px-6 py-2 border border-primary/20 bg-white/80 hover:border-primary-dark hover:text-primary transition-all duration-300 rounded-full shadow-sm text-background-dark/70">Islands</button>
|
||||
<button class="scenario-btn active-scenario px-6 py-2 bg-primary/10 border border-primary text-primary shadow-[0_4px_15px_rgba(19,236,164,0.3)] transition-all duration-300 rounded-full">Zero-Carbon Communities</button>
|
||||
<button class="scenario-btn px-6 py-2 border border-primary/20 bg-white/80 hover:border-primary-dark hover:text-primary transition-all duration-300 rounded-full shadow-sm text-background-dark/70">New Energy Stations</button>
|
||||
</div>
|
||||
|
||||
<div id="card-wrapper" class="relative mt-12 w-full max-w-5xl h-[450px] z-20" style="transform-style: preserve-3d;">
|
||||
<div id="scenario-card" class="absolute inset-0 w-full h-full rounded-3xl overflow-hidden border-4 border-white shadow-2xl bg-white opacity-0" style="transform: translateZ(-500px) rotateX(20deg); pointer-events: none;">
|
||||
<div class="absolute inset-0 z-0 overflow-hidden">
|
||||
<img id="card-img" src="" class="w-full h-full object-cover opacity-90" alt="Scenario Image" />
|
||||
</div>
|
||||
<!-- Light theme inner gradient -->
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-white via-white/80 to-transparent z-10"></div>
|
||||
<div class="absolute bottom-12 left-12 z-20 text-left">
|
||||
<h3 id="card-title" class="text-4xl font-display text-background-dark mb-4 tracking-tighter"></h3>
|
||||
<p id="card-desc" class="text-lg text-background-dark/80 max-w-2xl leading-relaxed font-medium"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- === Contact === -->
|
||||
<section id="mod-contact" class="w-full py-24 relative z-10 overflow-hidden bg-white/50 border-t border-white shadow-[0_-10px_40px_rgba(13,166,112,0.05)]">
|
||||
<div class="max-w-7xl mx-auto px-6">
|
||||
<div class="flex items-center gap-4 mb-12" data-aos="fade-right">
|
||||
<div class="h-[2px] w-12 bg-primary-dark"></div>
|
||||
<span class="text-primary font-mono text-xs uppercase tracking-[0.4em] ">Get in Touch</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-12">
|
||||
<div data-aos="fade-up" data-aos-delay="100">
|
||||
<h2 class="text-background-dark/50 text-sm font-mono uppercase tracking-widest mb-2 ">Microgrid Project Leader</h2>
|
||||
<div class="flex items-baseline gap-6">
|
||||
<span class="text-5xl tracking-tighter text-background-dark">Yan</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 w-full lg:max-w-2xl" data-aos="fade-left" data-aos-delay="200">
|
||||
<a href="mailto:sheyanxin@nenghui.com" class="group relative p-6 bg-white border border-primary/20 rounded-2xl overflow-hidden transition-all duration-500 hover:border-primary hover:shadow-lg">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-primary/5 to-transparent translate-x-[-100%] group-hover:translate-x-[0%] transition-transform duration-500"></div>
|
||||
<div class="relative flex items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center text-primary">
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-background-dark/50 text-[10px] uppercase tracking-widest ">Send Email</div>
|
||||
<div class="text-background-dark group-hover:text-primary transition-colors">sheyanxin@nenghui.com</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="tel:8619120593729" class="group relative p-6 bg-white border border-primary/20 rounded-2xl overflow-hidden transition-all duration-500 hover:border-primary hover:shadow-lg">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-primary/5 to-transparent translate-x-[-100%] group-hover:translate-x-[0%] transition-transform duration-500"></div>
|
||||
<div class="relative flex items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center text-primary">
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-background-dark/50 text-[10px] uppercase tracking-widest ">Call Directly</div>
|
||||
<div class="text-background-dark group-hover:text-primary transition-colors">+86 191 2059 3729</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.js"></script>
|
||||
|
||||
<!-- JavaScript Logic & Animations -->
|
||||
<script>
|
||||
AOS.init({ once: true });
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
// --- 卡片倾斜与打字机特效 (保持不变) ---
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const cards = document.querySelectorAll('.tilt-card');
|
||||
cards.forEach(card => {
|
||||
card.addEventListener('mousemove', e => {
|
||||
card.classList.remove('reset');
|
||||
const rect = card.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
const centerX = rect.width / 2;
|
||||
const centerY = rect.height / 2;
|
||||
const rotateX = ((y - centerY) / centerY) * -4;
|
||||
const rotateY = ((x - centerX) / centerX) * 4;
|
||||
card.style.setProperty('--rx', `${rotateX}deg`);
|
||||
card.style.setProperty('--ry', `${rotateY}deg`);
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', () => {
|
||||
card.classList.add('reset');
|
||||
card.style.setProperty('--rx', `0deg`);
|
||||
card.style.setProperty('--ry', `0deg`);
|
||||
const decryptEl = card.querySelector('.decrypt-text');
|
||||
if(decryptEl) {
|
||||
decryptEl.dataset.isAnimating = "false";
|
||||
decryptEl.innerHTML = `>_ Awaiting command...`;
|
||||
}
|
||||
});
|
||||
|
||||
card.addEventListener('mouseenter', () => {
|
||||
const decryptEl = card.querySelector('.decrypt-text');
|
||||
if(!decryptEl || decryptEl.dataset.isAnimating === "true") return;
|
||||
decryptEl.dataset.isAnimating = "true";
|
||||
const targetText = decryptEl.getAttribute('data-target');
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%&*';
|
||||
let iteration = 0;
|
||||
function decryptFrame() {
|
||||
if(decryptEl.dataset.isAnimating === "false") return;
|
||||
decryptEl.innerHTML = targetText.split("").map((letter, index) => {
|
||||
if(index < iteration) return letter;
|
||||
return chars[Math.floor(Math.random() * chars.length)];
|
||||
}).join("") + `<span class="animate-pulse text-primary ml-1">_</span>`;
|
||||
if(iteration < targetText.length) {
|
||||
iteration += 1;
|
||||
setTimeout(() => requestAnimationFrame(decryptFrame), 15);
|
||||
} else {
|
||||
decryptEl.dataset.isAnimating = "false";
|
||||
decryptEl.innerHTML = targetText + `<span class="animate-pulse text-primary ml-1">_</span>`;
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(decryptFrame);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// --- Three.js 亮色主题粒子引擎 ---
|
||||
const scene = new THREE.Scene();
|
||||
// 增加雾化效果,让远处的粒子融入背景
|
||||
scene.fog = new THREE.FogExp2(0xf8fcfa, 0.0015);
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000);
|
||||
camera.position.z = 500;
|
||||
|
||||
function updateCameraFOV() {
|
||||
camera.fov = window.innerWidth < 768 ? 75 : 55;
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
updateCameraFOV();
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
document.getElementById('canvas-container').appendChild(renderer.domElement);
|
||||
|
||||
const particleCount = window.innerWidth < 768 ? 9000 : 12000;
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
|
||||
const currentPositions = new Float32Array(particleCount * 3);
|
||||
const renderPositions = new Float32Array(particleCount * 3);
|
||||
const startPos = new Float32Array(particleCount * 3);
|
||||
const targetPos = new Float32Array(particleCount * 3);
|
||||
const colors = new Float32Array(particleCount * 3);
|
||||
|
||||
// --- 核心改动:适合亮色背景的粒子颜色 ---
|
||||
const colorPrimary = new THREE.Color('#09ab75'); // 深翠绿,保证在白底上清晰
|
||||
const colorAccent = new THREE.Color('#64e8bc'); // 亮绿色用于点缀
|
||||
const colorGray = new THREE.Color('#94a3b8'); // 蓝灰色替代原有的银白色
|
||||
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const mix = Math.random();
|
||||
const color = colorPrimary.clone().lerp(colorAccent, mix * 0.5);
|
||||
colors.set([color.r, color.g, color.b], i * 3);
|
||||
}
|
||||
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
||||
|
||||
// 形状生成器 (保持原有计算逻辑)
|
||||
const shapesGen = {
|
||||
text: () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 1200; canvas.height = 300;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#000'; ctx.fillRect(0, 0, 1200, 300);
|
||||
ctx.font = 'bold 200px "Arial", sans-serif';
|
||||
ctx.fillStyle = '#fff'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
||||
ctx.fillText( window.innerWidth < 768 ? 'NH' :'NENGHUI', 600, 150);
|
||||
const data = ctx.getImageData(0, 0, 1200, 300).data;
|
||||
let points = [];
|
||||
for (let y = 0; y < 300; y += 3) {
|
||||
for (let x = 0; x < 1200; x += 3) {
|
||||
if (data[(y * 1200 + x) * 4] > 128) {
|
||||
points.push({ x: x - 600, y: -(y - 150), z: (Math.random() - 0.5) * 20 });
|
||||
}
|
||||
}
|
||||
}
|
||||
const arr = new Float32Array(particleCount * 3);
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const p = points[i % points.length];
|
||||
arr.set([p.x, p.y, p.z], i * 3);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
nestedPolyhedron: () => {
|
||||
const arr = new Float32Array(particleCount * 3);
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const r = i % 2 === 0 ? 100 : 50;
|
||||
const theta = Math.random() * Math.PI * 2;
|
||||
const phi = Math.acos(Math.random() * 2 - 1);
|
||||
let x = r * Math.sin(phi) * Math.cos(theta);
|
||||
let y = r * Math.sin(phi) * Math.sin(theta);
|
||||
let z = r * Math.cos(phi);
|
||||
if (Math.abs(x) > r * 0.7) x = Math.sign(x) * r * 0.7;
|
||||
arr.set([x, y, z], i * 3);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
symbioticLeaf: () => {
|
||||
const arr = new Float32Array(particleCount * 3);
|
||||
const leafParticles = Math.floor(particleCount * 0.85);
|
||||
const carbonParticles = particleCount - leafParticles;
|
||||
for (let i = 0; i < leafParticles; i++) {
|
||||
const t = Math.random();
|
||||
const y = t * 400 - 150;
|
||||
const widthFactor = Math.sin(t * Math.PI) * (1.2 - 0.2 * t);
|
||||
const maxWidth = 160;
|
||||
let x, z;
|
||||
const isVein = Math.random() < 0.4;
|
||||
if (isVein) {
|
||||
if (Math.random() < 0.3) {
|
||||
x = (Math.random() - 0.5) * 4;
|
||||
z = (Math.random() - 0.5) * 4;
|
||||
} else {
|
||||
const side = Math.random() > 0.5 ? 1 : -1;
|
||||
const veinT = Math.floor(t * 10) / 10;
|
||||
x = side * (t - veinT) * 300 + (side * veinT * 20);
|
||||
x = THREE.MathUtils.clamp(x, -maxWidth * widthFactor, maxWidth * widthFactor);
|
||||
z = (Math.random() - 0.5) * 10;
|
||||
}
|
||||
} else {
|
||||
const rawX = (Math.random() - 0.5) * 2 * maxWidth * widthFactor;
|
||||
x = Math.round(rawX / 15) * 15;
|
||||
z = (Math.random() - 0.5) * 20;
|
||||
}
|
||||
const bend = Math.sin(t * Math.PI) * 30;
|
||||
arr.set([x + bend, y, z], i * 3);
|
||||
}
|
||||
for (let i = 0; i < carbonParticles; i++) {
|
||||
const idx = (leafParticles + i) * 3;
|
||||
arr.set([
|
||||
(Math.random() - 0.5) * 300,
|
||||
-250 - Math.random() * 150,
|
||||
(Math.random() - 0.5) * 60
|
||||
], idx);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
concentricSphere: () => {
|
||||
const arr = new Float32Array(particleCount * 3);
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const r = i % 3 === 0 ? 140 : (i % 3 === 1 ? 90 : 40);
|
||||
const phi = Math.acos(-1 + Math.random() * 2);
|
||||
const theta = Math.random() * Math.PI * 2;
|
||||
arr.set([r * Math.sin(phi) * Math.cos(theta), r * Math.sin(phi) * Math.sin(theta), r * Math.cos(phi)], i * 3);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
barGraph: () => {
|
||||
const arr = new Float32Array(particleCount * 3);
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const gridX = Math.floor(Math.random() * 8) - 4;
|
||||
const gridZ = Math.floor(Math.random() * 8) - 4;
|
||||
const height = (Math.sin(gridX) + Math.cos(gridZ) + 2) * 40 + 20;
|
||||
arr.set([gridX * 40 + (Math.random() - 0.5) * 20, Math.random() * height - 100, gridZ * 40 + (Math.random() - 0.5) * 20], i * 3);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
shield: () => {
|
||||
const arr = new Float32Array(particleCount * 3);
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
let x = (Math.random() - 0.5) * 200;
|
||||
let y, z;
|
||||
let topEdge = 100 + 15 * Math.cos(x * Math.PI / 100);
|
||||
let bottomEdge = -120 + 220 * Math.pow(Math.abs(x) / 100, 2);
|
||||
let t = Math.random();
|
||||
if (Math.random() > 0.6) {
|
||||
t = Math.random() > 0.5 ? Math.random() * 0.1 : 0.9 + Math.random() * 0.1;
|
||||
}
|
||||
y = bottomEdge + t * (topEdge - bottomEdge);
|
||||
let maxZ = 30 * (1 - Math.pow(Math.abs(x) / 100, 2));
|
||||
if (Math.abs(x) < 8 || Math.abs(y - 10) < 8) maxZ += 10;
|
||||
z = (Math.random() - 0.5) * maxZ;
|
||||
arr.set([x, y, z], i * 3);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
torusKnot: () => {
|
||||
const arr = new Float32Array(particleCount * 3);
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const t = Math.random() * Math.PI * 2;
|
||||
const p = 2; const q = 3;
|
||||
const r = 50 * (2 + Math.cos(q * t));
|
||||
const x = r * Math.cos(p * t);
|
||||
const y = r * Math.sin(p * t);
|
||||
const z = 50 * Math.sin(q * t);
|
||||
const noise = 15;
|
||||
arr.set([x + (Math.random() - 0.5) * noise, y + (Math.random() - 0.5) * noise, z + (Math.random() - 0.5) * noise], i * 3);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
networkNode: () => {
|
||||
const arr = new Float32Array(particleCount * 3);
|
||||
const nodes = [
|
||||
{x: 0, y: 0, z: 0, r: 35},
|
||||
{x: 120, y: 70, z: 40, r: 15},
|
||||
{x: -110, y: -80, z: 20, r: 20},
|
||||
{x: 90, y: -100, z: -40, r: 15},
|
||||
{x: -100, y: 90, z: -30, r: 18},
|
||||
{x: 0, y: 130, z: 10, r: 12},
|
||||
{x: 0, y: -130, z: 0, r: 12}
|
||||
];
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const pType = Math.random();
|
||||
if (pType < 0.5) {
|
||||
const node = nodes[Math.floor(Math.random() * nodes.length)];
|
||||
const theta = Math.random() * Math.PI * 2;
|
||||
const phi = Math.acos((Math.random() * 2) - 1);
|
||||
const r = node.r * (0.5 + 0.5 * Math.random());
|
||||
arr.set([
|
||||
node.x + r * Math.sin(phi) * Math.cos(theta),
|
||||
node.y + r * Math.sin(phi) * Math.sin(theta),
|
||||
node.z + r * Math.cos(phi)
|
||||
], i * 3);
|
||||
} else {
|
||||
const targetNode = nodes[1 + Math.floor(Math.random() * (nodes.length - 1))];
|
||||
const t = Math.random();
|
||||
const noise = 3;
|
||||
arr.set([
|
||||
t * targetNode.x + (Math.random() - 0.5) * noise,
|
||||
t * targetNode.y + (Math.random() - 0.5) * noise,
|
||||
t * targetNode.z + (Math.random() - 0.5) * noise
|
||||
], i * 3);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
};
|
||||
|
||||
const precalculatedShapes = {
|
||||
text: shapesGen.text(),
|
||||
nestedPolyhedron: shapesGen.nestedPolyhedron(),
|
||||
symbioticLeaf: shapesGen.symbioticLeaf(),
|
||||
concentricSphere: shapesGen.concentricSphere(),
|
||||
barGraph: shapesGen.barGraph(),
|
||||
shield: shapesGen.shield(),
|
||||
torusKnot: shapesGen.torusKnot(),
|
||||
networkNode: shapesGen.networkNode()
|
||||
};
|
||||
|
||||
currentPositions.set(precalculatedShapes.text);
|
||||
renderPositions.set(precalculatedShapes.text);
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(renderPositions, 3));
|
||||
|
||||
// --- 核心改动:必须使用 NormalBlending 才能在白底上显示颜色 ---
|
||||
const material = new THREE.PointsMaterial({
|
||||
size: window.innerWidth < 768 ? 3.5 : 4.0,
|
||||
vertexColors: true,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
blending: THREE.NormalBlending, // AdditiveBlending 会在白底上完全消失!
|
||||
sizeAttenuation: true,
|
||||
depthWrite: false
|
||||
});
|
||||
const particles = new THREE.Points(geometry, material);
|
||||
scene.add(particles);
|
||||
|
||||
let time = 0;
|
||||
let morphData = { progress: 0 };
|
||||
let currentShapeKey = 'text';
|
||||
let morphTween = null;
|
||||
|
||||
const rotationSpeed = { x: 0, y: 0 };
|
||||
const waveConfig = { amplitude: 20 };
|
||||
|
||||
function morphTo(shapeKey) {
|
||||
if (currentShapeKey === shapeKey) return;
|
||||
currentShapeKey = shapeKey;
|
||||
|
||||
if (morphTween) morphTween.kill();
|
||||
|
||||
const colorAttribute = geometry.attributes.color;
|
||||
const targetColors = new Float32Array(particleCount * 3);
|
||||
const leafParticles = Math.floor(particleCount * 0.85);
|
||||
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
let color;
|
||||
if (shapeKey === 'symbioticLeaf') {
|
||||
if (i < leafParticles) {
|
||||
color = colorPrimary.clone().lerp(colorAccent, Math.random() * 0.2);
|
||||
} else {
|
||||
color = colorGray.clone();
|
||||
}
|
||||
} else {
|
||||
color = colorPrimary.clone().lerp(colorAccent, Math.random() * 0.4);
|
||||
}
|
||||
targetColors.set([color.r, color.g, color.b], i * 3);
|
||||
}
|
||||
|
||||
gsap.to(colorAttribute.array, {
|
||||
endArray: targetColors,
|
||||
duration: 1.5,
|
||||
ease: "power2.inOut",
|
||||
onUpdate: () => colorAttribute.needsUpdate = true
|
||||
});
|
||||
|
||||
for (let i = 0; i < currentPositions.length; i++) {
|
||||
startPos[i] = currentPositions[i];
|
||||
}
|
||||
targetPos.set(precalculatedShapes[shapeKey]);
|
||||
|
||||
morphData.progress = 0;
|
||||
morphTween = gsap.to(morphData, {
|
||||
progress: 1,
|
||||
duration: 2.0,
|
||||
ease: "power3.inOut",
|
||||
onUpdate: () => {
|
||||
for (let i = 0; i < particleCount * 3; i++) {
|
||||
currentPositions[i] = startPos[i] + (targetPos[i] - startPos[i]) * morphData.progress;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (shapeKey === 'text') {
|
||||
gsap.to(rotationSpeed, { x: 0, y: 0, duration: 1.5 });
|
||||
const targetX = Math.round(particles.rotation.x / (Math.PI * 2)) * (Math.PI * 2);
|
||||
const targetY = Math.round(particles.rotation.y / (Math.PI * 2)) * (Math.PI * 2);
|
||||
gsap.to(particles.rotation, { x: targetX, y: targetY, duration: 2.0 });
|
||||
} else if (shapeKey === 'shield') {
|
||||
gsap.to(rotationSpeed, { x: 0, y: 0.002, duration: 1.5 });
|
||||
const targetX = Math.round(particles.rotation.x / (Math.PI * 2)) * (Math.PI * 2);
|
||||
gsap.to(particles.rotation, { x: targetX, duration: 2.0 });
|
||||
} else if (shapeKey === 'symbioticLeaf') {
|
||||
gsap.to(rotationSpeed, { x: 0.0005, y: 0.001, duration: 1.5 });
|
||||
} else if (shapeKey === 'barGraph') {
|
||||
gsap.to(rotationSpeed, { x: 0, y: 0.002, duration: 1.5 });
|
||||
const targetX = Math.round(particles.rotation.x / (Math.PI * 2)) * (Math.PI * 2);
|
||||
gsap.to(particles.rotation, { x: targetX + 0.4, duration: 2.0 });
|
||||
} else {
|
||||
gsap.to(rotationSpeed, { x: 0.001, y: 0.002, duration: 2.0 });
|
||||
}
|
||||
|
||||
gsap.to(waveConfig, {
|
||||
duration: 2.0,
|
||||
amplitude: (shapeKey === 'text') ? 20 : 0
|
||||
});
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
time += 0.02;
|
||||
|
||||
const positions = geometry.attributes.position.array;
|
||||
const colorsArray = geometry.attributes.color.array;
|
||||
const leafParticles = Math.floor(particleCount * 0.85);
|
||||
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
let idx = i * 3;
|
||||
let baseX = currentPositions[idx];
|
||||
let baseY = currentPositions[idx + 1];
|
||||
let baseZ = currentPositions[idx + 2];
|
||||
|
||||
if (currentShapeKey === 'symbioticLeaf' ) {
|
||||
if (i < leafParticles) {
|
||||
if (i % 7 === 0 ) {
|
||||
if (Math.abs(baseX) < 100) {
|
||||
const angle = time * 3 + (baseY * 0.02);
|
||||
const r = 10 * Math.sin(time * 0.5);
|
||||
positions[idx] = baseX + r * Math.cos(angle);
|
||||
positions[idx + 1] = baseY;
|
||||
positions[idx + 2] = baseZ + r * Math.sin(angle);
|
||||
}
|
||||
} else {
|
||||
positions[idx] = baseX + Math.sin(time + baseY * 0.1) * 2;
|
||||
positions[idx + 1] = baseY;
|
||||
positions[idx + 2] = baseZ;
|
||||
}
|
||||
}
|
||||
} else if (waveConfig.amplitude > 0.01) {
|
||||
const phase = baseX * 0.006 + time;
|
||||
const waveY = Math.sin(phase) * (waveConfig.amplitude * 0.4);
|
||||
const waveZ = Math.cos(phase * 0.8) * waveConfig.amplitude;
|
||||
positions[idx] = baseX;
|
||||
positions[idx + 1] = baseY + waveY;
|
||||
positions[idx + 2] = baseZ + waveZ;
|
||||
} else {
|
||||
positions[idx] = baseX;
|
||||
positions[idx + 1] = baseY;
|
||||
positions[idx + 2] = baseZ;
|
||||
}
|
||||
|
||||
if (currentShapeKey === 'symbioticLeaf') {
|
||||
if (i >= leafParticles) {
|
||||
let y = baseY + (time * 50) % 200;
|
||||
let opacity = 1.0;
|
||||
|
||||
if (y > -150) {
|
||||
opacity = THREE.MathUtils.lerp(1.0, 0.0, (y + 150) / 100);
|
||||
}
|
||||
if (y > -50) opacity = 0;
|
||||
positions[idx + 1] = y;
|
||||
colorsArray[idx] = colorGray.r * opacity;
|
||||
colorsArray[idx + 1] = colorGray.g * opacity;
|
||||
colorsArray[idx + 2] = colorGray.b * opacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
geometry.attributes.position.needsUpdate = true;
|
||||
if (currentShapeKey === 'symbioticLeaf') {
|
||||
geometry.attributes.color.needsUpdate = true;
|
||||
}
|
||||
|
||||
particles.rotation.x += rotationSpeed.x;
|
||||
particles.rotation.y += rotationSpeed.y;
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
animate();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
updateCameraFOV();
|
||||
});
|
||||
|
||||
const shapeList = ['text', 'shield', 'concentricSphere', 'symbioticLeaf', 'torusKnot', 'networkNode', 'barGraph', 'nestedPolyhedron'];
|
||||
const sections = ['#mod-hero', '#mod-security', '#mod-efficiency', '#mod-netzero', '#mod-autonomous', '#mod-topology', '#mod-scenarios', '#mod-contact'];
|
||||
|
||||
sections.forEach((id, idx) => {
|
||||
ScrollTrigger.create({
|
||||
trigger: id,
|
||||
start: "top 55%",
|
||||
end: "bottom 45%",
|
||||
onEnter: () => morphTo(shapeList[idx]),
|
||||
onEnterBack: () => morphTo(shapeList[idx])
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
<script>
|
||||
// --- 场景卡片 3D 动画 ---
|
||||
const scenarioData = {
|
||||
"Industrial Parks": { img: "https://nenghui.com/wp-content/uploads/2026/03/icrogrid-Industrial-parks.jpg", desc: "Optimize energy consumption and reduce operational costs for smart industrial zones with intelligent management." },
|
||||
"Data Centers": { img: "https://nenghui.com/wp-content/uploads/2026/03/icrogrid-data-center.jpg", desc: "Ensuring continuous mission-critical power supply for data centers with robust and resilient microgrids." },
|
||||
"Commercial Complexes": { img: "https://nenghui.com/wp-content/uploads/2026/03/icrogrid-Commercial-complexes.jpg", desc: "Optimize operational costs for commercial hubs through intelligent energy distribution and Peak Shaving." },
|
||||
"Hospitals & Schools": { img: "https://nenghui.com/wp-content/uploads/2026/03/icrogrid-Hospitals-schools.jpg", desc: "Ensuring uninterrupted power supply for critical medical services and secure educational campus environments." },
|
||||
"Remote Regions": { img: "https://nenghui.com/wp-content/uploads/2026/03/icrogrid-Remote-regions.jpg", desc: "Overcoming geographical barriers with advanced microgrid technology to illuminate the world's remotest corners." },
|
||||
"Islands": { img: "https://nenghui.com/wp-content/uploads/2026/03/icrogrid-Islands.jpg", desc: "Achieve reliable energy independence for remote islands through sustainable solar and storage systems." },
|
||||
"Zero-Carbon Communities": { img: "https://nenghui.com/wp-content/uploads/2026/03/icrogrid-Zero-carbon-communities.jpg", desc: "Building a sustainable future for modern living through intelligent and eco-friendly microgrid systems." },
|
||||
"New Energy Stations": { img: "https://nenghui.com/wp-content/uploads/2026/03/icrogrid-New-energy-stations.jpg", desc: "Enhancing grid stability and renewable integration through advanced storage-based microgrid management systems." }
|
||||
};
|
||||
|
||||
const scenarioButtons = document.querySelectorAll('.scenario-btn');
|
||||
const scenarioCard = document.getElementById('scenario-card');
|
||||
const cardImg = document.getElementById('card-img');
|
||||
const cardTitle = document.getElementById('card-title');
|
||||
const cardDesc = document.getElementById('card-desc');
|
||||
|
||||
let currentActiveBtn = document.querySelector('.active-scenario');
|
||||
|
||||
function updateCardContent(scenarioName) {
|
||||
if(!scenarioData[scenarioName]) return;
|
||||
const data = scenarioData[scenarioName];
|
||||
|
||||
const tl = gsap.timeline();
|
||||
tl.to(scenarioCard, { rotationY: 90, opacity: 0, duration: 0.4, ease: "power2.in" })
|
||||
.call(() => {
|
||||
cardImg.src = data.img;
|
||||
cardTitle.innerText = scenarioName;
|
||||
cardDesc.innerText = data.desc;
|
||||
})
|
||||
.fromTo(scenarioCard,
|
||||
{ rotationY: -90 },
|
||||
{ rotationY: 0, opacity: 1, duration: 0.6, ease: "back.out(1.7)" }
|
||||
);
|
||||
}
|
||||
|
||||
scenarioButtons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
if (btn === currentActiveBtn) return;
|
||||
|
||||
scenarioButtons.forEach(b => {
|
||||
b.classList.remove('active-scenario', 'bg-primary/10', 'border-primary', 'text-primary', 'shadow-[0_4px_15px_rgba(19,236,164,0.3)]');
|
||||
b.classList.add('border-primary/20', 'bg-white/80', 'text-background-dark/70');
|
||||
});
|
||||
btn.classList.add('active-scenario', 'bg-primary/10', 'border-primary', 'text-primary', 'shadow-[0_4px_15px_rgba(19,236,164,0.3)]');
|
||||
|
||||
currentActiveBtn = btn;
|
||||
updateCardContent(btn.innerText.trim());
|
||||
});
|
||||
});
|
||||
|
||||
if (currentActiveBtn) {
|
||||
const data = scenarioData[currentActiveBtn.innerText.trim()];
|
||||
if (data) {
|
||||
cardImg.src = data.img;
|
||||
cardTitle.innerText = currentActiveBtn.innerText.trim();
|
||||
cardDesc.innerText = data.desc;
|
||||
}
|
||||
}
|
||||
|
||||
ScrollTrigger.create({
|
||||
trigger: '#mod-scenarios',
|
||||
start: "top 60%",
|
||||
end: "bottom top",
|
||||
onEnter: () => {
|
||||
gsap.fromTo(scenarioCard,
|
||||
{ z: -1000, rotationX: 45, rotationY: -30, opacity: 0, scale: 0.5 },
|
||||
{ z: 0, rotationX: 0, rotationY: 0, opacity: 1, scale: 1, duration: 1.5, ease: "expo.out" }
|
||||
);
|
||||
},
|
||||
onLeaveBack: () => {
|
||||
gsap.to(scenarioCard, {
|
||||
z: -800, rotationX: 20, opacity: 0, scale: 0.7, duration: 0.8, ease: "power2.in"
|
||||
});
|
||||
},
|
||||
onLeave: () => {
|
||||
gsap.to(scenarioCard, {
|
||||
y: -200, z: 200, rotationX: -20, opacity: 0, duration: 0.8, ease: "power2.in"
|
||||
});
|
||||
},
|
||||
onEnterBack: () => {
|
||||
gsap.to(scenarioCard, {
|
||||
y: 0, z: 0, rotationX: 0, opacity: 1, scale: 1, duration: 1, ease: "back.out(1.2)"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,678 @@
|
||||
<!DOCTYPE html>
|
||||
<html class=" overflow-x-hidden" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
|
||||
<title>Join Us - TechCorp</title>
|
||||
|
||||
<!-- Google Fonts & Icons -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
||||
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;700;900&family=Montserrat:wght@400;500;700;800;900&family=Inter:wght@400;500;700;800;900&family=JetBrains+Mono:wght@500&display=swap" rel="stylesheet"/>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.min.css" />
|
||||
<!-- AOS Animation Library -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.css" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/vanilla-tilt/1.7.2/vanilla-tilt.min.js"></script>
|
||||
<!-- Tailwind & Config -->
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"primary": "#13eca4",
|
||||
"background-light": "#f8fcfa",
|
||||
"background-dark": "#10221c",
|
||||
"pearlescent": "#f0f7f5",
|
||||
"silver-accent": "#e2e8f0",
|
||||
"about-us": "#ff6b00"
|
||||
},
|
||||
fontFamily: {
|
||||
"display": ["Montserrat", "sans-serif"],
|
||||
"sans": ["Montserrat", "sans-serif"],
|
||||
"mono": ["JetBrains Mono", "Montserrat"],
|
||||
"inter": ["Inter", "Montserrat"],
|
||||
},
|
||||
borderRadius: {
|
||||
"DEFAULT": "0.5rem",
|
||||
"lg": "1rem",
|
||||
"xl": "1.5rem",
|
||||
"2xl": "2rem",
|
||||
"3xl": "3rem",
|
||||
"full": "9999px"
|
||||
},
|
||||
backgroundImage: {
|
||||
'liquid-gradient': 'linear-gradient(135deg, #f8fcfa 0%, #e8f5f1 50%, #dcfce7 100%)',
|
||||
'glass-gradient': 'linear-gradient(180deg, rgba(255, 255, 255, 0.6) 0%, rgba(255, 255, 255, 0.3) 100%)',
|
||||
'aurora': 'linear-gradient(135deg, rgba(19,236,164,0.1) 0%, rgba(13,166,112,0.05) 50%, rgba(248,252,250,0) 100%)',
|
||||
'mesh': 'radial-gradient(at 40% 20%, rgba(19,236,164,0.08) 0px, transparent 50%), radial-gradient(at 80% 0%, rgba(16,34,28,0.05) 0px, transparent 50%), radial-gradient(at 0% 50%, rgba(19,236,164,0.05) 0px, transparent 50%)',
|
||||
|
||||
},
|
||||
boxShadow: {
|
||||
// 将光效统一映射为 primary 颜色 (13eca4 的 RGBA)
|
||||
'glow': '0 0 20px rgba(19, 236, 164, 0.4)',
|
||||
'glow-lg': '0 10px 30px rgba(19, 236, 164, 0.4)',
|
||||
'bento': '0 20px 40px -15px rgba(0,0,0,0.05), 0 0 0 1px rgba(0,0,0,0.02)',
|
||||
'bento-hover': '0 30px 60px -20px rgba(19,236,164,0.15), 0 0 0 1px rgba(19,236,164,0.3)',
|
||||
'bento-dark': '0 30px 60px -15px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.1)',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0', transform: 'translateY(10px)' },
|
||||
'100%': { opacity: '1', transform: 'translateY(0)' },
|
||||
},
|
||||
scan: {
|
||||
'0%': { top: '-10%', opacity: '0' },
|
||||
'10%': { opacity: '1' },
|
||||
'90%': { opacity: '1' },
|
||||
'100%': { top: '110%', opacity: '0' },
|
||||
},
|
||||
rotateGlobe: {
|
||||
'0%': { transform: 'rotate(0deg)' },
|
||||
'100%': { transform: 'rotate(360deg)' },
|
||||
},
|
||||
dash: {
|
||||
'0%': { strokeDashoffset: '1000' },
|
||||
'100%': { strokeDashoffset: '0' },
|
||||
},
|
||||
globePulse: {
|
||||
'0%': { boxShadow: '0 0 0px rgba(19,236,164,0)' },
|
||||
'50%': { boxShadow: '0 0 50px rgba(19,236,164,0.4)', transform: 'scale(1.02)' },
|
||||
'100%': { boxShadow: '0 0 0px rgba(19,236,164,0)' },
|
||||
},
|
||||
// 高级交互动画
|
||||
pulseGlow: {
|
||||
'0%': { textShadow: '0 0 10px rgba(19, 236, 164, 0.4)' },
|
||||
'100%': { textShadow: '0 0 30px #ff6b00' }
|
||||
},
|
||||
sweep: { '100%': { left: '200%' } },
|
||||
cardFlip: {
|
||||
'0%': { opacity: '0', transform: 'rotateY(-90deg) translateZ(50px)' },
|
||||
'100%': { opacity: '1', transform: 'rotateY(0deg) translateZ(0)' }
|
||||
},
|
||||
floatY: {
|
||||
'0%, 100%': { transform: 'translateY(0)' },
|
||||
'50%': { transform: 'translateY(-15px)' }
|
||||
},
|
||||
textShimmer: {
|
||||
'0%': { backgroundPosition: '0% 50%' },
|
||||
'100%': { backgroundPosition: '100% 50%' },
|
||||
},
|
||||
blobSpin: {
|
||||
'0%': { transform: 'rotate(0deg) scale(1)' },
|
||||
'50%': { transform: 'rotate(180deg) scale(1.1)' },
|
||||
'100%': { transform: 'rotate(360deg) scale(1)' },
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fadeIn 0.5s ease-out forwards',
|
||||
'scan-slow': 'scan 6s linear infinite',
|
||||
'spin-slow': 'rotateGlobe 60s linear infinite',
|
||||
'dash-flow': 'dash 3s linear infinite',
|
||||
'globe-select': 'globePulse 0.8s ease-out',
|
||||
'pulse-glow': 'pulseGlow 2s infinite alternate',
|
||||
'sweep': 'sweep 3s infinite',
|
||||
'flip-in': 'cardFlip 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards',
|
||||
'float-1': 'floatY 3s ease-in-out infinite',
|
||||
'float-2': 'floatY 3s ease-in-out infinite 1s',
|
||||
'float-3': 'floatY 3s ease-in-out infinite 2s',
|
||||
'text-shimmer': 'textShimmer 3s ease-out infinite alternate',
|
||||
'blob-spin': 'blobSpin 20s infinite cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Swiper CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
|
||||
|
||||
<!-- AOS Animation CSS -->
|
||||
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
font-family: "Inter", "JetBrains Mono","sans-serif";
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
height: auto;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.swiper-wrapper {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.swiper-scrollbar {
|
||||
background: rgba(0, 0, 0, 0.06) !important;
|
||||
height: 6px !important;
|
||||
border-radius: 999px;
|
||||
width: 100%;
|
||||
}
|
||||
.dark .swiper-scrollbar {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
.swiper-scrollbar-drag {
|
||||
background: #2b8cee !important;
|
||||
border-radius: 9999px;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.swiper-button-disabled {
|
||||
opacity: 0.3 !important;
|
||||
cursor: not-allowed !important;
|
||||
pointer-events: none;
|
||||
box-shadow: none !important;
|
||||
filter: grayscale(100%);
|
||||
transform: none !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.dark .swiper-button-disabled {
|
||||
opacity: 0.2 !important;
|
||||
background-color: #1f2937 !important;
|
||||
}
|
||||
|
||||
.search-container:focus-within {
|
||||
box-shadow: 0 0 0 2px var(--tw-ring-offset-color, #fff), 0 0 0 4px rgba(43, 140, 238, 0.5);
|
||||
}
|
||||
|
||||
.card-image-bg {
|
||||
transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
}
|
||||
.group:hover .card-image-bg {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal-overlay {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
.modal-overlay.open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
transform: translateY(20px) scale(0.95);
|
||||
opacity: 0;
|
||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
.modal-overlay.open .modal-container {
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保留功能 */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-background-light dark:bg-background-dark text-[#111418] dark:text-white font-display antialiased w-screen overflow-x-hidden">
|
||||
|
||||
<div class="relative flex h-auto min-h-screen w-full flex-col group/design-root overflow-x-hidden">
|
||||
|
||||
<!-- Header -->
|
||||
<!-- <header class="sticky top-0 z-40 flex items-center justify-between border-b border-solid border-[#e5e7eb] dark:border-gray-800 bg-white/80 dark:bg-[#101922]/80 backdrop-blur-md px-4 sm:px-6 lg:px-10 py-3 transition-all duration-300">
|
||||
<div class="flex items-center gap-4 text-[#111418] dark:text-white">
|
||||
<div class="size-8 flex items-center justify-center text-primary flex-shrink-0">
|
||||
<span class="material-symbols-outlined text-3xl"></span>
|
||||
</div>
|
||||
<h2 class="text-[#111418] dark:text-white text-lg leading-tight tracking-[-0.015em] truncate">TechCorp</h2>
|
||||
</div>
|
||||
<div class="hidden md:flex flex-1 justify-end gap-8">
|
||||
<div class="flex items-center gap-9">
|
||||
<a class="text-[#111418] dark:text-gray-200 text-sm font-medium hover:text-primary transition-colors" href="#">Jobs</a>
|
||||
<a class="text-[#111418] dark:text-gray-200 text-sm font-medium hover:text-primary transition-colors" href="#">Culture</a>
|
||||
<a class="text-[#111418] dark:text-gray-200 text-sm font-medium hover:text-primary transition-colors" href="#">About Us</a>
|
||||
</div>
|
||||
<button class="flex min-w-[84px] items-center justify-center rounded-lg h-10 px-6 bg-primary text-white text-sm hover:bg-blue-600 transition-colors shadow-sm">
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
<div class="md:hidden p-2 -mr-2 cursor-pointer text-[#111418] dark:text-white">
|
||||
<span class="material-symbols-outlined text-2xl"></span>
|
||||
</div>
|
||||
</header> -->
|
||||
|
||||
<main class="layout-container flex h-full grow flex-col pt-6 sm:pt-10 pb-20 w-full overflow-x-hidden">
|
||||
<div class="w-full flex flex-1 justify-center">
|
||||
<div class="layout-content-container flex flex-col w-full max-w-[1400px] flex-1">
|
||||
|
||||
<!-- Heading & Search Section -->
|
||||
<div class="flex flex-col gap-6 px-4 sm:px-6 lg:px-40 mb-6 sm:mb-8 w-full max-w-[100vw] overflow-hidden">
|
||||
<div class="flex flex-wrap items-end justify-between gap-6">
|
||||
<div class="flex min-w-full md:min-w-[400px] flex-col gap-3 max-w-2xl" data-aos="fade-right" data-aos-duration="1000">
|
||||
<h1 class="text-[#111418] dark:text-white text-4xl md:text-[2.66rem] leading-tight tracking-[-0.033em]">
|
||||
Shape the future <br/><span class="text-primary bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-cyan-400">with us.</span>
|
||||
</h1>
|
||||
<p class="text-[#617589] dark:text-gray-400 text-[1rem] leading-normal max-w-lg mt-1 sm:mt-2">
|
||||
We are looking for thinkers, makers, and doers to join our high-energy team.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row items-end sm:items-center gap-4 w-full sm:w-auto" data-aos="fade-left" data-aos-delay="200">
|
||||
<div class="search-container relative w-full sm:w-64 group transition-all duration-300 rounded-full">
|
||||
<span class="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-primary transition-colors"></span>
|
||||
<input id="job-search" type="text" placeholder="Search positions..."
|
||||
class="w-full h-11 sm:h-12 pl-12 pr-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-full text-sm outline-none focus:border-primary dark:focus:border-primary transition-colors text-[#111418] dark:text-white shadow-sm"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="hidden sm:flex gap-3">
|
||||
<button class="swiper-button-prev-custom size-12 rounded-full border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 flex items-center justify-center hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-primary/50 text-[#111418] dark:text-white transition-all duration-300 shadow-sm hover:shadow-md active:scale-95 z-10 group cursor-pointer">
|
||||
<span class="material-symbols-outlined group-hover:text-primary transition-colors"></span>
|
||||
</button>
|
||||
<button class="swiper-button-next-custom size-12 rounded-full border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 flex items-center justify-center hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-primary/50 text-[#111418] dark:text-white transition-all duration-300 shadow-sm hover:shadow-md active:scale-95 z-10 group cursor-pointer">
|
||||
<span class="material-symbols-outlined group-hover:text-primary transition-colors"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Job Slider Area -->
|
||||
<div class="relative w-full min-h-[400px]">
|
||||
<div class="swiper mySwiper w-full pb-14 !overflow-visible">
|
||||
<div class="swiper-wrapper" id="jobs-container">
|
||||
<!-- JS will inject slides here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="no-results" class="hidden absolute inset-0 flex flex-col items-center justify-center text-center p-8 z-0 mt-12">
|
||||
<span class="material-symbols-outlined text-5xl sm:text-6xl text-gray-300 dark:text-gray-700 mb-4"></span>
|
||||
<h3 class="text-lg sm:text-xl text-[#111418] dark:text-white">No positions found</h3>
|
||||
<p class="text-sm sm:text-base text-[#617589] dark:text-gray-400 mt-2">Try adjusting your search criteria</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Section -->
|
||||
<div class="w-full flex justify-center px-4 sm:px-6 lg:px-40 mt-4 max-w-[100vw]" data-aos="fade-in" data-aos-delay="500">
|
||||
<div class="w-full flex flex-col gap-4">
|
||||
<div class="flex justify-between items-end">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<p class="text-[10px] sm:text-xs tracking-[0.15em] text-primary uppercase">Explore</p>
|
||||
<p class="text-[#111418] dark:text-white text-sm sm:text-base ">Open Positions</p>
|
||||
</div>
|
||||
<p class="text-[#617589] dark:text-gray-400 text-sm sm:text-base font-medium tabular-nums font-mono">
|
||||
<span id="slide-current" class="text-[#111418] dark:text-white text-lg">01</span>
|
||||
<span class="mx-1 text-gray-300 dark:text-gray-600">/</span>
|
||||
<span id="slide-total">00</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="w-full h-1.5 rounded-full bg-gray-100 dark:bg-gray-800 overflow-hidden relative">
|
||||
<div class="swiper-scrollbar !static !w-full !h-full !bg-transparent"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- [New] Job Detail Modal -->
|
||||
<div id="job-detail-modal" class="modal-overlay fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm p-0 sm:p-4">
|
||||
<div class="absolute inset-0 cursor-pointer" onclick="closeJobModal()"></div>
|
||||
|
||||
<div class="modal-container relative w-full max-w-2xl bg-white dark:bg-[#1a2632] rounded-t-2xl sm:rounded-2xl shadow-2xl flex flex-col max-h-[90vh] sm:max-h-[85vh] border border-white/10 overflow-hidden">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-start justify-between p-6 border-b border-gray-100 dark:border-gray-700 bg-white/50 dark:bg-[#1a2632]/50 backdrop-blur-sm sticky top-0 z-10">
|
||||
<div>
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span id="modal-tag" class="inline-flex items-center justify-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-primary/20 text-primary border border-primary/10">Product</span>
|
||||
<span class="text-xs text-gray-400 font-mono" id="modal-location">Remote</span>
|
||||
</div>
|
||||
<h2 id="modal-title" class="text-2xl text-[#111418] dark:text-white leading-tight">Senior Product Designer</h2>
|
||||
</div>
|
||||
<button onclick="closeJobModal()" class="size-8 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center text-gray-500 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-xl"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Body (Scrollable) -->
|
||||
<!-- [修复] 添加 ID 'modal-body' 用于 JS 控制 -->
|
||||
<div id="modal-body" class="p-6 overflow-y-auto no-scrollbar flex-1 scroll-smooth">
|
||||
<div class="prose dark:prose-invert max-w-none">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg text-[#111418] dark:text-white mb-2">About the Role</h3>
|
||||
<p id="modal-desc" class="text-[#617589] dark:text-gray-400 text-sm leading-relaxed">
|
||||
Lead design initiatives across our core products...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg text-[#111418] dark:text-white mb-2">Key Responsibilities</h3>
|
||||
<ul id="modal-responsibilities" class="list-disc pl-5 text-[#617589] dark:text-gray-400 text-sm leading-relaxed space-y-1"></ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg text-[#111418] dark:text-white mb-2">Requirements</h3>
|
||||
<ul id="modal-requirements" class="list-disc pl-5 text-[#617589] dark:text-gray-400 text-sm leading-relaxed space-y-1"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<!-- Job Detail Modal Footer (修改部分) -->
|
||||
<div class="p-5 border-t border-gray-100 dark:border-gray-700 bg-gray-50/80 dark:bg-[#151f28]/80 backdrop-blur-md flex flex-col gap-3">
|
||||
<!-- 主按钮 -->
|
||||
<a id="modal-apply-btn" href="#" class="flex w-full cursor-pointer items-center justify-center gap-2 rounded-lg h-12 bg-primary hover:bg-blue-600 text-white text-base transition-all shadow-md active:scale-[0.98] group">
|
||||
<span class="material-symbols-outlined"></span>
|
||||
Apply via Email
|
||||
</a>
|
||||
|
||||
<!-- 新增:邮箱文本提示 (兜底方案) -->
|
||||
<p class="text-center text-xs text-[#617589] dark:text-gray-400">
|
||||
Or email us directly at
|
||||
<!-- select-all 允许用户点击一次即可全选邮箱 -->
|
||||
<span class="text-[#111418] dark:text-white font-mono font-medium select-all cursor-text hover:text-primary transition-colors" id="email-link">zhoushu@nenghui.com</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
|
||||
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
|
||||
|
||||
<script>
|
||||
// --- Enhanced Data Source ---
|
||||
const jobsData = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Photovoltaic Field Engineer",
|
||||
type: "Engineering",
|
||||
dept: "Eng",
|
||||
location: "Iraq",
|
||||
image: "https://nenghui.com/wp-content/uploads/2026/02/job-1.avif",
|
||||
color: "bg-primary/90 border-white/10 text-white",
|
||||
desc: "",
|
||||
responsibilities: ["Responsible for on-site supervision, installation guidance, and problem-solving for photovoltaic and energy storage projects, ensuring construction complies with design specifications and safety standards.",
|
||||
"Inspect the construction site, coordinate the installation team, and conduct system commissioning and performance testing.",
|
||||
"Collect on-site data and report project progress, quality issues, and customer feedback.",
|
||||
"Provide customer training and technical support to ensure normal system operation and customer satisfaction.",
|
||||
"Responsible for site surveys and technical solution design.",
|
||||
"xResponsible for after-sales troubleshooting."
|
||||
],
|
||||
requirements: [
|
||||
"Full-time college diploma or above, majoring in electrical engineering or related fields, with basic conversational English skills;",
|
||||
"At least 1 year of experience in photovoltaic construction, understanding of photovoltaic construction details, and experience in on-site construction guidance;",
|
||||
"Responsible, meticulous, and with good professional ethics and communication and coordination skills;",
|
||||
"Able to accept long-term relocation to the Middle East; the company provides accommodation and overseas commercial insurance."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
title: "Overseas Sales Manager",
|
||||
type: "Sales",
|
||||
dept: "Sale",
|
||||
location: "ShangHai",
|
||||
image: "https://nenghui.com/wp-content/uploads/2026/02/job-2.avif",
|
||||
color: "bg-primary/90 border-white/10 text-white",
|
||||
desc: "",
|
||||
responsibilities: ["Develop the commercial and industrial energy storage and large-scale energy storage markets in Europe, identify and secure overseas energy storage orders and projects, and achieve company business goals, in accordance with the company's business strategy.",
|
||||
"Travel overseas as arranged by the company and as needed to conduct market research and customer visits, track and evaluate customer creditworthiness and purchasing intentions, and establish and maintain a regional customer resource pool.",
|
||||
"Participate in overseas industry exhibitions and seminars to develop customer resources and expand business channels.",
|
||||
"Assist the company in organizing overseas product launches and promoting brand marketing.",
|
||||
|
||||
|
||||
],
|
||||
requirements: [
|
||||
"Bachelor's degree or above, with preference given to candidates majoring in international trade, English, or German.",
|
||||
"At least 3 years of experience in the new energy industry trade, with preference given to candidates with experience in electrical, photovoltaic, or lithium battery energy storage related products.",
|
||||
"Fluent in spoken English and proficient in written communication, including international business emails.",
|
||||
"Familiar with various international trade terms and risk control, and proficient in using international social media and foreign trade customer acquisition software.",
|
||||
"Excellent communication and teamwork skills.",
|
||||
]
|
||||
},
|
||||
// {
|
||||
// id: 2,
|
||||
// title: "Lead Backend Engineer",
|
||||
// type: "Engineering",
|
||||
// dept: "Eng",
|
||||
// location: "San Francisco",
|
||||
// image: "https://images.unsplash.com/photo-1555099962-4199c345e5dd?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80",
|
||||
// color: "bg-emerald-500/20 border-white/10 text-emerald-100",
|
||||
// desc: "Join our backend team to architect scalable systems that handle millions of requests. You will be responsible for the core infrastructure and API development.",
|
||||
// responsibilities: ["Design and implement scalable APIs.", "Optimize database performance and queries.", "Mentor junior engineers and review code."],
|
||||
// requirements: ["Strong proficiency in Python, Go, or Java.", "Experience with distributed systems and microservices.", "Familiarity with AWS or Google Cloud."]
|
||||
// },
|
||||
{
|
||||
id: 3,
|
||||
title: "Business Development Manager ",
|
||||
type: "Development",
|
||||
dept: "Development",
|
||||
location: "Cambodia",
|
||||
image: "https://nenghui.com/wp-content/uploads/2026/02/job-3.avif",
|
||||
color: "bg-purple-500/90 border-white/10 text-purple-100",
|
||||
desc: "",
|
||||
responsibilities: ["Develop commercial and industrial photovoltaic (PV) projects within the assigned region, establishing strong client relationships, in accordance with the company's business strategy and development requirements.",
|
||||
"Handle all necessary procedures for project approval and grid connection, including preliminary data collection, site visits, and initial assessments of compliance and economic feasibility.",
|
||||
"Establish and improve distribution channels for commercial and industrial PV and energy storage projects within the assigned region, fostering a favorable development environment.",
|
||||
"Maintain relationships with local government/power grid authorities and relevant departments to promote project cooperation."
|
||||
],
|
||||
requirements: ["Bachelor's degree or above, any major, at least 2 years of experience in PV projects; experienced business professionals with large-scale project development experience are preferred.",
|
||||
"Familiar with the local/industry PV market and proficient in the development process of commercial and industrial power plants.",
|
||||
"Strong communication and execution skills, and excellent teamwork ability.",
|
||||
"Keen market insight, existing social or industry client resources, and strong ability to acquire project opportunities/leads are preferred."
|
||||
]
|
||||
},
|
||||
// {
|
||||
// id: 4,
|
||||
// title: "Financing Director/Manager",
|
||||
// type: "Financing",
|
||||
// dept: "Financing",
|
||||
// location: "ShangHai",
|
||||
// image: "https://nenghui.com/wp-content/uploads/2026/02/job-1.avif",
|
||||
// color: "bg-blue-500/90 border-white/10 text-blue-100",
|
||||
// desc: "",
|
||||
// responsibilities: ["Build and deploy machine learning models.", "Perform exploratory data analysis.", "Create data visualizations for stakeholders."],
|
||||
// requirements: ["Master's or PhD in Statistics, CS, or related field.", "Proficiency in Python, R, and SQL.", "Experience with TensorFlow or PyTorch."]
|
||||
// }
|
||||
];
|
||||
const email = 'zhoushu@nenghui.com'
|
||||
const emailLink = document.getElementById('email-link');
|
||||
if (emailLink) {
|
||||
emailLink.innerText = email;
|
||||
}
|
||||
let swiperInstance = null;
|
||||
const container = document.getElementById('jobs-container');
|
||||
const noResultsEl = document.getElementById('no-results');
|
||||
const searchInput = document.getElementById('job-search');
|
||||
|
||||
const modal = document.getElementById('job-detail-modal');
|
||||
|
||||
function openJobModal(jobId) {
|
||||
const job = jobsData.find(j => j.id === jobId);
|
||||
if (!job) return;
|
||||
|
||||
// Populate Data
|
||||
document.getElementById('modal-title').textContent = job.title;
|
||||
document.getElementById('modal-location').textContent = job.location;
|
||||
document.getElementById('modal-desc').textContent = job.desc;
|
||||
|
||||
const tag = document.getElementById('modal-tag');
|
||||
tag.textContent = job.type;
|
||||
tag.className = `inline-flex items-center justify-center px-2.5 py-0.5 rounded-full text-xs font-semibold border ${job.color}`;
|
||||
|
||||
const respList = document.getElementById('modal-responsibilities');
|
||||
respList.innerHTML = job.responsibilities.map(item => `<li>${item}</li>`).join('');
|
||||
|
||||
const reqList = document.getElementById('modal-requirements');
|
||||
reqList.innerHTML = job.requirements.map(item => `<li>${item}</li>`).join('');
|
||||
|
||||
const subject = encodeURIComponent(`Application for ${job.title} - [Your Name]`);
|
||||
const body = encodeURIComponent(`Dear Hiring Team,\n\nI am writing to express my interest in the ${job.title} position.\n\nPlease find my resume attached.\n\nBest regards,\n[Your Name]`);
|
||||
document.getElementById('modal-apply-btn').href = `mailto:${email}?subject=${subject}&body=${body}`;
|
||||
|
||||
// [修复] 强制重置滚动条位置到顶部
|
||||
document.getElementById('modal-body').scrollTop = 0;
|
||||
|
||||
modal.classList.add('open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeJobModal() {
|
||||
modal.classList.remove('open');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && modal.classList.contains('open')) {
|
||||
closeJobModal();
|
||||
}
|
||||
});
|
||||
|
||||
function renderJobs(data) {
|
||||
container.innerHTML = '';
|
||||
|
||||
if (data.length === 0) {
|
||||
noResultsEl.classList.remove('hidden');
|
||||
return;
|
||||
} else {
|
||||
noResultsEl.classList.add('hidden');
|
||||
}
|
||||
|
||||
data.forEach((job, index) => {
|
||||
const slide = document.createElement('div');
|
||||
slide.className = 'swiper-slide w-[85vw] sm:w-[320px] md:w-[380px]';
|
||||
slide.setAttribute('data-aos', 'fade-up');
|
||||
slide.setAttribute('data-aos-delay', window.innerWidth < 640 ? 0 : (index % 5) * 100);
|
||||
|
||||
slide.innerHTML = `
|
||||
<div class="flex flex-col h-full rounded-xl bg-white dark:bg-[#1a2632] shadow-[0_4px_20px_rgb(0,0,0,0.06)] hover:shadow-[0_20px_40px_rgb(0,0,0,0.1)] dark:shadow-none dark:hover:shadow-[0_20px_40px_rgba(0,0,0,0.4)] overflow-hidden group transition-all duration-300 border border-gray-100 dark:border-gray-800 hover:border-gray-200 dark:hover:border-gray-700">
|
||||
<div class="h-40 sm:h-48 w-full relative overflow-hidden">
|
||||
<div class="card-image-bg absolute inset-0 bg-cover bg-center" style='background-image: url("${job.image}");'></div>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent"></div>
|
||||
<div class="absolute bottom-4 left-4 z-10">
|
||||
<span class="inline-flex items-center justify-center px-2.5 py-0.5 sm:px-3 sm:py-1 rounded-full text-xs font-semibold ${job.color} backdrop-blur-md shadow-sm border border-white/10">${job.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-5 sm:p-6 flex flex-col flex-1 gap-3 sm:gap-4">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-xl sm:text-2xl text-[#111418] dark:text-white leading-tight mb-2 group-hover:text-primary transition-colors">${job.title}</h3>
|
||||
<div class="flex items-center gap-2 text-[#617589] dark:text-gray-400 text-xs sm:text-sm mb-3 sm:mb-4">
|
||||
<span class="material-symbols-outlined text-sm sm:text-base"></span><span>${job.location}</span>
|
||||
<span class="w-1 h-1 bg-gray-300 rounded-full mx-1"></span>
|
||||
<span class="material-symbols-outlined text-sm sm:text-base"></span><span>Full-time</span>
|
||||
</div>
|
||||
<p class="text-[#617589] dark:text-gray-400 text-sm line-clamp-2 leading-relaxed">${job.desc}</p>
|
||||
</div>
|
||||
<button onclick="openJobModal(${job.id})" class="mt-auto w-full flex items-center justify-center gap-2 rounded-lg h-10 sm:h-12 bg-white dark:bg-gray-800 border-2 border-gray-100 dark:border-gray-700 text-[#111418] dark:text-white text-sm hover:border-primary hover:text-primary dark:hover:border-primary dark:hover:text-primary transition-all duration-300 group/btn active:scale-95">
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(slide);
|
||||
});
|
||||
}
|
||||
|
||||
function initSwiper() {
|
||||
if (swiperInstance) {
|
||||
swiperInstance.destroy(true, true);
|
||||
}
|
||||
|
||||
swiperInstance = new Swiper(".mySwiper", {
|
||||
slidesPerView: "auto",
|
||||
centeredSlides: window.innerWidth < 640,
|
||||
spaceBetween: 16,
|
||||
grabCursor: true,
|
||||
speed: 600,
|
||||
resistance: true,
|
||||
resistanceRatio: 0.65,
|
||||
observer: true,
|
||||
observeParents: true,
|
||||
breakpoints: {
|
||||
640: {
|
||||
centeredSlides: false,
|
||||
spaceBetween: 24,
|
||||
}
|
||||
},
|
||||
navigation: {
|
||||
nextEl: ".swiper-button-next-custom",
|
||||
prevEl: ".swiper-button-prev-custom",
|
||||
},
|
||||
scrollbar: {
|
||||
el: ".swiper-scrollbar",
|
||||
draggable: true,
|
||||
snapOnRelease: false,
|
||||
},
|
||||
mousewheel: {
|
||||
forceToAxis: true,
|
||||
},
|
||||
keyboard: {
|
||||
enabled: true,
|
||||
onlyInViewport: true,
|
||||
},
|
||||
on: {
|
||||
init: function(sw) { updateCounter(sw); },
|
||||
slideChange: function(sw) { updateCounter(sw); },
|
||||
slidesLengthChange: function(sw) { updateCounter(sw); },
|
||||
resize: function(sw) {
|
||||
sw.params.centeredSlides = window.innerWidth < 640;
|
||||
sw.update();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateCounter(sw) {
|
||||
const currentEl = document.getElementById('slide-current');
|
||||
const totalEl = document.getElementById('slide-total');
|
||||
|
||||
let count = sw.slides.length;
|
||||
if(count === 0) {
|
||||
currentEl.textContent = "00";
|
||||
totalEl.textContent = "00";
|
||||
return;
|
||||
}
|
||||
let current = sw.activeIndex + 1;
|
||||
currentEl.textContent = String(current).padStart(2, '0');
|
||||
totalEl.textContent = String(count).padStart(2, '0');
|
||||
}
|
||||
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const query = e.target.value.toLowerCase().trim();
|
||||
const filtered = jobsData.filter(job =>
|
||||
job.title.toLowerCase().includes(query) ||
|
||||
job.type.toLowerCase().includes(query)
|
||||
);
|
||||
renderJobs(filtered);
|
||||
initSwiper();
|
||||
setTimeout(() => { AOS.refresh(); }, 100);
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
AOS.init({
|
||||
once: true,
|
||||
offset: 30,
|
||||
duration: 800,
|
||||
easing: 'ease-out-cubic',
|
||||
});
|
||||
renderJobs(jobsData);
|
||||
initSwiper();
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue