basic project skeleton

This commit is contained in:
2025-04-08 06:37:14 +04:00
parent 2c256a6ef5
commit 62d926fe69
17 changed files with 2705 additions and 2 deletions
+113 -2
View File
@@ -1,2 +1,113 @@
# kaubuntu.re
Media Platform
# Kaubuntu.re - Plateforme Multimédia
Une plateforme multimédia conçue pour diffuser du contenu à partir d'une instance PeerTube.
## Description
Kaubuntu.re est une interface web responsive qui permet de consulter et rechercher des vidéos hébergées sur une instance PeerTube. La plateforme est conçue pour être légère, facilement déployable sur un serveur mutualisé, et optimisée pour les appareils mobiles et desktop.
## Fonctionnalités
- Affichage des vidéos à la une et récentes
- Navigation par catégories
- Lecture de vidéos
- Recherche de contenu
- Interface responsive (mobile et desktop)
- Intégration avec une instance PeerTube
## Technologies utilisées
- HTML5
- CSS3 avec Media Queries pour le responsive design
- PHP pour le backend
- JavaScript pour les interactions côté client
- Bibliothèques externes via CDN:
- Font Awesome (icônes)
- jQuery
## Structure du projet
```
├── css/
│ ├── styles.css
│ ├── video-page.css
│ ├── categories.css
│ └── search.css
├── img/
│ ├── categories/
│ ├── video-thumbnails/
│ └── channels/
├── js/
│ └── main.js
├── includes/
│ ├── header.php
│ ├── footer.php
│ ├── mobile-menu.php
│ ├── featured-videos.php
│ ├── recent-videos.php
│ └── categories.php
├── index.php
├── video.php
├── categories.php
├── search.php
└── README.md
```
## Installation
1. Téléchargez ou clonez ce dépôt sur votre ordinateur
2. Importez les fichiers sur votre serveur web compatible PHP (via FTP ou SSH)
3. Assurez-vous que le serveur web peut exécuter des scripts PHP
4. Configurez les paramètres de connexion à votre instance PeerTube (voir configuration)
## Configuration
Pour connecter la plateforme à votre instance PeerTube:
1. Modifiez le fichier `includes/config.php` (à créer) avec les paramètres suivants:
```php
<?php
// URL de base de votre instance PeerTube
define('PEERTUBE_URL', 'https://votre-instance-peertube.com');
// Paramètres d'API (si nécessaire)
define('API_KEY', 'votre-clé-api');
// Autres paramètres de configuration
define('SITE_NAME', 'Kaubuntu.re');
define('SITE_DESCRIPTION', 'Votre plateforme de médias libres');
?>
```
## Personnalisation
Vous pouvez personnaliser l'apparence de la plateforme en modifiant les fichiers CSS dans le dossier `css/`. Pour changer le logo et les couleurs principales:
1. Remplacez le fichier `img/logo.png` par votre propre logo
2. Modifiez les couleurs dans `css/styles.css`
## Déploiement
Pour déployer sur un serveur mutualisé:
1. Assurez-vous que votre hébergeur supporte PHP (version 7.0 minimum recommandée)
2. Transférez tous les fichiers via FTP dans le répertoire racine de votre site
3. Vérifiez que les permissions des fichiers sont correctement définies (644 pour les fichiers, 755 pour les dossiers)
4. Configurez votre domaine pour pointer vers le dossier où vous avez installé l'application
## Développement
Si vous souhaitez contribuer au développement:
1. Créez une branche pour vos modifications: `git checkout -b ma-nouvelle-fonctionnalité`
2. Committez vos changements: `git commit -m 'Ajout d'une nouvelle fonctionnalité'`
3. Poussez vers la branche: `git push origin ma-nouvelle-fonctionnalité`
4. Soumettez une pull request
## Licence
Ce projet est sous licence [MIT](LICENSE).
## Contact
Pour toute question ou suggestion, veuillez nous contacter à [contact@kaubuntu.re](mailto:contact@kaubuntu.re).
+311
View File
@@ -0,0 +1,311 @@
<?php
// Récupération de l'ID de catégorie
$categoryId = isset($_GET['id']) ? intval($_GET['id']) : null;
// Définition des catégories
$categories = [
1 => [
'id' => 1,
'name' => 'Technologie',
'description' => 'Vidéos sur la technologie, l\'informatique, et les innovations tech.',
'image' => 'img/categories/tech.jpg'
],
2 => [
'id' => 2,
'name' => 'Culture',
'description' => 'Culture locale et internationale, événements culturels, et arts.',
'image' => 'img/categories/culture.jpg'
],
3 => [
'id' => 3,
'name' => 'Éducation',
'description' => 'Tutoriels, cours, et vidéos éducatives pour apprendre de nouvelles compétences.',
'image' => 'img/categories/education.jpg'
],
4 => [
'id' => 4,
'name' => 'Divertissement',
'description' => 'Vidéos de divertissement, humour, jeux et loisirs.',
'image' => 'img/categories/entertainment.jpg'
],
5 => [
'id' => 5,
'name' => 'Cuisine',
'description' => 'Recettes, conseils culinaires, et découverte de la gastronomie locale et internationale.',
'image' => 'img/categories/cuisine.jpg'
],
6 => [
'id' => 6,
'name' => 'Voyage',
'description' => 'Découverte de destinations, conseils de voyage, et aventures autour du monde.',
'image' => 'img/categories/travel.jpg'
],
7 => [
'id' => 7,
'name' => 'Sport',
'description' => 'Événements sportifs, tutoriels d\'entraînement, et actualités sportives.',
'image' => 'img/categories/sport.jpg'
],
8 => [
'id' => 8,
'name' => 'Musique',
'description' => 'Clips musicaux, concerts, et actualités musicales locales et internationales.',
'image' => 'img/categories/music.jpg'
]
];
// Définition des vidéos (dans un vrai projet, ces données viendraient d'une API)
$videos = [
[
'id' => 1,
'title' => 'Introduction à la culture libre et aux logiciels open source',
'thumbnail' => 'img/video-thumbnails/featured-1.jpg',
'duration' => 1245,
'channel' => 'Tech Libre',
'views' => 15420,
'date' => '2023-11-15',
'category_id' => 1
],
[
'id' => 2,
'title' => 'La Réunion: Découverte des sentiers cachés',
'thumbnail' => 'img/video-thumbnails/featured-2.jpg',
'duration' => 843,
'channel' => 'Île Aventure',
'views' => 8745,
'date' => '2023-12-02',
'category_id' => 6
],
[
'id' => 3,
'title' => 'Comment installer Linux sur un ancien ordinateur',
'thumbnail' => 'img/video-thumbnails/featured-3.jpg',
'duration' => 723,
'channel' => 'Tech Libre',
'views' => 24680,
'date' => '2023-10-25',
'category_id' => 1
],
[
'id' => 4,
'title' => 'Festival Sakifo 2023 - Les meilleurs moments',
'thumbnail' => 'img/video-thumbnails/recent-1.jpg',
'duration' => 1832,
'channel' => 'Culture 974',
'views' => 3420,
'date' => '2023-12-15',
'category_id' => 2
],
[
'id' => 5,
'title' => 'Cuisine créole: Recette du rougail saucisse traditionnel',
'thumbnail' => 'img/video-thumbnails/recent-2.jpg',
'duration' => 685,
'channel' => 'Saveurs des Îles',
'views' => 7245,
'date' => '2023-12-10',
'category_id' => 5
],
[
'id' => 6,
'title' => 'Tutoriel: Créer votre première application web avec PHP',
'thumbnail' => 'img/video-thumbnails/recent-3.jpg',
'duration' => 1540,
'channel' => 'CodeMastery',
'views' => 2180,
'date' => '2023-12-08',
'category_id' => 3
],
[
'id' => 7,
'title' => 'Les plus belles plages de La Réunion en 2023',
'thumbnail' => 'img/video-thumbnails/recent-4.jpg',
'duration' => 925,
'channel' => 'Île Aventure',
'views' => 5690,
'date' => '2023-12-05',
'category_id' => 6
],
[
'id' => 8,
'title' => 'Débat: L\'avenir du numérique à La Réunion',
'thumbnail' => 'img/video-thumbnails/recent-5.jpg',
'duration' => 3245,
'channel' => 'Tech Libre',
'views' => 1250,
'date' => '2023-12-01',
'category_id' => 1
],
[
'id' => 9,
'title' => 'Concert live: Groupe Sakili au Téat Plein Air',
'thumbnail' => 'img/video-thumbnails/recent-6.jpg',
'duration' => 4512,
'channel' => 'Culture 974',
'views' => 4325,
'date' => '2023-11-28',
'category_id' => 8
]
];
// Filtrer les vidéos par catégorie si une catégorie est sélectionnée
if ($categoryId) {
$categoryVideos = array_filter($videos, function($video) use ($categoryId) {
return $video['category_id'] == $categoryId;
});
$category = isset($categories[$categoryId]) ? $categories[$categoryId] : null;
} else {
$categoryVideos = [];
$category = null;
}
// Fonctions utilitaires
function formatDuration($seconds) {
$hours = floor($seconds / 3600);
$minutes = floor(($seconds % 3600) / 60);
$remainingSeconds = $seconds % 60;
if ($hours > 0) {
return sprintf('%d:%02d:%02d', $hours, $minutes, $remainingSeconds);
} else {
return sprintf('%d:%02d', $minutes, $remainingSeconds);
}
}
function formatViewCount($views) {
if ($views >= 1000000) {
return round($views / 1000000, 1) . 'M';
} elseif ($views >= 1000) {
return round($views / 1000, 1) . 'K';
} else {
return $views;
}
}
function formatDate($dateString) {
$date = new DateTime($dateString);
$now = new DateTime();
$interval = $now->diff($date);
if ($interval->days == 0) {
return 'Aujourd\'hui';
} elseif ($interval->days == 1) {
return 'Hier';
} elseif ($interval->days < 7) {
return 'Il y a ' . $interval->days . ' jours';
} elseif ($interval->days < 30) {
$weeks = floor($interval->days / 7);
return 'Il y a ' . $weeks . ' semaine' . ($weeks > 1 ? 's' : '');
} elseif ($interval->days < 365) {
$months = floor($interval->days / 30);
return 'Il y a ' . $months . ' mois';
} else {
$years = floor($interval->days / 365);
return 'Il y a ' . $years . ' an' . ($years > 1 ? 's' : '');
}
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $category ? $category['name'] : 'Catégories'; ?> - Kaubuntu.re</title>
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/categories.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<div class="container">
<?php include 'includes/header.php'; ?>
<main>
<?php if ($category): ?>
<!-- Affichage des vidéos d'une catégorie spécifique -->
<div class="category-header">
<div class="category-banner" style="background-image: url('<?php echo $category['image']; ?>')">
<div class="category-overlay">
<h1><?php echo $category['name']; ?></h1>
</div>
</div>
<div class="category-description">
<p><?php echo $category['description']; ?></p>
</div>
</div>
<?php if (count($categoryVideos) > 0): ?>
<div class="category-videos">
<div class="video-grid">
<?php foreach ($categoryVideos as $video): ?>
<div class="video-card" data-video-id="<?php echo $video['id']; ?>">
<div class="video-thumbnail">
<img src="<?php echo $video['thumbnail']; ?>" alt="<?php echo $video['title']; ?>" data-src="<?php echo $video['thumbnail']; ?>">
<div class="video-duration"><?php echo formatDuration($video['duration']); ?></div>
</div>
<div class="video-info">
<h3 class="video-title"><?php echo $video['title']; ?></h3>
<div class="video-channel"><?php echo $video['channel']; ?></div>
<div class="video-metadata">
<span class="video-views"><?php echo formatViewCount($video['views']); ?> vues</span>
<span class="video-date"><?php echo formatDate($video['date']); ?></span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php else: ?>
<div class="no-videos">
<p>Aucune vidéo trouvée dans cette catégorie.</p>
</div>
<?php endif; ?>
<?php else: ?>
<!-- Affichage de toutes les catégories -->
<section class="categories-showcase">
<h1>Catégories</h1>
<p class="section-description">Découvrez nos vidéos par catégories</p>
<div class="categories-grid">
<?php foreach ($categories as $category): ?>
<a href="categories.php?id=<?php echo $category['id']; ?>" class="category-card-large">
<img src="<?php echo $category['image']; ?>" alt="<?php echo $category['name']; ?>" data-src="<?php echo $category['image']; ?>">
<div class="category-overlay">
<div class="category-name-large"><?php echo $category['name']; ?></div>
<div class="category-description-short"><?php echo substr($category['description'], 0, 60); ?>...</div>
</div>
</a>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
</main>
<?php include 'includes/footer.php'; ?>
</div>
<div class="mobile-menu-overlay" id="mobileMenuOverlay">
<?php include 'includes/mobile-menu.php'; ?>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="js/main.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Gestion des clics sur les cartes vidéo
const videoCards = document.querySelectorAll('.video-card');
videoCards.forEach(card => {
card.addEventListener('click', function() {
const videoId = this.dataset.videoId;
if (videoId) {
window.location.href = 'video.php?id=' + videoId;
}
});
});
});
</script>
</body>
</html>
+157
View File
@@ -0,0 +1,157 @@
/* Styles pour la page des catégories */
/* Page d'accueil des catégories */
.categories-showcase {
margin: 30px 0;
}
.categories-showcase h1 {
font-size: 2rem;
margin-bottom: 10px;
color: #333;
text-align: center;
}
.section-description {
text-align: center;
color: #666;
margin-bottom: 30px;
}
.categories-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.category-card-large {
position: relative;
height: 200px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.3s;
}
.category-card-large:hover {
transform: translateY(-5px);
}
.category-card-large img {
width: 100%;
height: 100%;
object-fit: cover;
}
.category-card-large .category-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.3));
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 20px;
}
.category-name-large {
color: white;
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 5px;
}
.category-description-short {
color: rgba(255, 255, 255, 0.8);
font-size: 0.9rem;
}
/* Page d'une catégorie spécifique */
.category-header {
margin-bottom: 30px;
}
.category-banner {
height: 250px;
background-size: cover;
background-position: center;
position: relative;
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
}
.category-banner .category-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.3));
display: flex;
align-items: center;
justify-content: center;
}
.category-banner h1 {
color: white;
font-size: 2.5rem;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.category-description {
text-align: center;
max-width: 800px;
margin: 0 auto;
color: #555;
font-size: 1.1rem;
line-height: 1.6;
}
.category-videos {
margin-bottom: 40px;
}
.no-videos {
text-align: center;
padding: 40px 0;
color: #666;
}
/* Media Queries */
@media (min-width: 768px) {
.categories-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 992px) {
.categories-grid {
grid-template-columns: repeat(4, 1fr);
}
}
@media (max-width: 576px) {
.categories-grid {
grid-template-columns: 1fr;
}
.category-card-large {
height: 150px;
}
.category-banner {
height: 180px;
}
.category-banner h1 {
font-size: 1.8rem;
}
.category-description {
font-size: 0.95rem;
}
}
+206
View File
@@ -0,0 +1,206 @@
/* Styles pour la page de recherche */
.search-page {
margin: 30px 0;
}
.search-header {
margin-bottom: 30px;
}
.search-header h1 {
font-size: 1.8rem;
margin-bottom: 15px;
color: #333;
}
.search-container {
max-width: 600px;
margin-bottom: 20px;
}
.search-container form {
display: flex;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
.search-container input {
flex: 1;
padding: 12px 15px;
border: none;
font-size: 16px;
}
.search-container button {
background-color: #0d6efd;
color: white;
border: none;
padding: 0 20px;
cursor: pointer;
font-size: 18px;
}
.search-results-count {
margin-bottom: 20px;
color: #666;
font-size: 14px;
}
.search-results {
display: flex;
flex-direction: column;
gap: 20px;
}
.search-result-item {
display: flex;
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: transform 0.2s;
}
.search-result-item:hover {
transform: translateY(-3px);
}
.search-result-thumbnail {
width: 246px;
height: 138px;
position: relative;
flex-shrink: 0;
}
.search-result-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.video-duration {
position: absolute;
bottom: 8px;
right: 8px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
}
.search-result-info {
padding: 15px;
flex: 1;
}
.search-result-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
color: #333;
}
.search-result-metadata {
display: flex;
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
.search-result-views {
margin-right: 15px;
}
.search-result-channel {
font-size: 14px;
color: #0d6efd;
margin-bottom: 8px;
}
.search-result-description {
font-size: 14px;
color: #555;
margin-bottom: 10px;
line-height: 1.4;
}
.search-result-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag {
font-size: 12px;
color: #0d6efd;
}
.no-results, .search-instructions {
text-align: center;
padding: 60px 0;
color: #666;
}
.no-results i, .search-instructions i {
color: #ccc;
margin-bottom: 20px;
}
.no-results h2, .search-instructions h2 {
font-size: 20px;
margin-bottom: 10px;
color: #333;
}
.no-results p, .search-instructions p {
font-size: 16px;
max-width: 500px;
margin: 0 auto;
}
/* Media Queries */
@media (max-width: 768px) {
.search-result-item {
flex-direction: column;
}
.search-result-thumbnail {
width: 100%;
height: 0;
padding-top: 56.25%; /* 16:9 aspect ratio */
}
.search-result-thumbnail img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.search-header h1 {
font-size: 1.5rem;
}
}
@media (max-width: 576px) {
.search-container input {
padding: 10px;
}
.search-container button {
padding: 0 15px;
}
.search-result-title {
font-size: 16px;
}
.search-result-description {
font-size: 13px;
}
}
+397
View File
@@ -0,0 +1,397 @@
/* Styles généraux */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f8f9fa;
}
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 15px;
}
a {
text-decoration: none;
color: #0d6efd;
}
img {
max-width: 100%;
height: auto;
}
/* Header */
header {
background-color: #fff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 100;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
}
.logo img {
height: 40px;
}
.desktop-nav ul {
display: flex;
list-style: none;
}
.desktop-nav ul li {
margin-left: 20px;
}
.desktop-nav ul li a {
color: #333;
font-weight: 500;
transition: color 0.3s;
}
.desktop-nav ul li a:hover {
color: #0d6efd;
}
.mobile-menu-toggle {
display: none;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #333;
}
/* Hero Section */
.hero {
background-color: #0d6efd;
color: white;
padding: 60px 0;
text-align: center;
margin-bottom: 40px;
}
.hero-content h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.hero-content p {
font-size: 1.2rem;
margin-bottom: 30px;
}
.search-container {
max-width: 600px;
margin: 0 auto;
}
.search-container form {
display: flex;
}
.search-container input {
flex: 1;
padding: 12px 15px;
border: none;
border-radius: 4px 0 0 4px;
font-size: 16px;
}
.search-container button {
background-color: #fff;
color: #0d6efd;
border: none;
border-radius: 0 4px 4px 0;
padding: 0 20px;
cursor: pointer;
font-size: 18px;
}
/* Video Sections */
section {
margin-bottom: 40px;
}
section h2 {
font-size: 1.8rem;
margin-bottom: 20px;
color: #333;
}
.video-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.video-card {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.3s;
}
.video-card:hover {
transform: translateY(-5px);
}
.video-thumbnail {
position: relative;
padding-top: 56.25%; /* 16:9 aspect ratio */
}
.video-thumbnail img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.video-duration {
position: absolute;
bottom: 10px;
right: 10px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
}
.video-info {
padding: 15px;
}
.video-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
}
.video-channel {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.video-metadata {
display: flex;
font-size: 12px;
color: #888;
}
.video-views {
margin-right: 10px;
}
/* Categories Section */
.category-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
}
.category-card {
position: relative;
height: 120px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.category-card img {
width: 100%;
height: 100%;
object-fit: cover;
}
.category-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
display: flex;
align-items: flex-end;
padding: 15px;
}
.category-name {
color: white;
font-weight: 600;
}
/* Footer */
footer {
background-color: #333;
color: #fff;
padding: 40px 0;
}
.footer-container {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.footer-column {
flex: 1;
min-width: 200px;
margin-bottom: 20px;
}
.footer-column h3 {
margin-bottom: 15px;
font-size: 18px;
}
.footer-column ul {
list-style: none;
}
.footer-column ul li {
margin-bottom: 8px;
}
.footer-column ul li a {
color: #ccc;
transition: color 0.3s;
}
.footer-column ul li a:hover {
color: #fff;
}
.footer-bottom {
text-align: center;
padding-top: 20px;
margin-top: 20px;
border-top: 1px solid #444;
}
/* Mobile Menu */
.mobile-menu-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 200;
display: none;
}
.mobile-menu {
position: fixed;
top: 0;
left: 0;
width: 250px;
height: 100%;
background-color: white;
padding: 20px;
overflow-y: auto;
}
.mobile-menu-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.mobile-menu-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
}
.mobile-menu-nav ul {
list-style: none;
}
.mobile-menu-nav ul li {
margin-bottom: 15px;
}
.mobile-menu-nav ul li a {
color: #333;
font-size: 18px;
font-weight: 500;
}
/* Media Queries */
@media (max-width: 992px) {
.video-grid {
grid-template-columns: repeat(2, 1fr);
}
.category-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 768px) {
.desktop-nav {
display: none;
}
.mobile-menu-toggle {
display: block;
}
.hero {
padding: 40px 0;
}
.hero-content h1 {
font-size: 2rem;
}
.footer-container {
flex-direction: column;
}
.footer-column {
margin-bottom: 30px;
}
}
@media (max-width: 576px) {
.video-grid {
grid-template-columns: 1fr;
}
.category-grid {
grid-template-columns: repeat(2, 1fr);
}
.hero-content h1 {
font-size: 1.8rem;
}
.search-container input,
.search-container button {
padding: 10px;
}
}
+372
View File
@@ -0,0 +1,372 @@
/* Styles pour la page vidéo */
.video-page {
display: grid;
grid-template-columns: 1fr 350px;
gap: 30px;
margin-top: 20px;
}
.video-player-container {
grid-column: 1 / -1;
}
.video-player {
position: relative;
padding-top: 56.25%; /* Ratio 16:9 */
width: 100%;
}
.video-player iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 8px;
}
.video-content {
grid-column: 1;
}
.video-primary-info {
border-bottom: 1px solid #ddd;
padding-bottom: 15px;
margin-bottom: 15px;
}
.video-title {
font-size: 1.5rem;
margin-bottom: 10px;
font-weight: 600;
}
.video-stats {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
color: #666;
font-size: 14px;
}
.video-date {
margin-left: 15px;
}
.video-actions {
display: flex;
margin-top: 10px;
}
.action-button {
display: flex;
align-items: center;
background: none;
border: none;
cursor: pointer;
margin-right: 20px;
color: #333;
font-size: 14px;
}
.action-button i {
margin-right: 5px;
font-size: 18px;
}
.video-secondary-info {
margin-bottom: 30px;
}
.channel-info {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.channel-image {
width: 50px;
height: 50px;
margin-right: 15px;
}
.channel-image img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.channel-details {
flex: 1;
}
.channel-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 5px;
}
.subscribe-button {
background-color: #0d6efd;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s;
}
.subscribe-button:hover {
background-color: #0b5ed7;
}
.video-description {
margin-bottom: 20px;
line-height: 1.6;
font-size: 14px;
}
.video-tags {
margin-top: 10px;
}
.tag {
display: inline-block;
color: #0d6efd;
margin-right: 10px;
font-size: 14px;
}
.video-comments h2 {
font-size: 18px;
margin-bottom: 15px;
}
.comment-form {
margin-bottom: 25px;
}
.comment-form textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 10px;
resize: vertical;
min-height: 80px;
}
.comment-submit {
background-color: #0d6efd;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
float: right;
}
.comments-list {
margin-top: 20px;
}
.comment {
display: flex;
margin-bottom: 20px;
}
.comment-avatar {
width: 40px;
height: 40px;
margin-right: 15px;
}
.comment-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.comment-content {
flex: 1;
}
.comment-header {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.comment-author {
font-weight: 600;
margin-right: 10px;
}
.comment-date {
font-size: 12px;
color: #666;
}
.comment-text {
font-size: 14px;
margin-bottom: 5px;
}
.comment-actions button {
background: none;
border: none;
color: #666;
margin-right: 15px;
font-size: 12px;
cursor: pointer;
}
.comment-actions button i {
margin-right: 5px;
}
/* Vidéos associées */
.related-videos {
grid-column: 2;
}
.related-videos h2 {
font-size: 18px;
margin-bottom: 15px;
}
.related-video-card {
display: flex;
margin-bottom: 15px;
cursor: pointer;
}
.related-video-thumbnail {
width: 120px;
height: 68px;
margin-right: 10px;
position: relative;
}
.related-video-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 4px;
}
.related-video-info {
flex: 1;
}
.related-video-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.related-video-channel {
font-size: 12px;
color: #666;
margin-bottom: 2px;
}
.related-video-metadata {
font-size: 12px;
color: #888;
}
/* Media Queries */
@media (max-width: 992px) {
.video-page {
grid-template-columns: 1fr;
}
.related-videos {
grid-column: 1;
}
.related-videos-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
.related-video-card {
flex-direction: column;
margin-bottom: 0;
}
.related-video-thumbnail {
width: 100%;
height: 0;
padding-top: 56.25%;
margin-right: 0;
margin-bottom: 8px;
}
.related-video-thumbnail img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
@media (max-width: 768px) {
.video-stats {
flex-direction: column;
align-items: flex-start;
}
.video-actions {
width: 100%;
margin-top: 15px;
justify-content: space-between;
}
.action-button {
margin-right: 0;
}
.video-date {
margin-left: 0;
margin-top: 5px;
}
}
@media (max-width: 576px) {
.related-videos-list {
grid-template-columns: 1fr;
}
.related-video-card {
flex-direction: row;
margin-bottom: 15px;
}
.related-video-thumbnail {
width: 120px;
height: 68px;
padding-top: 0;
margin-right: 10px;
margin-bottom: 0;
}
.comment {
flex-direction: column;
}
.comment-avatar {
margin-bottom: 10px;
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
// Dans un vrai projet, ces données viendraient d'une API PeerTube
// Pour cet exemple, on utilise des données statiques
$categories = [
[
'id' => 1,
'name' => 'Technologie',
'image' => 'img/categories/tech.jpg'
],
[
'id' => 2,
'name' => 'Culture',
'image' => 'img/categories/culture.jpg'
],
[
'id' => 3,
'name' => 'Éducation',
'image' => 'img/categories/education.jpg'
],
[
'id' => 4,
'name' => 'Divertissement',
'image' => 'img/categories/entertainment.jpg'
],
[
'id' => 5,
'name' => 'Cuisine',
'image' => 'img/categories/cuisine.jpg'
],
[
'id' => 6,
'name' => 'Voyage',
'image' => 'img/categories/travel.jpg'
],
[
'id' => 7,
'name' => 'Sport',
'image' => 'img/categories/sport.jpg'
],
[
'id' => 8,
'name' => 'Musique',
'image' => 'img/categories/music.jpg'
]
];
// Affichage des catégories
foreach ($categories as $category) :
?>
<a href="categories.php?id=<?php echo $category['id']; ?>" class="category-card">
<img src="<?php echo $category['image']; ?>" alt="<?php echo $category['name']; ?>" data-src="<?php echo $category['image']; ?>">
<div class="category-overlay">
<div class="category-name"><?php echo $category['name']; ?></div>
</div>
</a>
<?php endforeach; ?>
+92
View File
@@ -0,0 +1,92 @@
<?php
/**
* Configuration de Kaubuntu.re
*
* Ce fichier contient les paramètres de configuration pour connecter
* la plateforme à une instance PeerTube et personnaliser le site.
*/
// URL de base de votre instance PeerTube
define('PEERTUBE_URL', 'https://gade.o-k-i.net');
// Paramètres d'API (si nécessaire)
define('API_KEY', ''); // Laissez vide si pas nécessaire
// Pagination et affichage
define('VIDEOS_PER_PAGE', 12);
define('FEATURED_VIDEOS_COUNT', 3);
define('RECENT_VIDEOS_COUNT', 6);
// Informations du site
define('SITE_NAME', 'Kaubuntu.re');
define('SITE_DESCRIPTION', 'Votre plateforme de médias libres');
define('SITE_LOGO', 'img/logo.png');
define('SITE_FAVICON', 'img/favicon.png');
// Réseaux sociaux
define('FACEBOOK_URL', '#');
define('TWITTER_URL', '#');
define('INSTAGRAM_URL', '#');
define('YOUTUBE_URL', '#');
// Contacts
define('CONTACT_EMAIL', 'contact@kaubuntu.re');
// Fonctionnalités
define('ENABLE_COMMENTS', true);
define('ENABLE_SEARCH', true);
define('ENABLE_USER_ACCOUNTS', false); // À implémenter dans une future version
// Cache
define('CACHE_ENABLED', false);
define('CACHE_DURATION', 3600); // En secondes (1 heure)
// Locale et fuseau horaire
setlocale(LC_TIME, 'fr_FR.UTF-8');
date_default_timezone_set('Indian/Reunion');
/**
* Fonction utilitaire pour appeler l'API PeerTube
*
* @param string $endpoint Point de terminaison de l'API
* @param array $params Paramètres optionnels pour la requête
* @return array Données retournées par l'API
*/
function callPeerTubeApi($endpoint, $params = []) {
$url = PEERTUBE_URL . '/api/v1/' . $endpoint;
// Ajouter les paramètres à l'URL
if (!empty($params)) {
$url .= '?' . http_build_query($params);
}
// Initialiser cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Ajouter la clé API si définie
if (defined('API_KEY') && !empty(API_KEY)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: ApiKey ' . API_KEY
]);
}
// Exécuter la requête
$response = curl_exec($ch);
curl_close($ch);
// Traiter la réponse
if ($response === false) {
// En cas d'erreur, retourner un tableau vide
return [];
}
// Décoder la réponse JSON
$data = json_decode($response, true);
return $data ?: [];
}
// Pour une version future: implémenter un système de mise en cache des requêtes API
?>
+99
View File
@@ -0,0 +1,99 @@
<?php
// Dans un vrai projet, ces données viendraient d'une API PeerTube
// Pour cet exemple, on utilise des données statiques
$featuredVideos = [
[
'id' => 1,
'title' => 'Introduction à la culture libre et aux logiciels open source',
'thumbnail' => 'img/video-thumbnails/featured-1.jpg',
'duration' => 1245, // en secondes
'channel' => 'Tech Libre',
'views' => 15420,
'date' => '2023-11-15'
],
[
'id' => 2,
'title' => 'La Réunion: Découverte des sentiers cachés',
'thumbnail' => 'img/video-thumbnails/featured-2.jpg',
'duration' => 843,
'channel' => 'Île Aventure',
'views' => 8745,
'date' => '2023-12-02'
],
[
'id' => 3,
'title' => 'Comment installer Linux sur un ancien ordinateur',
'thumbnail' => 'img/video-thumbnails/featured-3.jpg',
'duration' => 723,
'channel' => 'Tech Libre',
'views' => 24680,
'date' => '2023-10-25'
]
];
// Affichage des vidéos
foreach ($featuredVideos as $video) :
?>
<div class="video-card" data-video-id="<?php echo $video['id']; ?>">
<div class="video-thumbnail">
<img src="<?php echo $video['thumbnail']; ?>" alt="<?php echo $video['title']; ?>" data-src="<?php echo $video['thumbnail']; ?>">
<div class="video-duration"><?php echo formatDuration($video['duration']); ?></div>
</div>
<div class="video-info">
<h3 class="video-title"><?php echo $video['title']; ?></h3>
<div class="video-channel"><?php echo $video['channel']; ?></div>
<div class="video-metadata">
<span class="video-views"><?php echo formatViewCount($video['views']); ?> vues</span>
<span class="video-date"><?php echo formatDate($video['date']); ?></span>
</div>
</div>
</div>
<?php endforeach; ?>
<?php
// Fonctions utilitaires (dans un vrai projet, ces fonctions seraient dans un fichier séparé)
function formatDuration($seconds) {
$hours = floor($seconds / 3600);
$minutes = floor(($seconds % 3600) / 60);
$remainingSeconds = $seconds % 60;
if ($hours > 0) {
return sprintf('%d:%02d:%02d', $hours, $minutes, $remainingSeconds);
} else {
return sprintf('%d:%02d', $minutes, $remainingSeconds);
}
}
function formatViewCount($views) {
if ($views >= 1000000) {
return round($views / 1000000, 1) . 'M';
} elseif ($views >= 1000) {
return round($views / 1000, 1) . 'K';
} else {
return $views;
}
}
function formatDate($dateString) {
$date = new DateTime($dateString);
$now = new DateTime();
$interval = $now->diff($date);
if ($interval->days == 0) {
return 'Aujourd\'hui';
} elseif ($interval->days == 1) {
return 'Hier';
} elseif ($interval->days < 7) {
return 'Il y a ' . $interval->days . ' jours';
} elseif ($interval->days < 30) {
$weeks = floor($interval->days / 7);
return 'Il y a ' . $weeks . ' semaine' . ($weeks > 1 ? 's' : '');
} elseif ($interval->days < 365) {
$months = floor($interval->days / 30);
return 'Il y a ' . $months . ' mois';
} else {
$years = floor($interval->days / 365);
return 'Il y a ' . $years . ' an' . ($years > 1 ? 's' : '');
}
}
?>
+50
View File
@@ -0,0 +1,50 @@
<footer>
<div class="container">
<div class="footer-container">
<div class="footer-column">
<h3>À propos</h3>
<ul>
<li><a href="about.php">Qui sommes-nous</a></li>
<li><a href="mission.php">Notre mission</a></li>
<li><a href="team.php">L'équipe</a></li>
<li><a href="partners.php">Partenaires</a></li>
</ul>
</div>
<div class="footer-column">
<h3>Catégories</h3>
<ul>
<li><a href="categories.php?id=1">Technologie</a></li>
<li><a href="categories.php?id=2">Culture</a></li>
<li><a href="categories.php?id=3">Éducation</a></li>
<li><a href="categories.php?id=4">Divertissement</a></li>
</ul>
</div>
<div class="footer-column">
<h3>Aide</h3>
<ul>
<li><a href="faq.php">FAQ</a></li>
<li><a href="contact.php">Contact</a></li>
<li><a href="privacy.php">Politique de confidentialité</a></li>
<li><a href="terms.php">Conditions d'utilisation</a></li>
</ul>
</div>
<div class="footer-column">
<h3>Suivez-nous</h3>
<ul>
<li><a href="#"><i class="fab fa-facebook"></i> Facebook</a></li>
<li><a href="#"><i class="fab fa-twitter"></i> Twitter</a></li>
<li><a href="#"><i class="fab fa-instagram"></i> Instagram</a></li>
<li><a href="#"><i class="fab fa-youtube"></i> YouTube</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>&copy; <?php echo date('Y'); ?> Kaubuntu.re - Tous droits réservés</p>
<p>Propulsé par <a href="https://joinpeertube.org/" target="_blank">PeerTube</a></p>
</div>
</div>
</footer>
+25
View File
@@ -0,0 +1,25 @@
<header>
<div class="container">
<div class="header-container">
<div class="logo">
<a href="index.php">
<img src="img/logo.png" alt="Kaubuntu.re">
</a>
</div>
<nav class="desktop-nav">
<ul>
<li><a href="index.php">Accueil</a></li>
<li><a href="categories.php">Catégories</a></li>
<li><a href="channels.php">Chaînes</a></li>
<li><a href="about.php">À propos</a></li>
<li><a href="contact.php">Contact</a></li>
</ul>
</nav>
<button class="mobile-menu-toggle">
<i class="fas fa-bars"></i>
</button>
</div>
</div>
</header>
+42
View File
@@ -0,0 +1,42 @@
<div class="mobile-menu">
<div class="mobile-menu-header">
<div class="logo">
<a href="index.php">
<img src="img/logo.png" alt="Kaubuntu.re">
</a>
</div>
<button class="mobile-menu-close">
<i class="fas fa-times"></i>
</button>
</div>
<nav class="mobile-menu-nav">
<ul>
<li><a href="index.php"><i class="fas fa-home"></i> Accueil</a></li>
<li><a href="categories.php"><i class="fas fa-th-large"></i> Catégories</a></li>
<li><a href="channels.php"><i class="fas fa-users"></i> Chaînes</a></li>
<li><a href="about.php"><i class="fas fa-info-circle"></i> À propos</a></li>
<li><a href="contact.php"><i class="fas fa-envelope"></i> Contact</a></li>
</ul>
</nav>
<div class="mobile-menu-section">
<h3>Catégories populaires</h3>
<ul>
<li><a href="categories.php?id=1">Technologie</a></li>
<li><a href="categories.php?id=2">Culture</a></li>
<li><a href="categories.php?id=3">Éducation</a></li>
<li><a href="categories.php?id=4">Divertissement</a></li>
</ul>
</div>
<div class="mobile-menu-section">
<h3>Suivez-nous</h3>
<div class="social-icons">
<a href="#"><i class="fab fa-facebook"></i></a>
<a href="#"><i class="fab fa-twitter"></i></a>
<a href="#"><i class="fab fa-instagram"></i></a>
<a href="#"><i class="fab fa-youtube"></i></a>
</div>
</div>
</div>
+78
View File
@@ -0,0 +1,78 @@
<?php
// Dans un vrai projet, ces données viendraient d'une API PeerTube
// Pour cet exemple, on utilise des données statiques
$recentVideos = [
[
'id' => 4,
'title' => 'Festival Sakifo 2023 - Les meilleurs moments',
'thumbnail' => 'img/video-thumbnails/recent-1.jpg',
'duration' => 1832, // en secondes
'channel' => 'Culture 974',
'views' => 3420,
'date' => '2023-12-15'
],
[
'id' => 5,
'title' => 'Cuisine créole: Recette du rougail saucisse traditionnel',
'thumbnail' => 'img/video-thumbnails/recent-2.jpg',
'duration' => 685,
'channel' => 'Saveurs des Îles',
'views' => 7245,
'date' => '2023-12-10'
],
[
'id' => 6,
'title' => 'Tutoriel: Créer votre première application web avec PHP',
'thumbnail' => 'img/video-thumbnails/recent-3.jpg',
'duration' => 1540,
'channel' => 'CodeMastery',
'views' => 2180,
'date' => '2023-12-08'
],
[
'id' => 7,
'title' => 'Les plus belles plages de La Réunion en 2023',
'thumbnail' => 'img/video-thumbnails/recent-4.jpg',
'duration' => 925,
'channel' => 'Île Aventure',
'views' => 5690,
'date' => '2023-12-05'
],
[
'id' => 8,
'title' => 'Débat: L\'avenir du numérique à La Réunion',
'thumbnail' => 'img/video-thumbnails/recent-5.jpg',
'duration' => 3245,
'channel' => 'Tech Libre',
'views' => 1250,
'date' => '2023-12-01'
],
[
'id' => 9,
'title' => 'Concert live: Groupe Sakili au Téat Plein Air',
'thumbnail' => 'img/video-thumbnails/recent-6.jpg',
'duration' => 4512,
'channel' => 'Culture 974',
'views' => 4325,
'date' => '2023-11-28'
]
];
// Affichage des vidéos
foreach ($recentVideos as $video) :
?>
<div class="video-card" data-video-id="<?php echo $video['id']; ?>">
<div class="video-thumbnail">
<img src="<?php echo $video['thumbnail']; ?>" alt="<?php echo $video['title']; ?>" data-src="<?php echo $video['thumbnail']; ?>">
<div class="video-duration"><?php echo formatDuration($video['duration']); ?></div>
</div>
<div class="video-info">
<h3 class="video-title"><?php echo $video['title']; ?></h3>
<div class="video-channel"><?php echo $video['channel']; ?></div>
<div class="video-metadata">
<span class="video-views"><?php echo formatViewCount($video['views']); ?> vues</span>
<span class="video-date"><?php echo formatDate($video['date']); ?></span>
</div>
</div>
</div>
<?php endforeach; ?>
+60
View File
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kaubuntu.re - Plateforme Multimédia</title>
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<div class="container">
<?php include 'includes/header.php'; ?>
<main>
<section class="hero">
<div class="hero-content">
<h1>Bienvenue sur Kaubuntu.re</h1>
<p>Votre plateforme de médias libres</p>
<div class="search-container">
<form action="search.php" method="get">
<input type="text" name="query" placeholder="Rechercher des vidéos...">
<button type="submit"><i class="fas fa-search"></i></button>
</form>
</div>
</div>
</section>
<section class="featured-videos">
<h2>Vidéos à la une</h2>
<div class="video-grid">
<?php include 'includes/featured-videos.php'; ?>
</div>
</section>
<section class="recent-videos">
<h2>Vidéos récentes</h2>
<div class="video-grid">
<?php include 'includes/recent-videos.php'; ?>
</div>
</section>
<section class="categories">
<h2>Catégories</h2>
<div class="category-grid">
<?php include 'includes/categories.php'; ?>
</div>
</section>
</main>
<?php include 'includes/footer.php'; ?>
</div>
<div class="mobile-menu-overlay" id="mobileMenuOverlay">
<?php include 'includes/mobile-menu.php'; ?>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
+128
View File
@@ -0,0 +1,128 @@
document.addEventListener('DOMContentLoaded', function() {
// Gestion du menu mobile
const mobileMenuToggle = document.querySelector('.mobile-menu-toggle');
const mobileMenuOverlay = document.getElementById('mobileMenuOverlay');
const mobileMenuClose = document.querySelector('.mobile-menu-close');
if (mobileMenuToggle) {
mobileMenuToggle.addEventListener('click', function() {
mobileMenuOverlay.style.display = 'block';
document.body.style.overflow = 'hidden'; // Empêche le défilement de la page
});
}
if (mobileMenuClose) {
mobileMenuClose.addEventListener('click', function() {
mobileMenuOverlay.style.display = 'none';
document.body.style.overflow = ''; // Réactive le défilement
});
}
// Fermer le menu en cliquant en dehors
if (mobileMenuOverlay) {
mobileMenuOverlay.addEventListener('click', function(e) {
if (e.target === mobileMenuOverlay) {
mobileMenuOverlay.style.display = 'none';
document.body.style.overflow = '';
}
});
}
// Gestion des vidéos
const videoCards = document.querySelectorAll('.video-card');
videoCards.forEach(card => {
card.addEventListener('click', function() {
const videoId = this.dataset.videoId;
if (videoId) {
window.location.href = 'video.php?id=' + videoId;
}
});
});
// Lazy loading des images
if ('IntersectionObserver' in window) {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const src = img.getAttribute('data-src');
if (src) {
img.src = src;
img.removeAttribute('data-src');
}
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
} else {
// Fallback pour les navigateurs qui ne supportent pas IntersectionObserver
document.querySelectorAll('img[data-src]').forEach(img => {
const src = img.getAttribute('data-src');
if (src) {
img.src = src;
img.removeAttribute('data-src');
}
});
}
// Fonction pour formater le nombre de vues
function formatViewCount(views) {
if (views >= 1000000) {
return (views / 1000000).toFixed(1) + 'M';
} else if (views >= 1000) {
return (views / 1000).toFixed(1) + 'K';
} else {
return views.toString();
}
}
// Fonction pour formater la durée des vidéos
function formatDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
} else {
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
}
}
// Fonction pour formater la date
function formatDate(dateString) {
const date = new Date(dateString);
const now = new Date();
const diffTime = Math.abs(now - date);
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
if (diffDays < 1) {
return 'Aujourd\'hui';
} else if (diffDays === 1) {
return 'Hier';
} else if (diffDays < 7) {
return `Il y a ${diffDays} jours`;
} else if (diffDays < 30) {
const diffWeeks = Math.floor(diffDays / 7);
return `Il y a ${diffWeeks} semaine${diffWeeks > 1 ? 's' : ''}`;
} else if (diffDays < 365) {
const diffMonths = Math.floor(diffDays / 30);
return `Il y a ${diffMonths} mois`;
} else {
const diffYears = Math.floor(diffDays / 365);
return `Il y a ${diffYears} an${diffYears > 1 ? 's' : ''}`;
}
}
// Exposer ces fonctions utilitaires globalement
window.videoUtils = {
formatViewCount,
formatDuration,
formatDate
};
});
+292
View File
@@ -0,0 +1,292 @@
<?php
// Récupération de la requête de recherche
$query = isset($_GET['q']) ? htmlspecialchars($_GET['q']) : '';
// Dans un vrai projet, ces données viendraient d'une API PeerTube
// Pour cet exemple, on utilise des données statiques
$videos = [
[
'id' => 1,
'title' => 'Introduction à la culture libre et aux logiciels open source',
'thumbnail' => 'img/video-thumbnails/featured-1.jpg',
'duration' => 1245,
'channel' => 'Tech Libre',
'views' => 15420,
'date' => '2023-11-15',
'description' => 'Une introduction complète au monde de la culture libre et des logiciels open source. Découvrez les principes fondamentaux, les licences, et comment contribuer à des projets open source.',
'tags' => ['open source', 'logiciels libres', 'tech', 'formation']
],
[
'id' => 2,
'title' => 'La Réunion: Découverte des sentiers cachés',
'thumbnail' => 'img/video-thumbnails/featured-2.jpg',
'duration' => 843,
'channel' => 'Île Aventure',
'views' => 8745,
'date' => '2023-12-02',
'description' => 'Partez à la découverte des sentiers cachés de La Réunion. Cette vidéo vous guide à travers des paysages magnifiques et peu connus de l\'île.',
'tags' => ['La Réunion', 'voyage', 'randonnée', 'nature']
],
[
'id' => 3,
'title' => 'Comment installer Linux sur un ancien ordinateur',
'thumbnail' => 'img/video-thumbnails/featured-3.jpg',
'duration' => 723,
'channel' => 'Tech Libre',
'views' => 24680,
'date' => '2023-10-25',
'description' => 'Tutoriel détaillé pour installer une distribution Linux légère sur un ancien ordinateur et lui donner une seconde vie.',
'tags' => ['linux', 'tutoriel', 'open source', 'recyclage']
],
[
'id' => 4,
'title' => 'Festival Sakifo 2023 - Les meilleurs moments',
'thumbnail' => 'img/video-thumbnails/recent-1.jpg',
'duration' => 1832,
'channel' => 'Culture 974',
'views' => 3420,
'date' => '2023-12-15',
'description' => 'Revivez les meilleurs moments du Festival Sakifo 2023 à Saint-Pierre, avec les performances des artistes locaux et internationaux.',
'tags' => ['musique', 'festival', 'sakifo', 'La Réunion']
],
[
'id' => 5,
'title' => 'Cuisine créole: Recette du rougail saucisse traditionnel',
'thumbnail' => 'img/video-thumbnails/recent-2.jpg',
'duration' => 685,
'channel' => 'Saveurs des Îles',
'views' => 7245,
'date' => '2023-12-10',
'description' => 'Apprenez à préparer un délicieux rougail saucisse traditionnel de La Réunion, avec tous les secrets pour réussir ce plat emblématique.',
'tags' => ['cuisine', 'recette', 'La Réunion', 'créole']
],
[
'id' => 6,
'title' => 'Tutoriel: Créer votre première application web avec PHP',
'thumbnail' => 'img/video-thumbnails/recent-3.jpg',
'duration' => 1540,
'channel' => 'CodeMastery',
'views' => 2180,
'date' => '2023-12-08',
'description' => 'Un guide pas à pas pour créer votre première application web en PHP, depuis l\'installation de l\'environnement jusqu\'au déploiement.',
'tags' => ['php', 'web', 'programmation', 'tutoriel']
],
[
'id' => 7,
'title' => 'Les plus belles plages de La Réunion en 2023',
'thumbnail' => 'img/video-thumbnails/recent-4.jpg',
'duration' => 925,
'channel' => 'Île Aventure',
'views' => 5690,
'date' => '2023-12-05',
'description' => 'Découvrez les plus belles plages de La Réunion, des lagons de l\'ouest aux côtes sauvages du sud, avec des conseils pour chaque site.',
'tags' => ['plages', 'La Réunion', 'voyage', 'mer']
],
[
'id' => 8,
'title' => 'Débat: L\'avenir du numérique à La Réunion',
'thumbnail' => 'img/video-thumbnails/recent-5.jpg',
'duration' => 3245,
'channel' => 'Tech Libre',
'views' => 1250,
'date' => '2023-12-01',
'description' => 'Un débat passionnant sur l\'avenir du numérique à La Réunion, avec des intervenants du secteur public et privé qui discutent des enjeux et opportunités.',
'tags' => ['numérique', 'La Réunion', 'débat', 'technologie']
],
[
'id' => 9,
'title' => 'Concert live: Groupe Sakili au Téat Plein Air',
'thumbnail' => 'img/video-thumbnails/recent-6.jpg',
'duration' => 4512,
'channel' => 'Culture 974',
'views' => 4325,
'date' => '2023-11-28',
'description' => 'Le concert complet du groupe Sakili au Téat Plein Air de Saint-Gilles, un moment magique de musique traditionnelle réunionnaise revisitée.',
'tags' => ['concert', 'musique', 'La Réunion', 'maloya']
]
];
// Fonction de recherche simple (dans un vrai projet, ce serait géré par l'API)
function searchVideos($videos, $query) {
if (empty($query)) return [];
$results = [];
$lowercaseQuery = strtolower($query);
foreach ($videos as $video) {
// Recherche dans le titre
if (strpos(strtolower($video['title']), $lowercaseQuery) !== false) {
$results[] = $video;
continue;
}
// Recherche dans la description
if (strpos(strtolower($video['description']), $lowercaseQuery) !== false) {
$results[] = $video;
continue;
}
// Recherche dans les tags
foreach ($video['tags'] as $tag) {
if (strpos(strtolower($tag), $lowercaseQuery) !== false) {
$results[] = $video;
break;
}
}
// Recherche dans le nom de la chaîne
if (strpos(strtolower($video['channel']), $lowercaseQuery) !== false) {
$results[] = $video;
continue;
}
}
return $results;
}
$searchResults = !empty($query) ? searchVideos($videos, $query) : [];
// Fonctions utilitaires
function formatDuration($seconds) {
$hours = floor($seconds / 3600);
$minutes = floor(($seconds % 3600) / 60);
$remainingSeconds = $seconds % 60;
if ($hours > 0) {
return sprintf('%d:%02d:%02d', $hours, $minutes, $remainingSeconds);
} else {
return sprintf('%d:%02d', $minutes, $remainingSeconds);
}
}
function formatViewCount($views) {
if ($views >= 1000000) {
return round($views / 1000000, 1) . 'M';
} elseif ($views >= 1000) {
return round($views / 1000, 1) . 'K';
} else {
return $views;
}
}
function formatDate($dateString) {
$date = new DateTime($dateString);
$now = new DateTime();
$interval = $now->diff($date);
if ($interval->days == 0) {
return 'Aujourd\'hui';
} elseif ($interval->days == 1) {
return 'Hier';
} elseif ($interval->days < 7) {
return 'Il y a ' . $interval->days . ' jours';
} elseif ($interval->days < 30) {
$weeks = floor($interval->days / 7);
return 'Il y a ' . $weeks . ' semaine' . ($weeks > 1 ? 's' : '');
} elseif ($interval->days < 365) {
$months = floor($interval->days / 30);
return 'Il y a ' . $months . ' mois';
} else {
$years = floor($interval->days / 365);
return 'Il y a ' . $years . ' an' . ($years > 1 ? 's' : '');
}
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Recherche: <?php echo $query; ?> - Kaubuntu.re</title>
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/search.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<div class="container">
<?php include 'includes/header.php'; ?>
<main class="search-page">
<div class="search-header">
<h1>Résultats de recherche pour "<?php echo $query; ?>"</h1>
<div class="search-container">
<form action="search.php" method="get">
<input type="text" name="q" value="<?php echo $query; ?>" placeholder="Rechercher des vidéos...">
<button type="submit"><i class="fas fa-search"></i></button>
</form>
</div>
</div>
<?php if (!empty($query)): ?>
<?php if (count($searchResults) > 0): ?>
<div class="search-results-count">
<p><?php echo count($searchResults); ?> résultat(s) trouvé(s)</p>
</div>
<div class="search-results">
<?php foreach ($searchResults as $video): ?>
<div class="search-result-item" data-video-id="<?php echo $video['id']; ?>">
<div class="search-result-thumbnail">
<img src="<?php echo $video['thumbnail']; ?>" alt="<?php echo $video['title']; ?>">
<div class="video-duration"><?php echo formatDuration($video['duration']); ?></div>
</div>
<div class="search-result-info">
<h3 class="search-result-title"><?php echo $video['title']; ?></h3>
<div class="search-result-metadata">
<span class="search-result-views"><?php echo formatViewCount($video['views']); ?> vues</span>
<span class="search-result-date"><?php echo formatDate($video['date']); ?></span>
</div>
<div class="search-result-channel"><?php echo $video['channel']; ?></div>
<div class="search-result-description"><?php echo substr($video['description'], 0, 150); ?>...</div>
<div class="search-result-tags">
<?php foreach ($video['tags'] as $tag): ?>
<a href="search.php?q=<?php echo urlencode($tag); ?>" class="tag">#<?php echo $tag; ?></a>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="no-results">
<i class="fas fa-search fa-3x"></i>
<h2>Aucun résultat trouvé pour "<?php echo $query; ?>"</h2>
<p>Essayez avec d'autres mots-clés ou vérifiez l'orthographe.</p>
</div>
<?php endif; ?>
<?php else: ?>
<div class="search-instructions">
<i class="fas fa-search fa-3x"></i>
<h2>Recherchez du contenu</h2>
<p>Utilisez la barre de recherche pour trouver des vidéos, des chaînes ou des mots-clés.</p>
</div>
<?php endif; ?>
</main>
<?php include 'includes/footer.php'; ?>
</div>
<div class="mobile-menu-overlay" id="mobileMenuOverlay">
<?php include 'includes/mobile-menu.php'; ?>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="js/main.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Gestion des clics sur les résultats de recherche
const searchResultItems = document.querySelectorAll('.search-result-item');
searchResultItems.forEach(item => {
item.addEventListener('click', function() {
const videoId = this.dataset.videoId;
if (videoId) {
window.location.href = 'video.php?id=' + videoId;
}
});
});
});
</script>
</body>
</html>
+227
View File
@@ -0,0 +1,227 @@
<?php
// Dans un projet réel, on récupérerait l'ID de la vidéo et on ferait une requête à l'API PeerTube
$videoId = isset($_GET['id']) ? intval($_GET['id']) : 1;
// Exemple de données vidéo
$videos = [
1 => [
'id' => 1,
'title' => 'Introduction à la culture libre et aux logiciels open source',
'url' => 'https://peertube.example.com/embed/1',
'description' => 'Une introduction complète au monde de la culture libre et des logiciels open source. Découvrez les principes fondamentaux, les licences, et comment contribuer à des projets open source.',
'channel' => 'Tech Libre',
'channelId' => 1,
'views' => 15420,
'likes' => 1245,
'date' => '2023-11-15',
'tags' => ['open source', 'logiciels libres', 'tech', 'formation']
],
2 => [
'id' => 2,
'title' => 'La Réunion: Découverte des sentiers cachés',
'url' => 'https://peertube.example.com/embed/2',
'description' => 'Partez à la découverte des sentiers cachés de La Réunion. Cette vidéo vous guide à travers des paysages magnifiques et peu connus de l\'île.',
'channel' => 'Île Aventure',
'channelId' => 2,
'views' => 8745,
'likes' => 732,
'date' => '2023-12-02',
'tags' => ['La Réunion', 'voyage', 'randonnée', 'nature']
],
// Autres vidéos...
];
// Récupération de la vidéo
$video = isset($videos[$videoId]) ? $videos[$videoId] : $videos[1];
// Formatage des données
function formatViewCount($views) {
if ($views >= 1000000) {
return round($views / 1000000, 1) . 'M';
} elseif ($views >= 1000) {
return round($views / 1000, 1) . 'K';
} else {
return $views;
}
}
function formatDate($dateString) {
$date = new DateTime($dateString);
return $date->format('d/m/Y');
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $video['title']; ?> - Kaubuntu.re</title>
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/video-page.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<div class="container">
<?php include 'includes/header.php'; ?>
<main class="video-page">
<div class="video-player-container">
<div class="video-player">
<iframe src="<?php echo $video['url']; ?>" frameborder="0" allowfullscreen></iframe>
</div>
</div>
<div class="video-content">
<div class="video-primary-info">
<h1 class="video-title"><?php echo $video['title']; ?></h1>
<div class="video-stats">
<span class="video-views"><?php echo formatViewCount($video['views']); ?> vues</span>
<span class="video-date">Publié le <?php echo formatDate($video['date']); ?></span>
<div class="video-actions">
<button class="action-button">
<i class="fas fa-thumbs-up"></i>
<span><?php echo formatViewCount($video['likes']); ?></span>
</button>
<button class="action-button">
<i class="fas fa-share"></i>
<span>Partager</span>
</button>
<button class="action-button">
<i class="fas fa-bookmark"></i>
<span>Enregistrer</span>
</button>
</div>
</div>
</div>
<div class="video-secondary-info">
<div class="channel-info">
<div class="channel-image">
<img src="img/channels/<?php echo $video['channelId']; ?>.jpg" alt="<?php echo $video['channel']; ?>">
</div>
<div class="channel-details">
<h3 class="channel-name"><?php echo $video['channel']; ?></h3>
<button class="subscribe-button">S'abonner</button>
</div>
</div>
<div class="video-description">
<p><?php echo $video['description']; ?></p>
<div class="video-tags">
<?php foreach ($video['tags'] as $tag): ?>
<a href="search.php?q=<?php echo urlencode($tag); ?>" class="tag">#<?php echo $tag; ?></a>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="video-comments">
<h2>Commentaires</h2>
<div class="comment-form">
<textarea placeholder="Ajouter un commentaire..."></textarea>
<button class="comment-submit">Commenter</button>
</div>
<div class="comments-list">
<!-- Les commentaires seraient chargés dynamiquement dans un vrai projet -->
<div class="comment">
<div class="comment-avatar">
<img src="img/avatars/user1.jpg" alt="Utilisateur">
</div>
<div class="comment-content">
<div class="comment-header">
<span class="comment-author">Marie Dupont</span>
<span class="comment-date">Il y a 2 jours</span>
</div>
<div class="comment-text">
<p>Excellente vidéo ! J'ai beaucoup appris sur ce sujet. Merci pour ce contenu de qualité.</p>
</div>
<div class="comment-actions">
<button><i class="fas fa-thumbs-up"></i> 15</button>
<button><i class="fas fa-thumbs-down"></i></button>
<button>Répondre</button>
</div>
</div>
</div>
<div class="comment">
<div class="comment-avatar">
<img src="img/avatars/user2.jpg" alt="Utilisateur">
</div>
<div class="comment-content">
<div class="comment-header">
<span class="comment-author">Jean Martin</span>
<span class="comment-date">Il y a 5 jours</span>
</div>
<div class="comment-text">
<p>Pourriez-vous faire une vidéo plus détaillée sur certains aspects abordés ici ? J'aimerais approfondir ce sujet.</p>
</div>
<div class="comment-actions">
<button><i class="fas fa-thumbs-up"></i> 7</button>
<button><i class="fas fa-thumbs-down"></i></button>
<button>Répondre</button>
</div>
</div>
</div>
</div>
</div>
</div>
<aside class="related-videos">
<h2>Vidéos suggérées</h2>
<div class="related-videos-list">
<?php
// Dans un vrai projet, on récupérerait des vidéos similaires
foreach (array_slice($videos, 0, 5) as $relatedVideo):
if ($relatedVideo['id'] != $videoId):
?>
<div class="related-video-card" data-video-id="<?php echo $relatedVideo['id']; ?>">
<div class="related-video-thumbnail">
<img src="img/video-thumbnails/featured-<?php echo $relatedVideo['id']; ?>.jpg" alt="<?php echo $relatedVideo['title']; ?>">
</div>
<div class="related-video-info">
<h3 class="related-video-title"><?php echo $relatedVideo['title']; ?></h3>
<div class="related-video-channel"><?php echo $relatedVideo['channel']; ?></div>
<div class="related-video-metadata">
<span class="related-video-views"><?php echo formatViewCount($relatedVideo['views']); ?> vues</span>
</div>
</div>
</div>
<?php
endif;
endforeach;
?>
</div>
</aside>
</main>
<?php include 'includes/footer.php'; ?>
</div>
<div class="mobile-menu-overlay" id="mobileMenuOverlay">
<?php include 'includes/mobile-menu.php'; ?>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="js/main.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Gestion des clics sur les vidéos suggérées
const relatedVideoCards = document.querySelectorAll('.related-video-card');
relatedVideoCards.forEach(card => {
card.addEventListener('click', function() {
const videoId = this.dataset.videoId;
if (videoId) {
window.location.href = 'video.php?id=' + videoId;
}
});
});
});
</script>
</body>
</html>