anycoder-e02ae4f1 / index.html
mianma2121212's picture
Upload folder using huggingface_hub
858eb1f verified
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>心动连接 - 相遇从这里开始</title>
<!-- 引入 FontAwesome 图标库 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-gradient: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%);
--secondary-gradient: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%);
--bg-color: #f3f4f6;
--text-dark: #1f2937;
--text-gray: #6b7280;
--white: #ffffff;
--shadow-sm: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--glass-bg: rgba(255, 255, 255, 0.85);
--glass-border: rgba(255, 255, 255, 0.3);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: 'PingFang SC', 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: #e5e7eb;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden; /* 防止背景滚动 */
}
/* 手机容器模拟 */
.app-container {
width: 100%;
max-width: 414px; /* iPhone Max 宽度 */
height: 100vh;
max-height: 896px;
background-color: var(--white);
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
@media (min-width: 450px) {
.app-container {
height: 90vh;
border-radius: 40px;
border: 8px solid #fff;
}
}
/* 顶部 Header */
header {
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 10;
background: rgba(255,255,255,0.9);
backdrop-filter: blur(10px);
}
.logo {
font-size: 1.5rem;
font-weight: 800;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: flex;
align-items: center;
gap: 8px;
}
.header-actions button {
background: none;
border: none;
font-size: 1.2rem;
color: var(--text-gray);
margin-left: 15px;
cursor: pointer;
transition: transform 0.2s;
}
.header-actions button:active {
transform: scale(0.9);
}
/* 主内容区域 - 卡片堆叠 */
main {
flex: 1;
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
overflow: hidden;
}
.card-stack {
position: relative;
width: 100%;
height: 100%;
max-height: 600px;
}
.card {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 24px;
background-size: cover;
background-position: center;
box-shadow: var(--shadow-lg);
overflow: hidden;
transform-origin: 50% 100%;
transition: transform 0.1s linear; /* 拖拽时不需要过渡,JS控制,松开时加上 */
cursor: grab;
user-select: none;
}
.card:active {
cursor: grabbing;
}
/* 卡片上的渐变遮罩,保证文字清晰 */
.card-content {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 80px 20px 20px;
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 60%);
color: white;
pointer-events: none;
}
.card-name {
font-size: 2rem;
font-weight: 700;
margin-bottom: 5px;
display: flex;
align-items: baseline;
gap: 10px;
}
.card-age {
font-size: 1.2rem;
font-weight: 400;
background: rgba(255,255,255,0.2);
padding: 2px 8px;
border-radius: 12px;
}
.card-job {
font-size: 1rem;
opacity: 0.9;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.card-bio {
font-size: 0.9rem;
opacity: 0.8;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* 状态标签 (喜欢/不喜欢) */
.status-badge {
position: absolute;
top: 40px;
padding: 5px 15px;
border: 4px solid;
border-radius: 8px;
font-size: 2rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 2px;
opacity: 0;
transform: rotate(-15deg);
z-index: 10;
}
.status-like {
left: 40px;
color: #4ade80;
border-color: #4ade80;
transform: rotate(-15deg);
}
.status-nope {
right: 40px;
color: #f87171;
border-color: #f87171;
transform: rotate(15deg);
}
/* 底部操作按钮区 */
.action-area {
height: 100px;
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
padding-bottom: 10px;
}
.action-btn {
width: 60px;
height: 60px;
border-radius: 50%;
background: white;
border: none;
box-shadow: var(--shadow-sm);
font-size: 1.5rem;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
display: flex;
justify-content: center;
align-items: center;
}
.action-btn:hover {
transform: scale(1.1);
}
.action-btn:active {
transform: scale(0.9);
}
.btn-nope { color: #f87171; }
.btn-super { color: #3b82f6; width: 50px; height: 50px; font-size: 1.2rem; }
.btn-like { color: #4ade80; }
.btn-info { color: #fbbf24; width: 50px; height: 50px; font-size: 1.2rem; }
/* 底部导航栏 */
.bottom-nav {
height: 70px;
background: var(--glass-bg);
backdrop-filter: blur(12px);
border-top: 1px solid var(--glass-border);
display: flex;
justify-content: space-around;
align-items: center;
padding-bottom: 10px; /* 适配 iPhone X 底部 */
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--text-gray);
text-decoration: none;
font-size: 0.75rem;
gap: 4px;
transition: color 0.3s;
cursor: pointer;
}
.nav-item i {
font-size: 1.5rem;
transition: transform 0.2s;
}
.nav-item.active {
color: #ff6b6b;
}
.nav-item.active i {
transform: translateY(-2px);
}
/* 模态框 (匹配成功 & 详情) */
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(5px);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.modal-overlay.active {
opacity: 1;
pointer-events: auto;
}
.match-modal {
background: white;
width: 85%;
border-radius: 30px;
padding: 30px;
text-align: center;
transform: scale(0.8);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.modal-overlay.active .match-modal {
transform: scale(1);
}
.match-title {
font-family: 'Brush Script MT', cursive;
font-size: 3rem;
color: #4ade80;
margin-bottom: 20px;
transform: rotate(-5deg);
}
.match-avatars {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 30px;
}
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
border: 4px solid white;
box-shadow: var(--shadow-sm);
object-fit: cover;
}
.match-btn {
width: 100%;
padding: 15px;
border-radius: 30px;
border: none;
font-size: 1rem;
font-weight: 600;
margin-bottom: 10px;
cursor: pointer;
transition: opacity 0.2s;
}
.btn-primary {
background: var(--primary-gradient);
color: white;
}
.btn-secondary {
background: #f3f4f6;
color: var(--text-dark);
}
/* 详情模态框样式 */
.detail-modal {
background: white;
width: 90%;
max-height: 80%;
border-radius: 20px;
overflow-y: auto;
position: relative;
}
.detail-header-img {
width: 100%;
height: 250px;
object-fit: cover;
}
.detail-content {
padding: 20px;
}
.tag-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 15px 0;
}
.tag {
background: #f3f4f6;
padding: 5px 12px;
border-radius: 15px;
font-size: 0.85rem;
color: var(--text-gray);
}
.close-modal-btn {
position: absolute;
top: 15px;
right: 15px;
background: rgba(0,0,0,0.5);
color: white;
border: none;
width: 32px;
height: 32px;
border-radius: 50%;
cursor: pointer;
z-index: 2;
}
/* Built with anycoder link */
.anycoder-link {
font-size: 0.7rem;
color: #9ca3af;
text-decoration: none;
margin-top: 5px;
display: block;
text-align: center;
}
.anycoder-link:hover {
color: #ff6b6b;
}
/* 空状态 */
.empty-state {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: var(--text-gray);
width: 80%;
display: none;
}
.empty-state i {
font-size: 3rem;
margin-bottom: 15px;
color: #d1d5db;
}
</style>
</head>
<body>
<div class="app-container">
<!-- 头部 -->
<header>
<div class="logo">
<i class="fa-solid fa-heart"></i> 心动连接
</div>
<div class="header-actions">
<button aria-label="筛选"><i class="fa-solid fa-sliders"></i></button>
<button aria-label="聊天"><i class="fa-solid fa-comment-dots"></i></button>
</div>
</header>
<!-- 主内容区:卡片 -->
<main>
<div class="empty-state" id="emptyState">
<i class="fa-regular fa-face-sad-tear"></i>
<h3>暂时没有更多推荐</h3>
<p>请稍后再试或调整筛选条件</p>
<button class="match-btn btn-primary" style="margin-top: 20px; width: auto; padding: 10px 30px;" onclick="location.reload()">重新加载</button>
</div>
<div class="card-stack" id="cardStack">
<!-- 卡片将通过 JS 动态生成 -->
</div>
</main>
<!-- 底部操作按钮 -->
<div class="action-area">
<button class="action-btn btn-nope" id="btnNope" aria-label="跳过">
<i class="fa-solid fa-xmark"></i>
</button>
<button class="action-btn btn-info" id="btnInfo" aria-label="详情">
<i class="fa-solid fa-circle-info"></i>
</button>
<button class="action-btn btn-like" id="btnLike" aria-label="喜欢">
<i class="fa-solid fa-heart"></i>
</button>
</div>
<!-- 底部导航 -->
<nav class="bottom-nav">
<a href="#" class="nav-item active">
<i class="fa-solid fa-layer-group"></i>
<span>推荐</span>
</a>
<a href="#" class="nav-item">
<i class="fa-solid fa-compass"></i>
<span>发现</span>
</a>
<a href="#" class="nav-item">
<i class="fa-solid fa-heart-circle-bolt"></i>
<span>匹配</span>
</a>
<a href="#" class="nav-item">
<i class="fa-regular fa-user"></i>
<span>我的</span>
</a>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
Built with anycoder
</a>
</nav>
<!-- 匹配成功弹窗 -->
<div class="modal-overlay" id="matchModal">
<div class="match-modal">
<div class="match-title">It's a Match!</div>
<p style="color: #6b7280; margin-bottom: 20px;">你和 <span id="matchName"></span> 互相喜欢了对方</p>
<div class="match-avatars">
<img src="https://picsum.photos/seed/me/200/200" alt="My Avatar" class="avatar">
<img id="matchAvatar" src="" alt="Match Avatar" class="avatar">
</div>
<button class="match-btn btn-primary" onclick="closeModal('matchModal')">发起聊天</button>
<button class="match-btn btn-secondary" onclick="closeModal('matchModal')">继续浏览</button>
</div>
</div>
<!-- 详情弹窗 -->
<div class="modal-overlay" id="detailModal">
<div class="detail-modal">
<button class="close-modal-btn" onclick="closeModal('detailModal')"><i class="fa-solid fa-xmark"></i></button>
<img id="detailImg" src="" class="detail-header-img" alt="Detail">
<div class="detail-content">
<div class="card-name" style="color: var(--text-dark); justify-content: flex-start;">
<span id="detailName">Name</span>
<span id="detailAge" class="card-age" style="background: #ff6b6b; color: white;">Age</span>
</div>
<div class="card-job" style="color: var(--text-gray);">
<i class="fa-solid fa-briefcase"></i> <span id="detailJob">Job</span>
</div>
<div class="card-job" style="color: var(--text-gray);">
<i class="fa-solid fa-location-dot"></i> <span id="detailLoc">Location</span>
</div>
<h4 style="margin-top: 20px; margin-bottom: 10px;">关于我</h4>
<p id="detailBio" style="color: #4b5563; line-height: 1.6;">Bio info...</p>
<h4 style="margin-top: 20px; margin-bottom: 10px;">兴趣标签</h4>
<div class="tag-container" id="detailTags">
<!-- Tags generated by JS -->
</div>
</div>
</div>
</div>
</div>
<script>
// 模拟用户数据
const profiles = [
{
id: 1,
name: '林婉儿',
age: 24,
job: 'UI 设计师',
location: '上海 · 徐汇',
bio: '喜欢猫、咖啡和周末的探店。寻找一个有趣灵魂一起探索这座城市。',
tags: ['摄影', '旅行', '猫奴', '极简主义'],
img: 'https://picsum.photos/seed/lin/600/900'
},
{
id: 2,
name: '苏晓',
age: 26,
job: '产品经理',
location: '杭州 · 西湖',
bio: '工作很忙,但生活不能只有工作。热爱户外运动,偶尔也会宅在家里看书。',
tags: ['健身', '阅读', '烹饪', '电影'],
img: 'https://picsum.photos/seed/su/600/900'
},
{
id: 3,
name: '陈思思',
age: 23,
job: '插画师',
location: '成都 · 高新',
bio: '用画笔记录生活。希望你是一个有耐心、有幽默感的人。',
tags: ['画画', '动漫', '甜食', '音乐节'],
img: 'https://picsum.photos/seed/chen/600/900'
},
{
id: 4,
name: '张雨薇',
age: 25,
job: '教师',
location: '北京 · 朝阳',
bio: '温柔是外衣,坚强是内核。喜欢逛博物馆和看展。',
tags: ['历史', '展览', '瑜伽', '茶道'],
img: 'https://picsum.photos/seed/zhang/600/900'
},
{
id: 5,
name: '赵露',
age: 27,
job: '自由撰稿人',
location: '深圳 · 南山',
bio: '文字工作者,喜欢深夜的灵感和清晨的阳光。',
tags: ['写作', '夜跑', '咖啡', '独立音乐'],
img: 'https://picsum.photos/seed/zhao/600/900'
}
];
let currentProfileIndex = profiles.length - 1; // 从最后一张开始渲染,利用堆叠顺序
const cardStack = document.getElementById('cardStack');
const emptyState = document.getElementById('emptyState');
// 初始化渲染
function initCards() {
cardStack.innerHTML = '';
profiles.forEach((profile, index) => {
const card = createCardElement(profile, index);
cardStack.appendChild(card);
});
updateZIndex();
attachDragEvents();
}
// 创建卡片 DOM
function createCardElement(profile, index) {
const el = document.createElement('div');
el.className = 'card';
el.style.backgroundImage = `url(${profile.img})`;
el.dataset.index = index;
el.innerHTML = `
<div class="status-badge status-like">LIKE</div>
<div class="status-badge status-nope">NOPE</div>
<div class="card-content">
<div class="card-name">${profile.name} <span class="card-age">${profile.age}</span></div>
<div class="card-job"><i class="fa-solid fa-briefcase"></i> ${profile.job}</div>
<div class="card-bio">${profile.bio}</div>
</div>
`;
return el;
}
// 更新堆叠顺序
function updateZIndex() {
const cards = document.querySelectorAll('.card');
cards.forEach((card, index) => {
// 确保当前的卡片在最上面
// index 0 是数组第一个(最底下),index length-1 是最上面
// 但为了拖拽逻辑,我们实际上是在操作 DOM 顺序或 transform
// 这里我们用 z-index 确保视觉正确
card.style.zIndex = index;
// 微微缩放后面的卡片,制造层次感
const offset = (cards.length - 1 - index) * 10;
if (index < cards.length - 1) {
card.style.transform = `scale(${1 - (cards.length - 1 - index) * 0.05}) translateY(${offset}px)`;
card.style.opacity = 1 - (cards.length - 1 - index) * 0.2;
card.style.pointerEvents = 'none'; // 只有最上面的卡片可以交互
} else {
card.style.transform = 'scale(1) translateY(0)';
card.style.opacity = 1;
card.style.pointerEvents = 'auto';
}
});
}
// 绑定拖拽事件
function attachDragEvents() {
const cards = document.querySelectorAll('.card');
// 只给最上面的卡片绑定事件
const topCard = cards[cards.length - 1];
if (!topCard) return;
let isDragging = false;
let startX = 0;
let currentX = 0;
let startY = 0;
let currentY = 0;
const likeBadge = topCard.querySelector('.status-like');
const nopeBadge = topCard.querySelector('.status-nope');
const onStart = (e) => {
isDragging = true;
startX = e.clientX || e.touches[0].clientX;
startY = e.clientY || e.touches[0].clientY;
topCard.style.transition = 'none'; // 拖拽时移除过渡,实现跟手
};
const onMove = (e) => {
if (!isDragging) return;
const x = e.clientX || e.touches[0].clientX;
const y = e.clientY || e.touches[0].clientY;
currentX = x - startX;
currentY = y - startY;
// 旋转角度
const rotate = currentX * 0.1;
topCard.style.transform = `translate(${currentX}px, ${currentY}px) rotate(${rotate}deg)`;
// 显示状态标签
if (currentX > 0) {
likeBadge.style.opacity = Math.min(currentX / 100, 1);
nopeBadge.style.opacity = 0;
} else {
nopeBadge.style.opacity = Math.min(Math.abs(currentX) / 100, 1);
likeBadge.style.opacity = 0;
}
};
const onEnd = () => {
if (!isDragging) return;
isDragging = false;
const threshold = 100; // 滑动阈值
if (currentX > threshold) {
swipeCard('right');
} else if (currentX < -threshold) {
swipeCard('left');
} else {
// 回弹
topCard.style.transition = 'transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
topCard.style.transform = 'translate(0, 0) rotate(0)';
likeBadge.style.opacity = 0;
nopeBadge.style.opacity = 0;
}
};
// 鼠标事件
topCard.addEventListener('mousedown', onStart);
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onEnd);
// 触摸事件
topCard.addEventListener('touchstart', onStart);
document.addEventListener('touchmove', onMove);
document.addEventListener('touchend', onEnd);
}
// 执行滑动逻辑
function swipeCard(direction) {
const cards = document.querySelectorAll('.card');
const topCard = cards[cards.length - 1];
if (!topCard) return;
const profile = profiles[topCard.dataset.index];
// 飞出动画
topCard.style.transition = 'transform 0.5s ease-out';
const endX = direction === 'right' ? window.innerWidth + 200 : -window.innerWidth - 200;
const rotate = direction === 'right' ? 30 : -30;
topCard.style.transform = `translate(${endX}px, 50px) rotate(${rotate}deg)`;
// 移除 DOM
setTimeout(() => {
topCard.remove();
updateZIndex();
attachDragEvents(); // 重新绑定给新的顶卡
// 检查是否匹配
if (direction === 'right') {
checkMatch(profile);
}
// 检查是否空了
if (document.querySelectorAll('.card').length === 0) {
emptyState.style.display = 'block';
}
}, 300);
}
// 底部按钮逻辑
document.getElementById('btnNope').addEventListener('click', () => {
const cards = document.querySelectorAll('.card');
if (cards.length > 0) {
swipeCard('left');
}
});
document.getElementById('btnLike').addEventListener('click', () => {
const cards = document.querySelectorAll('.card');
if (cards.length > 0) {
swipeCard('right');
}
});
document.getElementById('btnInfo').addEventListener('click', () => {
const cards = document.querySelectorAll('.card');
if (cards.length > 0) {
const topCard = cards[cards.length - 1];
const profile = profiles[topCard.dataset.index];
showDetail(profile);
}
});
// 匹配逻辑
function checkMatch(profile) {
// 30% 几率匹配成功
if (Math.random() < 0.3) {
document.getElementById('matchName').innerText = profile.name;
document.getElementById('matchAvatar').src = profile.img;
openModal('matchModal');
}
}
// 详情逻辑
function showDetail(profile) {
document.getElementById('detailImg').src = profile.img;
document.getElementById('detailName').innerText = profile.name;
document.getElementById('detailAge').innerText = profile.age;
document.getElementById('detailJob').innerText = profile.job;
document.getElementById('detailLoc').innerText = profile.location;
document.getElementById('detailBio').innerText = profile.bio;
const tagsContainer = document.getElementById('detailTags');
tagsContainer.innerHTML = profile.tags.map(tag => `<span class="tag">#${tag}</span>`).join('');
openModal('detailModal');
}
// 模态框通用
function openModal(id) {
document.getElementById(id).classList.add('active');
}
function closeModal(id) {
document.getElementById(id).classList.remove('active');
}
// 启动
initCards();
</script>
</body>
</html>