Files
vue-driven-cloud-storage/frontend/index.html
喻勇祥 c8f3ab5881 feat: 修复CORS安全漏洞 + 升级主页设计
🔒 安全修复:
- 修复分享链接HTTP/HTTPS协议识别问题
- 自动配置CORS安全策略(根据部署模式)
- 自动配置Cookie安全设置(HTTPS环境)
- 移除不安全的默认CORS配置

 功能改进:
- install.sh: 升级create_env_file()函数,智能配置CORS
  * 域名+HTTPS模式: ALLOWED_ORIGINS=https://domain
  * 域名+HTTP模式: ALLOWED_ORIGINS=http://domain
  * IP模式: 留空并显示安全警告
- backend/server.js: 添加getProtocol()函数,正确识别HTTPS
- backend/.env.example: 完全重写,添加详细的CORS配置说明

🎨 主页升级:
- frontend/index.html: 全新现代化设计
  * 渐变背景+动画效果
  * 9大功能特性展示
  * 8项技术栈展示
  * 完美响应式支持

📝 修改文件:
- backend/server.js (第63-83行, 1255行, 1282行)
- install.sh (第2108-2195行)
- backend/.env.example (完全重写)
- frontend/index.html (完全重写)

🔗 相关问题:
- 修复CORS允许任意域名访问的安全漏洞
- 修复分享链接使用HTTP的问题
- 解决Cookie在HTTP环境下的安全隐患

💡 向后兼容:
- 已部署项目可选择性升级
- 手动添加ALLOWED_ORIGINS配置即可生效

🎉 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 21:36:49 +08:00

