Initial commit: Unyo documentation website

Add Docusaurus-based documentation site with:
- Docker setup for deployment
- Documentation pages (intro, installation, getting-started, extensions)
- Custom React components (Hero, FeatureGrid, ScreenshotGallery)
- Static assets (logos, mascots, screenshots)
- Assets directory with promotional images and screenshots

Configure .gitignore to exclude:
- node_modules
- build outputs
- IDE files (.idea)
- OS files (.DS_Store)
This commit is contained in:
Kevin Rodrigues Borges
2026-04-19 18:09:32 +01:00
commit ceae22c0b0
44 changed files with 20112 additions and 0 deletions

10
.dockerignore Normal file
View File

@@ -0,0 +1,10 @@
node_modules
website/node_modules
website/.docusaurus
website/build
.git
.github
assets
.dockerignore
docker-compose.yml
*.md

17
.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
/website/node_modules/
/website/build/
/website/.docusaurus/
# IDE
.idea/
*.iml
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# OS
.DS_Store
Thumbs.db

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

BIN
assets/unyo_logo_image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

8
docker-compose.yml Normal file
View File

@@ -0,0 +1,8 @@
services:
unyo-web:
build:
context: .
dockerfile: docker/Dockerfile
ports:
- "80:80"
restart: unless-stopped

12
docker/Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM node:20-alpine AS build
WORKDIR /app
COPY website/package*.json ./
RUN npm ci
COPY website/ ./
RUN npm run build
FROM nginx:alpine
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

37
docker/nginx.conf Normal file
View File

