feat: 完成全站UI优化 - 科技感/未来风设计

- 前台页面全面升级为Tailwind CSS框架
- 引入Google Fonts (Space Grotesk, Noto Sans)
- 主色调更新为#25c0f4 (cyan blue)
- 实现玻璃态效果和渐变背景
- 优化首页网格卡片布局和悬停动画
- 优化详情页双栏布局和渐变Logo光晕
- 优化管理员登录页,添加科技网格背景
- Flask-Admin后台完整深色主题
- 统一Material Symbols图标系统
- 网站自动抓取功能界面优化

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jowe
2025-12-27 22:45:09 +08:00
commit 2fbca6ebc7
32 changed files with 4068 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
{
"permissions": {
"allow": [
"Bash(if [ -d \".git\" ])",
"Bash(then echo \"Git repository exists\")",
"Bash(else echo \"No git repository\")",
"Bash(fi)",
"Bash(python:*)",
"Bash(python3:*)",
"Bash(py test_db.py:*)",
"Bash(where:*)",
"Bash(/c/Users/linha/AppData/Local/Microsoft/WindowsApps/python test_db.py)",
"Bash(pip install:*)",
"Bash(pip uninstall:*)",
"Bash(tasklist:*)",
"Bash(findstr:*)",
"Bash(dir:*)",
"Bash(git init:*)",
"Bash(git add:*)"
]
}
}

12
.env.example Normal file
View File

@@ -0,0 +1,12 @@
# 数据库配置
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=ai_nav
# 安全配置
SECRET_KEY=your-secret-key-here
# 运行环境 (development/production)
FLASK_ENV=development

64
.gitignore vendored Normal file
View File