996 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>玩玩云 - 现代化云存储管理平台</title>
<link rel="stylesheet" href="libs/fontawesome/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #667eea;
--primary-dark: #5568d3;
--secondary: #764ba2;
--accent: #f093fb;
--dark: #1a202c;
--light: #f7fafc;
--gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--shadow-sm: 0 2px 10px rgba(0,0,0,0.05);
--shadow-md: 0 10px 40px rgba(0,0,0,0.1);
--shadow-lg: 0 20px 60px rgba(0,0,0,0.15);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--gradient);
min-height: 100vh;
overflow-x: hidden;
}
/* 动画背景 */
.animated-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
overflow: hidden;
}
.animated-bg::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 1%, transparent 1%);
background-size: 50px 50px;
animation: moveBackground 20s linear infinite;
}
@keyframes moveBackground {
0% { transform: translate(0, 0); }
100% { transform: translate(50px, 50px); }
}
/* 浮动元素 */
.floating-shape {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.05);
animation: float 6s ease-in-out infinite;
}
.shape-1 { width: 300px; height: 300px; top: 10%; left: 5%; animation-delay: 0s; }
.shape-2 { width: 200px; height: 200px; top: 60%; right: 10%; animation-delay: 2s; }
.shape-3 { width: 150px; height: 150px; bottom: 15%; left: 15%; animation-delay: 4s; }
@keyframes float {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-30px) rotate(10deg); }
}
/* 导航栏 */
.navbar {
background: rgba(255, 255, 255, 0.98);
padding: 20px 50px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: var(--shadow-md);
position: sticky;
top: 0;
z-index: 100;
backdrop-filter: blur(10px);
}
.logo {
font-size: 32px;
font-weight: 800;
background: var(--gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: transform 0.3s;
}
.logo:hover {
transform: scale(1.05);
}
.logo i {
background: var(--gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.nav-buttons {
display: flex;
gap: 15px;
}
.btn {
padding: 14px 32px;
border: none;
border-radius: 12px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255,255,255,0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.btn:hover::before {
width: 300px;
height: 300px;
}
.btn > * {
position: relative;
z-index: 1;
}
.btn-primary {
background: var(--gradient);
color: white;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
.btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
}
.btn-outline {
background: transparent;
color: var(--primary);
border: 2px solid var(--primary);
}
.btn-outline:hover {
background: var(--primary);
color: white;
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3);
}
/* Hero Section */
.hero {
max-width: 1200px;
margin: 100px auto 60px;
padding: 0 50px;
text-align: center;
color: white;
animation: fadeInUp 1s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.hero-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 20px;
background: rgba(255,255,255,0.2);
border-radius: 50px;
font-size: 14px;
font-weight: 600;
margin-bottom: 30px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.3);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.hero h1 {
font-size: 72px;
font-weight: 900;
margin-bottom: 25px;
line-height: 1.2;
text-shadow: 0 4px 20px rgba(0,0,0,0.1);
letter-spacing: -1px;
}
.hero h1 i {
animation: bounce 2s ease-in-out infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.hero-subtitle {
font-size: 24px;
margin-bottom: 50px;
opacity: 0.95;
line-height: 1.6;
font-weight: 400;
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.hero-buttons {
display: flex;
gap: 20px;
justify-content: center;
flex-wrap: wrap;
}
.btn-large {
padding: 20px 40px;
font-size: 20px;
border-radius: 16px;
}
.btn-large i {
font-size: 22px;
}
/* Stats Section */
.stats {
max-width: 1200px;
margin: 80px auto;
padding: 0 50px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 40px;
}
.stat-card {
text-align: center;
color: white;
animation: fadeInUp 1s ease-out;
animation-fill-mode: both;
}
.stat-card:nth-child(1) { animation-delay: 0.1s; }
.stat-card:nth-child(2) { animation-delay: 0.2s; }
.stat-card:nth-child(3) { animation-delay: 0.3s; }
.stat-card:nth-child(4) { animation-delay: 0.4s; }
.stat-number {
font-size: 56px;
font-weight: 900;
display: block;
margin-bottom: 10px;
background: linear-gradient(135deg, #fff 0%, rgba(255,255,255,0.7) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-label {
font-size: 18px;
opacity: 0.9;
font-weight: 500;
}
/* Features Section */
.features-section {
background: white;
padding: 100px 50px;
margin-top: 60px;
}
.section-header {
text-align: center;
max-width: 800px;
margin: 0 auto 80px;
}
.section-title {
font-size: 48px;
font-weight: 800;
color: var(--dark);
margin-bottom: 20px;
position: relative;
display: inline-block;
}
.section-title::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 4px;
background: var(--gradient);
border-radius: 2px;
}
.section-desc {
font-size: 20px;
color: #666;
line-height: 1.7;
margin-top: 30px;
}
.features {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 40px;
}
.feature-card {
background: white;
padding: 50px 40px;
border-radius: 24px;
box-shadow: var(--shadow-sm);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: 2px solid transparent;
position: relative;
overflow: hidden;
}
.feature-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--gradient);
opacity: 0;
transition: opacity 0.4s;
z-index: 0;
}
.feature-card:hover::before {
opacity: 0.03;
}
.feature-card:hover {
transform: translateY(-12px);
box-shadow: var(--shadow-lg);
border-color: var(--primary);
}
.feature-card > * {
position: relative;
z-index: 1;
}
.feature-icon {
width: 80px;
height: 80px;
background: var(--gradient);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30px;
transition: all 0.4s;
}
.feature-icon i {
font-size: 40px;
color: white;
}
.feature-card:hover .feature-icon {
transform: scale(1.1) rotate(5deg);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4);
}
.feature-title {
font-size: 24px;
font-weight: 700;
margin-bottom: 16px;
color: var(--dark);
}
.feature-desc {
font-size: 16px;
color: #666;
line-height: 1.7;
}
.feature-tag {
display: inline-block;
padding: 6px 14px;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
color: var(--primary);
border-radius: 20px;
font-size: 13px;
font-weight: 600;
margin-top: 16px;
}
/* Tech Stack Section */
.tech-stack {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 100px 50px;
}
.tech-grid {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 30px;
}
.tech-item {
background: white;
padding: 30px 20px;
border-radius: 16px;
text-align: center;
box-shadow: var(--shadow-sm);
transition: all 0.3s;
border: 2px solid transparent;
}
.tech-item:hover {
transform: translateY(-8px);
box-shadow: var(--shadow-md);
border-color: var(--primary);
}
.tech-icon {
font-size: 48px;
margin-bottom: 16px;
background: var(--gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.tech-name {
font-size: 16px;
font-weight: 600;
color: var(--dark);
}
/* CTA Section */
.cta-section {
background: var(--dark);
color: white;
padding: 100px 50px;
text-align: center;
}
.cta-content {
max-width: 800px;
margin: 0 auto;
}
.cta-title {
font-size: 48px;
font-weight: 800;
margin-bottom: 24px;
}
.cta-desc {
font-size: 20px;
opacity: 0.9;
margin-bottom: 40px;
line-height: 1.6;
}
.cta-buttons {
display: flex;
gap: 20px;
justify-content: center;
flex-wrap: wrap;
}
/* Footer */
.footer {
background: #111;
color: white;
padding: 60px 50px 30px;
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 50px;
margin-bottom: 40px;
}
.footer-section h3 {
font-size: 20px;
font-weight: 700;
margin-bottom: 20px;
background: var(--gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.footer-links {
list-style: none;
}
.footer-links li {
margin-bottom: 12px;
}
.footer-links a {
color: rgba(255,255,255,0.7);
text-decoration: none;
transition: color 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.footer-links a:hover {
color: white;
}
.footer-bottom {
max-width: 1200px;
margin: 0 auto;
padding-top: 30px;
border-top: 1px solid rgba(255,255,255,0.1);
text-align: center;
color: rgba(255,255,255,0.7);
}
.footer-bottom p {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
/* Responsive */
@media (max-width: 768px) {
.navbar {
padding: 15px 20px;
}
.logo {
font-size: 24px;
}
.hero {
margin: 60px auto 40px;
padding: 0 20px;
}
.hero h1 {
font-size: 42px;
}
.hero-subtitle {
font-size: 18px;
}
.btn-large {
padding: 16px 28px;
font-size: 16px;
}
.stats {
padding: 0 20px;
gap: 30px;
}
.stat-number {
font-size: 40px;
}
.features-section,
.tech-stack,
.cta-section {
padding: 60px 20px;
}
.section-title {
font-size: 36px;
}
.section-desc {
font-size: 16px;
}
.features {
grid-template-columns: 1fr;
gap: 30px;
}
.footer {
padding: 40px 20px 20px;
}
.footer-content {
grid-template-columns: 1fr;
gap: 30px;
}
}
/* Scroll reveal animation */
.reveal {
opacity: 0;
transform: translateY(30px);
transition: all 0.8s ease-out;
}
.reveal.active {
opacity: 1;
transform: translateY(0);
}
</style>
</head>
<body>
<!-- 动画背景 -->
<div class="animated-bg">
<div class="floating-shape shape-1"></div>
<div class="floating-shape shape-2"></div>
<div class="floating-shape shape-3"></div>
</div>
<!-- 导航栏 -->
<nav class="navbar">
<div class="logo">
<i class="fas fa-cloud"></i>
玩玩云
</div>
<div class="nav-buttons">
<a href="app.html?action=login" class="btn btn-outline">
<i class="fas fa-right-to-bracket"></i>
<span>登录</span>
</a>
<a href="app.html?action=register" class="btn btn-primary">
<i class="fas fa-rocket"></i>
<span>开始使用</span>
</a>
</div>
</nav>
<!-- Hero Section -->
<div class="hero">
<div class="hero-badge">
<i class="fas fa-star"></i>
<span>v1.0 生产就绪版本</span>
</div>
<h1>
<i class="fas fa-cloud"></i>
玩玩云管理平台
</h1>
<p class="hero-subtitle">
简单、安全、高效的现代化云存储管理解决方案<br>
连接你的 SFTP 服务器,随时随地管理文件,轻松分享
</p>
<div class="hero-buttons">
<a href="app.html?action=register" class="btn btn-primary btn-large">
<i class="fas fa-rocket"></i>
<span>立即开始</span>
</a>
<a href="app.html?action=login" class="btn btn-outline btn-large">
<i class="fas fa-right-to-bracket"></i>
<span>已有账号</span>
</a>
</div>
</div>
<!-- Stats -->
<div class="stats">
<div class="stat-card">
<span class="stat-number">100%</span>
<span class="stat-label">开源免费</span>
</div>
<div class="stat-card">
<span class="stat-number">5GB</span>
<span class="stat-label">单文件上限</span>
</div>
<div class="stat-card">
<span class="stat-number">Docker</span>
<span class="stat-label">一键部署</span>
</div>
<div class="stat-card">
<span class="stat-number">24/7</span>
<span class="stat-label">随时访问</span>
</div>
</div>
<!-- Features Section -->
<section class="features-section">
<div class="section-header reveal">
<h2 class="section-title">强大的功能特性</h2>
<p class="section-desc">
为现代化文件管理而生,提供完整的文件操作、分享、管理功能
</p>
</div>
<div class="features">
<div class="feature-card reveal">
<div class="feature-icon">
<i class="fas fa-server"></i>
</div>
<h3 class="feature-title">SFTP 连接</h3>
<p class="feature-desc">
支持连接任何 SFTP 服务器,数据存储在你自己的服务器上,更安全可靠。支持多用户配置,每个用户独立管理。
</p>
<span class="feature-tag">自主可控</span>
</div>
<div class="feature-card reveal">
<div class="feature-icon">
<i class="fas fa-cloud-upload-alt"></i>
</div>
<h3 class="feature-title">文件管理</h3>
<p class="feature-desc">
网盘式界面,支持文件浏览、上传、下载、重命名、删除。拖拽上传,实时进度显示,单文件最大 5GB。
</p>
<span class="feature-tag">简单易用</span>
</div>
<div class="feature-card reveal">
<div class="feature-icon">
<i class="fas fa-share-alt"></i>
</div>
<h3 class="feature-title">文件分享</h3>
<p class="feature-desc">
一键生成分享链接,支持密码保护和有效期设置。提供分享统计,查看访问次数和下载次数。
</p>
<span class="feature-tag">安全分享</span>
</div>
<div class="feature-card reveal">
<div class="feature-icon">
<i class="fas fa-download"></i>
</div>
<h3 class="feature-title">流式下载</h3>
<p class="feature-desc">
服务器零存储,纯中转下载。支持 HTTP 直链和 SFTP 流式下载,实时显示下载进度。
</p>
<span class="feature-tag">高效传输</span>
</div>
<div class="feature-card reveal">
<div class="feature-icon">
<i class="fas fa-users"></i>
</div>
<h3 class="feature-title">多用户系统</h3>
<p class="feature-desc">
完整的用户注册、登录、权限管理系统。管理员可以管理所有用户和分享,支持用户封禁。
</p>
<span class="feature-tag">权限管理</span>
</div>
<div class="feature-card reveal">
<div class="feature-icon">
<i class="fas fa-lock"></i>
</div>
<h3 class="feature-title">安全可靠</h3>
<p class="feature-desc">
JWT 认证,密码 bcrypt 加密存储SQL 注入防护XSS 防护。所有数据传输经过加密处理。
</p>
<span class="feature-tag">企业级安全</span>
</div>
<div class="feature-card reveal">
<div class="feature-icon">
<i class="fas fa-mobile-alt"></i>
</div>
<h3 class="feature-title">响应式设计</h3>
<p class="feature-desc">
完美支持桌面和移动设备,自适应各种屏幕尺寸。随时随地访问你的文件,移动办公更便捷。
</p>
<span class="feature-tag">跨平台</span>
</div>
<div class="feature-card reveal">
<div class="feature-icon">
<i class="fas fa-desktop"></i>
</div>
<h3 class="feature-title">桌面工具</h3>
<p class="feature-desc">
提供桌面端上传工具,支持拖拽上传,实时显示进度。自动配置连接,让大文件上传更简单。
</p>
<span class="feature-tag">效率工具</span>
</div>
<div class="feature-card reveal">
<div class="feature-icon">
<i class="fab fa-docker"></i>
</div>
<h3 class="feature-title">Docker 部署</h3>
<p class="feature-desc">
完整的 Docker 容器化部署方案,一键部署脚本,自动环境检查。易于维护,快速升级。
</p>
<span class="feature-tag">开箱即用</span>
</div>
</div>
</section>
<!-- Tech Stack -->
<section class="tech-stack">
<div class="section-header reveal">
<h2 class="section-title">现代化技术栈</h2>
<p class="section-desc">
基于最新技术构建,保证性能和可扩展性
</p>
</div>
<div class="tech-grid">
<div class="tech-item reveal">
<div class="tech-icon"><i class="fab fa-node-js"></i></div>
<div class="tech-name">Node.js 20</div>
</div>
<div class="tech-item reveal">
<div class="tech-icon"><i class="fab fa-vuejs"></i></div>
<div class="tech-name">Vue.js 3</div>
</div>
<div class="tech-item reveal">
<div class="tech-icon"><i class="fas fa-database"></i></div>
<div class="tech-name">SQLite</div>
</div>
<div class="tech-item reveal">
<div class="tech-icon"><i class="fab fa-docker"></i></div>
<div class="tech-name">Docker</div>
</div>
<div class="tech-item reveal">
<div class="tech-icon"><i class="fas fa-network-wired"></i></div>
<div class="tech-name">Express</div>
</div>
<div class="tech-item reveal">
<div class="tech-icon"><i class="fas fa-shield-alt"></i></div>
<div class="tech-name">JWT</div>
</div>
<div class="tech-item reveal">
<div class="tech-icon"><i class="fas fa-lock"></i></div>
<div class="tech-name">bcrypt</div>
</div>
<div class="tech-item reveal">
<div class="tech-icon"><i class="fab fa-nginx"></i></div>
<div class="tech-name">Nginx</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="cta-section">
<div class="cta-content">
<h2 class="cta-title">准备好开始了吗?</h2>
<p class="cta-desc">
立即注册,开始使用玩玩云管理你的文件。<br>
完全免费,开源透明,数据完全由你掌控。
</p>
<div class="cta-buttons">
<a href="app.html?action=register" class="btn btn-primary btn-large">
<i class="fas fa-rocket"></i>
<span>免费注册</span>
</a>
<a href="https://gitee.com/yu-yon/vue-driven-cloud-storage" class="btn btn-outline btn-large" style="background: white; color: var(--primary); border-color: white;">
<i class="fab fa-github"></i>
<span>查看源码</span>
</a>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-content">
<div class="footer-section">
<h3><i class="fas fa-cloud"></i> 玩玩云</h3>
<p style="color: rgba(255,255,255,0.7); line-height: 1.7;">
现代化的云存储管理平台,让文件管理更简单、更安全、更高效。
</p>
</div>
<div class="footer-section">
<h3>产品</h3>
<ul class="footer-links">
<li><a href="#"><i class="fas fa-chevron-right"></i> 功能特性</a></li>
<li><a href="#"><i class="fas fa-chevron-right"></i> 技术栈</a></li>
<li><a href="#"><i class="fas fa-chevron-right"></i> 部署文档</a></li>
<li><a href="#"><i class="fas fa-chevron-right"></i> API 文档</a></li>
</ul>
</div>
<div class="footer-section">
<h3>资源</h3>
<ul class="footer-links">
<li><a href="#"><i class="fas fa-chevron-right"></i> 使用教程</a></li>
<li><a href="#"><i class="fas fa-chevron-right"></i> 常见问题</a></li>
<li><a href="#"><i class="fas fa-chevron-right"></i> 更新日志</a></li>
<li><a href="#"><i class="fas fa-chevron-right"></i> 问题反馈</a></li>
</ul>
</div>
<div class="footer-section">
<h3>关于</h3>
<ul class="footer-links">
<li><a href="#"><i class="fas fa-chevron-right"></i> 项目介绍</a></li>
<li><a href="#"><i class="fas fa-chevron-right"></i> 开源协议</a></li>
<li><a href="#"><i class="fas fa-chevron-right"></i> 贡献指南</a></li>
<li><a href="#"><i class="fas fa-chevron-right"></i> 联系我们</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>
<i class="fas fa-heart" style="color: #ff6b6b;"></i>
<span>玩玩云 © 2025 - 让文件管理更简单</span>
</p>
</div>
</footer>
<script>
// Scroll reveal animation
function reveal() {
const reveals = document.querySelectorAll('.reveal');
reveals.forEach(element => {
const windowHeight = window.innerHeight;
const elementTop = element.getBoundingClientRect().top;
const elementVisible = 150;
if (elementTop < windowHeight - elementVisible) {
element.classList.add('active');
}
});
}
window.addEventListener('scroll', reveal);
reveal(); // Check on load
// Smooth scroll
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
</script>
</body>
</html>