@@ -0,0 +1,37 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml text/x-component text/x-cross-domain-policy;
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# Long cache for hashed assets
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Short cache for images
location /img/ {
expires 30d;
add_header Cache-Control "public";
access_log off;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

3
website/babel.config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View File

@@ -0,0 +1,55 @@
---
title: Extensions
---
# Extensions
Unyo uses the Aniyomi extension ecosystem to provide access to hundreds of anime and manga sources. There are two types of extensions:
## Remote Extensions
Remote extensions are powered by a custom backend API that runs [Aniyomi](https://github.com/aniyomiorg/aniyomi) extensions on the server. This means:
- **No additional setup needed** — they work out of the box.
- **Server-dependent** — availability depends on the server's uptime and location.
- **Fewer extensions** — not all extensions work in a remote environment.
If you want a source added, open an issue on the [Unyo GitHub](https://github.com/K3vinb5/Unyo/issues) with the extension name, and it will be evaluated for integration.
## Local Extensions
Local extensions run the extension API directly on your computer using a modular system, also based on Aniyomi extensions.
### Advantages
- **Faster response times** — no network round-trip to a remote server.
- **More extensions** — many extensions that don't work remotely work locally.
- **No server dependency** — works even if the remote backend is down.
### Disadvantages
- **Requires Java 17+** — you need Java 17 or later installed on your machine.
- **More RAM usage** — approximately 100–200 MB additional memory consumption.
### Java Installation
If you don't have Java 17+ installed, download it here:
| Platform | Download |
|----------|----------|
| Linux | [oracle.com/java/technologies/downloads/#jdk17-linux](https://www.oracle.com/java/technologies/downloads/#jdk17-linux) |
| macOS | [oracle.com/java/technologies/downloads/#jdk17-mac](https://www.oracle.com/java/technologies/downloads/#jdk17-mac) |
| Windows | [oracle.com/java/technologies/downloads/#jdk17-windows](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) |
### Installing Local Extensions
1. Open Unyo and go to **Settings → Extensions**.
2. Enable **Local Extensions**.
3. Browse available extensions from the [Unyo-Extensions](https://github.com/K3vinb5/Unyo-Extensions) repository directly within the app.
4. Install the ones you want.
## Which Should I Use?
If you have Java 17+ installed, **local extensions** are recommended for the best experience — faster, more reliable, and more sources available.
If you prefer a simpler setup or can't install Java, **remote extensions** work great and require no extra configuration.

View File

@@ -0,0 +1,42 @@
---
title: Getting Started
---
# Getting Started
Once you've installed Unyo, here's how to get the most out of it.
## First Launch
When you open Unyo for the first time, you'll be greeted by the home screen. From here you can browse anime, manga, and manage your watchlist.
## AniList Login
Unyo integrates tightly with AniList to sync your watch and read progress.
1. Open **Settings** from the sidebar.
2. Navigate to the **Account** section.
3. Click **Login with AniList**.
4. Authorize Unyo in your browser.
:::important
Your AniList profile must be set to **Public** or **Semi-Public** for Unyo to pull your anime and manga lists. Private profiles are currently not supported.
:::
## MyAnimeList (MAL) Sync
Unyo also supports syncing with MyAnimeList. Both services can be active simultaneously — your progress will be synced to whichever tracker you prefer.
## Browsing Content
- **Anime** — Browse trending and popular anime, or search for a specific title. Tap any title to see details, episodes, and available sources.
- **Manga** — Switch to the manga tab to browse and read manga with the same smooth experience.
## Settings
Unyo offers a range of customization options:
- **Theme** — Switch between light and dark mode.
- **Default source** — Set your preferred anime or manga source.
- **Notifications** — Enable or disable push notifications for new episodes.
- **Extensions** — Manage your remote and local extensions.

View File

@@ -0,0 +1,35 @@
---
title: Installation
---
# Installation
You can download the latest version of Unyo from the [releases page](https://github.com/K3vinb5/Unyo/releases/latest).
Choose the package that matches your operating system:
## Windows
- **Installer (.exe)** — Download the installer, run it, and follow the instructions. This is the recommended method.
- **Portable (.zip)** — Download the .zip, extract it, and run `unyo.exe`.
## macOS
- **DMG (.dmg)** — Download the .dmg and install it like any other macOS application. **Recommended**.
- **Portable (.zip)** — Download the .zip, extract it, and run the .app file.
## Linux
There are multiple options available:
| Method | Instructions |
|--------|--------------|
| **AM Package Manager** (Recommended) | `am -i unyo` — Also supports updating with `am -u unyo` |
| **AUR (Arch)** | Install `unyo-bin` from the AUR |
| **AppImage (.AppImage)** | Download and run it |
| **DEB package (.deb)** | `sudo apt install ./package_name.deb` (Debian-based only) |
| **Portable (.zip)** | Download, extract, and run the `unyo` executable |
:::tip
If you want to use **local extensions**, you need Java 17 or later installed on your machine. See the [Extensions](/docs/extensions) page for details.
:::

20
website/docs/intro.md Normal file
View File

@@ -0,0 +1,20 @@
---
slug: /
title: Welcome to Unyo
---
# Welcome to Unyo
**Unyo** is a desktop application where you can watch anime and read manga — completely ad-free, with automatic AniList & MyAnimeList sync.
![Unyo home screen](/img/screenshots/home.png)
## Why Unyo?
- **Ad-Free** — No interruptions, no trackers. Your content, your experience.
- **AniList & MAL Sync** — Your watch and read progress syncs automatically.
- **Aniyomi Extensions** — Access hundreds of anime and manga sources.
- **Cross-Platform** — Available on Linux, Windows, and macOS.
- **Open Source** — Fully transparent, GPL-3.0 licensed.
Ready to get started? Head to the [Installation guide](/docs/installation) to download and set up Unyo.

View File

@@ -0,0 +1,110 @@
// @ts-check
const {themes} = require('prism-react-renderer');
const lightCodeTheme = themes.github;
const darkCodeTheme = themes.dracula;
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Unyo',
tagline: 'Simple anime streaming & manga reading, ad-free.',
url: 'https://unyo.dev',
baseUrl: '/',
onBrokenLinks: 'throw',
markdown: {
format: 'mdx',
mdx1Compat: {
comments: true,
},
},
favicon: 'img/logo.png',
organizationName: 'K3vinb5',
projectName: 'Unyo',
trailingSlash: false,
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve('./sidebars.js'),
editUrl: 'https://github.com/K3vinb5/Unyo/tree/rewrite/website',
},
blog: false,
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
image: 'img/mascot.png',
navbar: {
title: 'Unyo',
logo: {
alt: 'Unyo Logo',
src: 'img/logo.png',
},
items: [
{
to: '/docs',
label: 'Docs',
position: 'left',
},
{
href: 'https://github.com/K3vinb5/Unyo',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Resources',
items: [
{
label: 'Documentation',
to: '/docs',
},
{
label: 'Releases',
href: 'https://github.com/K3vinb5/Unyo/releases',
},
],
},
{
title: 'Community',
items: [
{
label: 'GitHub Issues',
href: 'https://github.com/K3vinb5/Unyo/issues',
},
{
label: 'Discussions',
href: 'https://github.com/K3vinb5/Unyo/discussions',
},
],
},
{
title: 'Legal',
items: [
{
label: 'GPL-3.0 License',
href: 'https://github.com/K3vinb5/Unyo/blob/rewrite/LICENSE',
},
],
},
],
copyright: `© ${new Date().getFullYear()} Unyo. Built with Docusaurus.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
}),
};
module.exports = config;

18448
website/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
website/package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "unyo-website",
"version": "1.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "^3.10.0",
"@docusaurus/preset-classic": "^3.10.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"webpack": "^5.99.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.10.0",
"@docusaurus/tsconfig": "^3.10.0",
"@docusaurus/types": "^3.10.0"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
}
}

13
website/sidebars.js Normal file
View File

@@ -0,0 +1,13 @@
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
docs: [
'intro',
'installation',
'getting-started',
'extensions',
],
};
module.exports = sidebars;

View File

@@ -0,0 +1,97 @@
import React from 'react';
import styles from './styles.module.css';
const features = [
{
title: 'Anime Streaming',
description:
'Watch your favorite anime with subtitle support, smooth playback, and torrent streaming.',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="28" height="28">
<polygon points="5 3 19 12 5 21 5 3" />
</svg>
),
},
{
title: 'Manga Reader',
description:
'Read manga with a clean, distraction-free reader designed for comfort and immersion.',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="28" height="28">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
</svg>
),
},
{
title: 'Ad-Free',
description:
'No ads, ever. Enjoy your content without interruptions or trackers.',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="28" height="28">
<circle cx="12" cy="12" r="10" />
<line x1="4.93" y1="4.93" x2="19.07" y2="19.07" />
</svg>
),
},
{
title: 'AniList & MAL Sync',
description:
'Your watch and read progress syncs automatically with AniList and MyAnimeList.',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="28" height="28">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
),
},
{
title: 'Aniyomi Extensions',
description:
'Browse hundreds of sources via the Aniyomi extension ecosystem — both remote and local.',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="28" height="28">
<rect x="3" y="3" width="7" height="7" rx="1" />
<rect x="14" y="3" width="7" height="7" rx="1" />
<rect x="3" y="14" width="7" height="7" rx="1" />
<rect x="14" y="14" width="7" height="7" rx="1" />
</svg>
),
},
{
title: 'Cross-Platform',
description:
'Native builds for Linux, Windows, and macOS. One app, every OS.',
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="28" height="28">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
<line x1="8" y1="21" x2="16" y2="21" />
<line x1="12" y1="17" x2="12" y2="21" />
</svg>
),
},
];
export default function FeatureGrid() {
return (
<section className={styles.section}>
<h2 className={styles.sectionTitle}>
Everything you need,{' '}
<span className={styles.gradient}>nothing you don&apos;t.</span>
</h2>
<div className={styles.grid}>
{features.map((f, i) => (
<div
key={f.title}
className={styles.card}
style={{ animationDelay: `${i * 0.08}s` }}
>
<div className={styles.iconWrap}>{f.icon}</div>
<h3 className={styles.cardTitle}>{f.title}</h3>
<p className={styles.cardDesc}>{f.description}</p>
</div>
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,106 @@
@keyframes cardAppear {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.section {
padding: 6rem 2rem;
max-width: 1200px;
margin: 0 auto;
}
.sectionTitle {
font-family: 'Outfit', sans-serif;
font-weight: 800;
font-size: clamp(1.75rem, 3.5vw, 2.5rem);
text-align: center;
color: #f5f0ff;
margin-bottom: 3.5rem;
}
.gradient {
background: linear-gradient(135deg, #a855f7, #ec4899);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
.card {
background: rgba(21, 17, 38, 0.6);
border: 1px solid rgba(168, 85, 247, 0.1);
border-radius: 16px;
padding: 2rem;
transition: all 0.3s ease;
animation: cardAppear 0.6s ease-out both;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.card:hover {
border-color: rgba(168, 85, 247, 0.35);
background: rgba(21, 17, 38, 0.85);
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(168, 85, 247, 0.15),
0 0 0 1px rgba(168, 85, 247, 0.2);
}
.iconWrap {
display: inline-flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, rgba(168, 85, 247, 0.15), rgba(236, 72, 153, 0.15));
color: #c084fc;
margin-bottom: 1rem;
}
.card:hover .iconWrap {
background: linear-gradient(135deg, rgba(168, 85, 247, 0.25), rgba(236, 72, 153, 0.25));
color: #f472b6;
}
.cardTitle {
font-family: 'Outfit', sans-serif;
font-weight: 700;
font-size: 1.15rem;
color: #f5f0ff;
margin: 0 0 0.6rem;
}
.cardDesc {
font-family: 'IBM Plex Sans', sans-serif;
font-size: 0.9rem;
line-height: 1.6;
color: #9b8ec4;
margin: 0;
}
@media (max-width: 996px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 600px) {
.section {
padding: 4rem 1.25rem;
}
.grid {
grid-template-columns: 1fr;
}
}

View File

@@ -0,0 +1,59 @@
import React from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import styles from './styles.module.css';
export default function Hero() {
return (
<section className={styles.hero}>
<div className={styles.heroGlow} aria-hidden="true" />
<div className={styles.heroNoise} aria-hidden="true" />
<div className={styles.container}>
<div className={styles.heroContent}>
<span className={styles.badge}>
Open Source&nbsp;&bull;&nbsp;Ad-Free&nbsp;&bull;&nbsp;Multi-Platform
</span>
<h1 className={styles.heroTitle}>
Your anime &amp; manga,{' '}
<span className={styles.gradientText}>your way.</span>
</h1>
<p className={styles.heroSubtitle}>
Stream anime, read manga, sync with AniList &amp; MyAnimeList&nbsp;&mdash;
no ads, no nonsense.
</p>
<div className={styles.heroCtas}>
<Link className={styles.ctaPrimary} to="/docs">
Get Started&nbsp;&rarr;
</Link>
<Link
className={styles.ctaSecondary}
href="https://github.com/K3vinb5/Unyo"
>
<svg
className={styles.githubIcon}
viewBox="0 0 24 24"
fill="currentColor"
width="20"
height="20"
>
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
</svg>
GitHub
</Link>
</div>
</div>
<div className={styles.heroMascotWrap}>
<div className={styles.mascotRing} aria-hidden="true" />
<img
className={styles.heroMascot}
src="/img/mascot.png"
alt="Unyo mascot"
/>
</div>
</div>
<div className={styles.scrollHint} aria-hidden="true">
<div className={styles.scrollDot} />
</div>
</section>
);
}

View File

@@ -0,0 +1,310 @@
@keyframes fadeSlideUp {
from {
opacity: 0;
transform: translateY(32px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeSlideRight {
from {
opacity: 0;
transform: translateX(-32px) scale(0.92);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
@keyframes mascotFloat {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-14px);
}
}
@keyframes ringPulse {
0%, 100% {
opacity: 0.4;
transform: scale(1);
}
50% {
opacity: 0.7;
transform: scale(1.05);
}
}
@keyframes scrollBounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(6px);
}
}
.hero {
position: relative;
min-height: 92vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 8rem 2rem 4rem;
}
.heroGlow {
position: absolute;
top: 10%;
left: 50%;
transform: translateX(-50%);
width: 800px;
height: 800px;
background: radial-gradient(circle, rgba(168, 85, 247, 0.18) 0%, rgba(236, 72, 153, 0.1) 40%, transparent 70%);
pointer-events: none;
z-index: 0;
}
.heroNoise {
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E");
background-repeat: repeat;
pointer-events: none;
z-index: 0;
opacity: 0.6;
}
.container {
position: relative;
z-index: 1;
max-width: 1200px;
width: 100%;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 4rem;
}
.heroContent {
flex: 1;
max-width: 600px;
animation: fadeSlideUp 0.8s ease-out both;
}
.badge {
display: inline-block;
padding: 0.4rem 1rem;
border-radius: 999px;
font-size: 0.8rem;
font-weight: 600;
font-family: 'Outfit', sans-serif;
letter-spacing: 0.04em;
text-transform: uppercase;
background: rgba(168, 85, 247, 0.1);
border: 1px solid rgba(168, 85, 247, 0.25);
color: #c084fc;
margin-bottom: 1.5rem;
}
.heroTitle {
font-family: 'Outfit', sans-serif;
font-weight: 900;
font-size: clamp(2.5rem, 5vw, 4rem);
line-height: 1.1;
color: #f5f0ff;
margin: 0 0 1.25rem;
}
.gradientText {
background: linear-gradient(135deg, #a855f7, #ec4899, #f472b6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.heroSubtitle {
font-family: 'IBM Plex Sans', sans-serif;
font-size: 1.15rem;
line-height: 1.65;
color: #9b8ec4;
margin: 0 0 2rem;
max-width: 520px;
}
.heroCtas {
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
}
.ctaPrimary {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.85rem 2rem;
border-radius: 12px;
font-family: 'Outfit', sans-serif;
font-size: 1rem;
font-weight: 700;
text-decoration: none;
background: linear-gradient(135deg, #a855f7, #ec4899);
color: #fff;
box-shadow: 0 4px 24px rgba(168, 85, 247, 0.35), 0 0 0 1px rgba(236, 72, 153, 0.2);
transition: all 0.25s ease;
}
.ctaPrimary:hover {
background: linear-gradient(135deg, #7c3aed, #f472b6);
color: #fff;
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(168, 85, 247, 0.45), 0 0 0 1px rgba(236, 72, 153, 0.4);
}
.ctaSecondary {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.85rem 2rem;
border-radius: 12px;
font-family: 'Outfit', sans-serif;
font-size: 1rem;
font-weight: 600;
text-decoration: none;
background: rgba(168, 85, 247, 0.06);
border: 1px solid rgba(168, 85, 247, 0.2);
color: #c084fc;
transition: all 0.25s ease;
}
.ctaSecondary:hover {
background: rgba(168, 85, 247, 0.12);
border-color: rgba(168, 85, 247, 0.4);
color: #f5f0ff;
}
.githubIcon {
width: 20px;
height: 20px;
}
.heroMascotWrap {
position: relative;
flex-shrink: 0;
animation: fadeSlideRight 0.8s ease-out 0.2s both;
}
.mascotRing {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 380px;
height: 380px;
border-radius: 50%;
border: 2px solid rgba(168, 85, 247, 0.15);
background: radial-gradient(circle, rgba(168, 85, 247, 0.12) 0%, transparent 70%);
animation: ringPulse 4s ease-in-out infinite;
}
.heroMascot {
position: relative;
z-index: 1;
width: 300px;
height: 300px;
object-fit: contain;
animation: mascotFloat 3s ease-in-out infinite;
filter: drop-shadow(0 0 40px rgba(168, 85, 247, 0.3));
}
.scrollHint {
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
}
.scrollDot {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(168, 85, 247, 0.5);
animation: scrollBounce 2s ease-in-out infinite;
}
/* ====== RESPONSIVE ====== */
@media (max-width: 996px) {
.container {
flex-direction: column;
text-align: center;
gap: 3rem;
}
.heroContent {
max-width: 100%;
align-items: center;
}
.heroSubtitle {
max-width: 100%;
}
.heroCtas {
justify-content: center;
}
.heroMascot {
width: 220px;
height: 220px;
}
.mascotRing {
width: 280px;
height: 280px;
}
}
@media (max-width: 600px) {
.hero {
min-height: 85vh;
padding: 6rem 1.25rem 3rem;
}
.heroTitle {
font-size: 2.2rem;
}
.heroMascot {
width: 180px;
height: 180px;
}
.mascotRing {
width: 230px;
height: 230px;
}
.heroCtas {
flex-direction: column;
width: 100%;
}
.ctaPrimary,
.ctaSecondary {
width: 100%;
justify-content: center;
}
}

View File

@@ -0,0 +1,103 @@
import React, { useRef, useState, useEffect } from 'react';
import styles from './styles.module.css';
const screenshots = [
{ src: '/img/screenshots/home.png', alt: 'Unyo home screen' },
{ src: '/img/screenshots/anime-1.png', alt: 'Unyo anime browsing' },
{ src: '/img/screenshots/anime-2.png', alt: 'Unyo anime playback' },
{ src: '/img/screenshots/anime-details.png', alt: 'Unyo anime details' },
{ src: '/img/screenshots/manga.png', alt: 'Unyo manga reader' },
{ src: '/img/screenshots/details.png', alt: 'Unyo manga details' },
{ src: '/img/screenshots/extensions.png', alt: 'Unyo extensions' },
{ src: '/img/screenshots/settings.png', alt: 'Unyo settings' },
];
export default function ScreenshotGallery() {
const scrollRef = useRef(null);
const [canScrollLeft, setCanScrollLeft] = useState(false);
const [canScrollRight, setCanScrollRight] = useState(true);
const [lightbox, setLightbox] = useState(null);
const updateScrollButtons = () => {
const el = scrollRef.current;
if (!el) return;
setCanScrollLeft(el.scrollLeft > 2);
setCanScrollRight(el.scrollLeft < el.scrollWidth - el.clientWidth - 2);
};
useEffect(() => {
const el = scrollRef.current;
if (!el) return;
updateScrollButtons();
el.addEventListener('scroll', updateScrollButtons, { passive: true });
window.addEventListener('resize', updateScrollButtons);
return () => {
el.removeEventListener('scroll', updateScrollButtons);
window.removeEventListener('resize', updateScrollButtons);
};
}, []);
const scroll = (dir) => {
const el = scrollRef.current;
if (!el) return;
el.scrollBy({ left: dir * 340, behavior: 'smooth' });
};
return (
<section className={styles.section}>
<h2 className={styles.sectionTitle}>
See it in action
</h2>
<div className={styles.carouselWrap}>
{canScrollLeft && (
<button
className={`${styles.scrollBtn} ${styles.scrollBtnLeft}`}
onClick={() => scroll(-1)}
aria-label="Scroll left"
>
&#8249;
</button>
)}
<div className={styles.carousel} ref={scrollRef}>
{screenshots.map((s) => (
<button
key={s.src}
className={styles.slide}
onClick={() => setLightbox(s)}
type="button"
>
<img src={s.src} alt={s.alt} className={styles.screenshot} />
</button>
))}
</div>
{canScrollRight && (
<button
className={`${styles.scrollBtn} ${styles.scrollBtnRight}`}
onClick={() => scroll(1)}
aria-label="Scroll right"
>
&#8250;
</button>
)}
<div className={styles.fadeLeft} />
<div className={styles.fadeRight} />
</div>
{lightbox && (
<div className={styles.lightbox} onClick={() => setLightbox(null)}>
<img
src={lightbox.src}
alt={lightbox.alt}
className={styles.lightboxImg}
/>
<button
className={styles.lightboxClose}
onClick={() => setLightbox(null)}
aria-label="Close"
>
&times;
</button>
</div>
)}
</section>
);
}

View File

@@ -0,0 +1,179 @@
.section {
padding: 4rem 2rem 6rem;
max-width: 1200px;
margin: 0 auto;
position: relative;
}
.sectionTitle {
font-family: 'Outfit', sans-serif;
font-weight: 800;
font-size: clamp(1.75rem, 3.5vw, 2.5rem);
text-align: center;
color: #f5f0ff;
margin-bottom: 3rem;
}
.carouselWrap {
position: relative;
}
.carousel {
display: flex;
gap: 1.25rem;
overflow-x: auto;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
padding: 1rem 0;
}
.carousel::-webkit-scrollbar {
display: none;
}
.slide {
flex-shrink: 0;
scroll-snap-align: start;
background: none;
border: 2px solid rgba(168, 85, 247, 0.1);
border-radius: 16px;
padding: 0;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
outline: none;
}
.slide:hover {
border-color: rgba(168, 85, 247, 0.4);
box-shadow: 0 8px 32px rgba(168, 85, 247, 0.2);
transform: translateY(-4px);
}
.slide:focus-visible {
border-color: var(--unyo-pink);
}
.screenshot {
display: block;
width: 320px;
height: auto;
border-radius: 14px;
}
.scrollBtn {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 3;
width: 44px;
height: 44px;
border-radius: 50%;
border: 1px solid rgba(168, 85, 247, 0.3);
background: rgba(13, 10, 26, 0.9);
color: #c084fc;
font-size: 1.5rem;
line-height: 1;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.scrollBtn:hover {
background: rgba(168, 85, 247, 0.2);
border-color: rgba(168, 85, 247, 0.5);
}
.scrollBtnLeft {
left: -0.5rem;
}
.scrollBtnRight {
right: -0.5rem;
}
.fadeLeft {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 60px;
background: linear-gradient(to right, #0d0a1a, transparent);
pointer-events: none;
z-index: 2;
}
.fadeRight {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 60px;
background: linear-gradient(to left, #0d0a1a, transparent);
pointer-events: none;
z-index: 2;
}
.lightbox {
position: fixed;
inset: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.85);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
animation: fadeIn 0.2s ease-out;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.lightboxImg {
max-width: 90vw;
max-height: 85vh;
border-radius: 16px;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
}
.lightboxClose {
position: absolute;
top: 1.5rem;
right: 2rem;
background: none;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
width: 44px;
height: 44px;
font-size: 1.5rem;
color: #fff;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.lightboxClose:hover {
border-color: rgba(255, 255, 255, 0.6);
background: rgba(255, 255, 255, 0.1);
}
@media (max-width: 600px) {
.screenshot {
width: 260px;
}
.scrollBtn {
display: none;
}
}

259
website/src/css/custom.css Normal file
View File

@@ -0,0 +1,259 @@
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&family=IBM+Plex+Sans:wght@300;400;500;600;700&display=swap');
:root {
--unyo-bg: #0d0a1a;
--unyo-bg-surface: #151126;
--unyo-purple: #a855f7;
--unyo-purple-dark: #7c3aed;
--unyo-purple-darker: #5b21b6;
--unyo-pink: #ec4899;
--unyo-pink-glow: #f472b6;
--unyo-text: #e8e0f0;
--unyo-text-muted: #9b8ec4;
--unyo-gradient: linear-gradient(135deg, #a855f7, #ec4899);
--unyo-gradient-hover: linear-gradient(135deg, #7c3aed, #f472b6);
--ifm-color-primary: #a855f7;
--ifm-color-primary-dark: #9333ea;
--ifm-color-primary-darker: #7c3aed;
--ifm-color-primary-darkest: #5b21b6;
--ifm-color-primary-light: #c084fc;
--ifm-color-primary-lighter: #d8b4fe;
--ifm-color-primary-lightest: #f3e8ff;
--ifm-background-color: #0d0a1a;
--ifm-background-surface-color: #151126;
--ifm-font-color-base: #e8e0f0;
--ifm-heading-color: #f5f0ff;
--ifm-color-secondary: #1e1838;
--ifm-color-success: #22c55e;
--ifm-color-info: #38bdf8;
--ifm-color-warning: #f59e0b;
--ifm-color-danger: #ef4444;
--ifm-font-family-base: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
--ifm-font-family-heading: 'Outfit', 'IBM Plex Sans', system-ui, sans-serif;
--ifm-heading-font-weight: 700;
--ifm-hero-background-color: transparent;
--ifm-link-color: #c084fc;
--ifm-link-hover-color: #f472b6;
--ifm-link-decoration: none;
--ifm-navbar-background-color: rgba(13, 10, 26, 0.85);
--ifm-navbar-shadow: 0 1px 0 rgba(168, 85, 247, 0.1);
--ifm-footer-background-color: #0a0812;
--ifm-footer-link-color: var(--unyo-text-muted);
--ifm-code-background: #1e1838;
--ifm-code-border-radius: 8px;
--ifm-pre-background: #1e1838;
--ifm-pre-border-radius: 12px;
--docusaurus-highlighted-code-line-bg: rgba(168, 85, 247, 0.15);
}
/* ====== GLOBAL STYLES ====== */
html {
scroll-behavior: smooth;
}
body {
background-color: var(--unyo-bg);
}
::selection {
background: rgba(168, 85, 247, 0.4);
color: #fff;
}
/* ====== NAVBAR ====== */
.navbar {
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid rgba(168, 85, 247, 0.12);
transition: background-color 0.3s ease;
}
.navbar__title {
font-family: var(--ifm-font-family-heading);
font-weight: 800;
font-size: 1.3rem;
background: var(--unyo-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.navbar__link {
font-weight: 500;
color: var(--unyo-text-muted);
transition: color 0.2s ease;
}
.navbar__link:hover,
.navbar__link--active {
color: var(--ifm-link-color);
}
.navbar__logo {
height: 28px;
width: 28px;
border-radius: 6px;
margin-right: 0.5rem;
}
/* ====== SIDEBAR ====== */
.theme-doc-sidebar-container {
background-color: var(--unyo-bg-surface);
border-right: 1px solid rgba(168, 85, 247, 0.08);
}
.menu__link {
color: var(--unyo-text-muted);
border-radius: 8px;
transition: all 0.2s ease;
}
.menu__link:hover,
.menu__link--active {
color: var(--ifm-link-color);
background: rgba(168, 85, 247, 0.08);
}
.menu__list-item-collapsible {
border-radius: 8px;
}
/* ====== DOCS PAGES ====== */
.theme-doc-markdown {
color: var(--unyo-text);
}
.theme-doc-markdown h1 {
font-family: var(--ifm-font-family-heading);
background: var(--unyo-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.theme-doc-markdown h2,
.theme-doc-markdown h3,
.theme-doc-markdown h4 {
color: var(--ifm-heading-color);
font-family: var(--ifm-font-family-heading);
}
.theme-doc-markdown a {
color: var(--ifm-link-color);
background-image: linear-gradient(transparent 0%, transparent 60%, rgba(168, 85, 247, 0.2) 60%);
background-size: 0 100%;
background-repeat: no-repeat;
transition: background-size 0.3s ease;
}
.theme-doc-markdown a:hover {
background-size: 100% 100%;
}
.theme-doc-markdown code {
background: var(--ifm-code-background);
border: 1px solid rgba(168, 85, 247, 0.15);
color: #c084fc;
font-size: 0.875em;
}
.theme-doc-markdown blockquote {
border-left-color: var(--unyo-purple);
background: rgba(168, 85, 247, 0.06);
color: var(--unyo-text-muted);
}
.theme-doc-markdown table {
border-collapse: separate;
border-spacing: 0;
border: 1px solid rgba(168, 85, 247, 0.15);
border-radius: 12px;
overflow: hidden;
}
.theme-doc-markdown th {
background: var(--unyo-bg-surface);
color: var(--ifm-heading-color);
border-bottom-color: rgba(168, 85, 247, 0.2);
}
.theme-doc-markdown td {
border-bottom-color: rgba(168, 85, 247, 0.08);
}
/* ====== BREADCRUMB ====== */
.breadcrumbs__link {
color: var(--unyo-text-muted);
}
.breadcrumbs__link:hover {
color: var(--ifm-link-color);
}
/* ====== PAGINATION ====== */
.pagination-nav__link {
border: 1px solid rgba(168, 85, 247, 0.15);
border-radius: 12px;
background: var(--unyo-bg-surface);
color: var(--unyo-text);
transition: all 0.2s ease;
}
.pagination-nav__link:hover {
border-color: var(--unyo-purple);
background: rgba(168, 85, 247, 0.08);
}
/* ====== FOOTER ====== */
.footer {
border-top: 1px solid rgba(168, 85, 247, 0.1);
}
.footer__title {
font-family: var(--ifm-font-family-heading);
color: var(--ifm-heading-color);
font-weight: 700;
}
.footer__link-item {
color: var(--unyo-text-muted);
transition: color 0.2s ease;
}
.footer__link-item:hover {
color: var(--ifm-link-color);
}
/* ====== SEARCH ====== */
.DocSearch-Button {
border-radius: 12px;
background: var(--unyo-bg-surface);
border: 1px solid rgba(168, 85, 247, 0.15);
}
/* ====== MOBILE MENU ====== */
.menu--show {
background-color: var(--unyo-bg-surface);
}
/* ====== OVERFLOW UTILITY ====== */
.overflow-auto {
overflow: auto;
}

View File

@@ -0,0 +1,63 @@
import React from 'react';
import Layout from '@theme/Layout';
import Hero from '@site/src/components/Hero';
import FeatureGrid from '@site/src/components/FeatureGrid';
import ScreenshotGallery from '@site/src/components/ScreenshotGallery';
import styles from './index.module.css';
export default function Home() {
return (
<Layout
title="Unyo — Anime Streaming & Manga Reader"
description="Simple anime streaming and manga reader desktop app. Ad-free, cross-platform, with AniList & MAL sync."
>
<main className={styles.main}>
<Hero />
<div className={styles.divider} aria-hidden="true" />
<FeatureGrid />
<div className={styles.divider} aria-hidden="true" />
<ScreenshotGallery />
<footer className={styles.landingFooter}>
<div className={styles.footerInner}>
<div className={styles.footerLogo}>
<img src="/img/logo.png" alt="Unyo" className={styles.footerLogoImg} />
<span className={styles.footerName}>Unyo</span>
</div>
<p className={styles.footerTagline}>
Simple anime streaming &amp; manga reading, ad-free.
</p>
<div className={styles.footerLinks}>
<a
href="https://github.com/K3vinb5/Unyo"
target="_blank"
rel="noopener noreferrer"
className={styles.footerLink}
>
GitHub
</a>
<a
href="https://github.com/K3vinb5/Unyo/releases"
target="_blank"
rel="noopener noreferrer"
className={styles.footerLink}
>
Releases
</a>
<a
href="https://github.com/K3vinb5/Unyo/blob/rewrite/LICENSE"
target="_blank"
rel="noopener noreferrer"
className={styles.footerLink}
>
GPL-3.0 License
</a>
</div>
<p className={styles.footerCopy}>
&copy; {new Date().getFullYear()} Unyo. All rights reserved.
</p>
</div>
</footer>
</main>
</Layout>
);
}

View File

@@ -0,0 +1,83 @@
.main {
background-color: #0d0a1a;
overflow-x: hidden;
}
.divider {
max-width: 1200px;
margin: 0 auto;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(168, 85, 247, 0.25), transparent);
}
.landingFooter {
background: linear-gradient(180deg, #0d0a1a 0%, #0a0812 100%);
border-top: 1px solid rgba(168, 85, 247, 0.08);
padding: 4rem 2rem 2rem;
}
.footerInner {
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.footerLogo {
display: flex;
align-items: center;
gap: 0.75rem;
}
.footerLogoImg {
width: 32px;
height: 32px;
border-radius: 6px;
}
.footerName {
font-family: 'Outfit', sans-serif;
font-weight: 800;
font-size: 1.3rem;
background: linear-gradient(135deg, #a855f7, #ec4899);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.footerTagline {
font-family: 'IBM Plex Sans', sans-serif;
font-size: 0.95rem;
color: #9b8ec4;
text-align: center;
margin: 0;
}
.footerLinks {
display: flex;
gap: 1.5rem;
flex-wrap: wrap;
justify-content: center;
}
.footerLink {
font-family: 'IBM Plex Sans', sans-serif;
font-size: 0.9rem;
font-weight: 500;
color: #c084fc;
text-decoration: none;
transition: color 0.2s ease;
}
.footerLink:hover {
color: #f472b6;
}
.footerCopy {
font-family: 'IBM Plex Sans', sans-serif;
font-size: 0.8rem;
color: #5b4f7a;
margin: 0;
}

BIN
website/static/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB