mirror of
https://github.com/remnawave/panel.git
synced 2026-04-24 00:31:35 +00:00
feat: redesign Awesome Remnawave page
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -158,4 +158,6 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
|
||||
.wip/
|
||||
.wip/
|
||||
|
||||
samples/
|
||||
@@ -1,731 +0,0 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
title: ❤️ Awesome Remnawave
|
||||
---
|
||||
|
||||
# ❤️ Awesome Remnawave
|
||||
|
||||
### Xray Checker
|
||||
|
||||
Xray Checker is a tool for monitoring proxy server availability with support for VLESS, VMess, Trojan, and Shadowsocks protocols. It automatically tests connections through Xray Core and provides metrics for Prometheus, as well as API endpoints for integration with various monitoring systems.
|
||||
|
||||
Author: [kutovoys](https://github.com/kutovoys)
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem' }}>
|
||||
<Button label="Github repository" link="https://github.com/kutovoys/xray-checker" variant="secondary" size="md" outline />
|
||||
<Button label="Documentation" link="https://xray-checker.kutovoy.dev/" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/xray-checker.webp" alt="Xray Checker" width="600" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Geo File Viewer
|
||||
|
||||
A utility for viewing the contents of geoip and geofile (.dat) files in the v2fly format.
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem' }}>
|
||||
<Button label="Github repository" link="https://jomertix.github.io/geofileviewer" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/geofileviewer.webp" alt="GeoFileViewer" width="600" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Remnawave Management Scripts by DigneZzZ
|
||||
|
||||
Comprehensive bash scripts for **Remnawave Panel**, **Remnawave Node**, and **Reality Selfsteal** with full automation support. Features Docker integration, intelligent backup/restore system with version checking, advanced monitoring, and complete lifecycle management.
|
||||
|
||||
📦 **Key Features:**
|
||||
- 🚀 **One-line installation** for Panel, Node, and Selfsteal configurations
|
||||
- 🎛️ **Interactive menus** with real-time status monitoring and guided operations
|
||||
- 💾 **Smart backup system** with version compatibility checking and safety rollbacks
|
||||
- 🔄 **Complete lifecycle management** - install, update, backup, restore, uninstall
|
||||
- 🎯 **Reality masking** with 11 AI-generated website templates
|
||||
|
||||
📦 Full info, updates, and examples: [**/remnawave-scripts**](https://github.com/DigneZzZ/remnawave-scripts)
|
||||
|
||||
Author: [DigneZzZ](https://github.com/DigneZzZ)
|
||||
|
||||
Project: [GIG.ovh](https://gig.ovh)
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary>🚀 Quick Install Commands</summary>
|
||||
|
||||
**Remnawave Panel (v3.5.5+):**
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave.sh) @ install
|
||||
```
|
||||
|
||||
**Remnawave Node (v3.1.2+):**
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnanode.sh) @ install
|
||||
```
|
||||
|
||||
**Reality Selfsteal (v2.1.3+):**
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/selfsteal.sh) @ install
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- Add `--dev` for development version
|
||||
- Add `--name customname` for custom directory (default: `/opt/remnawave`, `/opt/remnanode`)
|
||||
|
||||
**For existing installations:**
|
||||
Use `install-script` to add CLI wrapper only:
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave.sh) @ install-script
|
||||
```
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnanode.sh) @ install-script
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>⚙️ Available CLI Commands</summary>
|
||||
|
||||
**Installation & Management:**
|
||||
- `install`, `update`, `uninstall`
|
||||
- `install-script`, `uninstall-script`
|
||||
|
||||
**Service Control:**
|
||||
- `up`, `down`, `restart`, `status`, `logs`
|
||||
|
||||
**Node-specific Commands:**
|
||||
- `core-update` - Manual Update/Install Xray-core to latest version
|
||||
- `xray-log-out`, `xray-log-err` - View Xray logs
|
||||
- `setup-logs` - Configure log rotation
|
||||
|
||||
**Configuration:**
|
||||
- `edit`, `edit-env`, `console` (Panel only)
|
||||
|
||||
**Interactive Menus:**
|
||||
- Run `remnawave`, `remnanode`, or `selfsteal` without arguments for interactive menu
|
||||
- Real-time status monitoring and resource usage
|
||||
- Step-by-step guided operations
|
||||
|
||||
**Backup & Restore (Panel):**
|
||||
- `backup` - Create manual backup
|
||||
- `schedule` - Configure automated backups
|
||||
- `restore` - Restore from backup archive
|
||||
|
||||
**Reality Selfsteal:**
|
||||
- Choose from 11 AI-generated website templates
|
||||
- Automatic SSL certificate management by Caddy
|
||||
- DNS validation before start
|
||||
|
||||
Run `remnawave help`, `remnanode help`, or `selfsteal help` for detailed usage.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>💾 Intelligent Backup & Restore System</summary>
|
||||
|
||||
**🔄 Smart Backup Features:**
|
||||
|
||||
- **Version-aware backups** with compatibility checking
|
||||
- **Safety restore** with automatic rollback protection
|
||||
- **Scheduled automation** with cron integration
|
||||
- **Telegram notifications** with file delivery and alerts
|
||||
- **Cross-server migration** support with detailed instructions
|
||||
- **Compressed archives** with unified structure
|
||||
|
||||
**📦 What's Backed Up:**
|
||||
|
||||
- PostgreSQL database as `db_backup.sql`
|
||||
- Configuration files: `docker-compose.yml`, `.env`, `app-config.json`
|
||||
- Optional: full directory backup with selective restore
|
||||
|
||||
**🎯 Quick Commands:**
|
||||
|
||||
```bash
|
||||
remnawave backup # Create instant backup
|
||||
remnawave schedule # Setup automated backups
|
||||
remnawave restore # Intelligent restore with version checks
|
||||
```
|
||||
|
||||
**🛡️ Safety Features:**
|
||||
|
||||
- Automatic version compatibility verification
|
||||
- Safety backup before restore operations
|
||||
- Rollback capability if restore fails
|
||||
- Real-time status monitoring during operations
|
||||
|
||||
**📋 Restore Options:**
|
||||
|
||||
- Full restore (replace all files and database)
|
||||
- Database-only restore (keep existing files)
|
||||
- Custom directory restoration
|
||||
- Manual restore commands included in each backup archive
|
||||
|
||||
**Legacy Standalone Scripts:**
|
||||
|
||||
Available for users who need legacy standalone backup/restore tools:
|
||||
|
||||
- [remnawave-backup.sh](https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave-backup.sh) - Standalone backup script
|
||||
- [restore.sh](https://github.com/DigneZzZ/remnawave-scripts/raw/main/restore.sh) - Standalone restore script
|
||||
|
||||
</details>
|
||||
|
||||
<br />
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/remnawave-script.webp" alt="Remnawave Management Scripts" width="600" />
|
||||
</div>
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Remnawave Reverse Proxy from eGames
|
||||
|
||||
Server Setup Using NGINX Reverse Proxy. This script is designed to streamline the setup of a reverse proxy server using NGINX and Xray, as well as to automate the installation of the Remnawave control panel and node. In this configuration, Xray operates directly on port 443, forwarding traffic through a socket that NGINX listens to.
|
||||
|
||||
Author: [eGamesAPI](https://github.com/eGamesAPI)
|
||||
|
||||
#### Installation Guidelines
|
||||
|
||||
Please read the [Installation Guidelines](https://github.com/eGamesAPI/remnawave-reverse-proxy/blob/main/README.md) or the [wiki](https://wiki.egam.es) before proceeding with the installation.
|
||||
|
||||
#### Installation
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/eGamesAPI/remnawave-reverse-proxy/refs/heads/main/install_remnawave.sh)
|
||||
```
|
||||
|
||||
Features:
|
||||
|
||||
- Support for automatic configuration updates via subscription
|
||||
- NGINX reverse proxy setup in combination with Xray
|
||||
- Cloudflare SSL certificates with automatic renewal
|
||||
- UFW setup for access management
|
||||
- BBR optimization for TCP connections
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem', flexWrap: 'wrap' }}>
|
||||
<Button label="Github repository" link="https://github.com/eGamesAPI/remnawave-reverse-proxy" variant="secondary" size="md" outline />
|
||||
<Button label="Documentation" link="https://wiki.egam.es" variant="secondary" size="md" outline />
|
||||
<Button label="Telegram Chat" link="https://t.me/+G8GtPf9dW9FlMWVi" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/remnawave-reverse-proxy.webp" alt="REMNAWAVE-REVERSE-PROXY" width="600" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Remnawave Telegram Subscription Mini App
|
||||
|
||||
This is a Telegram Subscription Mini App that allows users to view their subscriptions directly through Telegram. As a requirement for using the page, the Telegram ID must be set in the user's profile to ensure proper identification and linking of subscriptions.
|
||||
|
||||
Features:
|
||||
|
||||
- View your subscriptions in the mini app
|
||||
- Multi-language support (English, Russian)
|
||||
|
||||
Author: [maposia](https://github.com/maposia)
|
||||
|
||||
<details>
|
||||
<summary>Installation instructions</summary>
|
||||
#### Environment Variables
|
||||
|
||||
The application requires the following environment variables to be set:
|
||||
|
||||
| Variable | Description |
|
||||
|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `REMNAWAVE_PANEL_URL` | Remnawave Panel URL, can be `http://remnawave:3000` or `https://panel.example.com` |
|
||||
| `REMNAWAVE_TOKEN` | Authentication token for Remnawave API |
|
||||
| `BUY_LINK` | The URL for purchase actions |
|
||||
| `CRYPTO_LINK` | Allows using encrypted links (currently supported Happ application) |
|
||||
| `REDIRECT_LINK` | Allows you to specify a **custom redirect page URL** for deep links. Useful for handling protocols like `v2box://` in Telegram Desktop (Windows). For more details and examples, see [Telegram Deep Link Redirect](https://github.com/maposia/redirect-page/tree/main) |
|
||||
| `AUTH_API_KEY` | If you use "Caddy with security" or TinyAuth for Nginx addon, you can place here X-Api-Key, which will be applied to requests to Remnawave Panel. |
|
||||
#### Install and Run
|
||||
|
||||
1. Create a new directory for the mini app
|
||||
|
||||
```bash
|
||||
mkdir /opt/remnawave-telegram-sub-mini-app && cd /opt/remnawave-telegram-sub-mini-app
|
||||
```
|
||||
|
||||
2. Download the sample environment variables.
|
||||
|
||||
```bash
|
||||
curl -o .env https://raw.githubusercontent.com/maposia/remnawave-telegram-mini-bot/refs/heads/main/.env.example
|
||||
```
|
||||
|
||||
3. Configure the environment variables.
|
||||
|
||||
```bash
|
||||
nano .env
|
||||
```
|
||||
|
||||
4. Create docker-compose.yml file
|
||||
|
||||
```bash
|
||||
nano docker-compose.yml
|
||||
```
|
||||
|
||||
Example below:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
remnawave-mini-app:
|
||||
image: ghcr.io/maposia/remnawave-telegram-sub-mini-app:latest
|
||||
container_name: remnawave-telegram-mini-app
|
||||
hostname: remnawave-telegram-mini-app
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
# volumes:
|
||||
# - ./app-config.json:/app/public/assets/app-config.json
|
||||
ports:
|
||||
- '127.0.0.1:3020:3020'
|
||||
# networks:
|
||||
# - remnawave-network
|
||||
|
||||
#networks:
|
||||
# remnawave-network:
|
||||
# name: remnawave-network
|
||||
# driver: bridge
|
||||
# external: true
|
||||
```
|
||||
|
||||
Uncomment if you want to use your own template downloaded from the Remnawave panel.
|
||||
P.S. File must be placed in the same directory as this `docker-compose.yml` file
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./app-config.json:/app/public/assets/app-config.json
|
||||
```
|
||||
|
||||
Uncomment if you want to use local connection via single network in docker
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
- remnawave-network
|
||||
|
||||
networks:
|
||||
remnawave-network:
|
||||
name: remnawave-network
|
||||
driver: bridge
|
||||
external: true
|
||||
```
|
||||
|
||||
1. Run containers.
|
||||
|
||||
```bash
|
||||
docker compose up -d && docker compose logs -f
|
||||
```
|
||||
|
||||
2. Mini app is now running on http://127.0.0.1:3020
|
||||
Now we are ready to move on to Reverse Proxy installation.
|
||||
|
||||
</details>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem' }}>
|
||||
<Button label="Github repository" link="https://github.com/maposia/remnawave-telegram-sub-mini-app" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/remnawave-telegram-sub-mini-app.webp" alt="Remnawave Telegram Subscription Mini App" width="600" />
|
||||
</div>
|
||||
---
|
||||
|
||||
### Ansible playbook for Install Remnawave
|
||||
|
||||
This project helps install Remnawave via Ansible.
|
||||
|
||||
Author: [iphizic](https://github.com/iphizic)
|
||||
|
||||
<details>
|
||||
<summary>Installation instructions</summary>
|
||||
|
||||
#### Clone repo and change dir
|
||||
|
||||
```bash
|
||||
git clone https://github.com/iphizic/remna-playbook.git
|
||||
cd remna-playbook
|
||||
```
|
||||
|
||||
#### Now make Python .env:
|
||||
|
||||
```bash
|
||||
python3 -m venv .env
|
||||
```
|
||||
|
||||
#### Activate .env:
|
||||
|
||||
```bash
|
||||
source .env/bin/activate
|
||||
```
|
||||
|
||||
#### Install Ansible and requirements:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
ansible-galaxy install -r requirements.yml
|
||||
```
|
||||
|
||||
#### Make inventory and other vars
|
||||
|
||||
Make inventory like inventory.yml.example in inventory dir
|
||||
In directory group_vars make all.yml like all.yml.example
|
||||
|
||||
#### `Optional` Make some user but not root
|
||||
|
||||
:::warning
|
||||
To run this playbook, the GitHub username must match the username in all.yml
|
||||
:::
|
||||
|
||||
```bash
|
||||
ansible-playbook prepare-playbook.yml -u root -k
|
||||
```
|
||||
|
||||
First question it is root password
|
||||
Second question it is password for the created user
|
||||
|
||||
#### Run install Remnaware:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml -K
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem' }}>
|
||||
<Button label="Github repository" link="https://github.com/iphizic/remna-playbook" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/ansible.webp" alt="Ansible playbook for installation" width="600" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Remnawave Telegram Shop Bot
|
||||
|
||||
A Telegram bot for selling subscriptions with integration to Remnawave. This service allows users to purchase and manage subscriptions through Telegram with multiple payment system options. YooKass | Telegram Stars | CryptoBot
|
||||
|
||||
Author: [jolymmiles](https://github.com/Jolymmiles)
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem', flexWrap: 'wrap' }}>
|
||||
<Button label="Github repository" link="https://github.com/Jolymmiels/remnawave-telegram-shop" variant="secondary" size="md" outline />
|
||||
<Button label="Documentation" link="https://github.com/Jolymmiels/remnawave-telegram-shop/wiki" variant="secondary" size="md" outline />
|
||||
<Button label="Telegram Chat" link="https://t.me/remnawavetelegramshop" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/remnawave-telegram-shop-bot.webp" alt="Remnawave Telegram Shop Bot" width="600" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### RemnaSetup 🛠️
|
||||
|
||||
Universal script for automatic installation, configuration, and updating of Remnawave and Remnanode infrastructure. Includes installation of the control panel, node, subscription page, Caddy setup, Tblocker, BBR, WARP, and an automatic backup system with Telegram integration.
|
||||
|
||||
Author: [Capybara-z](https://github.com/Capybara-z)
|
||||
|
||||
<details>
|
||||
<summary>Quick Start</summary>
|
||||
|
||||
**Option 1:**
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL raw.githubusercontent.com/Capybara-z/RemnaSetup/refs/heads/main/install.sh)
|
||||
```
|
||||
|
||||
**Option 2:**
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/Capybara-z/RemnaSetup/refs/heads/main/install.sh -o install.sh && chmod +x install.sh && sudo bash ./install.sh
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**🔥 Main Features:**
|
||||
|
||||
- 📦 Complete Remnawave installation + Subscription page + Caddy
|
||||
- 🌐 Remnanode installation with Caddy, Tblocker, BBR, and WARP
|
||||
- 💾 Backup system with Telegram integration
|
||||
- ♻️ Recovery from local backups and Telegram
|
||||
- 🔄 Automatic component updates
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem' }}>
|
||||
<Button label="Github repository" link="https://github.com/Capybara-z/RemnaSetup" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/remnasetup.webp" alt="RemnaSetup" width="600" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Remnawave Backup & Restore by distillium
|
||||
|
||||
The script automates backups and performs a restore of the Remnawave database.
|
||||
|
||||
**Author:** [distillium](https://github.com/distillium)
|
||||
|
||||
<details>
|
||||
<summary>Instruction</summary>
|
||||
|
||||
#### Installation
|
||||
|
||||
```bash
|
||||
curl -o ~/backup-restore.sh https://raw.githubusercontent.com/distillium/remnawave-backup-restore/main/backup-restore.sh && chmod +x ~/backup-restore.sh && ~/backup-restore.sh
|
||||
```
|
||||
|
||||
:::danger
|
||||
As a precaution, use the restore function on the same panel version from which the backup was made (or create the backup from the latest panel version).
|
||||
:::
|
||||
|
||||
:::warning
|
||||
This script is designed to perform meaningful maintenance operations on the Remnawave database. Although it has been thoroughly tested, its functions affect the entire database and its components. It is recommended that you carefully follow the script’s instructions before executing any commands.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
The script backups and restores only the entire database, as well as the .env and .env-node files (if they exist in the /opt/remnawave/ or /root/remnawave/ directory). The backup and recovery of all other files and configurations are entirely the responsibility of the user.
|
||||
:::
|
||||
|
||||
</details>
|
||||
|
||||
**Features:**
|
||||
|
||||
- creating a manual backup and configuring automatic scheduled backups
|
||||
- notifications in Telegram with backup file attached
|
||||
- supports sending backup to Google Drive
|
||||
- restore from backup
|
||||
- backups retention policy (7 days) implemented
|
||||
- quick access from anywhere on the system with the `rw-backup` command
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem' }}>
|
||||
<Button label="Github repository" link="https://github.com/distillium/remnawave-backup-restore" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/remnawave-backup-restore.webp" alt="REMNAWAVE-BACKUP-RESTORE" width="600" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### WARP Native Installer
|
||||
|
||||
This script installs Cloudflare WARP in “native” mode via `WireGuard`, without using `warp-cli`.
|
||||
|
||||
**Script Author:** [distillium](https://github.com/distillium) <br />
|
||||
**Ansible Role Author:** [TheMelbine](https://github.com/TheMelbine)
|
||||
|
||||
It automates:
|
||||
- Installing the necessary packages (`wireguard`, `resolvconf`)
|
||||
- Downloading and configuring `wgcf`
|
||||
- Generating and modifying the WireGuard configuration
|
||||
- Connecting and checking status
|
||||
- Enabling autorun of the `warp` interface
|
||||
|
||||
**Installing:** <br />
|
||||
**Option 1: Shell Script (performed on each desired node):**
|
||||
```bash
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/distillium/warp-native/main/install.sh)
|
||||
```
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem' }}>
|
||||
<Button label="Shell Script Github Repository" link="https://github.com/distillium/warp-native" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
**Option 2: Ansible Role (Recommended for automation)**
|
||||
```bash
|
||||
ansible-galaxy install themelbine.warp_native
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>📝 Show example playbook</summary>
|
||||
|
||||
```yaml
|
||||
- hosts: warp_servers
|
||||
become: yes
|
||||
roles:
|
||||
- themelbine.warp_native
|
||||
vars:
|
||||
warp_native_state: present
|
||||
warp_native_modify_resolv: true
|
||||
```
|
||||
</details>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem' }}>
|
||||
<Button label="Ansible Role Github Repository" link="https://github.com/TheMelbine/ansible-role-warp-native" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br /><br />
|
||||
|
||||
**Templates for Xray configuration**
|
||||
<details>
|
||||
<summary>📝 Show example outbound</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"tag": "warp-out",
|
||||
"protocol": "freedom",
|
||||
"settings": {},
|
||||
"streamSettings": {
|
||||
"sockopt": {
|
||||
"interface": "warp",
|
||||
"tcpFastOpen": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>📝 Show example routing rule</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "field",
|
||||
"domain": [
|
||||
"netflix.com",
|
||||
"youtube.com",
|
||||
"twitter.com"
|
||||
],
|
||||
"inboundTag": [
|
||||
"Node-1",
|
||||
"Node-2"
|
||||
],
|
||||
"outboundTag": "warp-out"
|
||||
}
|
||||
```
|
||||
</details>
|
||||
<br />
|
||||
|
||||
**Interface management**
|
||||
<details>
|
||||
<summary>⚙️ Commands</summary>
|
||||
|
||||
| Operation | Command |
|
||||
|------------------------|----------------------------------------|
|
||||
| Check service status | `systemctl status wg-quick@warp` |
|
||||
| View interface status | `wg show warp` |
|
||||
| Stop the interface | `systemctl stop wg-quick@warp` |
|
||||
| Start the interface | `systemctl start wg-quick@warp` |
|
||||
| Restart the interface | `systemctl restart wg-quick@warp` |
|
||||
| Disable autorun | `systemctl disable wg-quick@warp` |
|
||||
| Enable autorun | `systemctl enable wg-quick@warp` |
|
||||
</details>
|
||||
<br />
|
||||
|
||||
**Uninstall:** <br />
|
||||
**Shell Script Method:**
|
||||
```bash
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/distillium/warp-native/main/uninstall.sh)
|
||||
```
|
||||
**Ansible Role Method:**
|
||||
<details>
|
||||
<summary>📝 Show</summary>
|
||||
```yaml
|
||||
- hosts: warp_servers
|
||||
become: yes
|
||||
roles:
|
||||
- themelbine.warp_native
|
||||
vars:
|
||||
warp_native_state: absent
|
||||
```
|
||||
</details>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/warp-native.webp" alt="warp-native" width="600" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Remnawave TG Shop
|
||||
|
||||
A Telegram bot for selling and managing Remnawave subscriptions. It supports user registration, trial periods, promo codes, referral programs, subscription management. Payments are accepted through YooKassa | CryptoPay | Telegram Stars | Tribute. Admins get access to a secure panel with statistics, user management, promo code creation, broadcast messaging and other functions
|
||||
|
||||
Author: [machka pasla](https://github.com/machka-pasla)
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem', flexWrap: 'wrap' }}>
|
||||
<Button label="Github repository" link="https://github.com/machka-pasla/remnawave-tg-shop" variant="secondary" size="md" outline />
|
||||
<Button label="Telegram Chat" link="https://t.me/remnawave_tg_shop" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/remnawave-tg-shop.webp" alt="Remnawave TG Shop" width="600" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Remnawave Bedolaga Bot
|
||||
|
||||
Modern Telegram bot for automating VPN business through Remnawave API. Full-featured solution with user management, multi-channel payments, referral system, detailed analytics, Web API, and Telegram Mini App integration.
|
||||
|
||||
Author: [Fr1ngg](https://github.com/Fr1ngg)
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem', flexWrap: 'wrap' }}>
|
||||
<Button label="Github repository" link="https://github.com/Fr1ngg/remnawave-bedolaga-telegram-bot" variant="secondary" size="md" outline />
|
||||
<Button label="Test Bot" link="https://t.me/FringVPN_bot" variant="secondary" size="md" outline />
|
||||
<Button label="Telegram Chat" link="https://t.me/+wTdMtSWq8YdmZmVi" variant="secondary" size="md" outline />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
**🔥 Key Features:**
|
||||
- 💰 **Multi-channel payments** - Telegram Stars, Tribute, YooKassa (SBP + receipts), CryptoBot, MulenPay, PayPalych
|
||||
- 🎯 **Full automation** - from registration to subscription renewals
|
||||
- 🎁 **Advanced promo system** - flexible promo codes (balance/days/extended trial), advertising campaigns with automatic bonuses, detailed usage statistics
|
||||
- 📊 **Powerful analytics** - comprehensive statistics for users, payments, and servers
|
||||
- 🛡️ **Enterprise ready** - AsyncIO, PostgreSQL, Redis, panel protection via cookies
|
||||
- 🗄️ **Backup system** - automatic backups with Telegram notifications
|
||||
- 🌐 **Multi-language** - Russian and English support
|
||||
- 🔧 **Flexible configuration** - customizable plans, traffic packages, devices, servers
|
||||
- 🌐 **Web API** - RESTful API for external integrations and custom admin panels
|
||||
- 📱 **Telegram Mini App** - built-in subscription page with deep links
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<img src="/awesome/bedolaga.webp" alt="Remnawave Bedolaga Bot" width="600" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Add project to the list
|
||||
|
||||
If you want to add your project to the list, please open a PR on [GitHub](https://github.com/remnawave/panel/blob/main/docs/awesome-remnawave.md).
|
||||
|
||||
Make sure that the target branch is `main`.
|
||||
|
||||
Also, the following requirements must be met:
|
||||
|
||||
- [x] Project must be open source.
|
||||
- [x] Project must be related to Remnawave or **useful for Remnawave users**.
|
||||
|
||||
The following format must be used:
|
||||
|
||||
```md
|
||||
### Project name
|
||||
|
||||
Short project description.
|
||||
|
||||
[Project link or repository link](https://project.com)
|
||||
|
||||

|
||||
```
|
||||
|
||||
Use the examples above to add your project to the list.
|
||||
|
||||
:::info
|
||||
|
||||
Please, use [https://squoosh.app/](https://squoosh.app/) to compress the image. Format must be `webp`.
|
||||
Add the image to the `static/awesome` directory.
|
||||
|
||||
:::
|
||||
288
docs/awesome-remnawave.mdx
Normal file
288
docs/awesome-remnawave.mdx
Normal file
@@ -0,0 +1,288 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
title: ❤️ Awesome Remnawave
|
||||
hide_title: true
|
||||
hide_table_of_contents: true
|
||||
description: 'Discover the amazing ecosystem of tools, scripts, and integrations built by the Remnawave community. From monitoring solutions to automation scripts, find everything you need to enhance your Remnawave experience.'
|
||||
image: '/awesome/awesome-preview.webp'
|
||||
---
|
||||
|
||||
import Button from '@site/src/components/Button'
|
||||
import ProjectCard from '@site/src/components/ProjectCard'
|
||||
import ProjectsGrid from '@site/src/components/ProjectsGrid'
|
||||
import HeroSection from '@site/src/components/HeroSection'
|
||||
import CategoryNav from '@site/src/components/CategoryNav'
|
||||
|
||||
import RemnavaweTelegramMiniAppGuide from '/docs/awesome-remnawave/\_install-guides/remnawave-telegram-mini-app.md'
|
||||
import RemnawaveManagementScriptsGuide from '/docs/awesome-remnawave/\_install-guides/remnawave-management-scripts.md'
|
||||
import RemnasetupGuide from '/docs/awesome-remnawave/\_install-guides/remnasetup.md'
|
||||
import AnsiblePlaybookGuide from '/docs/awesome-remnawave/\_install-guides/ansible-playbook.md'
|
||||
import RemnawaveReverseProxyGuide from '/docs/awesome-remnawave/\_install-guides/remnawave-reverse-proxy.md'
|
||||
import RemnawaveBackupRestoreGuide from '/docs/awesome-remnawave/\_install-guides/remnawave-backup-restore.md'
|
||||
import WarpNativeInstallerGuide from '/docs/awesome-remnawave/\_install-guides/warp-native-installer.md'
|
||||
import RemnavaweBedolagaBotGuide from '/docs/awesome-remnawave/\_install-guides/remnawave-bedolaga-bot.md'
|
||||
|
||||
<HeroSection
|
||||
title="❤️ Awesome Remnawave"
|
||||
subtitle="Discover the amazing ecosystem of tools, scripts, and integrations built by the Remnawave community. From monitoring solutions to automation scripts, find everything you need to enhance your Remnawave experience."
|
||||
/>
|
||||
|
||||
<CategoryNav
|
||||
categories={[
|
||||
{ id: 'monitoring-analytics', icon: '📊', title: 'Monitoring & Analytics' },
|
||||
{ id: 'commerce', icon: '💰', title: 'Commerce' },
|
||||
{ id: 'auto-installers', icon: '🚀', title: 'Auto-installers' },
|
||||
{ id: 'backup-restore', icon: '💾', title: 'Backup & Restore' },
|
||||
{ id: 'misc', icon: '🔧', title: 'Misc' }
|
||||
]}
|
||||
/>
|
||||
|
||||
<CategorySection
|
||||
id="monitoring-analytics"
|
||||
title="Monitoring & Analytics"
|
||||
description="Tools for monitoring, analyzing, and visualizing your Remnawave infrastructure"
|
||||
icon="📊"
|
||||
columns={1}
|
||||
>
|
||||
<ProjectCard
|
||||
id="xray-checker"
|
||||
title="Xray Checker"
|
||||
description="Xray Checker is a tool for monitoring proxy server availability with support for VLESS, VMess, Trojan, and Shadowsocks protocols. It automatically tests connections through Xray Core and provides metrics for Prometheus, as well as API endpoints for integration with various monitoring systems."
|
||||
author="kutovoys"
|
||||
authorLink="https://github.com/kutovoys"
|
||||
image="/awesome/xray-checker.webp"
|
||||
githubRepo="kutovoys/xray-checker"
|
||||
links={{
|
||||
github: 'https://github.com/kutovoys/xray-checker',
|
||||
docs: 'https://xray-checker.kutovoy.dev/'
|
||||
}}
|
||||
/>
|
||||
</CategorySection>
|
||||
|
||||
<CategorySection
|
||||
id="commerce"
|
||||
title="Commerce"
|
||||
description="Telegram bots and mini apps made for commerce with Remnawave."
|
||||
icon="📦"
|
||||
columns={2}
|
||||
>
|
||||
<ProjectCard
|
||||
id="remnawave-telegram-shop-bot"
|
||||
title="Remnawave Telegram Shop Bot"
|
||||
description="A Telegram bot for selling subscriptions with integration to Remnawave. This service allows users to purchase and manage subscriptions through Telegram with multiple payment system options."
|
||||
author="jolymmiles"
|
||||
authorLink="https://github.com/Jolymmiles"
|
||||
image="/awesome/remnawave-telegram-shop-bot.webp"
|
||||
githubRepo="Jolymmiels/remnawave-telegram-shop"
|
||||
links={{
|
||||
github: 'https://github.com/Jolymmiels/remnawave-telegram-shop',
|
||||
docs: 'https://github.com/Jolymmiels/remnawave-telegram-shop/wiki',
|
||||
telegram: 'https://t.me/remnawavetelegramshop'
|
||||
}}
|
||||
featured={true}
|
||||
/>
|
||||
<ProjectCard
|
||||
id="remnawave-telegram-mini-app"
|
||||
title="Remnawave Telegram Subscription Mini App"
|
||||
description="This is a Telegram Subscription Mini App that allows users to view their subscriptions directly through Telegram. As a requirement for using the page, the Telegram ID must be set in the user's profile to ensure proper identification and linking of subscriptions."
|
||||
author="maposia"
|
||||
authorLink="https://github.com/maposia"
|
||||
image="/awesome/remnawave-telegram-sub-mini-app.webp"
|
||||
githubRepo="maposia/remnawave-telegram-sub-mini-app"
|
||||
links={{
|
||||
github: 'https://github.com/maposia/remnawave-telegram-sub-mini-app'
|
||||
}}
|
||||
>
|
||||
<RemnavaweTelegramMiniAppGuide />
|
||||
</ProjectCard>
|
||||
<ProjectCard
|
||||
id="remnawave-tg-shop"
|
||||
title="Remnawave TG Shop"
|
||||
description="A Telegram bot for selling and managing Remnawave subscriptions. It supports user registration, trial periods, promo codes, referral programs, subscription management. Payments are accepted through YooKassa, CryptoPay, Telegram Stars, Tribute. Admins get access to a secure panel with statistics, user management, promo code creation, broadcast messaging and other functions"
|
||||
author="machka pasla"
|
||||
authorLink="https://github.com/machka-pasla"
|
||||
image="/awesome/remnawave-tg-shop.webp"
|
||||
githubRepo="machka-pasla/remnawave-tg-shop"
|
||||
links={{
|
||||
github: 'https://github.com/machka-pasla/remnawave-tg-shop',
|
||||
telegram: 'https://t.me/remnawave_tg_shop'
|
||||
}}
|
||||
/>
|
||||
<ProjectCard
|
||||
id="remnawave-bedolaga-bot"
|
||||
title="Remnawave Bedolaga Bot"
|
||||
description="Modern Telegram bot for automating VPN business through Remnawave API. Full-featured solution with user management, multi-channel payments, referral system, detailed analytics, Web API, and Telegram Mini App integration."
|
||||
author="Fr1ngg"
|
||||
authorLink="https://github.com/Fr1ngg"
|
||||
image="/awesome/bedolaga.webp"
|
||||
githubRepo="Fr1ngg/remnawave-bedolaga-telegram-bot"
|
||||
links={{
|
||||
github: 'https://github.com/Fr1ngg/remnawave-bedolaga-telegram-bot',
|
||||
website: 'https://t.me/FringVPN_bot',
|
||||
telegram: 'https://t.me/+wTdMtSWq8YdmZmVi'
|
||||
}}
|
||||
>
|
||||
<RemnavaweBedolagaBotGuide />
|
||||
</ProjectCard>
|
||||
|
||||
</CategorySection>
|
||||
|
||||
<CategorySection
|
||||
id="auto-installers"
|
||||
title="Auto-installers"
|
||||
description="Automated installation, configuration, and deployment tools for Remnawave infrastructure."
|
||||
icon="🚀 "
|
||||
columns={2}
|
||||
>
|
||||
<ProjectCard
|
||||
id="remnawave-reverse-proxy"
|
||||
title="Remnawave Reverse Proxy"
|
||||
description="Server Setup Using NGINX Reverse Proxy. This script is designed to streamline the setup of a reverse proxy server using NGINX and Xray, as well as to automate the installation of the Remnawave control panel and node. In this configuration, Xray operates directly on port 443, forwarding traffic through a socket that NGINX listens to."
|
||||
author="eGamesAPI"
|
||||
authorLink="https://github.com/eGamesAPI"
|
||||
image="/awesome/remnawave-reverse-proxy.webp"
|
||||
githubRepo="eGamesAPI/remnawave-reverse-proxy"
|
||||
links={{
|
||||
github: 'https://github.com/eGamesAPI/remnawave-reverse-proxy',
|
||||
docs: 'https://wiki.egam.es',
|
||||
telegram: 'https://t.me/+G8GtPf9dW9FlMWVi'
|
||||
}}
|
||||
featured={true}
|
||||
>
|
||||
<RemnawaveReverseProxyGuide />
|
||||
</ProjectCard>
|
||||
<ProjectCard
|
||||
id="remnawave-management-scripts"
|
||||
title="Remnawave Management Scripts"
|
||||
description="Comprehensive bash scripts for Remnawave Panel, Remnawave Node, and Reality Selfsteal with full automation support. Features Docker integration, intelligent backup/restore system with version checking, advanced monitoring, and complete lifecycle management."
|
||||
author="DigneZzZ"
|
||||
authorLink="https://github.com/DigneZzZ"
|
||||
image="/awesome/remnawave-script.webp"
|
||||
githubRepo="DigneZzZ/remnawave-scripts"
|
||||
links={{
|
||||
github: 'https://github.com/DigneZzZ/remnawave-scripts'
|
||||
}}
|
||||
>
|
||||
<RemnawaveManagementScriptsGuide />
|
||||
</ProjectCard>
|
||||
<ProjectCard
|
||||
id="remnasetup"
|
||||
title="RemnaSetup"
|
||||
description="Universal script for automatic installation, configuration, and updating of Remnawave and Remnanode infrastructure. Includes installation of the control panel, node, subscription page, Caddy setup, Tblocker, BBR, WARP, and an automatic backup system with Telegram integration."
|
||||
author="Capybara-z"
|
||||
authorLink="https://github.com/Capybara-z"
|
||||
image="/awesome/remnasetup.webp"
|
||||
githubRepo="Capybara-z/RemnaSetup"
|
||||
links={{
|
||||
github: 'https://github.com/Capybara-z/RemnaSetup'
|
||||
}}
|
||||
>
|
||||
<RemnasetupGuide />
|
||||
</ProjectCard>
|
||||
<ProjectCard
|
||||
id="ansible-playbook"
|
||||
title="Ansible playbook for Install Remnawave"
|
||||
description="This project helps install Remnawave via Ansible."
|
||||
author="iphizic"
|
||||
authorLink="https://github.com/iphizic"
|
||||
image="/awesome/ansible.webp"
|
||||
githubRepo="iphizic/remna-playbook"
|
||||
links={{
|
||||
github: 'https://github.com/iphizic/remna-playbook'
|
||||
}}
|
||||
>
|
||||
<AnsiblePlaybookGuide />
|
||||
</ProjectCard>
|
||||
</CategorySection>
|
||||
|
||||
<CategorySection
|
||||
id="backup-restore"
|
||||
title="Backup & Restore"
|
||||
description="Tools for backing up and restoring your Remnawave database and configurations"
|
||||
icon="💾"
|
||||
columns={1}
|
||||
>
|
||||
<ProjectCard
|
||||
id="remnawave-backup-restore"
|
||||
title="Remnawave Backup & Restore"
|
||||
description="The script automates backups and performs a restore of the Remnawave database."
|
||||
author="distillium"
|
||||
authorLink="https://github.com/distillium"
|
||||
image="/awesome/remnawave-backup-restore.webp"
|
||||
githubRepo="distillium/remnawave-backup-restore"
|
||||
links={{
|
||||
github: 'https://github.com/distillium/remnawave-backup-restore'
|
||||
}}
|
||||
>
|
||||
<RemnawaveBackupRestoreGuide />
|
||||
</ProjectCard>
|
||||
</CategorySection>
|
||||
|
||||
<CategorySection
|
||||
id="misc"
|
||||
title="Misc"
|
||||
description="Utilities, tools, and integrations that don't fit into other categories."
|
||||
icon="🔧"
|
||||
columns={2}
|
||||
>
|
||||
<ProjectCard
|
||||
id="warp-native-installer"
|
||||
title="WARP Native Installer"
|
||||
description="This script installs Cloudflare WARP in 'native' mode via WireGuard, without using warp-cli. Automates package installation, wgcf configuration, and enables autorun. Available as both shell script and Ansible role."
|
||||
author="distillium & TheMelbine"
|
||||
authorLink="https://github.com/distillium"
|
||||
image="/awesome/warp-native.webp"
|
||||
githubRepo="distillium/warp-native"
|
||||
links={{
|
||||
github: 'https://github.com/distillium/warp-native'
|
||||
}}
|
||||
>
|
||||
<WarpNativeInstallerGuide />
|
||||
</ProjectCard>
|
||||
<ProjectCard
|
||||
id="geo-file-viewer"
|
||||
title="Geo File Viewer"
|
||||
description="A utility for viewing the contents of geoip and geofile (.dat) files in the v2fly format."
|
||||
image="/awesome/geofileviewer.webp"
|
||||
links={{
|
||||
website: 'https://jomertix.github.io/geofileviewer'
|
||||
}}
|
||||
/>
|
||||
</CategorySection>
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Add Your Project
|
||||
|
||||
Want to showcase your Remnawave-related project? We'd love to see it!
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- ✅ Project must be open source
|
||||
- ✅ Project must be related to Remnawave or useful for Remnawave users
|
||||
|
||||
**How to submit:**
|
||||
|
||||
1. Open a PR on [GitHub](https://github.com/remnawave/panel/blob/main/docs/awesome-remnawave.md)
|
||||
2. Make sure the target branch is `main`
|
||||
|
||||
:::info Image Requirements
|
||||
|
||||
Please use [https://squoosh.app/](https://squoosh.app/) to compress your image.
|
||||
Format must be `webp` and placed in the `static/awesome` directory.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
marginTop: '4rem',
|
||||
padding: '2rem',
|
||||
borderRadius: '12px'
|
||||
}}
|
||||
>
|
||||
<h3>🌟 Thank you to all contributors!</h3>
|
||||
<p>The Remnawave community thrives because of amazing developers like you.</p>
|
||||
</div>
|
||||
51
docs/awesome-remnawave/_install-guides/ansible-playbook.md
Normal file
51
docs/awesome-remnawave/_install-guides/ansible-playbook.md
Normal file
@@ -0,0 +1,51 @@
|
||||
### Installation instructions
|
||||
|
||||
#### Clone repo and change dir
|
||||
|
||||
```bash
|
||||
git clone https://github.com/iphizic/remna-playbook.git
|
||||
cd remna-playbook
|
||||
```
|
||||
|
||||
#### Now make Python .env:
|
||||
|
||||
```bash
|
||||
python3 -m venv .env
|
||||
```
|
||||
|
||||
#### Activate .env:
|
||||
|
||||
```bash
|
||||
source .env/bin/activate
|
||||
```
|
||||
|
||||
#### Install Ansible and requirements:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
ansible-galaxy install -r requirements.yml
|
||||
```
|
||||
|
||||
#### Make inventory and other vars
|
||||
|
||||
Make inventory like inventory.yml.example in inventory dir
|
||||
In directory group_vars make all.yml like all.yml.example
|
||||
|
||||
#### `Optional` Make some user but not root
|
||||
|
||||
:::warning
|
||||
To run this playbook, the GitHub username must match the username in all.yml
|
||||
:::
|
||||
|
||||
```bash
|
||||
ansible-playbook prepare-playbook.yml -u root -k
|
||||
```
|
||||
|
||||
First question it is root password
|
||||
Second question it is password for the created user
|
||||
|
||||
#### Run install Remnaware:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml -K
|
||||
```
|
||||
21
docs/awesome-remnawave/_install-guides/remnasetup.md
Normal file
21
docs/awesome-remnawave/_install-guides/remnasetup.md
Normal file
@@ -0,0 +1,21 @@
|
||||
### 🔥 Main Features
|
||||
|
||||
- 📦 Complete Remnawave installation + Subscription page + Caddy
|
||||
- 🌐 Remnanode installation with Caddy, Tblocker, BBR, and WARP
|
||||
- 💾 Backup system with Telegram integration
|
||||
- ♻️ Recovery from local backups and Telegram
|
||||
- 🔄 Automatic component updates
|
||||
|
||||
### Quick Start
|
||||
|
||||
#### Option 1
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL raw.githubusercontent.com/Capybara-z/RemnaSetup/refs/heads/main/install.sh)
|
||||
```
|
||||
|
||||
#### Option 2
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/Capybara-z/RemnaSetup/refs/heads/main/install.sh -o install.sh && chmod +x install.sh && sudo bash ./install.sh
|
||||
```
|
||||
@@ -0,0 +1,26 @@
|
||||
### Features
|
||||
|
||||
- creating a manual backup and configuring automatic scheduled backups
|
||||
- notifications in Telegram with backup file attached
|
||||
- supports sending backup to Google Drive
|
||||
- restore from backup
|
||||
- backups retention policy (7 days) implemented
|
||||
- quick access from anywhere on the system with the `rw-backup` command
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
curl -o ~/backup-restore.sh https://raw.githubusercontent.com/distillium/remnawave-backup-restore/main/backup-restore.sh && chmod +x ~/backup-restore.sh && ~/backup-restore.sh
|
||||
```
|
||||
|
||||
:::danger
|
||||
As a precaution, use the restore function on the same panel version from which the backup was made (or create the backup from the latest panel version).
|
||||
:::
|
||||
|
||||
:::warning
|
||||
This script is designed to perform meaningful maintenance operations on the Remnawave database. Although it has been thoroughly tested, its functions affect the entire database and its components. It is recommended that you carefully follow the script's instructions before executing any commands.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
The script backups and restores only the entire database, as well as the .env and .env-node files (if they exist in the /opt/remnawave/ or /root/remnawave/ directory). The backup and recovery of all other files and configurations are entirely the responsibility of the user.
|
||||
:::
|
||||
@@ -0,0 +1,12 @@
|
||||
### 🔥 Key Features
|
||||
|
||||
- 💰 **Multi-channel payments** - Telegram Stars, Tribute, YooKassa (SBP + receipts), CryptoBot, MulenPay, PayPalych
|
||||
- 🎯 **Full automation** - from registration to subscription renewals
|
||||
- 🎁 **Advanced promo system** - flexible promo codes (balance/days/extended trial), advertising campaigns with automatic bonuses, detailed usage statistics
|
||||
- 📊 **Powerful analytics** - comprehensive statistics for users, payments, and servers
|
||||
- 🛡️ **Enterprise ready** - AsyncIO, PostgreSQL, Redis, panel protection via cookies
|
||||
- 🗄️ **Backup system** - automatic backups with Telegram notifications
|
||||
- 🌐 **Multi-language** - Russian and English support
|
||||
- 🔧 **Flexible configuration** - customizable plans, traffic packages, devices, servers
|
||||
- 🌐 **Web API** - RESTful API for external integrations and custom admin panels
|
||||
- 📱 **Telegram Mini App** - built-in subscription page with deep links
|
||||
@@ -0,0 +1,136 @@
|
||||
### 📦 Key Features
|
||||
|
||||
- 🚀 **One-line installation** for Panel, Node, and Selfsteal configurations
|
||||
- 🎛️ **Interactive menus** with real-time status monitoring and guided operations
|
||||
- 💾 **Smart backup system** with version compatibility checking and safety rollbacks
|
||||
- 🔄 **Complete lifecycle management** - install, update, backup, restore, uninstall
|
||||
- 🎯 **Reality masking** with 11 AI-generated website templates
|
||||
|
||||
### Quick Install Commands
|
||||
|
||||
#### Remnawave Panel (v3.5.5+)
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave.sh) @ install
|
||||
```
|
||||
|
||||
#### Remnawave Node (v3.1.2+)
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnanode.sh) @ install
|
||||
```
|
||||
|
||||
#### Reality Selfsteal (v2.1.3+)
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/selfsteal.sh) @ install
|
||||
```
|
||||
|
||||
:::info Options
|
||||
|
||||
- Add `--dev` for development version
|
||||
- Add `--name customname` for custom directory (default: `/opt/remnawave`, `/opt/remnanode`)
|
||||
:::
|
||||
|
||||
#### For existing installations
|
||||
|
||||
Use `install-script` to add CLI wrapper only:
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave.sh) @ install-script
|
||||
```
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnanode.sh) @ install-script
|
||||
```
|
||||
|
||||
### Available CLI Commands
|
||||
|
||||
**Installation & Management:**
|
||||
|
||||
- `install`, `update`, `uninstall`
|
||||
- `install-script`, `uninstall-script`
|
||||
|
||||
**Service Control:**
|
||||
|
||||
- `up`, `down`, `restart`, `status`, `logs`
|
||||
|
||||
**Node-specific Commands:**
|
||||
|
||||
- `core-update` - Manual Update/Install Xray-core to latest version
|
||||
- `xray-log-out`, `xray-log-err` - View Xray logs
|
||||
- `setup-logs` - Configure log rotation
|
||||
|
||||
**Configuration:**
|
||||
|
||||
- `edit`, `edit-env`, `console` (Panel only)
|
||||
|
||||
**Interactive Menus:**
|
||||
|
||||
- Run `remnawave`, `remnanode`, or `selfsteal` without arguments for interactive menu
|
||||
- Real-time status monitoring and resource usage
|
||||
- Step-by-step guided operations
|
||||
|
||||
**Backup & Restore (Panel):**
|
||||
|
||||
- `backup` - Create manual backup
|
||||
- `schedule` - Configure automated backups
|
||||
- `restore` - Restore from backup archive
|
||||
|
||||
**Reality Selfsteal:**
|
||||
|
||||
- Choose from 11 AI-generated website templates
|
||||
- Automatic SSL certificate management by Caddy
|
||||
- DNS validation before start
|
||||
|
||||
Run `remnawave help`, `remnanode help`, or `selfsteal help` for detailed usage.
|
||||
|
||||
### 💾 Intelligent Backup & Restore System
|
||||
|
||||
**🔄 Smart Backup Features:**
|
||||
|
||||
- **Version-aware backups** with compatibility checking
|
||||
- **Safety restore** with automatic rollback protection
|
||||
- **Scheduled automation** with cron integration
|
||||
- **Telegram notifications** with file delivery and alerts
|
||||
- **Cross-server migration** support with detailed instructions
|
||||
- **Compressed archives** with unified structure
|
||||
|
||||
**📦 What's Backed Up:**
|
||||
|
||||
- PostgreSQL database as `db_backup.sql`
|
||||
- Configuration files: `docker-compose.yml`, `.env`, `app-config.json`
|
||||
- Optional: full directory backup with selective restore
|
||||
|
||||
**🎯 Quick Commands:**
|
||||
|
||||
```bash
|
||||
remnawave backup # Create instant backup
|
||||
remnawave schedule # Setup automated backups
|
||||
remnawave restore # Intelligent restore with version checks
|
||||
```
|
||||
|
||||
**🛡️ Safety Features:**
|
||||
|
||||
- Automatic version compatibility verification
|
||||
- Safety backup before restore operations
|
||||
- Rollback capability if restore fails
|
||||
- Real-time status monitoring during operations
|
||||
|
||||
**📋 Restore Options:**
|
||||
|
||||
- Full restore (replace all files and database)
|
||||
- Database-only restore (keep existing files)
|
||||
- Custom directory restoration
|
||||
- Manual restore commands included in each backup archive
|
||||
|
||||
**Legacy Standalone Scripts:**
|
||||
|
||||
Available for users who need legacy standalone backup/restore tools:
|
||||
|
||||
- [remnawave-backup.sh](https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave-backup.sh) - Standalone backup script
|
||||
- [restore.sh](https://github.com/DigneZzZ/remnawave-scripts/raw/main/restore.sh) - Standalone restore script
|
||||
|
||||
📦 Full info, updates, and examples: [**remnawave-scripts**](https://github.com/DigneZzZ/remnawave-scripts)
|
||||
|
||||
Project: [GIG.ovh](https://gig.ovh)
|
||||
@@ -0,0 +1,19 @@
|
||||
### Installation Guidelines
|
||||
|
||||
:::warning
|
||||
Please read the [Installation Guidelines](https://github.com/eGamesAPI/remnawave-reverse-proxy/blob/main/README.md) or the [wiki](https://wiki.egam.es) before proceeding with the installation.
|
||||
:::
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/eGamesAPI/remnawave-reverse-proxy/refs/heads/main/install_remnawave.sh)
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- Support for automatic configuration updates via subscription
|
||||
- NGINX reverse proxy setup in combination with Xray
|
||||
- Cloudflare SSL certificates with automatic renewal
|
||||
- UFW setup for access management
|
||||
- BBR optimization for TCP connections
|
||||
136
docs/awesome-remnawave/_install-guides/remnawave-scripts.md
Normal file
136
docs/awesome-remnawave/_install-guides/remnawave-scripts.md
Normal file
@@ -0,0 +1,136 @@
|
||||
### 📦 Key Features
|
||||
|
||||
- 🚀 **One-line installation** for Panel, Node, and Selfsteal configurations
|
||||
- 🎛️ **Interactive menus** with real-time status monitoring and guided operations
|
||||
- 💾 **Smart backup system** with version compatibility checking and safety rollbacks
|
||||
- 🔄 **Complete lifecycle management** - install, update, backup, restore, uninstall
|
||||
- 🎯 **Reality masking** with 11 AI-generated website templates
|
||||
|
||||
### Quick Install Commands
|
||||
|
||||
#### Remnawave Panel (v3.5.5+)
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave.sh) @ install
|
||||
```
|
||||
|
||||
#### Remnawave Node (v3.1.2+)
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnanode.sh) @ install
|
||||
```
|
||||
|
||||
#### Reality Selfsteal (v2.1.3+)
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/selfsteal.sh) @ install
|
||||
```
|
||||
|
||||
:::info Options
|
||||
|
||||
- Add `--dev` for development version
|
||||
- Add `--name customname` for custom directory (default: `/opt/remnawave`, `/opt/remnanode`)
|
||||
:::
|
||||
|
||||
#### For existing installations
|
||||
|
||||
Use `install-script` to add CLI wrapper only:
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave.sh) @ install-script
|
||||
```
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnanode.sh) @ install-script
|
||||
```
|
||||
|
||||
### Available CLI Commands
|
||||
|
||||
**Installation & Management:**
|
||||
|
||||
- `install`, `update`, `uninstall`
|
||||
- `install-script`, `uninstall-script`
|
||||
|
||||
**Service Control:**
|
||||
|
||||
- `up`, `down`, `restart`, `status`, `logs`
|
||||
|
||||
**Node-specific Commands:**
|
||||
|
||||
- `core-update` - Manual Update/Install Xray-core to latest version
|
||||
- `xray-log-out`, `xray-log-err` - View Xray logs
|
||||
- `setup-logs` - Configure log rotation
|
||||
|
||||
**Configuration:**
|
||||
|
||||
- `edit`, `edit-env`, `console` (Panel only)
|
||||
|
||||
**Interactive Menus:**
|
||||
|
||||
- Run `remnawave`, `remnanode`, or `selfsteal` without arguments for interactive menu
|
||||
- Real-time status monitoring and resource usage
|
||||
- Step-by-step guided operations
|
||||
|
||||
**Backup & Restore (Panel):**
|
||||
|
||||
- `backup` - Create manual backup
|
||||
- `schedule` - Configure automated backups
|
||||
- `restore` - Restore from backup archive
|
||||
|
||||
**Reality Selfsteal:**
|
||||
|
||||
- Choose from 11 AI-generated website templates
|
||||
- Automatic SSL certificate management by Caddy
|
||||
- DNS validation before start
|
||||
|
||||
Run `remnawave help`, `remnanode help`, or `selfsteal help` for detailed usage.
|
||||
|
||||
### 💾 Intelligent Backup & Restore System
|
||||
|
||||
**🔄 Smart Backup Features:**
|
||||
|
||||
- **Version-aware backups** with compatibility checking
|
||||
- **Safety restore** with automatic rollback protection
|
||||
- **Scheduled automation** with cron integration
|
||||
- **Telegram notifications** with file delivery and alerts
|
||||
- **Cross-server migration** support with detailed instructions
|
||||
- **Compressed archives** with unified structure
|
||||
|
||||
**📦 What's Backed Up:**
|
||||
|
||||
- PostgreSQL database as `db_backup.sql`
|
||||
- Configuration files: `docker-compose.yml`, `.env`, `app-config.json`
|
||||
- Optional: full directory backup with selective restore
|
||||
|
||||
**🎯 Quick Commands:**
|
||||
|
||||
```bash
|
||||
remnawave backup # Create instant backup
|
||||
remnawave schedule # Setup automated backups
|
||||
remnawave restore # Intelligent restore with version checks
|
||||
```
|
||||
|
||||
**🛡️ Safety Features:**
|
||||
|
||||
- Automatic version compatibility verification
|
||||
- Safety backup before restore operations
|
||||
- Rollback capability if restore fails
|
||||
- Real-time status monitoring during operations
|
||||
|
||||
**📋 Restore Options:**
|
||||
|
||||
- Full restore (replace all files and database)
|
||||
- Database-only restore (keep existing files)
|
||||
- Custom directory restoration
|
||||
- Manual restore commands included in each backup archive
|
||||
|
||||
**Legacy Standalone Scripts:**
|
||||
|
||||
Available for users who need legacy standalone backup/restore tools:
|
||||
|
||||
- [remnawave-backup.sh](https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave-backup.sh) - Standalone backup script
|
||||
- [restore.sh](https://github.com/DigneZzZ/remnawave-scripts/raw/main/restore.sh) - Standalone restore script
|
||||
|
||||
📦 Full info, updates, and examples: [**remnawave-scripts**](https://github.com/DigneZzZ/remnawave-scripts)
|
||||
|
||||
Project: [GIG.ovh](https://gig.ovh)
|
||||
@@ -0,0 +1,100 @@
|
||||
### Features
|
||||
|
||||
- View your subscriptions in the mini app
|
||||
- Multi-language support (English, Russian)
|
||||
|
||||
### Environment Variables
|
||||
|
||||
The application requires the following environment variables to be set:
|
||||
|
||||
| Variable | Description |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `REMNAWAVE_PANEL_URL` | Remnawave Panel URL, can be `http://remnawave:3000` or `https://panel.example.com` |
|
||||
| `REMNAWAVE_TOKEN` | Authentication token for Remnawave API |
|
||||
| `BUY_LINK` | The URL for purchase actions |
|
||||
| `CRYPTO_LINK` | Allows using encrypted links (currently supported Happ application) |
|
||||
| `REDIRECT_LINK` | Allows you to specify a **custom redirect page URL** for deep links. Useful for handling protocols like `v2box://` in Telegram Desktop (Windows). For more details and examples, see [Telegram Deep Link Redirect](https://github.com/maposia/redirect-page/tree/main) |
|
||||
| `AUTH_API_KEY` | If you use "Caddy with security" or TinyAuth for Nginx addon, you can place here X-Api-Key, which will be applied to requests to Remnawave Panel. |
|
||||
|
||||
### Install and Run
|
||||
|
||||
#### 1. Create a new directory for the mini app
|
||||
|
||||
```bash
|
||||
mkdir /opt/remnawave-telegram-sub-mini-app && cd /opt/remnawave-telegram-sub-mini-app
|
||||
```
|
||||
|
||||
#### 2. Download the sample environment variables
|
||||
|
||||
```bash
|
||||
curl -o .env https://raw.githubusercontent.com/maposia/remnawave-telegram-mini-bot/refs/heads/main/.env.example
|
||||
```
|
||||
|
||||
#### 3. Configure the environment variables
|
||||
|
||||
```bash
|
||||
nano .env
|
||||
```
|
||||
|
||||
#### 4. Create docker-compose.yml file
|
||||
|
||||
```bash
|
||||
nano docker-compose.yml
|
||||
```
|
||||
|
||||
**Example below:**
|
||||
|
||||
```yaml
|
||||
services:
|
||||
remnawave-mini-app:
|
||||
image: ghcr.io/maposia/remnawave-telegram-sub-mini-app:latest
|
||||
container_name: remnawave-telegram-mini-app
|
||||
hostname: remnawave-telegram-mini-app
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
# volumes:
|
||||
# - ./app-config.json:/app/public/assets/app-config.json
|
||||
ports:
|
||||
- '127.0.0.1:3020:3020'
|
||||
# networks:
|
||||
# - remnawave-network
|
||||
|
||||
#networks:
|
||||
# remnawave-network:
|
||||
# name: remnawave-network
|
||||
# driver: bridge
|
||||
# external: true
|
||||
```
|
||||
|
||||
Uncomment if you want to use your own template downloaded from the Remnawave panel.
|
||||
P.S. File must be placed in the same directory as this `docker-compose.yml` file
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./app-config.json:/app/public/assets/app-config.json
|
||||
```
|
||||
|
||||
Uncomment if you want to use local connection via single network in docker
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
- remnawave-network
|
||||
|
||||
networks:
|
||||
remnawave-network:
|
||||
name: remnawave-network
|
||||
driver: bridge
|
||||
external: true
|
||||
```
|
||||
|
||||
#### 5. Run containers
|
||||
|
||||
```bash
|
||||
docker compose up -d && docker compose logs -f
|
||||
```
|
||||
|
||||
#### 6. Access the mini app
|
||||
|
||||
Mini app is now running on `http://127.0.0.1:3020`
|
||||
Now we are ready to move on to Reverse Proxy installation.
|
||||
@@ -0,0 +1,98 @@
|
||||
**Script Author:** [distillium](https://github.com/distillium)
|
||||
**Ansible Role Author:** [TheMelbine](https://github.com/TheMelbine)
|
||||
|
||||
### It automates
|
||||
|
||||
- Installing the necessary packages (`wireguard`, `resolvconf`)
|
||||
- Downloading and configuring `wgcf`
|
||||
- Generating and modifying the WireGuard configuration
|
||||
- Connecting and checking status
|
||||
- Enabling autorun of the `warp` interface
|
||||
|
||||
### Installing
|
||||
|
||||
#### Option 1: Shell Script (performed on each desired node)
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/distillium/warp-native/main/install.sh)
|
||||
```
|
||||
|
||||
#### Option 2: Ansible Role (Recommended for automation)
|
||||
|
||||
```bash
|
||||
ansible-galaxy install themelbine.warp_native
|
||||
```
|
||||
|
||||
**Example playbook:**
|
||||
|
||||
```yaml
|
||||
- hosts: warp_servers
|
||||
become: yes
|
||||
roles:
|
||||
- themelbine.warp_native
|
||||
vars:
|
||||
warp_native_state: present
|
||||
warp_native_modify_resolv: true
|
||||
```
|
||||
|
||||
[Ansible Role Github Repository](https://github.com/TheMelbine/ansible-role-warp-native)
|
||||
|
||||
### Templates for Xray configuration
|
||||
|
||||
**Example outbound:**
|
||||
|
||||
```json
|
||||
{
|
||||
"tag": "warp-out",
|
||||
"protocol": "freedom",
|
||||
"settings": {},
|
||||
"streamSettings": {
|
||||
"sockopt": {
|
||||
"interface": "warp",
|
||||
"tcpFastOpen": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example routing rule:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "field",
|
||||
"domain": ["netflix.com", "youtube.com", "twitter.com"],
|
||||
"inboundTag": ["Node-1", "Node-2"],
|
||||
"outboundTag": "warp-out"
|
||||
}
|
||||
```
|
||||
|
||||
### Interface management
|
||||
|
||||
| Operation | Command |
|
||||
| --------------------- | --------------------------------- |
|
||||
| Check service status | `systemctl status wg-quick@warp` |
|
||||
| View interface status | `wg show warp` |
|
||||
| Stop the interface | `systemctl stop wg-quick@warp` |
|
||||
| Start the interface | `systemctl start wg-quick@warp` |
|
||||
| Restart the interface | `systemctl restart wg-quick@warp` |
|
||||
| Disable autorun | `systemctl disable wg-quick@warp` |
|
||||
| Enable autorun | `systemctl enable wg-quick@warp` |
|
||||
|
||||
### Uninstall
|
||||
|
||||
**Shell Script Method:**
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/distillium/warp-native/main/uninstall.sh)
|
||||
```
|
||||
|
||||
**Ansible Role Method:**
|
||||
|
||||
```yaml
|
||||
- hosts: warp_servers
|
||||
become: yes
|
||||
roles:
|
||||
- themelbine.warp_native
|
||||
vars:
|
||||
warp_native_state: absent
|
||||
```
|
||||
48
src/components/CategoryNav/index.tsx
Normal file
48
src/components/CategoryNav/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react'
|
||||
|
||||
import styles from './styles.module.css'
|
||||
|
||||
interface Category {
|
||||
icon: string
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface CategoryNavProps {
|
||||
categories: Category[]
|
||||
}
|
||||
|
||||
export default function CategoryNav({ categories }: CategoryNavProps) {
|
||||
const scrollToCategory = (id: string) => {
|
||||
const element = document.getElementById(id)
|
||||
if (element) {
|
||||
const offset = 80
|
||||
const elementPosition = element.getBoundingClientRect().top
|
||||
const offsetPosition = elementPosition + window.pageYOffset - offset
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.categoryNav}>
|
||||
<h3 className={styles.navTitle}>📚 Browse by Category</h3>
|
||||
<div className={styles.categories}>
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
className={styles.categoryButton}
|
||||
key={category.id}
|
||||
onClick={() => scrollToCategory(category.id)}
|
||||
type="button"
|
||||
>
|
||||
<span className={styles.categoryIcon}>{category.icon}</span>
|
||||
<span className={styles.categoryTitle}>{category.title}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
128
src/components/CategoryNav/styles.module.css
Normal file
128
src/components/CategoryNav/styles.module.css
Normal file
@@ -0,0 +1,128 @@
|
||||
.categoryNav {
|
||||
margin: 3rem 0;
|
||||
padding: 2rem;
|
||||
background: var(--ifm-card-background-color);
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.navTitle {
|
||||
margin: 0 0 1.5rem 0;
|
||||
text-align: center;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
color: var(--ifm-heading-color);
|
||||
}
|
||||
|
||||
.categories {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.categoryButton {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 1.5rem 1rem;
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.categoryButton:hover {
|
||||
background: var(--ifm-color-primary-lightest);
|
||||
border-color: var(--ifm-color-primary);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px rgba(var(--ifm-color-primary-rgb), 0.2);
|
||||
}
|
||||
|
||||
.categoryButton:active {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.categoryIcon {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.categoryButton:hover .categoryIcon {
|
||||
transform: scale(1.1) rotate(5deg);
|
||||
}
|
||||
|
||||
.categoryTitle {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--ifm-heading-color);
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .categoryButton {
|
||||
background: var(--ifm-color-emphasis-200);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .categoryButton:hover {
|
||||
background: var(--ifm-color-emphasis-300);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .categoryTitle {
|
||||
color: var(--ifm-font-color-base);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .navTitle {
|
||||
color: var(--ifm-font-color-base);
|
||||
}
|
||||
|
||||
@media (max-width: 996px) {
|
||||
.categories {
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
}
|
||||
|
||||
.categoryButton {
|
||||
padding: 1.25rem 0.75rem;
|
||||
}
|
||||
|
||||
.categoryIcon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.categoryTitle {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.categoryNav {
|
||||
padding: 1.5rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.navTitle {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.categories {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.categoryButton {
|
||||
padding: 1rem 0.5rem;
|
||||
}
|
||||
|
||||
.categoryIcon {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.categoryTitle {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
47
src/components/CategorySection/index.tsx
Normal file
47
src/components/CategorySection/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react'
|
||||
|
||||
import styles from './styles.module.css'
|
||||
|
||||
interface CategorySectionProps {
|
||||
children: React.ReactNode
|
||||
columns?: 1 | 2 | 3
|
||||
description?: string
|
||||
icon?: string
|
||||
id?: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export default function CategorySection({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
children,
|
||||
columns = 3,
|
||||
id
|
||||
}: CategorySectionProps) {
|
||||
const sectionId =
|
||||
id ||
|
||||
title
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
|
||||
return (
|
||||
<section className={styles.categorySection}>
|
||||
<div className={styles.categoryHeader}>
|
||||
<div className={styles.categoryTitleWrapper} id={sectionId}>
|
||||
{icon && <span className={styles.categoryIcon}>{icon}</span>}
|
||||
<h2 className={styles.categoryTitle}>
|
||||
<a className={styles.anchorLink} href={`#${sectionId}`}>
|
||||
{title}
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
{description && <p className={styles.categoryDescription}>{description}</p>}
|
||||
</div>
|
||||
<div className={styles.categoryContent} data-columns={columns}>
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
112
src/components/CategorySection/styles.module.css
Normal file
112
src/components/CategorySection/styles.module.css
Normal file
@@ -0,0 +1,112 @@
|
||||
.categorySection {
|
||||
margin: 4rem 0;
|
||||
scroll-margin-top: 80px;
|
||||
}
|
||||
|
||||
.categoryHeader {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.categoryTitleWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
scroll-margin-top: 80px;
|
||||
}
|
||||
|
||||
.anchorLink {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.categoryIcon {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.categoryTitle {
|
||||
margin: 0;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
background: var(--ifm-color-white);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.categoryTitle:hover {
|
||||
background: var(--ifm-color-primary);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.categoryDescription {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
color: var(--ifm-color-emphasis-700);
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.categoryContent {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.categoryContent[data-columns='1'] {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.categoryContent[data-columns='2'] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.categoryContent[data-columns='3'] {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 996px) {
|
||||
.categoryContent[data-columns='3'] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.categorySection {
|
||||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
.categoryHeader {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.categoryTitleWrapper {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.categoryTitle {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.categoryDescription {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.categoryContent {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.categoryContent[data-columns='1'],
|
||||
.categoryContent[data-columns='2'],
|
||||
.categoryContent[data-columns='3'] {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
29
src/components/FeatureHighlight/index.tsx
Normal file
29
src/components/FeatureHighlight/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
|
||||
import styles from './styles.module.css'
|
||||
|
||||
interface Feature {
|
||||
description: string
|
||||
icon: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface FeatureHighlightProps {
|
||||
features: Feature[]
|
||||
}
|
||||
|
||||
export default function FeatureHighlight({ features }: FeatureHighlightProps) {
|
||||
return (
|
||||
<div className={styles.featureGrid}>
|
||||
{features.map((feature, index) => (
|
||||
<div className={styles.featureCard} key={index}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<span className={styles.icon}>{feature.icon}</span>
|
||||
</div>
|
||||
<h3 className={styles.title}>{feature.title}</h3>
|
||||
<p className={styles.description}>{feature.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
88
src/components/FeatureHighlight/styles.module.css
Normal file
88
src/components/FeatureHighlight/styles.module.css
Normal file
@@ -0,0 +1,88 @@
|
||||
.featureGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
.featureCard {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background: var(--ifm-card-background-color);
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.featureCard:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .featureCard:hover {
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.iconWrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--ifm-color-primary-lightest) 0%,
|
||||
var(--ifm-color-secondary-lightest) 100%
|
||||
);
|
||||
border-radius: 20px;
|
||||
margin-bottom: 1.5rem;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.featureCard:hover .iconWrapper {
|
||||
transform: scale(1.1) rotate(5deg);
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: var(--ifm-heading-color);
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
color: var(--ifm-color-emphasis-700);
|
||||
line-height: 1.6;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.featureGrid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.featureCard {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.iconWrapper {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
89
src/components/GitHubStars/index.tsx
Normal file
89
src/components/GitHubStars/index.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import styles from './styles.module.css'
|
||||
|
||||
interface GitHubStarsProps {
|
||||
repo: string // Format: "owner/repo"
|
||||
}
|
||||
|
||||
export default function GitHubStars({ repo }: GitHubStarsProps) {
|
||||
const [stars, setStars] = useState<null | number>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const cacheKey = `github-stars-${repo}`
|
||||
const cached = localStorage.getItem(cacheKey)
|
||||
|
||||
if (cached) {
|
||||
const { stars: cachedStars, timestamp } = JSON.parse(cached)
|
||||
if (Date.now() - timestamp < 3600000) {
|
||||
setStars(cachedStars)
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fetch(`https://api.github.com/repos/${repo}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data.stargazers_count !== undefined) {
|
||||
setStars(data.stargazers_count)
|
||||
localStorage.setItem(
|
||||
cacheKey,
|
||||
JSON.stringify({
|
||||
stars: data.stargazers_count,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
)
|
||||
}
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(() => {
|
||||
// Silently fail - stars are not critical
|
||||
setLoading(false)
|
||||
})
|
||||
}, [repo])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<span className={styles.stars}>
|
||||
<svg
|
||||
className={styles.icon}
|
||||
fill="currentColor"
|
||||
height="15"
|
||||
viewBox="0 0 16 16"
|
||||
width="15"
|
||||
>
|
||||
<path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25z" />
|
||||
</svg>
|
||||
<span className={styles.skeleton}>···</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
if (stars === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const formatStars = (count: number): string => {
|
||||
if (count >= 1000) {
|
||||
return `${(count / 1000).toFixed(1)}k`
|
||||
}
|
||||
return count.toString()
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={styles.stars}>
|
||||
<svg
|
||||
className={styles.icon}
|
||||
fill="currentColor"
|
||||
height="15"
|
||||
viewBox="0 0 16 16"
|
||||
width="15"
|
||||
>
|
||||
<path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25z" />
|
||||
</svg>
|
||||
<span className={styles.count}>{formatStars(stars)}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
40
src/components/GitHubStars/styles.module.css
Normal file
40
src/components/GitHubStars/styles.module.css
Normal file
@@ -0,0 +1,40 @@
|
||||
.stars {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: #daaa3f;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-weight: 700;
|
||||
color: inherit;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] .icon {
|
||||
color: #f1c40f;
|
||||
}
|
||||
25
src/components/HeroSection/index.tsx
Normal file
25
src/components/HeroSection/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
|
||||
import styles from './styles.module.css'
|
||||
|
||||
interface HeroSectionProps {
|
||||
gradient?: boolean
|
||||
subtitle?: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export default function HeroSection({ title, subtitle, gradient = true }: HeroSectionProps) {
|
||||
return (
|
||||
<div className={`${styles.hero} ${gradient ? styles.gradient : ''}`}>
|
||||
<div className={styles.content}>
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
{subtitle && <p className={styles.subtitle}>{subtitle}</p>}
|
||||
</div>
|
||||
<div className={styles.decoration}>
|
||||
<div className={styles.circle1}></div>
|
||||
<div className={styles.circle2}></div>
|
||||
<div className={styles.circle3}></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
170
src/components/HeroSection/styles.module.css
Normal file
170
src/components/HeroSection/styles.module.css
Normal file
@@ -0,0 +1,170 @@
|
||||
.hero {
|
||||
position: relative;
|
||||
padding: 4rem 2rem;
|
||||
margin: -2rem -2rem 4rem -2rem;
|
||||
overflow: hidden;
|
||||
border-radius: 0 0 24px 24px;
|
||||
}
|
||||
|
||||
.gradient {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--ifm-color-primary-lightest) 0%,
|
||||
var(--ifm-color-primary-lighter) 50%,
|
||||
var(--ifm-color-secondary-lighter) 100%
|
||||
);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .gradient {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(var(--ifm-color-primary-rgb), 0.15) 0%,
|
||||
rgba(var(--ifm-color-primary-rgb), 0.1) 50%,
|
||||
rgba(var(--ifm-color-secondary-rgb), 0.1) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 900;
|
||||
margin: 0 0 1rem 0;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--ifm-color-primary-darkest) 0%,
|
||||
var(--ifm-color-primary) 50%,
|
||||
var(--ifm-color-secondary) 100%
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.3rem;
|
||||
color: var(--ifm-color-emphasis-800);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.decoration {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.circle1,
|
||||
.circle2,
|
||||
.circle3 {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(var(--ifm-color-primary-rgb), 0.1) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
|
||||
.circle1 {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
top: -200px;
|
||||
left: -100px;
|
||||
animation: float 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.circle2 {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
bottom: -150px;
|
||||
right: -50px;
|
||||
animation: float 15s ease-in-out infinite reverse;
|
||||
}
|
||||
|
||||
.circle3 {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: pulse 10s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translate(30px, -30px) scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%, -50%) scale(1.2);
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 996px) {
|
||||
.hero {
|
||||
padding: 3rem 1.5rem;
|
||||
margin: -1rem -1rem 3rem -1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.circle1 {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.circle2 {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.circle3 {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
228
src/components/ProjectCard/index.tsx
Normal file
228
src/components/ProjectCard/index.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import GitHubStars from '@site/src/components/GitHubStars'
|
||||
import Link from '@docusaurus/Link'
|
||||
import React from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import styles from './styles.module.css'
|
||||
|
||||
interface ProjectCardProps {
|
||||
aiPowered?: boolean
|
||||
author?: string
|
||||
authorLink?: string
|
||||
children?: React.ReactNode
|
||||
description: string
|
||||
featured?: boolean
|
||||
githubRepo?: string
|
||||
id?: string
|
||||
image?: string
|
||||
links?: {
|
||||
docs?: string
|
||||
github?: string
|
||||
telegram?: string
|
||||
website?: string
|
||||
}
|
||||
tags?: string[]
|
||||
title: string
|
||||
}
|
||||
|
||||
export default function ProjectCard({
|
||||
title,
|
||||
description,
|
||||
author,
|
||||
authorLink,
|
||||
image,
|
||||
links,
|
||||
featured = false,
|
||||
aiPowered = false,
|
||||
githubRepo,
|
||||
id,
|
||||
children
|
||||
}: ProjectCardProps) {
|
||||
const [isModalOpen, setIsModalOpen] = React.useState(false)
|
||||
const [isClosing, setIsClosing] = React.useState(false)
|
||||
|
||||
const projectId =
|
||||
id ||
|
||||
title
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
|
||||
const handleClose = () => {
|
||||
setIsClosing(true)
|
||||
setTimeout(() => {
|
||||
setIsModalOpen(false)
|
||||
setIsClosing(false)
|
||||
}, 200)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={clsx(styles.projectCard, featured && styles.featured)} id={projectId}>
|
||||
<div className={styles.badgesContainer}>
|
||||
{aiPowered && (
|
||||
<div
|
||||
className={styles.aiPoweredBadge}
|
||||
data-tooltip="Author reported that AI was used in development of this product"
|
||||
>
|
||||
<svg fill="currentColor" height="16" viewBox="0 0 24 24" width="16">
|
||||
<path d="M12 2L2 7L12 12L22 7L12 2Z" />
|
||||
<path d="M2 17L12 22L22 17V12L12 17L2 12V17Z" opacity="0.7" />
|
||||
</svg>
|
||||
<span>AI</span>
|
||||
</div>
|
||||
)}
|
||||
{githubRepo && links?.github && (
|
||||
<Link className={styles.starsBadge} to={links.github}>
|
||||
<GitHubStars repo={githubRepo} />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
{image && (
|
||||
<div className={styles.imageContainer}>
|
||||
<img alt={title} className={styles.projectImage} src={image} />
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.cardContent}>
|
||||
<div className={styles.cardHeader}>
|
||||
<div className={styles.titleWrapper}>
|
||||
<h3 className={styles.projectTitle}>
|
||||
<a className={styles.projectAnchor} href={`#${projectId}`}>
|
||||
{title}
|
||||
</a>
|
||||
</h3>
|
||||
{author && (
|
||||
<div className={styles.authorInfo}>
|
||||
<span className={styles.byText}>by</span>
|
||||
{authorLink ? (
|
||||
<Link className={styles.authorLink} to={authorLink}>
|
||||
{author}
|
||||
</Link>
|
||||
) : (
|
||||
<span className={styles.authorName}>{author}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{featured && <span className={styles.featuredBadge}>⭐ Featured</span>}
|
||||
</div>
|
||||
|
||||
<p className={styles.description}>{description}</p>
|
||||
|
||||
{(links || children) && (
|
||||
<div className={styles.links}>
|
||||
{children && (
|
||||
<button
|
||||
className={clsx(styles.link, styles.installButton)}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
>
|
||||
<path d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811V2.828zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z" />
|
||||
</svg>
|
||||
Features & More Info
|
||||
</button>
|
||||
)}
|
||||
{links?.github && (
|
||||
<Link
|
||||
className={clsx(styles.link, styles.githubLink)}
|
||||
to={links.github}
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
>
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
|
||||
</svg>
|
||||
GitHub
|
||||
</Link>
|
||||
)}
|
||||
{links?.docs && (
|
||||
<Link
|
||||
className={clsx(styles.link, styles.docsLink)}
|
||||
to={links.docs}
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
>
|
||||
<path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v13.5a.5.5 0 0 1-.777.416L8 13.101l-5.223 2.815A.5.5 0 0 1 2 15.5V2zm2-1a1 1 0 0 0-1 1v12.566l4.723-2.482a.5.5 0 0 1 .554 0L13 14.566V2a1 1 0 0 0-1-1H4z" />
|
||||
</svg>
|
||||
Docs
|
||||
</Link>
|
||||
)}
|
||||
{links?.telegram && (
|
||||
<Link
|
||||
className={clsx(styles.link, styles.telegramLink)}
|
||||
to={links.telegram}
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
>
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.287 5.906c-.778.324-2.334.994-4.666 2.01-.378.15-.577.298-.595.442-.03.243.275.339.69.47l.175.055c.408.133.958.288 1.243.294.26.006.549-.1.868-.32 2.179-1.471 3.304-2.214 3.374-2.23.05-.012.12-.026.166.016.047.041.042.12.037.141-.03.129-1.227 1.241-1.846 1.817-.193.18-.33.307-.358.336a8.154 8.154 0 0 1-.188.186c-.38.366-.664.64.015 1.088.327.216.589.393.85.571.284.194.568.387.936.629.093.06.183.125.27.187.331.236.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.426 1.426 0 0 0-.013-.315.337.337 0 0 0-.114-.217.526.526 0 0 0-.31-.093c-.3.005-.763.166-2.984 1.09z" />
|
||||
</svg>
|
||||
Telegram
|
||||
</Link>
|
||||
)}
|
||||
{links?.website && (
|
||||
<Link
|
||||
className={clsx(styles.link, styles.websiteLink)}
|
||||
to={links.website}
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
>
|
||||
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855.173-.324.33-.682.468-1.068H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z" />
|
||||
</svg>
|
||||
Website
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isModalOpen && children && (
|
||||
<div
|
||||
className={`${styles.modalOverlay} ${isClosing ? styles.closing : ''}`}
|
||||
onClick={handleClose}
|
||||
>
|
||||
<div
|
||||
className={`${styles.modalContent} ${isClosing ? styles.closing : ''}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>
|
||||
<span className={styles.installIcon}>🚀</span>
|
||||
Features & More Info
|
||||
</h2>
|
||||
<button
|
||||
className={styles.closeButton}
|
||||
onClick={handleClose}
|
||||
type="button"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.modalBody}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
654
src/components/ProjectCard/styles.module.css
Normal file
654
src/components/ProjectCard/styles.module.css
Normal file
@@ -0,0 +1,654 @@
|
||||
.projectCard {
|
||||
background: var(--ifm-card-background-color);
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
border-radius: 12px;
|
||||
overflow: visible;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
scroll-margin-top: 100px;
|
||||
}
|
||||
|
||||
.projectCard:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
|
||||
border-color: var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .projectCard:hover {
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.featured {
|
||||
border: 2px solid var(--ifm-color-primary);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--ifm-card-background-color) 0%,
|
||||
rgba(var(--ifm-color-primary-rgb), 0.03) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.badgesContainer {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.aiPoweredBadge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.4rem 0.75rem;
|
||||
background: var(--ifm-color-emphasis-200);
|
||||
color: var(--ifm-color-emphasis-800);
|
||||
border: 1px solid var(--ifm-color-emphasis-300);
|
||||
border-radius: 6px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
cursor: help;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.starsBadge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.45rem 0.85rem;
|
||||
background: #24292e;
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.starsBadge:hover {
|
||||
background: #1a1e22;
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.aiPoweredBadge::before {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: calc(100% + 10px);
|
||||
right: 0;
|
||||
padding: 0.6rem 0.9rem;
|
||||
background: var(--ifm-color-emphasis-900);
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
transform: translateY(5px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.aiPoweredBadge::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: calc(100% + 2px);
|
||||
right: 20px;
|
||||
border: 6px solid transparent;
|
||||
border-top-color: var(--ifm-color-emphasis-900);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.aiPoweredBadge:hover::before {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.aiPoweredBadge:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.aiPoweredBadge:hover {
|
||||
background: var(--ifm-color-emphasis-300);
|
||||
border-color: var(--ifm-color-emphasis-400);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.aiPoweredBadge svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .aiPoweredBadge::before {
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
color: var(--ifm-color-emphasis-900);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .aiPoweredBadge::after {
|
||||
border-top-color: var(--ifm-color-emphasis-100);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .aiPoweredBadge {
|
||||
background: var(--ifm-color-emphasis-300);
|
||||
color: var(--ifm-color-emphasis-900);
|
||||
border-color: var(--ifm-color-emphasis-400);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .aiPoweredBadge:hover {
|
||||
background: var(--ifm-color-emphasis-400);
|
||||
border-color: var(--ifm-color-emphasis-500);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .starsBadge {
|
||||
background: #2d333b;
|
||||
color: white;
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .starsBadge:hover {
|
||||
background: #373e47;
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.badgesContainer {
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.aiPoweredBadge {
|
||||
padding: 0.3rem 0.55rem;
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.aiPoweredBadge svg {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
.starsBadge {
|
||||
padding: 0.3rem 0.55rem;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
overflow: hidden;
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.projectImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.projectCard:hover .projectImage {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.cardContent {
|
||||
padding: 1.5rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.cardHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.titleWrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.projectTitle {
|
||||
margin: 0;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
color: var(--ifm-heading-color);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.projectAnchor {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.projectAnchor:hover {
|
||||
color: var(--ifm-color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.featuredBadge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: var(--ifm-color-primary);
|
||||
color: white;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.authorInfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.byText {
|
||||
color: var(--ifm-color-emphasis-600);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.authorLink {
|
||||
color: var(--ifm-color-primary);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.authorLink::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: var(--ifm-color-primary);
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
.authorLink:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.authorLink:hover {
|
||||
text-decoration: none;
|
||||
color: var(--ifm-color-primary-dark);
|
||||
}
|
||||
|
||||
.authorName {
|
||||
color: var(--ifm-color-emphasis-800);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .byText {
|
||||
color: var(--ifm-color-emphasis-500);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .authorName {
|
||||
color: var(--ifm-color-emphasis-700);
|
||||
}
|
||||
|
||||
.description {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
color: var(--ifm-color-emphasis-800);
|
||||
line-height: 1.6;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
color: var(--ifm-color-emphasis-800);
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.tag:hover {
|
||||
background: var(--ifm-color-emphasis-200);
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-top: auto;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid var(--ifm-color-emphasis-200);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.githubLink {
|
||||
background: #24292e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.githubLink:hover {
|
||||
background: #1a1e22;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 2px 8px rgba(36, 41, 46, 0.3);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .githubLink {
|
||||
background: #2d333b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .githubLink:hover {
|
||||
background: #404852;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 2px 8px rgba(64, 72, 82, 0.3);
|
||||
}
|
||||
|
||||
.docsLink {
|
||||
background: var(--ifm-color-primary-lightest);
|
||||
color: var(--ifm-color-primary-darker);
|
||||
}
|
||||
|
||||
.docsLink:hover {
|
||||
background: var(--ifm-color-primary);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.telegramLink {
|
||||
background: #0088cc;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.telegramLink:hover {
|
||||
background: #006699;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 2px 8px rgba(0, 136, 204, 0.3);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .telegramLink {
|
||||
background: #0088cc;
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .telegramLink:hover {
|
||||
background: #33adff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.websiteLink {
|
||||
background: #0097a7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.websiteLink:hover {
|
||||
background: #00838f;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 2px 8px rgba(0, 151, 167, 0.3);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .websiteLink {
|
||||
background: #00acc1;
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .websiteLink:hover {
|
||||
background: #00bcd4;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.installButton {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--ifm-color-primary) 0%,
|
||||
var(--ifm-color-primary-dark) 100%
|
||||
);
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.installButton:hover {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--ifm-color-primary-dark) 0%,
|
||||
var(--ifm-color-primary-darker) 100%
|
||||
);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(var(--ifm-color-primary-rgb), 0.3);
|
||||
}
|
||||
|
||||
/* Modal styles */
|
||||
.modalOverlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(4px);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
animation: fadeIn 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modalOverlay.closing {
|
||||
animation: fadeOut 0.2s ease;
|
||||
}
|
||||
|
||||
.modalContent {
|
||||
background: var(--ifm-card-background-color);
|
||||
border-radius: 16px;
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
max-height: 85vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modalContent.closing {
|
||||
animation: slideDown 0.2s ease;
|
||||
}
|
||||
|
||||
.modalHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem 2rem;
|
||||
border-bottom: 1px solid var(--ifm-color-emphasis-200);
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
}
|
||||
|
||||
.modalTitle {
|
||||
margin: 0;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
color: var(--ifm-heading-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.installIcon {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--ifm-color-emphasis-700);
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.closeButton:hover {
|
||||
background: var(--ifm-color-emphasis-200);
|
||||
color: var(--ifm-color-emphasis-900);
|
||||
}
|
||||
|
||||
.modalBody {
|
||||
padding: 2rem;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .modalOverlay {
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .closeButton:hover {
|
||||
background: var(--ifm-color-emphasis-300);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.cardContent {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.projectTitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.modalContent {
|
||||
max-width: 100%;
|
||||
max-height: 90vh;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.modalHeader {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.modalTitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.installIcon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.modalBody {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
16
src/components/ProjectsGrid/index.tsx
Normal file
16
src/components/ProjectsGrid/index.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
|
||||
import styles from './styles.module.css'
|
||||
|
||||
interface ProjectsGridProps {
|
||||
children: React.ReactNode
|
||||
columns?: 1 | 2 | 3
|
||||
}
|
||||
|
||||
export default function ProjectsGrid({ children, columns = 3 }: ProjectsGridProps) {
|
||||
return (
|
||||
<div className={styles.projectsGrid} data-columns={columns}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
40
src/components/ProjectsGrid/styles.module.css
Normal file
40
src/components/ProjectsGrid/styles.module.css
Normal file
@@ -0,0 +1,40 @@
|
||||
.projectsGrid {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
margin: 2rem 0 4rem 0;
|
||||
}
|
||||
|
||||
.projectsGrid[data-columns='1'] {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.projectsGrid[data-columns='2'] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.projectsGrid[data-columns='3'] {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 996px) {
|
||||
.projectsGrid[data-columns='2'],
|
||||
.projectsGrid[data-columns='3'] {
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.projectsGrid {
|
||||
gap: 1.5rem;
|
||||
margin: 1.5rem 0 3rem 0;
|
||||
}
|
||||
|
||||
.projectsGrid[data-columns='1'],
|
||||
.projectsGrid[data-columns='2'],
|
||||
.projectsGrid[data-columns='3'] {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
29
src/components/StatsBar/index.tsx
Normal file
29
src/components/StatsBar/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
|
||||
import styles from './styles.module.css'
|
||||
|
||||
interface Stat {
|
||||
icon?: string
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface StatsBarProps {
|
||||
stats: Stat[]
|
||||
}
|
||||
|
||||
export default function StatsBar({ stats }: StatsBarProps) {
|
||||
return (
|
||||
<div className={styles.statsBar}>
|
||||
{stats.map((stat, index) => (
|
||||
<div className={styles.statItem} key={index}>
|
||||
{stat.icon && <span className={styles.icon}>{stat.icon}</span>}
|
||||
<div className={styles.statContent}>
|
||||
<div className={styles.value}>{stat.value}</div>
|
||||
<div className={styles.label}>{stat.label}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
74
src/components/StatsBar/styles.module.css
Normal file
74
src/components/StatsBar/styles.module.css
Normal file
@@ -0,0 +1,74 @@
|
||||
.statsBar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 3rem;
|
||||
padding: 2rem;
|
||||
margin: 3rem 0;
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
border-radius: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.statItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.statContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--ifm-color-primary) 0%,
|
||||
var(--ifm-color-primary-dark) 100%
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.9rem;
|
||||
color: var(--ifm-color-emphasis-700);
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.statsBar {
|
||||
gap: 2rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.statItem {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,23 @@
|
||||
import FeatureHighlight from '@site/src/components/FeatureHighlight'
|
||||
import CategorySection from '@site/src/components/CategorySection'
|
||||
import ProjectsGrid from '@site/src/components/ProjectsGrid'
|
||||
import HeroSection from '@site/src/components/HeroSection'
|
||||
import CategoryNav from '@site/src/components/CategoryNav'
|
||||
import GitHubStars from '@site/src/components/GitHubStars'
|
||||
import ProjectCard from '@site/src/components/ProjectCard'
|
||||
import MDXComponents from '@theme-original/MDXComponents'
|
||||
import StatsBar from '@site/src/components/StatsBar'
|
||||
import Button from '@site/src/components/Button'
|
||||
|
||||
export default {
|
||||
...MDXComponents,
|
||||
Button
|
||||
Button,
|
||||
CategoryNav,
|
||||
GitHubStars,
|
||||
ProjectCard,
|
||||
ProjectsGrid,
|
||||
CategorySection,
|
||||
HeroSection,
|
||||
StatsBar,
|
||||
FeatureHighlight
|
||||
}
|
||||
|
||||
BIN
static/awesome/awesome-preview.webp
Normal file
BIN
static/awesome/awesome-preview.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Reference in New Issue
Block a user