🎨 Theme Customization

Create beautiful custom themes for Xiuno BBS

Theme Overview

Default Themes

Xiuno BBS includes several themes out of the box:

Themes are located in view/ directory. Each theme contains template files (*.htm) and assets (CSS, JS, images).

Theme Structure

view/
└── mytheme/
    ├── css/
    │   ├── bootstrap.min.css
    │   ├── style.css          # Main theme styles
    │   └── custom.css         # Your customizations
    ├── js/
    │   ├── bootstrap.min.js
    │   └── theme.js           # Theme JavaScript
    ├── img/
    │   ├── logo.png
    │   ├── favicon.ico
    │   └── backgrounds/
    ├── htm/
    │   ├── header.htm         # Global header
    │   ├── footer.htm         # Global footer
    │   ├── index.htm          # Homepage template
    │   ├── thread.htm         # Thread list
    │   ├── thread_show.htm    # Single thread view
    │   ├── user.htm           # User profile
    │   └── ...
    └── conf.json              # Theme metadata

Creating a Custom Theme

Step 1: Copy Base Theme

# Copy default theme as starting point
cd /home/xiuno/public_html/view
cp -r default mytheme

# Update theme metadata
nano mytheme/conf.json

Step 2: Theme Metadata

Edit view/mytheme/conf.json:

{
    "name": "My Custom Theme",
    "version": "1.0.0",
    "author": "Your Name",
    "brief": "A beautiful custom theme",
    "bbs_version": "4.0.0",
    "screenshot": "screenshot.png",
    "responsive": true,
    "dark_mode": false
}

Step 3: Customize Styles

Edit view/mytheme/css/style.css:

/* Custom Color Scheme */
:root {
    --primary-color: #667eea;
    --secondary-color: #764ba2;
    --text-color: #2d3748;
    --bg-color: #f7fafc;
    --border-color: #e2e8f0;
    --success-color: #48bb78;
    --danger-color: #f56565;
    --warning-color: #ed8936;
}

/* Header Styling */
.header {
    background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
    padding: 20px 0;
    box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}

.logo {
    font-size: 28px;
    font-weight: 700;
    color: white;
    text-decoration: none;
}

/* Navigation */
.nav-menu {
    display: flex;
    gap: 30px;
    align-items: center;
}

.nav-menu a {
    color: rgba(255,255,255,0.9);
    text-decoration: none;
    font-weight: 500;
    transition: all 0.3s;
}

.nav-menu a:hover {
    color: white;
    transform: translateY(-2px);
}

/* Card Design */
.thread-card {
    background: white;
    border-radius: 12px;
    padding: 20px;
    margin-bottom: 15px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.05);
    transition: transform 0.3s, box-shadow 0.3s;
}

.thread-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 8px 25px rgba(0,0,0,0.1);
}

/* Buttons */
.btn-primary {
    background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
    border: none;
    padding: 12px 30px;
    border-radius: 8px;
    color: white;
    font-weight: 600;
    cursor: pointer;
    transition: transform 0.2s;
}

.btn-primary:hover {
    transform: scale(1.05);
}

/* User Avatar */
.avatar {
    width: 50px;
    height: 50px;
    border-radius: 50%;
    border: 3px solid var(--primary-color);
}

/* Footer */
.footer {
    background: #2d3748;
    color: rgba(255,255,255,0.8);
    padding: 40px 0;
    margin-top: 60px;
}

Template Customization

Header Template

Edit view/mytheme/htm/header.htm:

<!DOCTYPE html>
<html lang="<?php echo $lang; ?>">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo $title; ?> - <?php echo $conf['sitename']; ?></title>
    
    <!-- Meta Tags -->
    <meta name="description" content="<?php echo $description; ?>">
    <meta name="keywords" content="<?php echo $keywords; ?>">
    
    <!-- Stylesheets -->
    <link rel="stylesheet" href="<?php echo $conf['siteurl']; ?>view/mytheme/css/bootstrap.min.css">
    <link rel="stylesheet" href="<?php echo $conf['siteurl']; ?>view/mytheme/css/style.css">
    
    <!-- Favicon -->
    <link rel="icon" href="<?php echo $conf['siteurl']; ?>view/mytheme/img/favicon.ico">