@@ -0,0 +1,64 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# 虚拟环境
venv/
ENV/
env/
.venv
# Flask
instance/
.webassets-cache
# 环境变量
.env
.env.local
# 数据库
*.db
*.sqlite
*.sqlite3
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# 日志
*.log
logs/
# 系统文件
.DS_Store
Thumbs.db
# 上传文件
static/uploads/*
!static/uploads/.gitkeep
# 临时文件
*.tmp
*.bak
*.cache

219
README.md Normal file
View File

@@ -0,0 +1,219 @@
# AI工具导航网站
一个简洁美观的AI产品导航网站用于展示和管理各类AI工具和应用。
## 功能特点
- ✅ 按标签分类展示AI工具
- ✅ 卡片式设计,美观易用
- ✅ 详细的工具介绍页面
- ✅ 完善的后台管理系统
- ✅ SEO友好的URL结构
- ✅ 响应式设计,支持移动端
- ✅ 浏览量统计
## 技术栈
- **后端**: Flask 3.0 + Python 3.8+
- **数据库**: MySQL 5.7+
- **前端**: Bootstrap 5 + Jinja2模板
- **管理后台**: Flask-Admin
- **用户认证**: Flask-Login
## 项目结构
```
zjpb/
├── app.py # Flask应用主文件
├── config.py # 配置文件
├── models.py # 数据库模型
├── init_db.py # 数据库初始化脚本
├── requirements.txt # Python依赖
├── .env.example # 环境变量示例
├── templates/ # HTML模板
│ ├── base.html
│ ├── index.html # 首页
│ ├── detail.html # 详情页
│ └── admin_login.html # 登录页
├── static/ # 静态资源
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── main.js
│ └── images/
└── migrations/ # 数据库迁移文件
```
## 快速开始
### 1. 环境准备
确保已安装以下软件:
- Python 3.8+
- MySQL 5.7+
- pip
### 2. 安装依赖
```bash
# 创建虚拟环境(推荐)
python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# Linux/Mac:
source venv/bin/activate
# 安装依赖
pip install -r requirements.txt
```
### 3. 配置数据库
1. 在MySQL中创建数据库:
```sql
CREATE DATABASE ai_nav CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
2. 复制环境变量配置文件:
```bash
cp .env.example .env
```
3. 编辑 `.env` 文件,修改数据库配置:
```
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=ai_nav
SECRET_KEY=your-secret-key-here
```
### 4. 初始化数据库
```bash
python init_db.py
```
这将创建所有数据表,并添加示例数据和默认管理员账号。
### 5. 运行应用
```bash
python app.py
```
访问 `http://localhost:5000` 查看网站。
## 管理后台
### 访问后台
- 后台地址: `http://localhost:5000/admin`
- 登录页面: `http://localhost:5000/admin/login`
### 默认管理员账号
```
用户名: admin
密码: admin123
```
**⚠️ 重要**: 首次登录后请立即修改默认密码!
### 后台功能
- **网站管理**: 添加、编辑、删除AI工具
- **标签管理**: 管理分类标签
- **管理员管理**: 添加和管理管理员账号
## 宝塔面板部署
### 1. 安装Python环境
在宝塔面板中安装Python项目管理器选择Python 3.8+版本。
### 2. 上传项目
将项目文件上传到服务器,例如 `/www/wwwroot/ai_nav`
### 3. 配置项目
1. 在宝塔面板中创建Python项目
2. 项目路径: `/www/wwwroot/ai_nav`
3. 启动文件: `app.py`
4. 端口: 5000或其他可用端口
### 4. 安装依赖
在项目目录下执行:
```bash
pip install -r requirements.txt
```
### 5. 配置反向代理
在宝塔面板的网站设置中配置反向代理:
- 目标URL: `http://127.0.0.1:5000`
- 启用缓存和gzip压缩
### 6. 配置SSL证书可选
为网站配置SSL证书以启用HTTPS。
## 使用说明
### 添加新网站
1. 登录后台管理系统
2. 点击"网站管理" -> "Create"
3. 填写网站信息:
- 网站名称
- URL地址
- URL别名用于SEO友好的URL
- Logo图片URL
- 简短描述
- 详细介绍
- 主要功能
- 选择标签
- 排序权重(数字越大越靠前)
### 管理标签
1. 登录后台
2. 点击"标签管理"
3. 可以添加、编辑或删除标签
4. 为标签设置图标Font Awesome类名
## 开发计划
- [ ] 搜索功能
- [ ] 用户评论和评分
- [ ] 关联新闻搜索2.0版本)
- [ ] 数据统计和分析
- [ ] API接口
- [ ] 网站收藏功能
## 常见问题
### 1. 数据库连接失败
检查 `.env` 文件中的数据库配置是否正确确保MySQL服务正在运行。
### 2. 启动时出现端口占用
修改 `app.py` 中的端口号或关闭占用5000端口的其他程序。
### 3. 静态资源加载失败
检查 `static` 目录权限确保Web服务器有读取权限。
## 许可证
MIT License
## 联系方式
如有问题或建议请提交Issue。

243
app.py Normal file
View File

@@ -0,0 +1,243 @@
import os
from flask import Flask, render_template, redirect, url_for, request, flash, jsonify
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from flask_admin import Admin, AdminIndexView
from flask_admin.contrib.sqla import ModelView
from datetime import datetime
from config import config
from models import db, Site, Tag, Admin as AdminModel
from utils.website_fetcher import WebsiteFetcher
def create_app(config_name='default'):
"""应用工厂函数"""
app = Flask(__name__)
# 加载配置
app.config.from_object(config[config_name])
# 初始化数据库
db.init_app(app)
# 初始化登录管理
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'admin_login'
login_manager.login_message = '请先登录'
@login_manager.user_loader
def load_user(user_id):
return AdminModel.query.get(int(user_id))
# ========== 前台路由 ==========
@app.route('/')
def index():
"""首页"""
# 获取所有启用的标签
tags = Tag.query.order_by(Tag.sort_order.desc(), Tag.id).all()
# 获取筛选的标签
tag_slug = request.args.get('tag')
selected_tag = None
if tag_slug:
selected_tag = Tag.query.filter_by(slug=tag_slug).first()
if selected_tag:
sites = Site.query.filter(
Site.is_active == True,
Site.tags.contains(selected_tag)
).order_by(Site.sort_order.desc(), Site.id.desc()).all()
else:
sites = []
else:
# 获取所有启用的网站
sites = Site.query.filter_by(is_active=True).order_by(
Site.sort_order.desc(), Site.id.desc()
).all()
return render_template('index.html', sites=sites, tags=tags, selected_tag=selected_tag)
@app.route('/site/<slug>')
def site_detail(slug):
"""网站详情页"""
site = Site.query.filter_by(slug=slug, is_active=True).first_or_404()
# 增加浏览次数
site.view_count += 1
db.session.commit()
return render_template('detail.html', site=site)
# ========== 后台登录路由 ==========
@app.route('/admin/login', methods=['GET', 'POST'])
def admin_login():
"""管理员登录"""
if current_user.is_authenticated:
return redirect(url_for('admin.index'))
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
admin = AdminModel.query.filter_by(username=username).first()
if admin and admin.check_password(password) and admin.is_active:
login_user(admin)
admin.last_login = datetime.now()
db.session.commit()
return redirect(url_for('admin.index'))
else:
flash('用户名或密码错误', 'error')
return render_template('admin_login.html')
@app.route('/admin/logout')
@login_required
def admin_logout():
"""管理员登出"""
logout_user()
return redirect(url_for('index'))
# ========== API路由 ==========
@app.route('/api/fetch-website-info', methods=['POST'])
@login_required
def fetch_website_info():
"""抓取网站信息API"""
try:
data = request.get_json()
url = data.get('url', '').strip()
if not url:
return jsonify({
'success': False,
'message': '请提供网站URL'
}), 400
# 创建抓取器
fetcher = WebsiteFetcher(timeout=15)
# 抓取网站信息
info = fetcher.fetch_website_info(url)
if not info:
return jsonify({
'success': False,
'message': '无法获取网站信息请检查URL是否正确或手动填写'
})
# 下载Logo如果有
logo_path = None
if info.get('logo_url'):
logo_path = fetcher.download_logo(info['logo_url'])
return jsonify({
'success': True,
'data': {
'name': info.get('name', ''),
'description': info.get('description', ''),
'logo': logo_path or info.get('logo_url', '')
}
})
except Exception as e:
return jsonify({
'success': False,
'message': f'抓取失败: {str(e)}'
}), 500
# ========== Flask-Admin 配置 ==========
class SecureModelView(ModelView):
"""需要登录的模型视图"""
def is_accessible(self):
return current_user.is_authenticated
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for('admin_login'))
class SecureAdminIndexView(AdminIndexView):
"""需要登录的管理首页"""
def is_accessible(self):
return current_user.is_authenticated
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for('admin_login'))
# 网站管理视图
class SiteAdmin(SecureModelView):
# 自定义模板
create_template = 'admin/site/create.html'
edit_template = 'admin/site/edit.html'
column_list = ['id', 'name', 'url', 'slug', 'is_active', 'view_count', 'created_at']
column_searchable_list = ['name', 'url', 'description']
column_filters = ['is_active', 'tags']
column_labels = {
'id': 'ID',
'name': '网站名称',
'url': 'URL',
'slug': 'URL别名',
'logo': 'Logo',
'short_desc': '简短描述',
'description': '详细介绍',
'features': '主要功能',
'is_active': '是否启用',
'view_count': '浏览次数',
'sort_order': '排序权重',
'tags': '标签',
'created_at': '创建时间',
'updated_at': '更新时间'
}
form_columns = ['name', 'url', 'slug', 'logo', 'short_desc', 'description', 'features', 'tags', 'is_active', 'sort_order']
# 标签管理视图
class TagAdmin(SecureModelView):
column_list = ['id', 'name', 'slug', 'description', 'sort_order']
column_searchable_list = ['name', 'description']
column_labels = {
'id': 'ID',
'name': '标签名称',
'slug': 'URL别名',
'description': '标签描述',
'icon': '图标',
'sort_order': '排序权重',
'created_at': '创建时间'
}
form_columns = ['name', 'slug', 'description', 'icon', 'sort_order']
# 管理员视图
class AdminAdmin(SecureModelView):
column_list = ['id', 'username', 'email', 'is_active', 'last_login', 'created_at']
column_searchable_list = ['username', 'email']
column_filters = ['is_active']
column_labels = {
'id': 'ID',
'username': '用户名',
'email': '邮箱',
'is_active': '是否启用',
'created_at': '创建时间',
'last_login': '最后登录'
}
form_columns = ['username', 'email', 'is_active']
def on_model_change(self, form, model, is_created):
# 如果是新建管理员,设置默认密码
if is_created:
model.set_password('admin123') # 默认密码
# 初始化 Flask-Admin
admin = Admin(
app,
name='ZJPB 焦提示词 - 后台管理',
template_mode='bootstrap4',
index_view=SecureAdminIndexView(),
base_template='admin/custom_base.html'
)
admin.add_view(SiteAdmin(Site, db.session, name='网站管理'))
admin.add_view(TagAdmin(Tag, db.session, name='标签管理'))
admin.add_view(AdminAdmin(AdminModel, db.session, name='管理员', endpoint='admin_users'))
return app
if __name__ == '__main__':
app = create_app(os.getenv('FLASK_ENV', 'development'))
app.run(host='0.0.0.0', port=5000, debug=True)

46
config.py Normal file
View File

@@ -0,0 +1,46 @@
import os
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
class Config:
"""基础配置"""
# 密钥配置
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production'
# 数据库配置
DB_HOST = os.environ.get('DB_HOST') or 'localhost'
DB_PORT = os.environ.get('DB_PORT') or '3306'
DB_USER = os.environ.get('DB_USER') or 'root'
DB_PASSWORD = os.environ.get('DB_PASSWORD') or ''
DB_NAME = os.environ.get('DB_NAME') or 'ai_nav'
SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?charset=utf8mb4'
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
# 分页配置
SITES_PER_PAGE = 20
# 上传文件配置
UPLOAD_FOLDER = 'static/uploads'
MAX_CONTENT_LENGTH = 5 * 1024 * 1024 # 5MB
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
class DevelopmentConfig(Config):
"""开发环境配置"""
DEBUG = True
SQLALCHEMY_ECHO = True
class ProductionConfig(Config):
"""生产环境配置"""
DEBUG = False
SQLALCHEMY_ECHO = False
# 配置字典
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}

117
init_db.py Normal file
View File

@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""
数据库初始化脚本
用于创建数据库表和初始化示例数据
"""
import os
import sys
from app import create_app
from models import db, Site, Tag, Admin
# 设置UTF-8编码输出
if sys.platform.startswith('win'):
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
def init_database():
"""初始化数据库"""
app = create_app('development')
with app.app_context():
print("正在创建数据库表...")
# 删除所有表(开发环境)
db.drop_all()
# 创建所有表
db.create_all()
print("✓ 数据库表创建成功")
# 创建默认管理员
print("\n正在创建默认管理员...")
admin = Admin(
username='admin',
email='admin@example.com',
is_active=True
)
admin.set_password('admin123') # 默认密码
db.session.add(admin)
print("✓ 默认管理员创建成功")
print(" 用户名: admin")
print(" 密码: admin123")
# 创建示例标签
print("\n正在创建示例标签...")
tags_data = [
{'name': 'AI对话', 'slug': 'ai-chat', 'description': 'AI聊天和对话工具', 'icon': 'fas fa-comments', 'sort_order': 100},
{'name': '图像生成', 'slug': 'image-gen', 'description': 'AI图像生成和编辑工具', 'icon': 'fas fa-image', 'sort_order': 90},
{'name': '视频制作', 'slug': 'video', 'description': 'AI视频生成和编辑工具', 'icon': 'fas fa-video', 'sort_order': 80},
{'name': '写作助手', 'slug': 'writing', 'description': 'AI写作和文本生成工具', 'icon': 'fas fa-pen', 'sort_order': 70},
{'name': '代码助手', 'slug': 'coding', 'description': 'AI编程和代码生成工具', 'icon': 'fas fa-code', 'sort_order': 60},
{'name': '音频处理', 'slug': 'audio', 'description': 'AI音频生成和处理工具', 'icon': 'fas fa-music', 'sort_order': 50},
]
tags = []
for tag_data in tags_data:
tag = Tag(**tag_data)
db.session.add(tag)
tags.append(tag)
db.session.commit()
print(f"✓ 创建了 {len(tags)} 个示例标签")
# 创建示例网站
print("\n正在创建示例网站...")
sites_data = [
{
'name': 'ChatGPT',
'url': 'https://chat.openai.com',
'slug': 'chatgpt',
'short_desc': '最强大的AI对话助手可以回答问题、写作、编程等',
'description': 'ChatGPT是OpenAI开发的大型语言模型能够进行自然对话、回答问题、协助写作、编程等多种任务。它基于GPT-4架构拥有强大的理解和生成能力。',
'features': '• 自然语言对话\n• 代码编写和调试\n• 文章写作和润色\n• 数据分析\n• 创意头脑风暴',
'tags': [tags[0], tags[3], tags[4]],
'sort_order': 100
},
{
'name': 'Midjourney',
'url': 'https://www.midjourney.com',
'slug': 'midjourney',
'short_desc': '顶级AI绘画工具可以根据文字描述生成精美图片',
'description': 'Midjourney是一款强大的AI图像生成工具通过简单的文字描述就能创作出高质量的艺术作品。支持多种艺术风格广泛应用于设计、插画等领域。',
'features': '• 文字转图像\n• 多种艺术风格\n• 高清图片输出\n• 图片变体生成\n• 社区画廊',
'tags': [tags[1]],
'sort_order': 95
},
{
'name': 'GitHub Copilot',
'url': 'https://github.com/features/copilot',
'slug': 'github-copilot',
'short_desc': 'AI编程助手帮助你更快地编写代码',
'description': 'GitHub Copilot是由GitHub和OpenAI联合开发的AI编程助手可以根据上下文自动建议代码补全支持多种编程语言。',
'features': '• 智能代码补全\n• 多语言支持\n• 函数生成\n• 代码注释生成\n• IDE集成',
'tags': [tags[4]],
'sort_order': 85
},
]
for site_data in sites_data:
site = Site(**site_data)
db.session.add(site)
db.session.commit()
print(f"✓ 创建了 {len(sites_data)} 个示例网站")
print("\n" + "="*50)
print("数据库初始化完成!")
print("="*50)
print("\n你可以使用以下命令启动应用:")
print(" python app.py")
print("\n然后访问:")
print(" 前台: http://localhost:5000")
print(" 后台: http://localhost:5000/admin")
print(" 登录: http://localhost:5000/admin/login")
print("\n管理员账号:")
print(" 用户名: admin")
print(" 密码: admin123")
print("\n⚠️ 请在生产环境中修改默认密码!")
if __name__ == '__main__':
init_database()

102
models.py Normal file
View File

@@ -0,0 +1,102 @@
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
db = SQLAlchemy()
# 网站和标签的多对多关系表
site_tags = db.Table('site_tags',
db.Column('site_id', db.Integer, db.ForeignKey('sites.id'), primary_key=True),
db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'), primary_key=True)
)
class Site(db.Model):
"""网站模型"""
__tablename__ = 'sites'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False, comment='网站名称')
url = db.Column(db.String(500), nullable=False, comment='网站URL')
slug = db.Column(db.String(100), unique=True, nullable=False, comment='URL别名')
logo = db.Column(db.String(500), comment='Logo图片路径')
short_desc = db.Column(db.String(200), comment='简短描述')
description = db.Column(db.Text, comment='详细介绍')
features = db.Column(db.Text, comment='主要功能')
is_active = db.Column(db.Boolean, default=True, comment='是否启用')
view_count = db.Column(db.Integer, default=0, comment='浏览次数')
sort_order = db.Column(db.Integer, default=0, comment='排序权重')
created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
# 关联标签
tags = db.relationship('Tag', secondary=site_tags, lazy='subquery',
backref=db.backref('sites', lazy=True))
def __repr__(self):
return f'<Site {self.name}>'
def to_dict(self):
"""转换为字典"""
return {
'id': self.id,
'name': self.name,
'url': self.url,
'slug': self.slug,
'logo': self.logo,
'short_desc': self.short_desc,
'description': self.description,
'features': self.features,
'is_active': self.is_active,
'view_count': self.view_count,
'tags': [tag.name for tag in self.tags],
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None
}
class Tag(db.Model):
"""标签模型"""
__tablename__ = 'tags'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True, nullable=False, comment='标签名称')
slug = db.Column(db.String(50), unique=True, nullable=False, comment='URL别名')
description = db.Column(db.String(200), comment='标签描述')
icon = db.Column(db.String(100), comment='图标')
sort_order = db.Column(db.Integer, default=0, comment='排序权重')
created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
def __repr__(self):
return f'<Tag {self.name}>'
def to_dict(self):
"""转换为字典"""
return {
'id': self.id,
'name': self.name,
'slug': self.slug,
'description': self.description,
'icon': self.icon
}
class Admin(UserMixin, db.Model):
"""管理员模型"""
__tablename__ = 'admins'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False, comment='用户名')
password_hash = db.Column(db.String(255), nullable=False, comment='密码哈希')
email = db.Column(db.String(100), comment='邮箱')
is_active = db.Column(db.Boolean, default=True, comment='是否启用')
created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
last_login = db.Column(db.DateTime, comment='最后登录时间')
def set_password(self, password):
"""设置密码"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""验证密码"""
return check_password_hash(self.password_hash, password)
def __repr__(self):
return f'<Admin {self.username}>'

12
requirements.txt Normal file
View File

@@ -0,0 +1,12 @@
Flask==3.0.0
Flask-SQLAlchemy==3.1.1
Flask-Admin==1.6.1
Flask-Login==0.6.3
pymysql==1.1.0
python-dotenv==1.0.0
Werkzeug==3.0.1
cryptography==41.0.7
WTForms==2.3.3
requests==2.31.0
beautifulsoup4==4.12.2
Pillow>=10.2.0

313
static/css/admin-theme.css Normal file
View File

@@ -0,0 +1,313 @@
/* ========== Flask-Admin 后台科技感主题 - ZJPB焦提示词 ========== */
/* 深色主题覆盖 */
body.admin-theme {
background: #111618 !important;
background-image:
radial-gradient(at 20% 20%, rgba(37, 192, 244, 0.08) 0px, transparent 50%),
radial-gradient(at 80% 80%, rgba(124, 58, 237, 0.08) 0px, transparent 50%);
color: #ffffff !important;
font-family: 'Space Grotesk', 'Noto Sans', sans-serif !important;
}
/* 导航栏 */
.navbar-admin {
background: rgba(27, 36, 39, 0.95) !important;
backdrop-filter: blur(20px);
border-bottom: 1px solid #283539;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
}
.navbar-admin .navbar-brand {
background: linear-gradient(to right, #25c0f4, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 700;
font-family: 'Space Grotesk', sans-serif !important;
}
/* 侧边栏 */
.nav-sidebar {
background: rgba(27, 36, 39, 0.8) !important;
}
.nav-sidebar .nav-link {
color: #9cb2ba !important;
transition: all 0.3s ease;
}
.nav-sidebar .nav-link:hover,
.nav-sidebar .nav-link.active {
color: #ffffff !important;
background: rgba(37, 192, 244, 0.15) !important;
border-left: 3px solid #25c0f4;
}
/* 卡片和面板 */
.card, .panel {
background: rgba(27, 36, 39, 0.6) !important;
backdrop-filter: blur(20px);
border: 1px solid #283539 !important;
border-radius: 12px !important;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.card-header, .panel-heading {
background: rgba(37, 192, 244, 0.08) !important;
border-bottom: 1px solid #283539 !important;
color: #ffffff !important;
font-weight: 600;
}
/* 表格 */
.table {
color: #ffffff !important;
}
.table thead th {
background: rgba(30, 39, 44, 0.8) !important;
border-color: #283539 !important;
color: #9cb2ba !important;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.table tbody tr {
background: transparent !important;
border-color: #283539 !important;
transition: all 0.2s ease;
}
.table tbody tr:hover {
background: rgba(30, 39, 44, 0.5) !important;
}
.table td, .table th {
border-color: #283539 !important;
color: #ffffff !important;
}
/* 表单 */
.form-control {
background: #111618 !important;
border: 1px solid #283539 !important;
color: #ffffff !important;
border-radius: 8px !important;
transition: all 0.3s ease;
}
.form-control:focus {
background: rgba(27, 36, 39, 0.8) !important;
border-color: #25c0f4 !important;
box-shadow: 0 0 0 3px rgba(37, 192, 244, 0.1) !important;
}
.form-control::placeholder {
color: #9cb2ba !important;
}
.form-label, label {
color: #9cb2ba !important;
font-weight: 500;
font-size: 13px;
}
/* 按钮 */
.btn-primary {
background: #25c0f4 !important;
border: none !important;
color: #111618 !important;
font-weight: 600;
box-shadow: 0 0 20px rgba(37, 192, 244, 0.3);
transition: all 0.3s ease;
}
.btn-primary:hover {
background: #1fa8d8 !important;
transform: translateY(-2px);
box-shadow: 0 0 30px rgba(37, 192, 244, 0.5);
}
.btn-info {
background: linear-gradient(135deg, #25c0f4 0%, #00f2fe 100%) !important;
border: none !important;
color: #111618 !important;
font-weight: 600;
}
.btn-secondary, .btn-default {
background: rgba(40, 53, 57, 0.6) !important;
border: 1px solid #283539 !important;
color: #ffffff !important;
transition: all 0.3s ease;
}
.btn-secondary:hover, .btn-default:hover {
background: rgba(52, 66, 71, 0.8) !important;
border-color: #4a5a60 !important;
}
/* 模态框 */
.modal-content {
background: rgba(27, 36, 39, 0.95) !important;
backdrop-filter: blur(20px);
border: 1px solid #283539 !important;
border-radius: 12px !important;
}
.modal-header {
border-bottom-color: #283539 !important;
background: rgba(37, 192, 244, 0.05);
}
.modal-footer {
border-top-color: #283539 !important;
}
/* 分页 */
.pagination .page-link {
background: rgba(27, 36, 39, 0.6) !important;
border-color: #283539 !important;
color: #9cb2ba !important;
transition: all 0.2s ease;
}
.pagination .page-link:hover {
background: rgba(37, 192, 244, 0.1) !important;
border-color: #25c0f4 !important;
color: #ffffff !important;
}
.pagination .page-item.active .page-link {
background: #25c0f4 !important;
border-color: #25c0f4 !important;
color: #111618 !important;
}
/* 警告框 */
.alert {
background: rgba(27, 36, 39, 0.8) !important;
border: 1px solid #283539 !important;
color: #ffffff !important;
border-radius: 8px !important;
}
.alert-success {
background: rgba(34, 197, 94, 0.1) !important;
border-color: rgba(34, 197, 94, 0.3) !important;
color: #4ade80 !important;
}
.alert-danger {
background: rgba(239, 68, 68, 0.1) !important;
border-color: rgba(239, 68, 68, 0.3) !important;
color: #f87171 !important;
}
.alert-info {
background: rgba(37, 192, 244, 0.1) !important;
border-color: rgba(37, 192, 244, 0.3) !important;
color: #25c0f4 !important;
}
.alert-warning {
background: rgba(251, 191, 36, 0.1) !important;
border-color: rgba(251, 191, 36, 0.3) !important;
color: #fbbf24 !important;
}
/* 链接 */
a {
color: #25c0f4 !important;
text-decoration: none;
transition: color 0.2s ease;
}
a:hover {
color: #1fa8d8 !important;
}
/* 文本颜色 */
.text-muted {
color: #9cb2ba !important;
}
/* 输入组 */
.input-group-text {
background: rgba(27, 36, 39, 0.6) !important;
border-color: #283539 !important;
color: #9cb2ba !important;
}
/* Select2 下拉框 */
.select2-container--bootstrap4 .select2-selection {
background: #111618 !important;
border-color: #283539 !important;
color: #ffffff !important;
}
.select2-dropdown {
background: rgba(27, 36, 39, 0.95) !important;
border-color: #283539 !important;
backdrop-filter: blur(10px);
}
.select2-results__option {
color: #ffffff !important;
}
.select2-results__option--highlighted {
background: rgba(37, 192, 244, 0.2) !important;
}
/* 徽章 */
.badge-primary {
background: #25c0f4 !important;
color: #111618 !important;
}
.badge-secondary {
background: #283539 !important;
color: #9cb2ba !important;
}
/* 进度条 */
.progress {
background: rgba(27, 36, 39, 0.6) !important;
}
.progress-bar {
background: #25c0f4 !important;
}
/* 额外优化 */
.navbar-nav .nav-link {
color: #9cb2ba !important;
}
.navbar-nav .nav-link:hover {
color: #ffffff !important;
}
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #111618;
}
::-webkit-scrollbar-thumb {
background: #283539;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #3a4b50;
}

614
static/css/style.css Normal file
View File

@@ -0,0 +1,614 @@
/* ========== 科技感/未来风主题样式 ========== */
/* 全局变量 */
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--tech-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--dark-bg: #0a0e27;
--dark-card: rgba(15, 23, 42, 0.8);
--glass-bg: rgba(255, 255, 255, 0.05);
--glass-border: rgba(255, 255, 255, 0.1);
--text-primary: #ffffff;
--text-secondary: #a0aec0;
--glow-color: #667eea;
--success-glow: #00f2fe;
}
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Microsoft YaHei', sans-serif;
background: var(--dark-bg);
background-image:
radial-gradient(at 0% 0%, rgba(102, 126, 234, 0.2) 0px, transparent 50%),
radial-gradient(at 100% 0%, rgba(118, 75, 162, 0.2) 0px, transparent 50%),
radial-gradient(at 100% 100%, rgba(79, 172, 254, 0.2) 0px, transparent 50%),
radial-gradient(at 0% 100%, rgba(0, 242, 254, 0.2) 0px, transparent 50%);
background-attachment: fixed;
color: var(--text-primary);
min-height: 100vh;
}
/* 导航栏样式 */
.navbar {
background: var(--dark-card) !important;
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--glass-border);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
}
.navbar-brand {
font-weight: 700;
font-size: 1.4rem;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
transition: all 0.3s ease;
}
.navbar-brand:hover {
transform: translateY(-2px);
filter: brightness(1.2);
}
.navbar-brand i {
background: var(--tech-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.nav-link {
color: var(--text-secondary) !important;
font-weight: 500;
transition: all 0.3s ease;
position: relative;
}
.nav-link:hover {
color: var(--text-primary) !important;
}
.nav-link::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 2px;
background: var(--tech-gradient);
transition: all 0.3s ease;
transform: translateX(-50%);
}
.nav-link:hover::after {
width: 80%;
}
/* 页面标题 */
.page-header {
text-align: center;
padding: 4rem 0 2rem;
}
.page-title {
font-size: 3.5rem;
font-weight: 800;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 1rem;
text-shadow: 0 0 40px rgba(102, 126, 234, 0.3);
}
.page-subtitle {
color: var(--text-secondary);
font-size: 1.2rem;
font-weight: 300;
}
/* 标签筛选 */
.tag-filter, .tags-filter {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
padding: 2rem 0;
}
.tag-item {
padding: 0.75rem 1.5rem;
background: var(--glass-bg);
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border);
border-radius: 50px;
color: var(--text-secondary);
text-decoration: none;
font-weight: 500;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.tag-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: var(--tech-gradient);
transition: all 0.4s ease;
z-index: -1;
}
.tag-item:hover {
color: var(--text-primary);
border-color: transparent;
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(79, 172, 254, 0.3);
}
.tag-item:hover::before {
left: 0;
}
.tag-item.active {
background: var(--primary-gradient);
color: var(--text-primary);
border-color: transparent;
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
}
.tag-item i {
margin-right: 0.5rem;
}
/* 网站卡片 */
.site-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 2rem;
padding: 2rem 0;
}
.site-card {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 2rem;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
cursor: pointer;
position: relative;
overflow: hidden;
}
.site-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--primary-gradient);
opacity: 0;
transition: opacity 0.4s ease;
z-index: 0;
}
.site-card:hover {
transform: translateY(-10px) scale(1.02);
border-color: rgba(102, 126, 234, 0.5);
box-shadow:
0 20px 60px rgba(102, 126, 234, 0.3),
0 0 40px rgba(79, 172, 254, 0.2);
}
.site-card:hover::before {
opacity: 0.1;
}
.site-card > * {
position: relative;
z-index: 1;
}
.site-logo {
width: 80px;
height: 80px;
object-fit: contain;
margin-bottom: 1.5rem;
border-radius: 15px;
background: rgba(255, 255, 255, 0.05);
padding: 10px;
transition: all 0.3s ease;
}
.site-card:hover .site-logo {
transform: scale(1.1) rotate(5deg);
filter: drop-shadow(0 0 20px rgba(79, 172, 254, 0.5));
}
.site-logo-placeholder {
width: 80px;
height: 80px;
background: var(--tech-gradient);
border-radius: 15px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 32px;
margin-bottom: 1.5rem;
}
.site-name, .card-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.75rem;
}
.site-desc, .card-text {
color: var(--text-secondary);
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: 1rem;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.site-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 1rem;
}
.site-tags .badge {
padding: 0.4rem 0.8rem;
background: rgba(79, 172, 254, 0.1);
border: 1px solid rgba(79, 172, 254, 0.3);
border-radius: 20px;
color: #4facfe;
font-size: 0.85rem;
font-weight: 500;
}
.site-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid var(--glass-border);
color: var(--text-secondary);
font-size: 0.9rem;
}
.site-meta i {
margin-right: 0.3rem;
color: #4facfe;
}
/* 详情页样式 */
.detail-container {
max-width: 900px;
margin: 0 auto;
padding: 3rem 1rem;
}
.detail-header {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 3rem;
margin-bottom: 2rem;
text-align: center;
}
.site-logo-large {
width: 120px !important;
height: 120px !important;
margin: 0 auto 2rem;
padding: 15px;
background: rgba(255, 255, 255, 0.05);
border-radius: 25px;
border: 2px solid var(--glass-border);
}
.detail-title {
font-size: 2.5rem;
font-weight: 800;
margin-bottom: 1rem;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.detail-url {
display: inline-block;
padding: 1rem 2rem;
background: var(--tech-gradient);
color: white;
text-decoration: none;
border-radius: 50px;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 8px 25px rgba(79, 172, 254, 0.3);
}
.detail-url:hover {
transform: translateY(-3px);
box-shadow: 0 12px 35px rgba(79, 172, 254, 0.5);
color: white;
}
.detail-section, .site-description, .site-features {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 2.5rem;
margin-bottom: 2rem;
}
.detail-section h3 {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 1.5rem;
color: var(--text-primary);
display: flex;
align-items: center;
}
.detail-section h3 i {
margin-right: 0.75rem;
background: var(--tech-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.detail-section p,
.detail-section ul,
.site-description p,
.site-description ul,
.site-features ul {
color: var(--text-secondary);
line-height: 1.8;
font-size: 1.05rem;
}
.detail-section ul,
.site-description ul,
.site-features ul {
list-style: none;
padding: 0;
}
.detail-section li,
.site-description li,
.site-features li {
padding: 0.75rem 0;
padding-left: 2rem;
position: relative;
}
.detail-section li::before,
.site-description li::before,
.site-features li::before {
content: '▸';
position: absolute;
left: 0;
color: #4facfe;
font-size: 1.2rem;
}
/* 页脚 */
footer {
background: var(--dark-card) !important;
backdrop-filter: blur(20px);
border-top: 1px solid var(--glass-border);
margin-top: 5rem;
}
/* 响应式设计 */
@media (max-width: 768px) {
.page-title {
font-size: 2.5rem;
}
.site-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.tag-filter, .tags-filter {
gap: 0.75rem;
}
.tag-item {
padding: 0.6rem 1.2rem;
font-size: 0.9rem;
}
.site-logo-large {
width: 80px !important;
height: 80px !important;
}
}
/* 按钮样式 */
.btn-primary {
background: var(--primary-gradient) !important;
border: none !important;
padding: 0.75rem 2rem;
border-radius: 50px;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
.btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 12px 35px rgba(102, 126, 234, 0.5);
}
.btn-secondary {
background: var(--glass-bg) !important;
backdrop-filter: blur(10px);
border: 1px solid var(--glass-border) !important;
color: var(--text-primary) !important;
padding: 0.75rem 2rem;
border-radius: 50px;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1) !important;
border-color: rgba(255, 255, 255, 0.3) !important;
}
.btn {
border-radius: 50px;
padding: 0.75rem 1.5rem;
font-weight: 600;
transition: all 0.3s ease;
}
.btn:hover {
transform: translateY(-2px);
}
/* 表单样式 */
.form-control {
background: var(--glass-bg) !important;
border: 1px solid var(--glass-border) !important;
color: var(--text-primary) !important;
border-radius: 15px;
padding: 0.75rem 1.25rem;
}
.form-control:focus {
background: rgba(255, 255, 255, 0.1) !important;
border-color: #4facfe !important;
box-shadow: 0 0 20px rgba(79, 172, 254, 0.3) !important;
color: var(--text-primary) !important;
}
.form-control::placeholder {
color: var(--text-secondary);
}
.form-label {
color: var(--text-secondary);
font-weight: 600;
margin-bottom: 0.75rem;
}
/* 卡片样式 */
.card {
background: var(--glass-bg) !important;
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border) !important;
border-radius: 20px !important;
}
.card-body {
color: var(--text-primary);
}
/* 警告框样式 */
.alert {
background: var(--glass-bg) !important;
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border) !important;
border-radius: 15px !important;
color: var(--text-primary) !important;
}
.alert-danger {
border-color: rgba(245, 87, 108, 0.5) !important;
background: rgba(245, 87, 108, 0.1) !important;
}
.alert-success {
border-color: rgba(0, 242, 254, 0.5) !important;
background: rgba(0, 242, 254, 0.1) !important;
}
/* 空状态样式 */
.empty-state {
padding: 60px 20px;
text-align: center;
color: var(--text-secondary);
}
.empty-state i {
font-size: 4rem;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 1rem;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: var(--dark-bg);
}
::-webkit-scrollbar-thumb {
background: var(--glass-bg);
border-radius: 5px;
border: 1px solid var(--glass-border);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.15);
}
/* 加载动画 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.site-card {
animation: fadeIn 0.5s ease-out;
}
@keyframes glow {
0%, 100% {
box-shadow: 0 0 20px rgba(102, 126, 234, 0.5);
}
50% {
box-shadow: 0 0 40px rgba(79, 172, 254, 0.8);
}
}

121
static/js/main.js Normal file
View File

@@ -0,0 +1,121 @@
// 主要JavaScript功能
document.addEventListener('DOMContentLoaded', function() {
// 初始化所有功能
initCardAnimations();
initExternalLinks();
initTooltips();
});
/**
* 卡片动画效果
*/
function initCardAnimations() {
const cards = document.querySelectorAll('.site-card');
cards.forEach((card, index) => {
// 添加延迟动画效果
card.style.animationDelay = `${index * 0.05}s`;
// 点击卡片查看详情
card.addEventListener('click', function(e) {
// 如果点击的是按钮,不触发卡片点击
if (e.target.tagName === 'A' || e.target.closest('a')) {
return;
}
// 找到"查看详情"按钮并触发点击
const detailBtn = card.querySelector('a[href*="site_detail"]');
if (detailBtn) {
window.location.href = detailBtn.href;
}
});
});
}
/**
* 外部链接处理
*/
function initExternalLinks() {
const externalLinks = document.querySelectorAll('a[target="_blank"]');
externalLinks.forEach(link => {
// 添加安全属性
link.setAttribute('rel', 'noopener noreferrer');
// 添加点击统计(可选)
link.addEventListener('click', function() {
console.log('External link clicked:', this.href);
// 这里可以添加统计代码
});
});
}
/**
* 初始化Bootstrap提示框
*/
function initTooltips() {
const tooltipTriggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="tooltip"]')
);
tooltipTriggerList.map(function(tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
}
/**
* 平滑滚动到顶部
*/
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
/**
* 显示加载动画
*/
function showLoading() {
const loader = document.createElement('div');
loader.id = 'loading';
loader.className = 'loading-overlay';
loader.innerHTML = '<div class="spinner-border text-primary" role="status"></div>';
document.body.appendChild(loader);
}
/**
* 隐藏加载动画
*/
function hideLoading() {
const loader = document.getElementById('loading');
if (loader) {
loader.remove();
}
}
/**
* 显示通知消息
*/
function showNotification(message, type = 'info') {
const alert = document.createElement('div');
alert.className = `alert alert-${type} alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-3`;
alert.style.zIndex = '9999';
alert.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alert);
// 3秒后自动关闭
setTimeout(() => {
alert.remove();
}, 3000);
}
// 导出函数供全局使用
window.scrollToTop = scrollToTop;
window.showLoading = showLoading;
window.hideLoading = hideLoading;
window.showNotification = showNotification;

0
static/uploads/.gitkeep Normal file
View File

View File

@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Admin Login - AI Discovery</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&amp;family=Noto+Sans:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#25c0f4",
"background-light": "#f5f8f8",
"background-dark": "#101e22",
"surface-dark": "#162125",
"input-bg": "#1b2427",
"input-border": "#3b4e54",
"text-muted": "#9cb2ba",
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"],
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
backgroundImage: {
'tech-grid': "radial-gradient(circle at center, rgba(37, 192, 244, 0.05) 0%, transparent 70%), linear-gradient(rgba(37, 192, 244, 0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(37, 192, 244, 0.03) 1px, transparent 1px)",
}
},
},
}
</script>
<style>
.glass-panel {
background: rgba(22, 33, 37, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.tech-bg-size {
background-size: 100% 100%, 40px 40px, 40px 40px;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark font-display text-slate-900 dark:text-white min-h-screen flex flex-col items-center justify-center relative overflow-hidden selection:bg-primary/30">
<!-- Ambient Background -->
<div class="absolute inset-0 z-0 bg-tech-grid tech-bg-size pointer-events-none" data-alt="Abstract subtle grid pattern background representing technology"></div>
<div class="absolute top-[-20%] right-[-10%] w-[600px] h-[600px] bg-primary/10 rounded-full blur-[120px] pointer-events-none"></div>
<div class="absolute bottom-[-10%] left-[-10%] w-[500px] h-[500px] bg-purple-600/10 rounded-full blur-[100px] pointer-events-none"></div>
<!-- Main Content Wrapper -->
<div class="w-full max-w-[480px] px-4 z-10 flex flex-col gap-6">
<!-- Back Navigation -->
<a class="group flex items-center gap-2 text-text-muted hover:text-white transition-colors w-fit" href="#">
<div class="flex items-center justify-center w-8 h-8 rounded-full border border-input-border bg-surface-dark group-hover:border-primary/50 transition-colors">
<span class="material-symbols-outlined text-sm">arrow_back</span>
</div>
<span class="text-sm font-medium">Back to Home</span>
</a>
<!-- Login Card -->
<div class="glass-panel border border-white/5 dark:border-input-border/50 rounded-xl shadow-2xl p-8 md:p-10 relative overflow-hidden">
<!-- Decorative Accent -->
<div class="absolute top-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary to-transparent opacity-70"></div>
<!-- Header -->
<div class="flex flex-col gap-2 mb-8">
<div class="flex items-center gap-3 mb-2">
<div class="p-2 rounded-lg bg-primary/10 text-primary">
<span class="material-symbols-outlined text-2xl">shield_person</span>
</div>
<span class="text-xs font-bold tracking-widest uppercase text-primary/80">System Access</span>
</div>
<h1 class="text-3xl font-black tracking-tight text-white leading-tight">管理员登录</h1>
<p class="text-text-muted text-base font-normal">Enter your credentials to access the AI Control Panel.</p>
</div>
<!-- Error Message Area (Static Example) -->
<!-- <div class="mb-6 p-3 rounded-lg bg-red-500/10 border border-red-500/20 flex items-start gap-3">
<span class="material-symbols-outlined text-red-400 text-sm mt-0.5">error</span>
<p class="text-red-400 text-sm">Invalid credentials provided.</p>
</div> -->
<!-- Form -->
<form class="flex flex-col gap-5">
<!-- Username Field -->
<div class="space-y-2">
<label class="text-white text-sm font-medium leading-normal" for="username">Username</label>
<div class="relative group">
<input class="form-input flex w-full h-14 pl-12 pr-4 rounded-lg text-white focus:outline-0 focus:ring-0 border border-input-border bg-input-bg focus:border-primary placeholder:text-text-muted text-base font-normal leading-normal transition-colors" id="username" placeholder="Enter your username or email" type="text"/>
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-text-muted group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none">
<span class="material-symbols-outlined text-[20px]">person</span>
</div>
</div>
</div>
<!-- Password Field -->
<div class="space-y-2">
<div class="flex justify-between items-end">
<label class="text-white text-sm font-medium leading-normal" for="password">Password</label>
<a class="text-xs text-primary hover:text-white transition-colors mb-1" href="#">Forgot password?</a>
</div>
<div class="flex w-full items-stretch rounded-lg group relative">
<input class="form-input flex w-full h-14 pl-12 pr-12 rounded-lg text-white focus:outline-0 focus:ring-0 border border-input-border bg-input-bg focus:border-primary placeholder:text-text-muted text-base font-normal leading-normal transition-colors z-10" id="password" placeholder="Enter your password" type="password"/>
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-text-muted group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none z-20">
<span class="material-symbols-outlined text-[20px]">lock</span>
</div>
<button class="absolute right-0 top-0 h-full px-4 text-text-muted hover:text-white transition-colors flex items-center justify-center z-20 focus:outline-none" type="button">
<span class="material-symbols-outlined text-[20px]">visibility_off</span>
</button>
</div>
</div>
<!-- Login Button -->
<button class="mt-4 flex w-full h-12 cursor-pointer items-center justify-center overflow-hidden rounded-lg bg-primary text-background-dark text-base font-bold leading-normal tracking-[0.015em] hover:bg-primary/90 transition-all active:scale-[0.98] shadow-[0_0_20px_rgba(37,192,244,0.3)] hover:shadow-[0_0_30px_rgba(37,192,244,0.5)]" type="button">
<span class="truncate">Login</span>
</button>
</form>
<!-- Footer Meta -->
<div class="mt-8 flex justify-center gap-6 border-t border-input-border/30 pt-6">
<p class="text-text-muted text-xs text-center">
Protected by reCAPTCHA. <a class="underline hover:text-primary" href="#">Privacy</a> &amp; <a class="underline hover:text-primary" href="#">Terms</a>.
</p>
</div>
</div>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

View File

@@ -0,0 +1,394 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Admin Management Interface</title>
<!-- Fonts -->
<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&amp;family=Noto+Sans:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<!-- Theme Configuration -->
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#25c0f4",
"primary-hover": "#0ea5e9",
"background-light": "#f5f8f8",
"background-dark": "#111618", /* Matched to snippets */
"surface-dark": "#192226",
"surface-highlight": "#283539",
"text-secondary": "#9cb2ba",
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"],
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "2xl": "1rem", "full": "9999px"},
},
},
}
</script>
<style>
body {
font-family: "Noto Sans", sans-serif;
}
h1, h2, h3, h4, h5, h6, .font-display {
font-family: "Space Grotesk", sans-serif;
}
/* Custom Scrollbar for dark theme */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #111618;
}
::-webkit-scrollbar-thumb {
background: #283539;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #3a4b50;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white overflow-hidden flex h-screen w-full">
<!-- Sidebar -->
<aside class="flex flex-col w-64 h-full border-r border-[#283539] bg-background-dark flex-shrink-0 transition-all duration-300">
<div class="p-6 flex items-center gap-3">
<div class="size-8 text-primary">
<svg fill="none" viewbox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M24 18.4228L42 11.475V34.3663C42 34.7796 41.7457 35.1504 41.3601 35.2992L24 42V18.4228Z" fill="currentColor" fill-rule="evenodd"></path>
<path clip-rule="evenodd" d="M24 8.18819L33.4123 11.574L24 15.2071L14.5877 11.574L24 8.18819ZM9 15.8487L21 20.4805V37.6263L9 32.9945V15.8487ZM27 37.6263V20.4805L39 15.8487V32.9945L27 37.6263ZM25.354 2.29885C24.4788 1.98402 23.5212 1.98402 22.646 2.29885L4.98454 8.65208C3.7939 9.08038 3 10.2097 3 11.475V34.3663C3 36.0196 4.01719 37.5026 5.55962 38.098L22.9197 44.7987C23.6149 45.0671 24.3851 45.0671 25.0803 44.7987L42.4404 38.098C43.9828 37.5026 45 36.0196 45 34.3663V11.475C45 10.2097 44.2061 9.08038 43.0155 8.65208L25.354 2.29885Z" fill="currentColor" fill-rule="evenodd"></path>
</svg>
</div>
<h1 class="text-white text-xl font-bold tracking-tight">AI Discovery</h1>
</div>
<nav class="flex-1 px-4 flex flex-col gap-2 mt-4">
<div class="px-3 py-2">
<p class="text-text-secondary text-xs font-bold uppercase tracking-wider mb-2">Main Menu</p>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-surface-highlight text-white group transition-colors" href="#">
<span class="material-symbols-outlined text-primary group-hover:text-white transition-colors">language</span>
<span class="text-sm font-medium">Website Management</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-white transition-colors mt-1" href="#">
<span class="material-symbols-outlined">label</span>
<span class="text-sm font-medium">Tag Management</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-white transition-colors mt-1" href="#">
<span class="material-symbols-outlined">group</span>
<span class="text-sm font-medium">Administrators</span>
</a>
</div>
<div class="px-3 py-2 mt-auto">
<p class="text-text-secondary text-xs font-bold uppercase tracking-wider mb-2">System</p>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-secondary hover:bg-surface-highlight hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined">settings</span>
<span class="text-sm font-medium">Settings</span>
</a>
</div>
</nav>
<div class="p-4 border-t border-[#283539]">
<div class="flex items-center gap-3">
<div class="bg-center bg-no-repeat bg-cover rounded-full size-10 border border-[#283539]" data-alt="User profile picture showing a minimal avatar" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuDlD4gGno1M8YZQOQ6QyLdUZtaU7klHxTjeH2Kf3aH39GkQ56ld-iSPZ6RCbkxzCeXJzMq6_29atBkPks_wP-Q7pvpyNAAZW2FJKxOFTSrcQZp2O30dVhn-DPYELqG1fdqM-hPfluduBN-I0k_Q8cJrJ3LKVUUgnxsE8bWD4CSo8CoYvXmtK03j86TehszyrfWfKpoBcu8XaHpjh10EGoDHFYeSJ6AKJQDsp_0iz5LC4bOXvyg0_Nef5mQOBtQ9zKuKSbCYhRuu7Wyo");'></div>
<div class="flex flex-col overflow-hidden">
<h2 class="text-white text-sm font-medium truncate">Admin User</h2>
<p class="text-text-secondary text-xs truncate">admin@ai-discovery.com</p>
</div>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 flex flex-col h-full overflow-hidden bg-background-dark relative">
<!-- Top Header -->
<header class="h-16 border-b border-[#283539] bg-background-dark/95 backdrop-blur px-8 flex items-center justify-between shrink-0 z-20">
<!-- Breadcrumbs -->
<div class="flex items-center gap-2 text-sm">
<a class="text-text-secondary hover:text-white transition-colors" href="#">Dashboard</a>
<span class="text-text-secondary">/</span>
<span class="text-white font-medium">Website Management</span>
</div>
<!-- Global Actions -->
<div class="flex items-center gap-6">
<!-- Search -->
<div class="relative w-64 hidden md:block">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-text-secondary">
<span class="material-symbols-outlined text-[20px]">search</span>
</span>
<input class="w-full bg-surface-highlight border-none rounded-lg py-2 pl-10 pr-4 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:bg-[#1f292d] transition-all" placeholder="Global search..." type="text"/>
</div>
<!-- Notifications -->
<button class="relative text-text-secondary hover:text-white transition-colors">
<span class="material-symbols-outlined">notifications</span>
<span class="absolute top-0 right-0 size-2 bg-primary rounded-full"></span>
</button>
<!-- Logout -->
<button class="text-text-secondary hover:text-white transition-colors">
<span class="material-symbols-outlined">logout</span>
</button>
</div>
</header>
<!-- Scrollable Content Area -->
<div class="flex-1 overflow-y-auto overflow-x-hidden p-8 scroll-smooth">
<div class="max-w-[1400px] mx-auto flex flex-col gap-8 pb-10">
<!-- Page Title & Toolbar -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div>
<h1 class="text-3xl md:text-4xl font-bold text-white mb-2">Website Management</h1>
<p class="text-text-secondary text-base">Manage and curate AI tools for the discovery platform.</p>
</div>
<div class="flex gap-3">
<button class="flex items-center gap-2 h-10 px-4 rounded-lg bg-surface-highlight hover:bg-[#344247] text-white text-sm font-medium transition-colors border border-transparent hover:border-[#4a5a60]">
<span class="material-symbols-outlined text-[20px]">filter_list</span>
<span>Filter</span>
</button>
<button class="flex items-center gap-2 h-10 px-4 rounded-lg bg-surface-highlight hover:bg-[#344247] text-white text-sm font-medium transition-colors border border-transparent hover:border-[#4a5a60]">
<span class="material-symbols-outlined text-[20px]">download</span>
<span>Export</span>
</button>
</div>
</div>
<!-- Content Grid: Table & Quick Add Form -->
<div class="grid grid-cols-1 xl:grid-cols-3 gap-8 items-start">
<!-- Left Column: Data Table -->
<div class="xl:col-span-2 flex flex-col gap-4">
<!-- Table Toolbar -->
<div class="bg-surface-dark border border-[#283539] rounded-t-xl p-4 flex flex-col sm:flex-row justify-between items-center gap-4">
<div class="relative w-full sm:w-72">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-text-secondary">
<span class="material-symbols-outlined text-[20px]">search</span>
</span>
<input class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2 pl-10 pr-4 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary" placeholder="Search websites..." type="text"/>
</div>
<div class="text-sm text-text-secondary">
Showing <span class="text-white font-medium">1-10</span> of <span class="text-white font-medium">128</span>
</div>
</div>
<!-- Table -->
<div class="bg-surface-dark border border-[#283539] rounded-b-xl overflow-hidden shadow-xl">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-[#1e272c] border-b border-[#283539]">
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary w-10">
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Website / Tool</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Tags</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary">Status</th>
<th class="p-4 text-xs font-semibold uppercase tracking-wider text-text-secondary text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-[#283539]">
<!-- Row 1 -->
<tr class="group hover:bg-[#1e272c] transition-colors">
<td class="p-4">
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
</td>
<td class="p-4">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-[#283539]" data-alt="Icon of Chat AI Tool" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuB_TTlnbkP1ygA1GNesXOhx_H9yup1gOKwumL9Qnh8VcPEUlwDRA89LRKhO8tyDtOtvtfrGh7CRSnP5xMOyb_GvJgQ-FQToY20cgHuTpVeI97COVlA4e2wOp04SH5ogk98S2SFF4jtSoS7RldXDdaafrysnsulA7_euKFIDf42NTPk_YwR4aglKFOeiQCQT-ITD6hCZjE8kqEfam8E6onBbJ14KZt2sE8gn0VP-pzzgAgPlr3htq5j6T26Tijk8V7SIFQGlB20GjSsb");'></div>
<div>
<h3 class="text-sm font-bold text-white group-hover:text-primary transition-colors">ChatGPT</h3>
<p class="text-xs text-text-secondary truncate max-w-[200px]">openai.com/chatgpt</p>
</div>
</div>
</td>
<td class="p-4">
<div class="flex flex-wrap gap-1">
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">LLM</span>
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Chatbot</span>
</div>
</td>
<td class="p-4">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/20">
Active
</span>
</td>
<td class="p-4 text-right">
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded-md hover:bg-primary/20 hover:text-primary text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">edit</span>
</button>
<button class="p-1.5 rounded-md hover:bg-red-500/20 hover:text-red-400 text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</td>
</tr>
<!-- Row 2 -->
<tr class="group hover:bg-[#1e272c] transition-colors">
<td class="p-4">
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
</td>
<td class="p-4">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-[#283539]" data-alt="Icon of Midjourney AI" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuA-IWUA_5FInPrqeTtZDcb85HR0X3jABxlDeG811Bd-_2x4ISEl8huiNp-kAG75fBmopT3dd5nSp1E0wCtOxXMFoP6HR9nrg4PfNDxjLao4ulneOOrJMgL4LdtuXgKxrQHm1WDoG4TviJ-lIYXzLVI9Yw-9mjDFihMurEOdJlarrMZekyEVrogy_jliQPo3oXvcemTUiJAZz9UCwq8LOEr09EqzU25kXCXx3Z3B0XvdM6VDUrQN8mKtKU1eU9CVwsLJfOKZ09CpJIwx");'></div>
<div>
<h3 class="text-sm font-bold text-white group-hover:text-primary transition-colors">Midjourney</h3>
<p class="text-xs text-text-secondary truncate max-w-[200px]">midjourney.com</p>
</div>
</div>
</td>
<td class="p-4">
<div class="flex flex-wrap gap-1">
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Image Gen</span>
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Art</span>
</div>
</td>
<td class="p-4">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/20">
Active
</span>
</td>
<td class="p-4 text-right">
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded-md hover:bg-primary/20 hover:text-primary text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">edit</span>
</button>
<button class="p-1.5 rounded-md hover:bg-red-500/20 hover:text-red-400 text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</td>
</tr>
<!-- Row 3 -->
<tr class="group hover:bg-[#1e272c] transition-colors">
<td class="p-4">
<input class="rounded bg-[#111618] border-[#283539] text-primary focus:ring-offset-[#111618]" type="checkbox"/>
</td>
<td class="p-4">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-cover bg-center shrink-0 border border-[#283539]" data-alt="Icon of Copy AI Tool" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuB6XdW_8pAKAs4YPwwYM4bcub_ilJ2i6L7baANaKdNNmDP5k1TRngBOsHE0aTy6sBuBJG49alX-4PRkz55qTcxsrRy6_S8rqD5gCTWLquEDcjqfSai-s7GEdZcBCNJ6G5hEFJM7JFli5r5ETcPq2QBz_NQT0W94XYhzaYpZQVEX-su5ywWxAcrfOFrWrhwC5RCiZL0B7AinM4c1pRSfeDtDpVTzxTvx0MrDi6gBH-duuCUmbr0YjkqhyaZqpRpX-ZhW-JiJQ3sKPUCA");'></div>
<div>
<h3 class="text-sm font-bold text-white group-hover:text-primary transition-colors">Copy.ai</h3>
<p class="text-xs text-text-secondary truncate max-w-[200px]">copy.ai</p>
</div>
</div>
</td>
<td class="p-4">
<div class="flex flex-wrap gap-1">
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Writing</span>
<span class="px-2 py-0.5 rounded text-[10px] font-medium bg-[#283539] text-white border border-[#3a4b50]">Marketing</span>
</div>
</td>
<td class="p-4">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-500/10 text-yellow-400 border border-yellow-500/20">
Pending
</span>
</td>
<td class="p-4 text-right">
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button class="p-1.5 rounded-md hover:bg-primary/20 hover:text-primary text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">edit</span>
</button>
<button class="p-1.5 rounded-md hover:bg-red-500/20 hover:text-red-400 text-text-secondary transition-colors">
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="flex items-center justify-between px-4 py-3 border-t border-[#283539]">
<div class="flex flex-1 justify-between sm:hidden">
<a class="relative inline-flex items-center rounded-md border border-[#283539] bg-[#111618] px-4 py-2 text-sm font-medium text-text-secondary hover:bg-[#1e272c] hover:text-white" href="#">Previous</a>
<a class="relative ml-3 inline-flex items-center rounded-md border border-[#283539] bg-[#111618] px-4 py-2 text-sm font-medium text-text-secondary hover:bg-[#1e272c] hover:text-white" href="#">Next</a>
</div>
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-end">
<nav aria-label="Pagination" class="isolate inline-flex -space-x-px rounded-md shadow-sm">
<a class="relative inline-flex items-center rounded-l-md px-2 py-2 text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">
<span class="material-symbols-outlined text-[20px]">chevron_left</span>
</a>
<a aria-current="page" class="relative z-10 inline-flex items-center bg-primary px-4 py-2 text-sm font-semibold text-[#111618] focus:z-20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary" href="#">1</a>
<a class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">2</a>
<a class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">3</a>
<a class="relative inline-flex items-center rounded-r-md px-2 py-2 text-text-secondary ring-1 ring-inset ring-[#283539] hover:bg-[#1e272c] focus:z-20 focus:outline-offset-0" href="#">
<span class="material-symbols-outlined text-[20px]">chevron_right</span>
</a>
</nav>
</div>
</div>
</div>
</div>
<!-- Right Column: Add/Edit Form -->
<div class="xl:col-span-1">
<div class="bg-surface-dark border border-[#283539] rounded-xl shadow-xl sticky top-8">
<div class="px-6 py-4 border-b border-[#283539] flex justify-between items-center bg-[#1e272c] rounded-t-xl">
<h2 class="text-lg font-bold text-white">Add New Website</h2>
<span class="material-symbols-outlined text-text-secondary cursor-pointer hover:text-white">close</span>
</div>
<div class="p-6 flex flex-col gap-6">
<!-- URL Input + Auto Fetch -->
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-white">Website URL <span class="text-primary">*</span></label>
<div class="flex flex-col gap-2">
<div class="relative">
<input class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2.5 px-3 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary transition-all" placeholder="https://example.com" type="url"/>
<div class="absolute inset-y-0 right-3 flex items-center">
<span class="material-symbols-outlined text-green-500 text-[18px]" style="display:none;">check_circle</span>
</div>
</div>
<!-- CRITICAL: Auto-fetch Button -->
<button class="flex items-center justify-center gap-2 w-full py-2 px-3 rounded-lg border border-primary/30 bg-primary/5 hover:bg-primary/10 text-primary text-sm font-medium transition-colors group" type="button">
<span class="material-symbols-outlined text-[18px] group-hover:animate-spin">autorenew</span>
Auto-fetch Website Info
</button>
<p class="text-xs text-text-secondary mt-1">
Our AI will attempt to scrape metadata and descriptions automatically.
</p>
</div>
</div>
<!-- Other Fields -->
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-white">Tool Name</label>
<input class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2.5 px-3 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary" placeholder="e.g. ChatGPT" type="text"/>
</div>
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-white">Short Description</label>
<textarea class="w-full bg-[#111618] border border-[#283539] rounded-lg py-2.5 px-3 text-sm text-white placeholder-text-secondary focus:ring-1 focus:ring-primary focus:border-primary resize-none" placeholder="Enter a brief description of the AI tool..." rows="4"></textarea>
</div>
<div class="flex flex-col gap-3">
<label class="text-sm font-medium text-white">Tags</label>
<div class="p-3 bg-[#111618] border border-[#283539] rounded-lg min-h-[44px] flex flex-wrap gap-2">
<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-[#283539] text-xs text-white">
Productivity
<span class="material-symbols-outlined text-[14px] cursor-pointer hover:text-red-400">close</span>
</span>
<input class="bg-transparent border-none p-0 text-sm text-white placeholder-text-secondary focus:ring-0 w-24" placeholder="+ Add tag" type="text"/>
</div>
</div>
<!-- Status Toggle -->
<div class="flex items-center justify-between py-2">
<label class="text-sm font-medium text-white">Active Status</label>
<button class="w-11 h-6 bg-primary rounded-full relative cursor-pointer">
<span class="absolute top-1 left-6 size-4 bg-white rounded-full shadow-sm transition-all"></span>
</button>
</div>
</div>
<div class="px-6 py-4 border-t border-[#283539] flex gap-3 bg-[#1e272c] rounded-b-xl">
<button class="flex-1 py-2.5 rounded-lg bg-primary hover:bg-primary-hover text-[#111618] text-sm font-bold shadow-lg shadow-primary/20 transition-all">
Save Website
</button>
<button class="px-4 py-2.5 rounded-lg border border-[#283539] bg-transparent hover:bg-[#283539] text-white text-sm font-medium transition-colors">
Cancel
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

View File

@@ -0,0 +1,270 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>AI Tool Detail Page</title>
<!-- Google Fonts -->
<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;600;700&amp;family=Noto+Sans:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<!-- Theme Config -->
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#25c0f4",
"primary-dark": "#0ea5e9",
"secondary": "#a855f7", // Adding purple for the gradient request
"background-light": "#f5f8f8",
"background-dark": "#101e22",
"surface-dark": "#1a262b",
"surface-border": "#283539",
"text-secondary": "#9cb2ba"
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "2xl": "1rem", "full": "9999px"},
},
},
}
</script>
<style>
body {
font-family: 'Space Grotesk', sans-serif;
}
.glass-card {
background: rgba(26, 38, 43, 0.6);
backdrop-filter: blur(12px);
border: 1px solid rgba(40, 53, 57, 0.5);
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white min-h-screen flex flex-col selection:bg-primary selection:text-background-dark">
<!-- Top Navigation -->
<div class="w-full border-b border-surface-border bg-background-dark/95 backdrop-blur sticky top-0 z-50">
<div class="max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8">
<header class="flex items-center justify-between h-16">
<!-- Logo Area -->
<div class="flex items-center gap-8">
<div class="flex items-center gap-3 text-white">
<div class="size-8 text-primary">
<span class="material-symbols-outlined text-3xl">token</span>
</div>
<h2 class="text-white text-xl font-bold tracking-tight">AI Discovery</h2>
</div>
<!-- Desktop Nav -->
<div class="hidden md:flex items-center gap-6">
<a class="text-text-secondary hover:text-white text-sm font-medium transition-colors" href="#">Categories</a>
<a class="text-text-secondary hover:text-white text-sm font-medium transition-colors" href="#">Submit Tool</a>
<a class="text-text-secondary hover:text-white text-sm font-medium transition-colors" href="#">Blog</a>
</div>
</div>
<!-- Right Actions -->
<div class="flex items-center gap-4">
<div class="hidden sm:flex relative group">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span class="material-symbols-outlined text-text-secondary text-[20px]">search</span>
</div>
<input class="bg-surface-border/50 hover:bg-surface-border focus:bg-surface-border text-white text-sm rounded-lg pl-10 pr-4 py-2 w-48 focus:w-64 transition-all outline-none ring-0 border border-transparent focus:border-primary/50 placeholder:text-text-secondary/70" placeholder="Search tools..."/>
</div>
<button class="flex items-center justify-center rounded-lg h-9 px-4 bg-primary hover:bg-primary-dark text-background-dark text-sm font-bold transition-colors">
Log In
</button>
</div>
</header>
</div>
</div>
<!-- Main Layout -->
<main class="flex-grow w-full max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Back Button -->
<div class="mb-8">
<button class="group flex items-center gap-2 text-text-secondary hover:text-primary transition-colors text-sm font-bold">
<span class="material-symbols-outlined text-[20px] group-hover:-translate-x-1 transition-transform">arrow_back</span>
<span>Back to Home</span>
</button>
</div>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
<!-- Left Column: Header Info + Content -->
<div class="lg:col-span-8 flex flex-col gap-8">
<!-- Header Section -->
<div class="flex flex-col sm:flex-row gap-6 items-start">
<!-- Logo Container -->
<div class="relative shrink-0">
<div class="absolute -inset-1 bg-gradient-to-br from-primary via-purple-500 to-secondary rounded-2xl opacity-30 blur-md"></div>
<div class="relative size-[120px] rounded-xl bg-surface-border flex items-center justify-center overflow-hidden border border-surface-border bg-cover bg-center" data-alt="Abstract neural network pattern in deep blue and purple" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAFm-_1orioA8YwgKaNGCzxomQeon7z13YmJCs2VpIoXP6J2a5kLgu_mC9IFA-cRA5s6yNaDPiWRxhF8tTX4FFOTbngaHydfCmAMNxU2EuZ2burR-hLOggJYl24v1Ry7GaNu6ETcYui4SIfvWqNs43vwA4vEmoogV3TxVzuDRDHsy5nj9ab4TnW4c5mVgqHPttLIF1NKTm6rtEcPnWW1jtCihIAAqZVnst9o0Z-IyAYGJoPjNajRdJZGR5NDfiBf-tdr79Bo42KY_DX');">
</div>
</div>
<!-- Title & Meta -->
<div class="flex flex-col gap-3 flex-1">
<div>
<h1 class="text-4xl font-bold text-white tracking-tight font-display mb-1">NeuroGen AI</h1>
<a class="text-primary hover:text-primary-dark hover:underline text-base font-medium flex items-center gap-1" href="#">
www.neurogen.ai
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
</a>
</div>
<!-- Tags -->
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-border text-text-secondary border border-transparent hover:border-primary/30 transition-colors cursor-default">Generative AI</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-border text-text-secondary border border-transparent hover:border-primary/30 transition-colors cursor-default">Copywriting</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-border text-text-secondary border border-transparent hover:border-primary/30 transition-colors cursor-default">Productivity</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-500/10 text-purple-400 border border-purple-500/20">Free Trial</span>
</div>
<!-- Stats Row -->
<div class="flex items-center gap-6 mt-1 text-text-secondary text-sm">
<div class="flex items-center gap-2" title="Total Views">
<span class="material-symbols-outlined text-[18px]">visibility</span>
<span>12,450 Views</span>
</div>
<div class="flex items-center gap-2" title="Date Added">
<span class="material-symbols-outlined text-[18px]">calendar_today</span>
<span>Added Oct 24, 2023</span>
</div>
</div>
</div>
</div>
<hr class="border-surface-border/60"/>
<!-- Main Content Body -->
<div class="space-y-8">
<!-- Overview -->
<section>
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">info</span>
Product Overview
</h3>
<p class="text-text-secondary leading-relaxed text-lg">
NeuroGen AI is an advanced copywriting assistant designed to help marketers, writers, and businesses generate high-converting content in seconds. By leveraging state-of-the-art natural language processing models, it understands context, tone, and brand voice to deliver tailored outputs.
</p>
</section>
<!-- Image Gallery / Preview (Optional visual break) -->
<div class="rounded-xl overflow-hidden border border-surface-border relative group aspect-video w-full bg-surface-dark">
<div class="absolute inset-0 bg-cover bg-center" data-alt="Dashboard interface of NeuroGen AI showing analytics and text editor" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCtwM-4OkGIPG4sQqABpRcLvOgKvLzuyVlQJUVcU8SqFsVod_jck_vFm-q7UX70vUaaF7UPEa--rkf18dMi5_qYcXYsjTzd5ehM3ga_OoIr1r6wnRTnRxKcrUUC5USP72vdsFJkRFeKmH6epH1Avi5tglJ732VTvoNiuys499jctwZ67d4Yu8Slfn0EO4Ny8zIjzPRGLer8lMaEeZPMagNbgn4asMLvmDtTep3mgPzwTscfzoOFVChGqNak0ZmLyOiVY7vKIapdHyoz');">
</div>
<div class="absolute inset-0 bg-gradient-to-t from-background-dark via-transparent to-transparent opacity-60"></div>
<div class="absolute bottom-4 left-4">
<p class="text-white font-medium text-sm bg-black/50 backdrop-blur px-3 py-1 rounded-lg">Dashboard Interface</p>
</div>
</div>
<!-- Detailed Description -->
<section>
<h3 class="text-xl font-bold text-white mb-4">Detailed Description</h3>
<div class="text-text-secondary space-y-4 leading-relaxed">
<p>
Writing high-quality content consistently is a challenge for modern businesses. NeuroGen AI bridges the gap between human creativity and machine speed. Unlike generic text generators, NeuroGen allows users to fine-tune specific parameters such as emotional resonance, sentence structure complexity, and SEO keyword density.
</p>
<p>
The platform includes specialized templates for:
</p>
<ul class="list-disc pl-5 space-y-2 text-gray-400 marker:text-primary">
<li>Social media captions (Instagram, LinkedIn, Twitter)</li>
<li>Long-form blog posts with automatic formatting</li>
<li>Email marketing sequences</li>
<li>Ad copy variants for A/B testing</li>
</ul>
<p>
Security is paramount; NeuroGen ensures that your proprietary data is never used to train public models. Enterprise-grade encryption and team collaboration features make it a suitable choice for large organizations looking to scale their content operations.
</p>
</div>
</section>
</div>
</div>
<!-- Right Column: Sidebar -->
<div class="lg:col-span-4 space-y-6">
<!-- CTA Card -->
<div class="glass-card rounded-2xl p-6 flex flex-col gap-4 shadow-xl shadow-black/20 sticky top-24">
<div class="flex items-center justify-between mb-2">
<span class="text-white font-bold text-lg">Try it now</span>
<span class="px-2 py-1 rounded bg-green-500/20 text-green-400 text-xs font-bold border border-green-500/20">ONLINE</span>
</div>
<a class="group relative flex items-center justify-center w-full overflow-hidden rounded-xl bg-gradient-to-r from-primary via-primary to-purple-500 p-[1px] focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50 transition-all hover:scale-[1.02]" href="#">
<span class="absolute inset-[-1000%] animate-[spin_2s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,#E2CBFF_0%,#393BB2_50%,#E2CBFF_100%)] opacity-0 group-hover:opacity-100 transition-opacity"></span>
<div class="relative flex h-12 w-full items-center justify-center rounded-xl bg-background-dark/10 group-hover:bg-transparent px-8 py-1 backdrop-blur-3xl transition-colors">
<span class="flex items-center gap-2 text-background-dark font-bold text-base uppercase tracking-wide">
Visit Website
<span class="material-symbols-outlined text-[20px] font-bold">arrow_outward</span>
</span>
</div>
<!-- Overlay for text color correction on hover/default since bg changes -->
<div class="absolute inset-0 flex items-center justify-center pointer-events-none">
<span class="flex items-center gap-2 text-white font-bold text-base uppercase tracking-wide group-hover:text-white transition-colors">
Visit Website
<span class="material-symbols-outlined text-[20px] font-bold">arrow_outward</span>
</span>
</div>
</a>
<p class="text-xs text-center text-text-secondary">
Opens in a new tab • <span class="text-white">neurogen.ai</span>
</p>
</div>
<!-- Features Box -->
<div class="rounded-2xl border border-surface-border bg-surface-dark/50 p-6">
<h3 class="text-lg font-bold text-white mb-5 flex items-center gap-2">
<span class="material-symbols-outlined text-secondary">verified</span>
Main Features
</h3>
<ul class="space-y-4">
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-white">Contextual Awareness</p>
<p class="text-xs text-text-secondary mt-0.5">Understands previous inputs to maintain thread continuity.</p>
</div>
</li>
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-white">Multi-language Support</p>
<p class="text-xs text-text-secondary mt-0.5">Generate content in over 25 languages natively.</p>
</div>
</li>
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-white">SEO Optimization</p>
<p class="text-xs text-text-secondary mt-0.5">Built-in keyword analyzer and suggestion tool.</p>
</div>
</li>
<li class="flex items-start gap-3">
<div class="mt-0.5 rounded-full bg-primary/20 p-1">
<span class="material-symbols-outlined text-primary text-[16px] block">check</span>
</div>
<div>
<p class="text-sm font-bold text-white">Export to CMS</p>
<p class="text-xs text-text-secondary mt-0.5">Direct integration with WordPress and Ghost.</p>
</div>
</li>
</ul>
</div>
<!-- Share / Report -->
<div class="flex gap-4">
<button class="flex-1 py-3 rounded-lg border border-surface-border bg-transparent text-text-secondary hover:text-white hover:bg-surface-border transition-colors text-sm font-medium flex items-center justify-center gap-2">
<span class="material-symbols-outlined text-[18px]">share</span>
Share
</button>
<button class="flex-1 py-3 rounded-lg border border-surface-border bg-transparent text-text-secondary hover:text-red-400 hover:border-red-400/30 hover:bg-red-400/10 transition-colors text-sm font-medium flex items-center justify-center gap-2">
<span class="material-symbols-outlined text-[18px]">flag</span>
Report
</button>
</div>
</div>
</div>
<!-- Footer Spacer -->
<div class="h-20"></div>
</main>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

View File

@@ -0,0 +1,383 @@
<!DOCTYPE html>
<html class="dark" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>ZJPB - AI Tool Discovery</title>
<!-- Google Fonts -->
<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;600;700&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;700&amp;display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
primary: "#25c0f4",
secondary: "#7c3aed", // Adding purple for the gradient mix
"background-light": "#f5f8f8",
"background-dark": "#111618",
"surface-dark": "#1b2427",
"border-dark": "#283539",
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"],
},
backgroundImage: {
'gradient-tech': 'linear-gradient(135deg, #25c0f4 0%, #7c3aed 100%)',
'gradient-text': 'linear-gradient(to right, #25c0f4, #a855f7)',
}
},
},
}
</script>
<style>
body {
font-family: 'Space Grotesk', sans-serif;
}
.text-gradient {
background: linear-gradient(to right, #25c0f4, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 10px 30px -10px rgba(37, 192, 244, 0.15);
border-color: #25c0f4;
}
</style>
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white antialiased selection:bg-primary selection:text-black">
<div class="relative flex min-h-screen w-full flex-col overflow-x-hidden">
<!-- Background Gradient Elements for Tech Vibe -->
<div class="fixed top-0 left-0 w-full h-96 bg-primary/5 blur-[120px] rounded-full pointer-events-none -translate-y-1/2"></div>
<div class="fixed top-20 right-0 w-96 h-96 bg-secondary/10 blur-[100px] rounded-full pointer-events-none translate-x-1/3"></div>
<!-- Top Navigation -->
<header class="sticky top-0 z-50 w-full border-b border-solid border-border-dark bg-background-dark/80 backdrop-blur-md">
<div class="mx-auto flex h-16 max-w-[1440px] items-center justify-between px-6 lg:px-10">
<div class="flex items-center gap-8">
<!-- Logo -->
<a class="flex items-center gap-3 text-white hover:opacity-90 transition-opacity" href="#">
<div class="flex items-center justify-center size-8 rounded-lg bg-gradient-tech text-black">
<span class="material-symbols-outlined" style="font-size: 20px; font-weight: 700;">bolt</span>
</div>
<h2 class="text-white text-lg font-bold leading-tight tracking-tight">ZJPB</h2>
</a>
<!-- Desktop Nav -->
<nav class="hidden md:flex items-center gap-8">
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">Home</a>
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">Categories</a>
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">Community</a>
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="#">About</a>
</nav>
</div>
<!-- Actions -->
<div class="flex items-center gap-4">
<!-- Search -->
<div class="hidden lg:flex items-center bg-surface-dark border border-border-dark rounded-full h-10 px-4 w-64 focus-within:border-primary transition-colors">
<span class="material-symbols-outlined text-gray-400" style="font-size: 20px;">search</span>
<input class="bg-transparent border-none text-white text-sm placeholder-gray-500 focus:ring-0 w-full ml-2" placeholder="Search AI tools..." type="text"/>
</div>
<!-- Auth Buttons -->
<button class="hidden sm:flex h-9 px-4 items-center justify-center rounded-lg text-sm font-bold text-white hover:bg-white/5 transition-colors">
Log In
</button>
<button class="flex h-9 px-5 items-center justify-center rounded-lg bg-primary hover:bg-primary/90 text-background-dark text-sm font-bold transition-colors shadow-[0_0_15px_rgba(37,192,244,0.3)]">
Sign Up
</button>
<!-- Mobile Menu Toggle -->
<button class="md:hidden text-white">
<span class="material-symbols-outlined">menu</span>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-1 flex flex-col items-center w-full px-6 lg:px-10 py-8">
<div class="w-full max-w-[1200px] flex flex-col gap-10">
<!-- Hero Section -->
<div class="flex flex-col md:flex-row items-start md:items-end justify-between gap-6 pb-6 border-b border-border-dark/50">
<div class="flex flex-col gap-2 max-w-2xl">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-black tracking-tighter text-white mb-2">
ZJPB - <span class="text-gradient">焦提示词</span>
</h1>
<p class="text-gray-400 text-lg md:text-xl font-light">
发现最新最好用的AI工具和产品. Discover the best AI tools tailored for your workflow.
</p>
</div>
<button class="flex items-center gap-2 bg-surface-dark border border-border-dark hover:border-primary text-white px-5 py-2.5 rounded-lg transition-all group whitespace-nowrap">
<span class="material-symbols-outlined text-primary group-hover:scale-110 transition-transform">add_circle</span>
<span class="font-bold text-sm">Submit a Tool</span>
</button>
</div>
<!-- Filter Chips -->
<div class="w-full overflow-x-auto pb-2 scrollbar-hide">
<div class="flex gap-3 min-w-max">
<button class="flex items-center h-9 px-5 rounded-full bg-primary text-background-dark font-bold text-sm shadow-[0_0_10px_rgba(37,192,244,0.4)]">
All Tools
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">chat</span> AI Chat
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">image</span> Image Gen
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">movie</span> Video
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">edit_note</span> Writing
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">code</span> Coding
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">graphic_eq</span> Audio
</button>
<button class="flex items-center gap-2 h-9 px-5 rounded-full bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500 font-medium text-sm transition-all">
<span class="material-symbols-outlined text-sm">view_in_ar</span> 3D Assets
</button>
</div>
</div>
<!-- Tool Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<!-- Card 1 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="ChatGPT logo icon green background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBStb1HsLpCc4Hlu8FzN0ecAT6pMnnu_pzF4cUUQDG6FdYLA65ua60D-3NxKDOHqkuVsgsy9ku-vUZ0_Jyc5O9Xi1NlwR5L7oC9gt_jretgJqsEeOk1dm2yo4GvB26KgmMWzpUKeCtIg1NyZDWRNJF3gfIVxF95sT29iy6tKSXsUdfVsOo-o_5kEpB3qCFKZdMo4fhOe8DVh4JnmmdZuS8z3PflupzZpru6F0QK3xL407vfIULuQ3L5NRtFdTfZs7O-elYl2FrwjJ_x');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">ChatGPT</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">OpenAI's advanced conversational model capable of understanding and generating human-like text.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Chat</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">NLP</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>1.2M</span>
</div>
</div>
</div>
<!-- Card 2 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Midjourney logo icon blue background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBW8nuPBGShgvw9aCK5p0mKlki3EMfhi8ZZKvLTaM-QupUQDzsBx5wkIvN_Unyt30H2WHXBw5GB4Kz8gL68peWqK99Cpo6gRM-Bk3f94xWdYji9osPrreu5UMVtu2W1Rn8IyBCfPDUu3LJnbDd_viZz4nb04tliq9O_ezo8OCYMede7GZYxIxHTrEkhNBADzC2Z7KjeF-DR7fRBf4dxHYz5rJUGakf1qyEouaAlEDWMnBymOKFkQGK19UvizQiQIUeDFMGMxgL0ng_x');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Midjourney</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Hyper-realistic AI image generator that creates stunning visuals from text prompts.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Image</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Art</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>850k</span>
</div>
</div>
</div>
<!-- Card 3 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Jasper logo icon yellow background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAQv96KYBZ3uV5NpDJyuDzc5J-mGo3fTWcBzVv00wC062ApfHezk6XkvKI6lsad5hFopxB7Qqhbqifto971lBo_7ASfOFUvtSU-Z3omU0q4PGcmea1YR_Va7vKPBtpA1DS_uq779QNTY5oF_kjapJrp7-Nfi5tI0NfQ3kU5KOd-dwBrN2T1Md6nT1jUEeOKG3zEgmAfEYan4mQXQXpz7Ywh2ZaoFCOY0OTiJP9RAnzIYL0Tv-ywt3_r66CSTY5pyzfqqP7sUk4vegAv');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Jasper AI</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">AI copywriter for marketing content, blog posts, and social media captions.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Writing</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Marketing</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>300k</span>
</div>
</div>
</div>
<!-- Card 4 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Runway logo icon pink background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAkmxk1Ar1KxgHHM5pKMer4rOra0KUieDAXhm9nE4sQp5WjsnGYENKmbM-EN-hRjhV7OC2ha7Go-Cl90HgfNpbgt--grIpJYKoUjgvsp0juLIDgX_fN4rKrjdkezxRU8YVJBuTaM3lWZJCptm3I6isDO10xidrChOc5kWV9SQny79KEVKENXxOJXEVT3c0m16M3JnGRTRmu-6EY8XU68-On78-7SXxLyP5TESi-ooZ2wHaOwxqJUwFb-oQyQoKAkXnzXAqGwwITBiLS');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Runway Gen-2</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Next-generation video creation tool that turns text into high-quality video clips.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Video</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Gen-AI</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>420k</span>
</div>
</div>
</div>
<!-- Card 5 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Copilot logo icon purple background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBki2piUPdnWstozizUU63ouakjjA3B4xN5uQs7rA84yX4NsYFIaNybtfVRy0bXUKfUBYdixo22jb3bbN6TlKindW5iQU0sJc3FF2GXS_CRpgLoNMLziOyf4rLArNmM2VoycHJ3DyrIsI8847xlO3BHrK8kyemNc9mBfaXyPnWsJ0hnF2lXfljCOcYNQTtY1qgzw5RfOHwA0NUnLwuRG53tU-mnJPPejbDyzW7YWdBnh9yTj3dnJJDOfzYiEx252wgf7Q6GBjTGrQnG');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">GitHub Copilot</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Your AI pair programmer that helps you write better code faster.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Dev</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Coding</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>1.5M</span>
</div>
</div>
</div>
<!-- Card 6 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="ElevenLabs logo icon red background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuBVqan6FEM01JlSNs0aKYTXdgzFAXn1xT_Yx0S3VszDGf5x0hsptwFAxMNtCEtJZOW65aZMncYKyPpzdAoIHLaBDk_ORHur89Redtuublqbd51Cr6sKVdMJ2VAAihNuVPsvpNBdIiLGuf40kXbe3HJrdYVNAKi27xcxBICheI4OzkFF7uJvChUyDbumqLRFUDMssCuxZIQYTeDKbTT628ZBFUN2H8u0RfVSPEyEhJVSE2NCAD4mH-dWGmsQzMWjmeZkwXJKpw1TGHFp');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">ElevenLabs</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">The most realistic AI voice generator and text-to-speech software.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Audio</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">TTS</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>210k</span>
</div>
</div>
</div>
<!-- Card 7 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Stable Diffusion logo icon teal background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuB4zzlwlZK6dJVjV4vvFGvRmtsvd7dvQMCC1mAzFfEqA2sYnJnEdTDAF76lfVYkaKzibwuppHRIcEgbT8xaoWOBF1qdMiV3Qs3P9fEOLJ2jApPPqJyBnPXsvxFsI6FTlZ0SS_Rpblvjln_n_YLh8Wi_VWh8lCJirMM5vMCuyrNGK9Crxv3bnJzlbH34i9vmxSxSQ5uCmoAE5GPxkD23vzun9BBOKjRK-Ln7DhQ_bGhnVxkxEtM8Z-DVuGg2dNE7WnD7BEBXTOBh5XQa');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Stable Diffusion</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Open source latent text-to-image diffusion model for image generation.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Open Source</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Image</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>950k</span>
</div>
</div>
</div>
<!-- Card 8 -->
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" data-alt="Notion AI logo icon indigo background" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuAgnpQqxiQhRd6xj43Q0hY2EQTna5khOzvl_NxO3OJN_7_nsfC0wP1x7ZmfBEvEEOoGi7NHVx6pvOpJb7Zk7dvUQWD64Y7Nrhc0mi0wsVIB1U8OfG1wioEusBqkI6zJQQh8W0om8gi2ubnJn2McHrvANulIAVnRxn1zPcy-SAnW1rLynAdVFKurBX5qK3BFBi_knAMoL8bTL_1LTxIdhdCggw6WeCGsFgUu6vqwjq9prGC-j94Fr8GibbjDSABY36P1OoZg59Aiv1M4');"></div>
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">Notion AI</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">Access the limitless power of AI, right inside your Notion workspace.</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2">
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">Productivity</span>
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>600k</span>
</div>
</div>
</div>
</div>
<!-- Pagination -->
<div class="flex items-center justify-center py-4">
<nav class="flex items-center gap-2">
<a class="flex size-10 items-center justify-center rounded-full border border-border-dark text-gray-400 hover:text-white hover:border-primary transition-colors" href="#">
<span class="material-symbols-outlined text-base">chevron_left</span>
</a>
<a class="flex size-10 items-center justify-center rounded-full bg-primary text-background-dark text-sm font-bold shadow-[0_0_10px_rgba(37,192,244,0.3)]" href="#">1</a>
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-surface-dark text-gray-400 hover:text-white text-sm font-medium transition-colors" href="#">2</a>
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-surface-dark text-gray-400 hover:text-white text-sm font-medium transition-colors" href="#">3</a>
<span class="flex size-10 items-center justify-center text-gray-600">...</span>
<a class="flex size-10 items-center justify-center rounded-full border border-transparent hover:bg-surface-dark text-gray-400 hover:text-white text-sm font-medium transition-colors" href="#">12</a>
<a class="flex size-10 items-center justify-center rounded-full border border-border-dark text-gray-400 hover:text-white hover:border-primary transition-colors" href="#">
<span class="material-symbols-outlined text-base">chevron_right</span>
</a>
</nav>
</div>
</div>
</main>
<!-- Simple Footer -->
<footer class="w-full border-t border-border-dark bg-background-dark py-8 mt-auto">
<div class="mx-auto max-w-[1200px] flex flex-col md:flex-row items-center justify-between gap-6 px-6 lg:px-10">
<div class="flex items-center gap-2 text-gray-400 text-sm">
<span>© 2023 ZJPB AI Directory. All rights reserved.</span>
</div>
<div class="flex gap-6">
<a class="text-gray-500 hover:text-primary transition-colors" href="#">
<i class="fab fa-twitter text-lg"></i> <!-- Placeholder for social icon -->
<span class="text-sm">Twitter</span>
</a>
<a class="text-gray-500 hover:text-primary transition-colors" href="#">
<span class="text-sm">Discord</span>
</a>
<a class="text-gray-500 hover:text-primary transition-colors" href="#">
<span class="text-sm">Privacy Policy</span>
</a>
</div>
</div>
</footer>
</div>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

View File

@@ -0,0 +1,17 @@
{% extends 'admin/base.html' %}
{% block head_css %}
{{ super() }}
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<!-- Custom Admin Theme -->
<link href="{{ url_for('static', filename='css/admin-theme.css') }}" rel="stylesheet">
{% endblock %}
{% block body %}
<body class="admin-theme">
{{ super() }}
</body>
{% endblock %}

View File

@@ -0,0 +1,124 @@
{% extends 'admin/model/create.html' %}
{% block tail %}
{{ super() }}
<style>
.auto-fetch-btn {
margin-top: 10px;
margin-bottom: 15px;
}
.fetch-status {
margin-top: 10px;
padding: 10px;
border-radius: 8px;
display: none;
}
.fetch-status.success {
background-color: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.3);
color: #4ade80;
}
.fetch-status.error {
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #f87171;
}
.auto-fetch-btn .loading-icon {
display: none;
}
.auto-fetch-btn.loading .loading-icon {
display: inline-block;
animation: spin 1s linear infinite;
}
.auto-fetch-btn.loading .normal-icon {
display: none;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 在URL字段后添加"自动获取"按钮
const urlField = document.querySelector('input[name="url"]');
if (urlField) {
const fetchBtn = document.createElement('button');
fetchBtn.type = 'button';
fetchBtn.className = 'btn btn-info auto-fetch-btn';
fetchBtn.innerHTML = '<span class="normal-icon">↻</span><span class="loading-icon">↻</span> 自动获取网站信息';
const statusDiv = document.createElement('div');
statusDiv.className = 'fetch-status';
urlField.parentNode.appendChild(fetchBtn);
urlField.parentNode.appendChild(statusDiv);
fetchBtn.addEventListener('click', function() {
const url = urlField.value.trim();
if (!url) {
showStatus('请先输入网站URL', 'error');
return;
}
// 显示加载状态
fetchBtn.disabled = true;
fetchBtn.classList.add('loading');
statusDiv.style.display = 'none';
// 调用API获取网站信息
fetch('/api/fetch-website-info', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ url: url })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 自动填充表单字段
const nameField = document.querySelector('input[name="name"]');
const shortDescField = document.querySelector('input[name="short_desc"]');
const descriptionField = document.querySelector('textarea[name="description"]');
const logoField = document.querySelector('input[name="logo"]');
if (nameField && data.data.name) {
nameField.value = data.data.name;
}
if (shortDescField && data.data.description) {
shortDescField.value = data.data.description.substring(0, 100);
}
if (descriptionField && data.data.description) {
descriptionField.value = data.data.description;
}
if (logoField && data.data.logo) {
logoField.value = data.data.logo;
}
showStatus('✓ 网站信息获取成功!已自动填充表单', 'success');
} else {
showStatus('✗ ' + (data.message || '获取失败,请手动填写'), 'error');
}
})
.catch(error => {
console.error('Error:', error);
showStatus('✗ 网络请求失败,请手动填写', 'error');
})
.finally(() => {
fetchBtn.disabled = false;
fetchBtn.classList.remove('loading');
});
});
function showStatus(message, type) {
statusDiv.textContent = message;
statusDiv.className = 'fetch-status ' + type;
statusDiv.style.display = 'block';
}
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,124 @@
{% extends 'admin/model/edit.html' %}
{% block tail %}
{{ super() }}
<style>
.auto-fetch-btn {
margin-top: 10px;
margin-bottom: 15px;
}
.fetch-status {
margin-top: 10px;
padding: 10px;
border-radius: 8px;
display: none;
}
.fetch-status.success {
background-color: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.3);
color: #4ade80;
}
.fetch-status.error {
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #f87171;
}
.auto-fetch-btn .loading-icon {
display: none;
}
.auto-fetch-btn.loading .loading-icon {
display: inline-block;
animation: spin 1s linear infinite;
}
.auto-fetch-btn.loading .normal-icon {
display: none;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 在URL字段后添加"自动获取"按钮
const urlField = document.querySelector('input[name="url"]');
if (urlField) {
const fetchBtn = document.createElement('button');
fetchBtn.type = 'button';
fetchBtn.className = 'btn btn-info auto-fetch-btn';
fetchBtn.innerHTML = '<span class="normal-icon">↻</span><span class="loading-icon">↻</span> 自动获取网站信息';
const statusDiv = document.createElement('div');
statusDiv.className = 'fetch-status';
urlField.parentNode.appendChild(fetchBtn);
urlField.parentNode.appendChild(statusDiv);
fetchBtn.addEventListener('click', function() {
const url = urlField.value.trim();
if (!url) {
showStatus('请先输入网站URL', 'error');
return;
}
// 显示加载状态
fetchBtn.disabled = true;
fetchBtn.classList.add('loading');
statusDiv.style.display = 'none';
// 调用API获取网站信息
fetch('/api/fetch-website-info', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ url: url })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 自动填充表单字段
const nameField = document.querySelector('input[name="name"]');
const shortDescField = document.querySelector('input[name="short_desc"]');
const descriptionField = document.querySelector('textarea[name="description"]');
const logoField = document.querySelector('input[name="logo"]');
if (nameField && data.data.name) {
nameField.value = data.data.name;
}
if (shortDescField && data.data.description) {
shortDescField.value = data.data.description.substring(0, 100);
}
if (descriptionField && data.data.description) {
descriptionField.value = data.data.description;
}
if (logoField && data.data.logo) {
logoField.value = data.data.logo;
}
showStatus('✓ 网站信息获取成功!已自动填充表单', 'success');
} else {
showStatus('✗ ' + (data.message || '获取失败,请手动填写'), 'error');
}
})
.catch(error => {
console.error('Error:', error);
showStatus('✗ 网络请求失败,请手动填写', 'error');
})
.finally(() => {
fetchBtn.disabled = false;
fetchBtn.classList.remove('loading');
});
});
function showStatus(message, type) {
statusDiv.textContent = message;
statusDiv.className = 'fetch-status ' + type;
statusDiv.style.display = 'block';
}
}
});
</script>
{% endblock %}