</head>
<body>
    <header class="header">
        <div class="container">
            <div class="nav-wrapper">
                <a href="<?php echo $conf['siteurl']; ?>" class="logo">
                    <img src="<?php echo $conf['siteurl']; ?>view/mytheme/img/logo.png" alt="Logo">
                    <?php echo $conf['sitename']; ?>
                </a>
                
                <nav class="nav-menu">
                    <a href="<?php echo $conf['siteurl']; ?>">Home</a>
                    <a href="<?php echo $conf['siteurl']; ?>docs/">Docs</a>
                    <a href="<?php echo $conf['siteurl']; ?>downloads/">Downloads</a>
                    <a href="<?php echo $conf['siteurl']; ?>forum.php">Forum</a>
                    
                    <?php if($uid): ?>
                        <a href="?user-<?php echo $uid; ?>.htm"><?php echo $user['username']; ?></a>
                        <a href="?user-logout.htm">Logout</a>
                    <?php else: ?>
                        <a href="?user-login.htm">Login</a>
                        <a href="?user-register.htm">Register</a>
                    <?php endif; ?>
                </nav>
            </div>
        </div>
    </header>
    
    <main class="main-content">

Thread List Template

Edit view/mytheme/htm/thread.htm:

<div class="container">
    <div class="forum-header">
        <h1><?php echo $forum['name']; ?></h1>
        <p><?php echo $forum['brief']; ?></p>
    </div>
    
    <div class="thread-list">
        <?php foreach($threads as $thread): ?>
            <div class="thread-card">
                <div class="thread-header">
                    <div class="user-info">
                        <img src="<?php echo user_avatar($thread['uid']); ?>" class="avatar">
                        <div>
                            <strong><?php echo $thread['username']; ?></strong>
                            <small><?php echo humantime($thread['create_date']); ?></small>
                        </div>
                    </div>
                </div>
                
                <h3 class="thread-title">
                    <a href="?thread-<?php echo $thread['tid']; ?>.htm">
                        <?php if($thread['top']): ?>
                            <span class="badge badge-top">📌 Pinned</span>
                        <?php endif; ?>
                        <?php echo $thread['subject']; ?>
                    </a>
                </h3>
                
                <div class="thread-meta">
                    <span>💬 <?php echo $thread['posts']; ?> replies</span>
                    <span>👁️ <?php echo $thread['views']; ?> views</span>
                    <span>📅 <?php echo humantime($thread['last_date']); ?></span>
                </div>
            </div>
        <?php endforeach; ?>
    </div>
</div>

Responsive Design

Mobile-First CSS

/* Base styles (mobile) */
.container {
    width: 100%;
    padding: 0 15px;
}

.thread-card {
    padding: 15px;
}

/* Tablet (768px+) */
@media (min-width: 768px) {
    .container {
        max-width: 750px;
        margin: 0 auto;
    }
    
    .nav-menu {
        display: flex;
    }
}

/* Desktop (1024px+) */
@media (min-width: 1024px) {
    .container {
        max-width: 1200px;
    }
    
    .thread-list {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: 20px;
    }
}

/* Hide mobile menu on desktop */
.mobile-menu-toggle {
    display: block;
}

@media (min-width: 768px) {
    .mobile-menu-toggle {
        display: none;
    }
}

Color Schemes

Purple Gradient (Current)

Primary
#667eea
Secondary
#764ba2

Alternative Color Schemes

/* Blue Professional */
:root {
    --primary-color: #2563eb;
    --secondary-color: #1e40af;
}

/* Green Nature */
:root {
    --primary-color: #10b981;
    --secondary-color: #059669;
}

/* Orange Energy */
:root {
    --primary-color: #f97316;
    --secondary-color: #ea580c;
}

/* Red Bold */
:root {
    --primary-color: #ef4444;
    --secondary-color: #dc2626;
}

Dark Mode Theme

Dark Mode CSS

/* Dark mode color scheme */
body.dark-mode {
    --text-color: #e2e8f0;
    --bg-color: #1a202c;
    --card-bg: #2d3748;
    --border-color: #4a5568;
}

body.dark-mode .thread-card {
    background: var(--card-bg);
    color: var(--text-color);
}

body.dark-mode .header {
    background: #2d3748;
    border-bottom: 1px solid var(--border-color);
}