121
templates/admin_login.html Normal file
View File

@@ -0,0 +1,121 @@
{% extends "base.html" %}
{% block title %}管理员登录 - ZJPB 焦提示词{% endblock %}
{% block extra_css %}
<style>
.glass-panel {
background: rgba(22, 33, 37, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.tech-bg-grid {
background-image: radial-gradient(circle at center, rgba(37, 192, 244, 0.05) 0%, transparent 70%),
linear-gradient(rgba(37, 192, 244, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(37, 192, 244, 0.03) 1px, transparent 1px);
background-size: 100% 100%, 40px 40px, 40px 40px;
}
</style>
{% endblock %}
{% block content %}
<!-- Override default layout -->
</div> <!-- Close main wrapper -->
<body class="bg-background-dark font-display text-white min-h-screen flex flex-col items-center justify-center relative overflow-hidden selection:bg-primary/30">
<!-- Ambient Background -->
<div class="absolute inset-0 z-0 tech-bg-grid pointer-events-none"></div>
<div class="absolute top-[-20%] right-[-10%] w-[600px] h-[600px] bg-primary/10 rounded-full blur-[120px] pointer-events-none"></div>
<div class="absolute bottom-[-10%] left-[-10%] w-[500px] h-[500px] bg-purple-600/10 rounded-full blur-[100px] pointer-events-none"></div>
<!-- Main Content Wrapper -->
<div class="w-full max-w-[480px] px-4 z-10 flex flex-col gap-6">
<!-- Back Navigation -->
<a class="group flex items-center gap-2 text-gray-400 hover:text-white transition-colors w-fit" href="{{ url_for('index') }}">
<div class="flex items-center justify-center w-8 h-8 rounded-full border border-border-dark bg-surface-dark group-hover:border-primary/50 transition-colors">
<span class="material-symbols-outlined text-sm">arrow_back</span>
</div>
<span class="text-sm font-medium">返回首页</span>
</a>
<!-- Login Card -->
<div class="glass-panel border border-white/5 dark:border-border-dark/50 rounded-xl shadow-2xl p-8 md:p-10 relative overflow-hidden">
<!-- Decorative Accent -->
<div class="absolute top-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary to-transparent opacity-70"></div>
<!-- Header -->
<div class="flex flex-col gap-2 mb-8">
<div class="flex items-center gap-3 mb-2">
<div class="p-2 rounded-lg bg-primary/10 text-primary">
<span class="material-symbols-outlined text-2xl">shield_person</span>
</div>
<span class="text-xs font-bold tracking-widest uppercase text-primary/80">System Access</span>
</div>
<h1 class="text-3xl font-black tracking-tight text-white leading-tight">管理员登录</h1>
<p class="text-gray-400 text-base font-normal">输入您的登录凭据以访问后台管理系统</p>
</div>
<!-- Error Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="mb-6 p-3 rounded-lg bg-red-500/10 border border-red-500/20 flex items-start gap-3">
<span class="material-symbols-outlined text-red-400 text-sm mt-0.5">error</span>
<p class="text-red-400 text-sm">{{ message }}</p>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Form -->
<form method="POST" action="{{ url_for('admin_login') }}" class="flex flex-col gap-5">
<!-- Username Field -->
<div class="space-y-2">
<label class="text-white text-sm font-medium leading-normal" for="username">用户名</label>
<div class="relative group">
<input class="form-input flex w-full h-14 pl-12 pr-4 rounded-lg text-white focus:outline-0 focus:ring-0 border border-border-dark bg-surface-dark focus:border-primary placeholder:text-gray-500 text-base font-normal leading-normal transition-colors"
id="username"
name="username"
placeholder="输入您的用户名或邮箱"
type="text"
required
autofocus/>
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none">
<span class="material-symbols-outlined text-[20px]">person</span>
</div>
</div>
</div>
<!-- Password Field -->
<div class="space-y-2">
<label class="text-white text-sm font-medium leading-normal" for="password">密码</label>
<div class="flex w-full items-stretch rounded-lg group relative">
<input class="form-input flex w-full h-14 pl-12 pr-12 rounded-lg text-white focus:outline-0 focus:ring-0 border border-border-dark bg-surface-dark focus:border-primary placeholder:text-gray-500 text-base font-normal leading-normal transition-colors z-10"
id="password"
name="password"
placeholder="输入您的密码"
type="password"
required/>
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-primary transition-colors flex items-center justify-center pointer-events-none z-20">
<span class="material-symbols-outlined text-[20px]">lock</span>
</div>
</div>
</div>
<!-- Login Button -->
<button class="mt-4 flex w-full h-12 cursor-pointer items-center justify-center overflow-hidden rounded-lg bg-primary text-black text-base font-bold leading-normal tracking-[0.015em] hover:bg-primary/90 transition-all active:scale-[0.98] shadow-[0_0_20px_rgba(37,192,244,0.3)] hover:shadow-[0_0_30px_rgba(37,192,244,0.5)]"
type="submit">
<span class="truncate">登录</span>
</button>
</form>
<!-- Footer Meta -->
<div class="mt-8 flex justify-center gap-6 border-t border-border-dark/30 pt-6">
<p class="text-gray-400 text-xs text-center">
ZJPB - 焦提示词 管理系统
</p>
</div>
</div>
</div>
</body>
{% endblock %}

125
templates/base.html Normal file
View File

@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html class="dark" lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}ZJPB - 焦提示词{% endblock %}</title>
<meta name="description" content="{% block description %}焦提示词 - 精选AI工具和产品导航发现最新最好用的人工智能应用{% endblock %}">
<meta name="keywords" content="{% block keywords %}ZJPB,焦提示词,AI工具,人工智能,ChatGPT,AI导航{% endblock %}">
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
primary: "#25c0f4",
secondary: "#7c3aed",
"background-light": "#f5f8f8",
"background-dark": "#111618",
"surface-dark": "#1b2427",
"border-dark": "#283539",
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"body": ["Noto Sans", "sans-serif"],
},
backgroundImage: {
'gradient-tech': 'linear-gradient(135deg, #25c0f4 0%, #7c3aed 100%)',
'gradient-text': 'linear-gradient(to right, #25c0f4, #a855f7)',
}
},
},
}
</script>
<style>
body {
font-family: 'Space Grotesk', sans-serif;
}
.text-gradient {
background: linear-gradient(to right, #25c0f4, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 10px 30px -10px rgba(37, 192, 244, 0.15);
border-color: #25c0f4;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-white antialiased selection:bg-primary selection:text-black">
<div class="relative flex min-h-screen w-full flex-col overflow-x-hidden">
<!-- Background Gradient Elements -->
<div class="fixed top-0 left-0 w-full h-96 bg-primary/5 blur-[120px] rounded-full pointer-events-none -translate-y-1/2"></div>
<div class="fixed top-20 right-0 w-96 h-96 bg-secondary/10 blur-[100px] rounded-full pointer-events-none translate-x-1/3"></div>
<!-- Top Navigation -->
<header class="sticky top-0 z-50 w-full border-b border-solid border-border-dark bg-background-dark/80 backdrop-blur-md">
<div class="mx-auto flex h-16 max-w-[1440px] items-center justify-between px-6 lg:px-10">
<div class="flex items-center gap-8">
<!-- Logo -->
<a class="flex items-center gap-3 text-white hover:opacity-90 transition-opacity" href="{{ url_for('index') }}">
<div class="flex items-center justify-center size-8 rounded-lg bg-gradient-tech text-black">
<span class="material-symbols-outlined" style="font-size: 20px; font-weight: 700;">bolt</span>
</div>
<h2 class="text-white text-lg font-bold leading-tight tracking-tight">ZJPB</h2>
</a>
<!-- Desktop Nav -->
<nav class="hidden md:flex items-center gap-8">
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="{{ url_for('index') }}">首页</a>
{% if current_user.is_authenticated %}
<a class="text-gray-300 hover:text-primary text-sm font-medium transition-colors" href="{{ url_for('admin.index') }}">后台管理</a>
{% endif %}
</nav>
</div>
<!-- Actions -->
<div class="flex items-center gap-4">
{% if current_user.is_authenticated %}
<button onclick="window.location.href='{{ url_for('admin_logout') }}'" class="hidden sm:flex h-9 px-4 items-center justify-center rounded-lg text-sm font-bold text-white hover:bg-white/5 transition-colors">
退出
</button>
{% else %}
<button onclick="window.location.href='{{ url_for('admin_login') }}'" class="hidden sm:flex h-9 px-4 items-center justify-center rounded-lg text-sm font-bold text-white hover:bg-white/5 transition-colors">
登录
</button>
{% endif %}
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-1">
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="w-full border-t border-border-dark bg-background-dark py-8 mt-auto">
<div class="mx-auto max-w-[1200px] flex flex-col md:flex-row items-center justify-between gap-6 px-6 lg:px-10">
<div class="flex items-center gap-2 text-gray-400 text-sm">
<span>© 2024 ZJPB - 焦提示词. All rights reserved.</span>
</div>
<div class="flex gap-6">
<a class="text-gray-500 hover:text-primary transition-colors text-sm" href="#">
发现最好用的AI工具
</a>
</div>
</div>
</footer>
</div>
{% block extra_js %}{% endblock %}
</body>
</html>

136
templates/detail.html Normal file
View File

@@ -0,0 +1,136 @@
{% extends "base.html" %}
{% block title %}{{ site.name }} - ZJPB 焦提示词{% endblock %}
{% block description %}{{ site.short_desc or site.description[:150] }}{% endblock %}
{% block keywords %}{{ site.name }},{% for tag in site.tags %}{{ tag.name }},{% endfor %}ZJPB,焦提示词,AI工具{% endblock %}
{% block content %}
<main class="flex-grow w-full max-w-[1280px] mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Back Button -->
<div class="mb-8">
<a href="{{ url_for('index') }}" class="group flex items-center gap-2 text-gray-400 hover:text-primary transition-colors text-sm font-bold">
<span class="material-symbols-outlined text-[20px] group-hover:-translate-x-1 transition-transform">arrow_back</span>
<span>返回首页</span>
</a>
</div>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
<!-- Left Column: Main Content -->
<div class="lg:col-span-8 flex flex-col gap-8">
<!-- Header Section -->
<div class="flex flex-col sm:flex-row gap-6 items-start">
<!-- Logo Container -->
<div class="relative shrink-0">
<div class="absolute -inset-1 bg-gradient-to-br from-primary via-purple-500 to-secondary rounded-2xl opacity-30 blur-md"></div>
{% if site.logo %}
<div class="relative size-[120px] rounded-xl bg-surface-dark flex items-center justify-center overflow-hidden border border-border-dark" style="background-image: url('{{ site.logo }}'); background-size: cover; background-position: center;"></div>
{% else %}
<div class="relative size-[120px] rounded-xl bg-gradient-to-br from-primary to-secondary flex items-center justify-center border border-border-dark">
<span class="text-5xl font-bold text-black">{{ site.name[0] }}</span>
</div>
{% endif %}
</div>
<!-- Title & Meta -->
<div class="flex flex-col gap-3 flex-1">
<div>
<h1 class="text-4xl font-bold text-white tracking-tight font-display mb-1">{{ site.name }}</h1>
<a class="text-primary hover:text-primary-dark hover:underline text-base font-medium flex items-center gap-1" href="{{ site.url }}" target="_blank">
{{ site.url }}
<span class="material-symbols-outlined text-[16px]">open_in_new</span>
</a>
</div>
<!-- Tags -->
<div class="flex flex-wrap gap-2">
{% for tag in site.tags %}
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-surface-dark text-gray-300 border border-border-dark hover:border-primary/30 transition-colors cursor-default">
{{ tag.name }}
</span>
{% endfor %}
</div>
<!-- Stats Row -->
<div class="flex items-center gap-6 mt-1 text-gray-400 text-sm">
<div class="flex items-center gap-2">
<span class="material-symbols-outlined text-[18px]">visibility</span>
<span>{{ site.view_count }} 次浏览</span>
</div>
<div class="flex items-center gap-2">
<span class="material-symbols-outlined text-[18px]">calendar_today</span>
<span>{{ site.created_at.strftime('%Y年%m月%d日') }}</span>
</div>
</div>
</div>
</div>
<hr class="border-border-dark/60"/>
<!-- Main Content Body -->
<div class="space-y-8">
<!-- Overview -->
{% if site.short_desc %}
<section>
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">info</span>
产品简介
</h3>
<p class="text-gray-400 leading-relaxed text-lg">
{{ site.short_desc }}
</p>
</section>
{% endif %}
<!-- Detailed Description -->
{% if site.description %}
<section>
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">article</span>
详细介绍
</h3>
<div class="text-gray-400 space-y-4 leading-relaxed whitespace-pre-wrap">
{{ site.description|safe }}
</div>
</section>
{% endif %}
<!-- Features -->
{% if site.features %}
<section>
<h3 class="text-xl font-bold text-white mb-4 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">star</span>
主要功能
</h3>
<div class="text-gray-400 space-y-4 leading-relaxed whitespace-pre-wrap">
{{ site.features|safe }}
</div>
</section>
{% endif %}
</div>
</div>
<!-- Right Column: Sidebar -->
<div class="lg:col-span-4 space-y-6">
<!-- CTA Card -->
<div class="rounded-2xl p-6 flex flex-col gap-4 shadow-xl shadow-black/20 sticky top-24 bg-surface-dark/60 backdrop-blur-xl border border-border-dark">
<div class="flex items-center justify-between mb-2">
<span class="text-white font-bold text-lg">立即体验</span>
</div>
<a class="group relative flex items-center justify-center w-full overflow-hidden rounded-xl bg-primary hover:bg-primary/90 p-4 focus:outline-none transition-all hover:scale-[1.02] shadow-[0_0_20px_rgba(37,192,244,0.3)] hover:shadow-[0_0_30px_rgba(37,192,244,0.5)]" href="{{ site.url }}" target="_blank">
<span class="flex items-center gap-2 text-black font-bold text-base uppercase tracking-wide">
访问网站
<span class="material-symbols-outlined text-[20px] font-bold">arrow_outward</span>
</span>
</a>
<div class="pt-4 border-t border-border-dark">
<p class="text-gray-500 text-xs text-center">
{{ site.url }}
</p>
</div>
</div>
</div>
</div>
</main>
{% endblock %}

82
templates/index.html Normal file
View File

@@ -0,0 +1,82 @@
{% extends "base.html" %}
{% block title %}ZJPB - 焦提示词 - 发现最好用的AI产品{% endblock %}
{% block content %}
<main class="flex-1 flex flex-col items-center w-full px-6 lg:px-10 py-8">
<div class="w-full max-w-[1200px] flex flex-col gap-10">
<!-- Hero Section -->
<div class="flex flex-col md:flex-row items-start md:items-end justify-between gap-6 pb-6 border-b border-border-dark/50">
<div class="flex flex-col gap-2 max-w-2xl">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-black tracking-tighter text-white mb-2">
ZJPB - <span class="text-gradient">焦提示词</span>
</h1>
<p class="text-gray-400 text-lg md:text-xl font-light">
发现最新最好用的AI工具和产品
</p>
</div>
</div>
<!-- Filter Chips -->
<div class="w-full overflow-x-auto pb-2 scrollbar-hide">
<div class="flex gap-3 min-w-max">
<a href="{{ url_for('index') }}" class="flex items-center h-9 px-5 rounded-full {% if not selected_tag %}bg-primary text-background-dark shadow-[0_0_10px_rgba(37,192,244,0.4)]{% else %}bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500{% endif %} font-bold text-sm transition-all">
全部
</a>
{% for tag in tags %}
<a href="{{ url_for('index', tag=tag.slug) }}" class="flex items-center gap-2 h-9 px-5 rounded-full {% if selected_tag and selected_tag.id == tag.id %}bg-primary text-background-dark shadow-[0_0_10px_rgba(37,192,244,0.4)]{% else %}bg-surface-dark border border-border-dark text-gray-300 hover:text-white hover:border-gray-500{% endif %} font-medium text-sm transition-all">
{% if tag.icon %}<span class="material-symbols-outlined text-sm">{{ tag.icon.replace('fas fa-', '').replace('fab fa-', '') }}</span>{% endif %}
{{ tag.name }}
</a>
{% endfor %}
</div>
</div>
<!-- Tool Grid -->
{% if sites %}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{% for site in sites %}
<div class="group relative flex flex-col gap-4 rounded-xl border border-border-dark bg-surface-dark p-5 card-hover transition-all duration-300 cursor-pointer overflow-hidden" onclick="window.location.href='{{ url_for('site_detail', slug=site.slug) }}'">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary to-secondary opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="flex items-start justify-between">
{% if site.logo %}
<div class="size-12 rounded-lg bg-cover bg-center shadow-lg" style="background-image: url('{{ site.logo }}');"></div>
{% else %}
<div class="size-12 rounded-lg bg-gradient-to-br from-primary to-secondary flex items-center justify-center text-black font-bold text-xl shadow-lg">
{{ site.name[0] }}
</div>
{% endif %}
<div class="flex items-center justify-center size-8 rounded-full bg-background-dark text-gray-400 group-hover:text-primary group-hover:bg-primary/10 transition-colors">
<span class="material-symbols-outlined text-lg">arrow_outward</span>
</div>
</div>
<div>
<h3 class="text-white text-lg font-bold leading-tight group-hover:text-primary transition-colors">{{ site.name }}</h3>
<p class="text-gray-400 text-sm mt-2 line-clamp-2">{{ site.short_desc or '暂无描述' }}</p>
</div>
<div class="mt-auto flex items-center justify-between pt-4 border-t border-border-dark">
<div class="flex gap-2 flex-wrap">
{% for tag in site.tags[:2] %}
<span class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-white/5 text-gray-300 border border-white/10">{{ tag.name }}</span>
{% endfor %}
</div>
<div class="flex items-center gap-1 text-gray-500 text-xs">
<span class="material-symbols-outlined text-[14px]">visibility</span>
<span>{{ site.view_count }}</span>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-20">
<span class="material-symbols-outlined text-6xl text-gray-600 mb-4 block">search_off</span>
<p class="text-gray-400 text-lg">暂无相关AI工具</p>
</div>
{% endif %}
</div>
</main>
{% endblock %}

65
test_db.py Normal file
View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
"""
测试数据库连接
"""
import sys
import pymysql
from dotenv import load_dotenv
import os
# 设置UTF-8编码输出
if sys.platform.startswith('win'):
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
load_dotenv()
def test_connection():
"""测试MySQL连接"""
print("正在测试数据库连接...")
print(f"主机: {os.getenv('DB_HOST')}")
print(f"端口: {os.getenv('DB_PORT')}")
print(f"用户: {os.getenv('DB_USER')}")
print(f"数据库: {os.getenv('DB_NAME')}")
try:
connection = pymysql.connect(
host=os.getenv('DB_HOST'),
port=int(os.getenv('DB_PORT', 3306)),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASSWORD'),
database=os.getenv('DB_NAME'),
charset='utf8mb4'
)
print("\n✓ 数据库连接成功!")
# 测试查询
with connection.cursor() as cursor:
cursor.execute("SELECT VERSION()")
version = cursor.fetchone()
print(f"✓ MySQL版本: {version[0]}")
cursor.execute("SHOW TABLES")
tables = cursor.fetchall()
if tables:
print(f"✓ 现有表: {len(tables)}")
for table in tables:
print(f" - {table[0]}")
else:
print(" 当前数据库为空,可以运行 init_db.py 初始化")
connection.close()
return True
except Exception as e:
print(f"\n✗ 数据库连接失败: {str(e)}")
print("\n请检查:")
print("1. 服务器MySQL是否开放了3306端口")
print("2. .env文件中的数据库配置是否正确")
print("3. 数据库用户是否有远程访问权限")
print("4. 服务器防火墙/安全组是否允许3306端口")
return False
if __name__ == '__main__':
test_connection()

47
test_fetch.py Normal file
View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
"""
测试网站信息抓取功能
"""
import sys
if sys.platform.startswith('win'):
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
from utils.website_fetcher import WebsiteFetcher
def test_fetch():
"""测试抓取百度网站信息"""
print("="*50)
print("测试网站信息抓取功能")
print("="*50)
# 创建抓取器
fetcher = WebsiteFetcher(timeout=15)
# 测试抓取百度
test_url = "https://www.baidu.com"
print(f"\n正在抓取: {test_url}")
info = fetcher.fetch_website_info(test_url)
if info:
print("\n抓取成功!")
print("-"*50)
print(f"网站名称: {info.get('name', '')}")
print(f"网站描述: {info.get('description', '')}")
print(f"Logo URL: {info.get('logo_url', '')}")
print("-"*50)
# 测试下载Logo
if info.get('logo_url'):
print(f"\n正在下载Logo...")
logo_path = fetcher.download_logo(info['logo_url'])
if logo_path:
print(f"Logo下载成功: {logo_path}")
else:
print("Logo下载失败")
else:
print("\n抓取失败!")
if __name__ == '__main__':
test_fetch()

1
utils/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Utils package

168
utils/website_fetcher.py Normal file
View File

@@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
"""
网站信息抓取工具
"""
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
import os
from PIL import Image
from io import BytesIO
class WebsiteFetcher:
"""网站信息抓取器"""
def __init__(self, timeout=10):
self.timeout = timeout
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
def fetch_website_info(self, url):
"""
抓取网站信息
Args:
url: 网站URL
Returns:
dict: 包含name, description, logo_url的字典失败返回None
"""
try:
# 确保URL包含协议
if not url.startswith(('http://', 'https://')):
url = 'https://' + url
# 请求网页
response = requests.get(url, headers=self.headers, timeout=self.timeout, allow_redirects=True)
response.raise_for_status()
response.encoding = response.apparent_encoding # 自动检测编码
# 解析HTML
soup = BeautifulSoup(response.text, 'html.parser')
# 提取信息
info = {
'name': self._extract_title(soup),
'description': self._extract_description(soup),
'logo_url': self._extract_logo(soup, url)
}
return info
except Exception as e:
print(f"抓取网站信息失败: {str(e)}")
return None
def _extract_title(self, soup):
"""提取网站标题"""
# 优先使用 og:title
og_title = soup.find('meta', property='og:title')
if og_title and og_title.get('content'):
return og_title['content'].strip()
# 使用 title 标签
title_tag = soup.find('title')
if title_tag:
return title_tag.get_text().strip()
return ''
def _extract_description(self, soup):
"""提取网站描述"""
# 优先使用 og:description
og_desc = soup.find('meta', property='og:description')
if og_desc and og_desc.get('content'):
return og_desc['content'].strip()
# 使用 meta description
meta_desc = soup.find('meta', attrs={'name': 'description'})
if meta_desc and meta_desc.get('content'):
return meta_desc['content'].strip()
# 使用 meta keywords 作为fallback
meta_keywords = soup.find('meta', attrs={'name': 'keywords'})
if meta_keywords and meta_keywords.get('content'):
return meta_keywords['content'].strip()
return ''
def _extract_logo(self, soup, base_url):
"""提取网站Logo"""
logo_url = None
# 1. 尝试 og:image
og_image = soup.find('meta', property='og:image')
if og_image and og_image.get('content'):
logo_url = og_image['content']
# 2. 尝试 link rel="icon" 或 "shortcut icon"
if not logo_url:
icon_link = soup.find('link', rel=lambda x: x and ('icon' in x.lower() if isinstance(x, str) else 'icon' in ' '.join(x).lower()))
if icon_link and icon_link.get('href'):
logo_url = icon_link['href']
# 3. 尝试 apple-touch-icon
if not logo_url:
apple_icon = soup.find('link', rel='apple-touch-icon')
if apple_icon and apple_icon.get('href'):
logo_url = apple_icon['href']
# 4. 默认使用 /favicon.ico
if not logo_url:
logo_url = '/favicon.ico'
# 转换为绝对URL
if logo_url:
logo_url = urljoin(base_url, logo_url)
return logo_url
def download_logo(self, logo_url, save_dir='static/uploads'):
"""
下载并保存Logo
Args:
logo_url: Logo的URL
save_dir: 保存目录
Returns:
str: 保存后的相对路径失败返回None
"""
if not logo_url:
return None
try:
# 创建保存目录
os.makedirs(save_dir, exist_ok=True)
# 下载图片
response = requests.get(logo_url, headers=self.headers, timeout=self.timeout)
response.raise_for_status()
# 检查是否是图片
content_type = response.headers.get('content-type', '')
if not content_type.startswith('image/'):
return None
# 生成文件名
parsed_url = urlparse(logo_url)
ext = os.path.splitext(parsed_url.path)[1]
if not ext or len(ext) > 5:
ext = '.png' # 默认扩展名
# 使用域名作为文件名
domain = parsed_url.netloc.replace(':', '_').replace('.', '_')
filename = f"logo_{domain}{ext}"
filepath = os.path.join(save_dir, filename)
# 保存图片
with open(filepath, 'wb') as f:
f.write(response.content)
# 返回相对路径(用于数据库存储)
return f'/{filepath.replace(os.sep, "/")}'
except Exception as e:
print(f"下载Logo失败: {str(e)}")
return None