/* Dark mode toggle button */
.dark-mode-toggle {
    cursor: pointer;
    padding: 10px;
    border-radius: 50%;
    background: rgba(255,255,255,0.1);
}

body.dark-mode .dark-mode-toggle {
    background: rgba(255,255,255,0.2);
}

Dark Mode JavaScript

Add to view/mytheme/js/theme.js:

// Dark mode toggle
const darkModeToggle = document.getElementById('dark-mode-toggle');
const body = document.body;

// Load saved preference
const darkMode = localStorage.getItem('darkMode');
if (darkMode === 'enabled') {
    body.classList.add('dark-mode');
}

// Toggle dark mode
darkModeToggle.addEventListener('click', () => {
    body.classList.toggle('dark-mode');
    
    // Save preference
    if (body.classList.contains('dark-mode')) {
        localStorage.setItem('darkMode', 'enabled');
    } else {
        localStorage.setItem('darkMode', 'disabled');
    }
});

Custom Fonts

Using Google Fonts

<!-- In header.htm -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">

/* In style.css */
body {
    font-family: 'Poppins', sans-serif;
}

h1, h2, h3, h4, h5, h6 {
    font-family: 'Poppins', sans-serif;
    font-weight: 600;
}

code, pre {
    font-family: 'JetBrains Mono', monospace;
}

Advanced Customization

Custom Homepage

Create view/mytheme/htm/index_custom.htm:

<div class="hero-section">
    <div class="container">
        <h1>Welcome to Xiuno Wiki</h1>
        <p>The fastest, most powerful PHP forum platform</p>
        <div class="hero-buttons">
            <a href="/docs/" class="btn btn-primary">Get Started</a>
            <a href="/downloads/" class="btn btn-secondary">Download</a>
        </div>
    </div>
</div>

<div class="features-section">
    <div class="container">
        <div class="feature-grid">
            <div class="feature-card">
                <div class="feature-icon">⚡</div>
                <h3>Lightning Fast</h3>
                <p>Optimized for speed with built-in caching</p>
            </div>
            
            <div class="feature-card">
                <div class="feature-icon">🔒</div>
                <h3>Secure</h3>
                <p>Built with security best practices</p>
            </div>
            
            <div class="feature-card">
                <div class="feature-icon">🎨</div>
                <h3>Customizable</h3>
                <p>Themes and plugins for endless possibilities</p>
            </div>
        </div>
    </div>
</div>

Adding Animations

/* Fade in animation */
@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.thread-card {
    animation: fadeIn 0.5s ease-out;
}

/* Smooth transitions */
* {
    transition: all 0.3s ease;
}

/* Hover effects */
.thread-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 30px rgba(0,0,0,0.15);
}

Theme Testing

Browser Testing

Test your theme in:

Responsive Testing

# Test different screen sizes
Desktop: 1920x1080, 1366x768
Tablet: 1024x768, 768x1024
Mobile: 375x667, 414x896, 360x640

Performance Optimization

/* Minify CSS */
npm install -g clean-css-cli
cleancss -o style.min.css style.css

/* Optimize images */
# Use tools like TinyPNG or ImageOptim

/* Lazy load images */
<img src="placeholder.jpg" data-src="image.jpg" class="lazy-load">

<script>
document.addEventListener("DOMContentLoaded", function() {
    const lazyImages = document.querySelectorAll('.lazy-load');
    
    const imageObserver = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                const img = entry.target;
                img.src = img.dataset.src;
                imageObserver.unobserve(img);
            }
        });
    });
    
    lazyImages.forEach(img => imageObserver.observe(img));
});
</script>

Publishing Your Theme

  1. Test thoroughly on multiple devices
  2. Create screenshot.png (1200x800px)
  3. Write README.md with:
    • Theme description and features
    • Installation instructions
    • Customization options
    • Browser compatibility
    • Changelog
  4. Package as ZIP: mytheme-1.0.0.zip
  5. Submit to Theme Directory
📦 Package Structure:
ZIP should contain theme folder directly:
✓ mytheme/css/style.css
❌ mytheme-1.0/mytheme/css/style.css
⚠️ Best Practices:
• Keep CSS organized and well-commented
• Use CSS variables for easy customization
• Optimize images and minimize HTTP requests
• Test on slow connections (throttle network)
• Validate HTML and CSS
• Ensure accessibility (WCAG 2.1)
• Support RTL languages if possible

Resources