feat: social media aggregator (YouTube, Instagram, TikTok, WordPress)
This commit is contained in:
+784
@@ -0,0 +1,784 @@
|
|||||||
|
/* ============================================================
|
||||||
|
HERO SECTION
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.social-hero {
|
||||||
|
background: linear-gradient(135deg, #0a0a0a 0%, #1a0000 60%, #2d0000 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 48px 40px;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-hero-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-hero-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-hero-logo {
|
||||||
|
height: 90px;
|
||||||
|
width: auto;
|
||||||
|
filter: drop-shadow(0 4px 16px rgba(255, 0, 0, 0.4));
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-hero-text h1 {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-hero-text p {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
max-width: 480px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Platform Navigation Badges */
|
||||||
|
.platform-nav {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-badge-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-badge-count {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 11px 22px;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
color: white !important;
|
||||||
|
letter-spacing: 0.2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-badge:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-badge:active {
|
||||||
|
transform: scale(0.93) translateY(0);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
transition-duration: 0.08s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-badge.platform-youtube { background: #FF0000; }
|
||||||
|
.platform-badge.platform-instagram {
|
||||||
|
background: linear-gradient(135deg, #F58529, #DD2A7B, #8134AF);
|
||||||
|
}
|
||||||
|
.platform-badge.platform-tiktok {
|
||||||
|
background: #000;
|
||||||
|
border-color: #EE1D52;
|
||||||
|
}
|
||||||
|
.platform-badge.platform-wp {
|
||||||
|
background: linear-gradient(135deg, #1a0000, #2d0000);
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
SOCIAL SECTIONS
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.social-section {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 18px 24px;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-section-header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-section-header h2 {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-section-header > .social-section-header-left > i {
|
||||||
|
font-size: 26px;
|
||||||
|
color: white;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-handle {
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
font-weight: 400;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-stat-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
background: rgba(255, 255, 255, 0.18);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-stat-badge i {
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Platform header gradients */
|
||||||
|
.platform-youtube-header { background: linear-gradient(135deg, #a80000, #FF0000); }
|
||||||
|
.platform-facebook-header { background: linear-gradient(135deg, #0d47a1, #1877F2); }
|
||||||
|
.platform-instagram-header {
|
||||||
|
background: linear-gradient(135deg, #6a0dad, #DD2A7B, #F77737);
|
||||||
|
}
|
||||||
|
.platform-tiktok-header {
|
||||||
|
background: linear-gradient(135deg, #010101, #1a1a1a);
|
||||||
|
border-bottom: 3px solid #EE1D52;
|
||||||
|
}
|
||||||
|
.platform-x-header {
|
||||||
|
background: linear-gradient(135deg, #000, #1a1a1a);
|
||||||
|
border-bottom: 3px solid rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CTA Buttons */
|
||||||
|
.platform-cta-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
color: white !important;
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-cta-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-youtube-btn { background: rgba(255, 255, 255, 0.2); }
|
||||||
|
.platform-youtube-btn:hover { background: rgba(255, 255, 255, 0.3); }
|
||||||
|
|
||||||
|
.platform-facebook-btn { background: rgba(255, 255, 255, 0.2); }
|
||||||
|
.platform-facebook-btn:hover { background: rgba(255, 255, 255, 0.3); }
|
||||||
|
|
||||||
|
.platform-instagram-btn { background: rgba(255, 255, 255, 0.2); }
|
||||||
|
.platform-instagram-btn:hover { background: rgba(255, 255, 255, 0.3); }
|
||||||
|
|
||||||
|
.platform-tiktok-btn { background: rgba(238, 29, 82, 0.7); }
|
||||||
|
.platform-tiktok-btn:hover { background: #EE1D52; }
|
||||||
|
|
||||||
|
.platform-x-btn { background: rgba(255, 255, 255, 0.15); }
|
||||||
|
.platform-x-btn:hover { background: rgba(255, 255, 255, 0.25); }
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
TWO-COLUMN LAYOUT
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.social-two-col {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 30px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Colonnes alignées en haut (chacune prend sa hauteur naturelle) */
|
||||||
|
.social-two-col--top {
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.social-two-col {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
EMBED CONTAINERS (Facebook, X)
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.social-embed-container {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 500px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
padding: 12px;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empêche les iframes tierces de dépasser du conteneur */
|
||||||
|
.social-embed-container iframe,
|
||||||
|
.social-embed-container > div {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-embed-container .twitter-timeline {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Profile iframes (TikTok creator embed, Instagram profile embed) */
|
||||||
|
.platform-profile-embed {
|
||||||
|
padding: 0;
|
||||||
|
min-height: unset;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-profile-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 560px;
|
||||||
|
border: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-profile-iframe--instagram {
|
||||||
|
height: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.platform-profile-iframe {
|
||||||
|
height: 480px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-profile-iframe--instagram {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
YOUTUBE VIDEO GRID
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.social-video-grid {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-video-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-decoration: none !important;
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--card-bg);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-video-card:hover {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-video-card .video-thumbnail {
|
||||||
|
position: relative;
|
||||||
|
padding-top: 56.25%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-video-card .video-thumbnail img {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-video-card:hover .video-thumbnail img {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sous-titre de section (Shorts / Vidéos) */
|
||||||
|
.yt-subgrid-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted, #888);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
padding: 16px 20px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yt-subgrid-label i {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grille Shorts : cartes portrait centrées */
|
||||||
|
.social-shorts-grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(160px, 220px));
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shorts — ratio portrait 9:16 */
|
||||||
|
.video-card--short .video-thumbnail {
|
||||||
|
padding-top: 177.78%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.short-badge {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
background: #FF0000;
|
||||||
|
color: white;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
pointer-events: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.youtube-play-icon {
|
||||||
|
font-size: 44px !important;
|
||||||
|
color: #FF0000 !important;
|
||||||
|
filter: drop-shadow(0 2px 10px rgba(0, 0, 0, 0.6));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
FALLBACK CARDS
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.platform-fallback-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 32px;
|
||||||
|
padding: 48px 40px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
min-height: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-fallback-icon {
|
||||||
|
font-size: 90px;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.youtube-fallback-icon i { color: #FF0000; }
|
||||||
|
|
||||||
|
.instagram-fallback-icon i {
|
||||||
|
background: linear-gradient(45deg, #F58529, #DD2A7B, #8134AF);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiktok-fallback-icon i { color: #010101; }
|
||||||
|
[data-theme="dark"] .tiktok-fallback-icon i { color: #ffffff; }
|
||||||
|
|
||||||
|
.platform-fallback-content h3 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-fallback-content p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
max-width: 380px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
INSTAGRAM POSTS GRID
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.instagram-posts-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.instagram-post-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
TIKTOK VIDEOS GRID
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.tiktok-videos-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiktok-video-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
WORDPRESS ARTICLES
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.wp-section-header { background: linear-gradient(135deg, #0a0a0a 0%, #1a0000 60%, #2d0000 100%); }
|
||||||
|
|
||||||
|
.wp-section-logo {
|
||||||
|
height: 28px;
|
||||||
|
width: auto;
|
||||||
|
filter: drop-shadow(0 2px 6px rgba(255, 0, 0, 0.4));
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-cta-btn { background: rgba(255, 255, 255, 0.2); }
|
||||||
|
.wp-cta-btn:hover { background: rgba(255, 255, 255, 0.3); }
|
||||||
|
|
||||||
|
.wp-posts-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-decoration: none !important;
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--card-bg);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-card:hover {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-thumbnail {
|
||||||
|
position: relative;
|
||||||
|
padding-top: 56.25%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-thumbnail img {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-card:hover .wp-post-thumbnail img {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-thumbnail--placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-thumbnail--placeholder i {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%; left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-size: 40px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-info {
|
||||||
|
padding: 14px 16px 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-excerpt {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: auto;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-post-meta span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.wp-posts-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
HEADER BRAND
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
.header-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-logo-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-logo-img {
|
||||||
|
height: 40px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-site-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
DARK THEME
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
[data-theme="dark"] .social-embed-container,
|
||||||
|
[data-theme="dark"] .platform-fallback-card,
|
||||||
|
[data-theme="dark"] .social-video-grid,
|
||||||
|
[data-theme="dark"] .instagram-posts-grid,
|
||||||
|
[data-theme="dark"] .tiktok-videos-grid {
|
||||||
|
background: var(--card-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
RESPONSIVE
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
.header-site-name {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.social-hero {
|
||||||
|
padding: 28px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-hero-text h1 {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-hero-logo {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-badge span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-badge {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
padding: 0;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-badge-count {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-section-header {
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-section-header h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-fallback-card {
|
||||||
|
padding: 32px 20px;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-fallback-icon {
|
||||||
|
font-size: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-fallback-content h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instagram-posts-grid,
|
||||||
|
.tiktok-videos-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-embed-container {
|
||||||
|
min-height: 400px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-profile-embed {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.platform-nav {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-hero-brand {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-hero-logo {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
SCROLL ARRIVAL HIGHLIGHT
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
@keyframes section-arrive {
|
||||||
|
0% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.5); }
|
||||||
|
40% { box-shadow: 0 0 0 10px rgba(255, 0, 0, 0.15); }
|
||||||
|
100% { box-shadow: 0 0 0 18px rgba(255, 0, 0, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-arrive {
|
||||||
|
animation: section-arrive 0.9s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
+41
-49
@@ -1,80 +1,72 @@
|
|||||||
<!-- Footer -->
|
<?php
|
||||||
|
$currentPage = basename($_SERVER['PHP_SELF']);
|
||||||
|
?>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div class="footer-header">
|
<div class="footer-header">
|
||||||
<div class="footer-logo">
|
<div class="footer-logo">
|
||||||
<img src="img/logo.png" alt="kaubuntu.re">
|
<img src="img/logo.png" alt="<?php echo htmlspecialchars(SITE_NAME); ?>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer-contact-info">
|
<div class="footer-contact-info">
|
||||||
<div class="footer-contact">CONTACT</div>
|
<div class="footer-contact">CONTACT</div>
|
||||||
<div class="footer-email"><a href="mailto:<?php echo CONTACT_EMAIL; ?>"><?php echo CONTACT_EMAIL; ?></a></div>
|
<div class="footer-email">
|
||||||
|
<a href="mailto:<?php echo htmlspecialchars(CONTACT_EMAIL); ?>"><?php echo htmlspecialchars(CONTACT_EMAIL); ?></a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer-columns">
|
<div class="footer-columns">
|
||||||
<div class="footer-column">
|
<div class="footer-column">
|
||||||
<h3 class="footer-title">Catégories</h3>
|
<h3 class="footer-title">Réseaux sociaux</h3>
|
||||||
<div>
|
|
||||||
<ul class="footer-links">
|
<ul class="footer-links">
|
||||||
<li><a href="index.php" <?php echo ($currentPage === 'index.php') ? 'class="active"' : ''; ?>>Accueil</a></li>
|
<li>
|
||||||
<li><a href="direct.php" <?php echo ($currentPage === 'direct.php') ? 'class="active"' : ''; ?>>Direct</a></li>
|
<a href="<?php echo htmlspecialchars(YOUTUBE_URL); ?>" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i> YouTube
|
||||||
<?php
|
</a>
|
||||||
if (defined('PRIORITY_CATEGORIES') && !empty(PRIORITY_CATEGORIES)) {
|
</li>
|
||||||
foreach (PRIORITY_CATEGORIES as $id => $name) {
|
<li>
|
||||||
$isActive = ($currentPage === 'categories.php' && $currentCategoryId === $id);
|
<a href="<?php echo htmlspecialchars(INSTAGRAM_URL); ?>" target="_blank" rel="noopener noreferrer">
|
||||||
echo '<li><a href="categories.php?id=' . $id . '"' . ($isActive ? ' class="active"' : '') . '>' . htmlspecialchars($name) . '</a></li>';
|
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i> Instagram
|
||||||
}
|
</a>
|
||||||
}
|
</li>
|
||||||
?>
|
<li>
|
||||||
|
<a href="<?php echo htmlspecialchars(TIKTOK_URL); ?>" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i> TikTok
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-column">
|
|
||||||
<h3 class="footer-title">Hashtags</h3>
|
|
||||||
<div>
|
|
||||||
<ul class="footer-links">
|
|
||||||
<?php
|
|
||||||
if (defined('IMPORTANT_TAGS') && !empty(IMPORTANT_TAGS)):
|
|
||||||
foreach (IMPORTANT_TAGS as $tag):
|
|
||||||
$encodedTag = urlencode('#' . $tag);
|
|
||||||
$isActive = ($isTagSearch && strtolower($currentTag) === strtolower($tag));
|
|
||||||
?>
|
|
||||||
<li><a href="recherche.php?q=<?php echo $encodedTag; ?>" <?php echo $isActive ? 'class="active"' : ''; ?>><?php echo htmlspecialchars($tag); ?></a></li>
|
|
||||||
<?php
|
|
||||||
endforeach;
|
|
||||||
endif;
|
|
||||||
?>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-column">
|
<div class="footer-column">
|
||||||
<h3 class="footer-title">Informations légales</h3>
|
<h3 class="footer-title">Informations légales</h3>
|
||||||
<div>
|
|
||||||
<ul class="footer-links">
|
<ul class="footer-links">
|
||||||
<li><a href="mentions-legales.php" <?php echo ($currentPage === 'mentions-legales.php') ? 'class="active"' : ''; ?>>Mentions légales</a></li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href="<?php echo LEGAL_SOURCE_CODE_URL; ?>" target="_blank" rel="noopener noreferrer">
|
<a href="mentions-legales.php" <?php echo ($currentPage === 'mentions-legales.php') ? 'class="active"' : ''; ?>>
|
||||||
<i class="fab fa-git-alt"></i> Code source
|
Mentions légales
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="<?php echo htmlspecialchars(LEGAL_SOURCE_CODE_URL); ?>" target="_blank" rel="noopener noreferrer">
|
||||||
|
<i class="fab fa-git-alt" aria-hidden="true"></i> Code source
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-social">
|
<div class="footer-social">
|
||||||
<a target="_blank" rel="me noreferrer" href="<?php echo MASTODON_URL; ?>"><i class="fab fa-mastodon icon-mastodon"></i></a>
|
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(YOUTUBE_URL); ?>" aria-label="YouTube">
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo FACEBOOK_URL; ?>"><i class="fab fa-facebook icon-facebook"></i></a>
|
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i>
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo YOUTUBE_URL; ?>"><i class="fab fa-youtube icon-youtube"></i></a>
|
</a>
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo INSTAGRAM_URL; ?>"><i class="fab fa-instagram icon-instagram"></i></a>
|
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(INSTAGRAM_URL); ?>" aria-label="Instagram">
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo X_URL; ?>"><i class="fab fa-x-twitter icon-x"></i></a>
|
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i>
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo TIKTOK_URL; ?>"><i class="fab fa-tiktok icon-tiktok"></i></a>
|
</a>
|
||||||
|
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(TIKTOK_URL); ?>" aria-label="TikTok">
|
||||||
|
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer-copyright">
|
<div class="footer-copyright">
|
||||||
<?php echo LEGAL_COPYRIGHT; ?> <?php echo date('Y'); ?> - Licence libre <a href="<?php echo LEGAL_LICENSE_URL; ?>" target="_blank" rel="noopener noreferrer">GNU AGPL-V3</a>
|
<?php echo htmlspecialchars(LEGAL_COPYRIGHT); ?> <?php echo date('Y'); ?> —
|
||||||
|
Licence libre <a href="<?php echo htmlspecialchars(LEGAL_LICENSE_URL); ?>" target="_blank" rel="noopener noreferrer">GNU AGPL-V3</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+15
-32
@@ -1,47 +1,30 @@
|
|||||||
<!-- Header avec barre de recherche et icônes -->
|
<!-- Header -->
|
||||||
<header class="header" role="banner">
|
<header class="header" role="banner">
|
||||||
<div class="search-container">
|
<div class="header-brand">
|
||||||
<form action="recherche.php" method="get" role="search" aria-label="Recherche de vidéos">
|
<a href="/" class="header-logo-link" aria-label="Accueil <?php echo htmlspecialchars(SITE_NAME); ?>">
|
||||||
<label for="search-input" class="sr-only">Rechercher des vidéos</label>
|
<img src="img/logo.png" alt="<?php echo htmlspecialchars(SITE_NAME); ?>" class="header-logo-img">
|
||||||
<input type="text" id="search-input" name="q" placeholder="Rechercher..." aria-describedby="search-help">
|
<span class="header-site-name"><?php echo htmlspecialchars(SITE_NAME); ?></span>
|
||||||
<button type="submit" aria-label="Lancer la recherche">
|
</a>
|
||||||
<i class="fas fa-search" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<div id="search-help" class="sr-only">Tapez vos mots-clés pour rechercher des vidéos</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="social-icons" aria-label="Réseaux sociaux">
|
<nav class="social-icons" aria-label="Nos réseaux sociaux">
|
||||||
<a target="_blank" rel="me noreferrer" href="<?php echo MASTODON_URL; ?>" class="icon-button" aria-label="Suivre sur Mastodon">
|
<a target="_blank" rel="noreferrer" href="<?php echo htmlspecialchars(YOUTUBE_URL); ?>"
|
||||||
<i class="fab fa-mastodon icon-mastodon" aria-hidden="true"></i>
|
class="icon-button" aria-label="YouTube">
|
||||||
|
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo INSTAGRAM_URL; ?>" class="icon-button" aria-label="Suivre sur Instagram">
|
<a target="_blank" rel="noreferrer" href="<?php echo htmlspecialchars(INSTAGRAM_URL); ?>"
|
||||||
|
class="icon-button" aria-label="Instagram">
|
||||||
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i>
|
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo TIKTOK_URL; ?>" class="icon-button" aria-label="Suivre sur TikTok">
|
<a target="_blank" rel="noreferrer" href="<?php echo htmlspecialchars(TIKTOK_URL); ?>"
|
||||||
|
class="icon-button" aria-label="TikTok">
|
||||||
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i>
|
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
<div class="more-social-container">
|
|
||||||
<button type="button" class="icon-button more-social-toggle" aria-expanded="false" aria-controls="social-dropdown" aria-label="Voir plus de réseaux sociaux">
|
|
||||||
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<div id="social-dropdown" class="more-social-dropdown" role="menu">
|
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo FACEBOOK_URL; ?>" class="more-social-item" role="menuitem">
|
|
||||||
<i class="fab fa-facebook icon-facebook" aria-hidden="true"></i> Facebook
|
|
||||||
</a>
|
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo YOUTUBE_URL; ?>" class="more-social-item" role="menuitem">
|
|
||||||
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i> YouTube
|
|
||||||
</a>
|
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo X_URL; ?>" class="more-social-item" role="menuitem">
|
|
||||||
<i class="fab fa-x-twitter icon-x" aria-hidden="true"></i> X
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="action-icons">
|
<div class="action-icons">
|
||||||
<?php if (defined('DONATIONS_ENABLED') && DONATIONS_ENABLED && !empty(PAYPAL_ME_URL)): ?>
|
<?php if (defined('DONATIONS_ENABLED') && DONATIONS_ENABLED && !empty(PAYPAL_ME_URL)): ?>
|
||||||
<a href="dons.php" class="icon-button donation-link" aria-label="Soutenir KA UBUNTU" title="Faire un don">
|
<a href="dons.php" class="icon-button donation-link" aria-label="Soutenir <?php echo htmlspecialchars(SITE_NAME); ?>" title="Faire un don">
|
||||||
<i class="fas fa-heart" aria-hidden="true"></i>
|
<i class="fas fa-heart" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
+46
-76
@@ -1,92 +1,62 @@
|
|||||||
<!-- Menu mobile (masqué par défaut) -->
|
<?php
|
||||||
<div class="mobile-menu">
|
$currentPage = basename($_SERVER['PHP_SELF']);
|
||||||
<button class="mobile-menu-close">
|
$isHome = ($currentPage === 'index.php' || $currentPage === '/');
|
||||||
<i class="fas fa-times"></i>
|
?>
|
||||||
|
<!-- Menu mobile -->
|
||||||
|
<div class="mobile-menu" id="mobile-menu" role="dialog" aria-label="Menu de navigation" aria-modal="true">
|
||||||
|
<button class="mobile-menu-close" aria-label="Fermer le menu">
|
||||||
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="search-container">
|
<nav>
|
||||||
<form action="recherche.php" method="get">
|
<a href="/"
|
||||||
<input type="text" name="q" placeholder="Rechercher...">
|
class="nav-item <?php echo $isHome ? 'active' : ''; ?>">
|
||||||
<button type="submit"><i class="fas fa-search"></i></button>
|
<i class="fas fa-home" aria-hidden="true"></i> Accueil
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<a href="index.php" class="nav-item <?php echo ($currentPage === 'index.php') ? 'active' : ''; ?>">
|
|
||||||
<i class="fas fa-home"></i> Accueil
|
|
||||||
</a>
|
|
||||||
<a href="direct.php" class="nav-item <?php echo ($currentPage === 'direct.php') ? 'active' : ''; ?>">
|
|
||||||
<i class="fas fa-broadcast-tower"></i> Direct
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<hr class="nav-divider">
|
<hr class="nav-divider">
|
||||||
|
|
||||||
<?php
|
<p class="mobile-section-title">Nos réseaux</p>
|
||||||
// Afficher les catégories prioritaires
|
|
||||||
if (defined('PRIORITY_CATEGORIES') && !empty(PRIORITY_CATEGORIES)) {
|
|
||||||
// Tableau associatif des icônes pour les catégories
|
|
||||||
$categoryIcons = [
|
|
||||||
1 => 'fas fa-music', // Musique
|
|
||||||
2 => 'fas fa-film', // Films
|
|
||||||
3 => 'fas fa-car', // Véhicules
|
|
||||||
4 => 'fas fa-palette', // Jeux
|
|
||||||
5 => 'fas fa-running', // Sport
|
|
||||||
6 => 'fas fa-laugh', // Humour
|
|
||||||
7 => 'fas fa-gamepad', // Art
|
|
||||||
8 => 'fas fa-person', // Personnalités
|
|
||||||
9 => 'fas fa-face-grin-tears', // Comédie
|
|
||||||
10 => 'fas fa-tv', // Divertissement
|
|
||||||
11 => 'fas fa-globe', // Actualité & Politique
|
|
||||||
12 => 'fas fa-chalkboard-user', // Tutoriel
|
|
||||||
13 => 'fas fa-graduation-cap', // Education
|
|
||||||
14 => 'fas fa-fist-raised', // Activisme
|
|
||||||
15 => 'fas fa-microscope', // Science & Technologie
|
|
||||||
16 => 'fas fa-paw', // Animaux
|
|
||||||
17 => 'fas fa-child', // Enfants
|
|
||||||
18 => 'fas fa-utensils' // Cuisine
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach (PRIORITY_CATEGORIES as $id => $name) {
|
<a href="<?php echo $isHome ? '#youtube' : 'index.php#youtube'; ?>"
|
||||||
$isActive = ($currentPage === 'categories.php' && $currentCategoryId === $id);
|
class="nav-item">
|
||||||
$icon = isset($categoryIcons[$id]) ? $categoryIcons[$id] : 'fas fa-folder';
|
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i> YouTube
|
||||||
echo '<a href="categories.php?id=' . $id . '" class="nav-item ' . ($isActive ? 'active' : '') . '">';
|
|
||||||
echo '<i class="' . $icon . '"></i> ' . htmlspecialchars($name);
|
|
||||||
echo '</a>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
|
|
||||||
<hr class="nav-divider">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 class="mobile-section-title">Hashtags populaires</h3>
|
|
||||||
<?php
|
|
||||||
if (defined('IMPORTANT_TAGS') && !empty(IMPORTANT_TAGS)):
|
|
||||||
foreach (IMPORTANT_TAGS as $tag):
|
|
||||||
$encodedTag = urlencode('#' . $tag);
|
|
||||||
$isActive = ($isTagSearch && strtolower($currentTag) === strtolower($tag));
|
|
||||||
?>
|
|
||||||
<a href="recherche.php?q=<?php echo $encodedTag; ?>" class="nav-item <?php echo $isActive ? 'active' : ''; ?>">
|
|
||||||
<i class="fas fa-hashtag"></i> <?php echo htmlspecialchars($tag); ?>
|
|
||||||
</a>
|
</a>
|
||||||
<?php
|
<a href="<?php echo $isHome ? '#instagram' : 'index.php#instagram'; ?>"
|
||||||
endforeach;
|
class="nav-item">
|
||||||
endif;
|
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i> Instagram
|
||||||
?>
|
</a>
|
||||||
</div>
|
<a href="<?php echo $isHome ? '#tiktok' : 'index.php#tiktok'; ?>"
|
||||||
|
class="nav-item">
|
||||||
|
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i> TikTok
|
||||||
|
</a>
|
||||||
|
<a href="<?php echo $isHome ? '#actualites' : 'index.php#actualites'; ?>"
|
||||||
|
class="nav-item">
|
||||||
|
<i class="fas fa-newspaper" aria-hidden="true"></i> Actualités
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php if (defined('DONATIONS_ENABLED') && DONATIONS_ENABLED && !empty(PAYPAL_ME_URL)): ?>
|
||||||
|
<hr class="nav-divider">
|
||||||
|
<a href="dons.php" class="nav-item donation-nav-link <?php echo ($currentPage === 'dons.php') ? 'active' : ''; ?>">
|
||||||
|
<i class="fas fa-heart" aria-hidden="true"></i> Soutenir
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<hr class="nav-divider">
|
<hr class="nav-divider">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mobile-section-title">Suivez-nous</h3>
|
<p class="mobile-section-title">Suivez-nous</p>
|
||||||
<div class="mobile-social-icons">
|
<div class="mobile-social-icons">
|
||||||
<a target="_blank" rel="me noreferrer" href="<?php echo MASTODON_URL; ?>"><i class="fab fa-mastodon icon-mastodon"></i></a>
|
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(YOUTUBE_URL); ?>" aria-label="YouTube">
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo FACEBOOK_URL; ?>"><i class="fab fa-facebook icon-facebook"></i></a>
|
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i>
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo INSTAGRAM_URL; ?>"><i class="fab fa-instagram icon-instagram"></i></a>
|
</a>
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo TIKTOK_URL; ?>"><i class="fab fa-tiktok icon-tiktok"></i></a>
|
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(INSTAGRAM_URL); ?>" aria-label="Instagram">
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo YOUTUBE_URL; ?>"><i class="fab fa-youtube icon-youtube"></i></a>
|
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i>
|
||||||
<a target="_blank" rel="noreferrer" href="<?php echo X_URL; ?>"><i class="fab fa-x-twitter icon-x"></i></a>
|
</a>
|
||||||
|
<a target="_blank" rel="noopener noreferrer" href="<?php echo htmlspecialchars(TIKTOK_URL); ?>" aria-label="TikTok">
|
||||||
|
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+26
-86
@@ -181,107 +181,47 @@ function validateCSRFToken($token) {
|
|||||||
* Applique des en-têtes de sécurité HTTP
|
* Applique des en-têtes de sécurité HTTP
|
||||||
*/
|
*/
|
||||||
function setSecurityHeaders() {
|
function setSecurityHeaders() {
|
||||||
// Protection contre le clickjacking (permettre les iframes du même site)
|
|
||||||
header('X-Frame-Options: SAMEORIGIN');
|
header('X-Frame-Options: SAMEORIGIN');
|
||||||
|
|
||||||
// Protection contre le MIME sniffing
|
|
||||||
header('X-Content-Type-Options: nosniff');
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
|
||||||
// Protection XSS basique
|
|
||||||
header('X-XSS-Protection: 1; mode=block');
|
header('X-XSS-Protection: 1; mode=block');
|
||||||
|
|
||||||
// Politique de référent
|
|
||||||
header('Referrer-Policy: strict-origin-when-cross-origin');
|
header('Referrer-Policy: strict-origin-when-cross-origin');
|
||||||
|
|
||||||
// Content Security Policy avec support Mastodon et PeerTube
|
$isLocalDev = in_array(
|
||||||
$mastodonDomain = '';
|
$_SERVER['HTTP_HOST'] ?? '',
|
||||||
$peertubeDomain = '';
|
['127.0.0.1:8080', '127.0.0.1:8001', 'localhost:8080', 'localhost:8001', '127.0.0.1', 'localhost']
|
||||||
|
);
|
||||||
|
|
||||||
// Extraire le domaine Mastodon si configuré
|
// Domaines des 5 plateformes sociales
|
||||||
if (defined('MASTODON_INSTANCE_URL')) {
|
$fbScripts = 'https://connect.facebook.net';
|
||||||
$mastodonParsed = parse_url(MASTODON_INSTANCE_URL);
|
$fbFrames = 'https://www.facebook.com https://staticxx.facebook.com https://www.facebook.net';
|
||||||
if ($mastodonParsed && isset($mastodonParsed['host'])) {
|
$xScripts = 'https://platform.twitter.com';
|
||||||
$mastodonDomain = $mastodonParsed['scheme'] . '://' . $mastodonParsed['host'];
|
$xFrames = 'https://platform.twitter.com https://syndication.twitter.com https://cdn.syndication.twimg.com';
|
||||||
}
|
$igScripts = 'https://www.instagram.com';
|
||||||
}
|
$igFrames = 'https://www.instagram.com';
|
||||||
|
$ttScripts = 'https://www.tiktok.com https://lf16-tiktok-web.ttwstatic.com';
|
||||||
// Extraire le domaine PeerTube si configuré
|
$ttFrames = 'https://www.tiktok.com';
|
||||||
if (defined('PEERTUBE_URL')) {
|
$ytImages = 'https://i.ytimg.com https://yt3.ggpht.com';
|
||||||
$peertubeParsed = parse_url(PEERTUBE_URL);
|
|
||||||
if ($peertubeParsed && isset($peertubeParsed['host'])) {
|
|
||||||
$peertubeDomain = $peertubeParsed['scheme'] . '://' . $peertubeParsed['host'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Détecter si on est en développement local
|
|
||||||
$isLocalDev = in_array($_SERVER['HTTP_HOST'] ?? '', ['127.0.0.1:8080', '127.0.0.1:8001', 'localhost:8080', 'localhost:8001', '127.0.0.1', 'localhost']);
|
|
||||||
|
|
||||||
$csp = "default-src 'self'; ";
|
$csp = "default-src 'self'; ";
|
||||||
$csp .= "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; ";
|
$csp .= "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; ";
|
||||||
$csp .= "script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://plausible.io; "; // PLAUSIBLE UPDATED
|
|
||||||
|
|
||||||
// Images : autoriser les domaines externes plus HTTPS général en dev
|
|
||||||
$imgSrc = "'self' data: " . ($mastodonDomain ? $mastodonDomain : '') . " " . ($peertubeDomain ? $peertubeDomain : '');
|
|
||||||
if ($isLocalDev) {
|
|
||||||
$imgSrc .= " https: http:";
|
|
||||||
} else {
|
|
||||||
$imgSrc .= " https:";
|
|
||||||
}
|
|
||||||
$csp .= "img-src " . $imgSrc . "; ";
|
|
||||||
|
|
||||||
$csp .= "font-src 'self' https://cdnjs.cloudflare.com; ";
|
$csp .= "font-src 'self' https://cdnjs.cloudflare.com; ";
|
||||||
|
$csp .= "script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://plausible.io "
|
||||||
// Frames : autoriser PeerTube et HTTPS général
|
. "{$fbScripts} {$xScripts} {$igScripts} {$ttScripts}; ";
|
||||||
$frameSrc = "'self' " . ($peertubeDomain ? $peertubeDomain : '');
|
$csp .= "img-src 'self' data: {$ytImages} https://www.facebook.com https://pbs.twimg.com https://abs.twimg.com"
|
||||||
if ($isLocalDev) {
|
. ($isLocalDev ? " https: http:" : " https:") . "; ";
|
||||||
$frameSrc .= " https: http:";
|
$csp .= "frame-src 'self' {$fbFrames} {$xFrames} {$igFrames} {$ttFrames} https://www.youtube.com"
|
||||||
} else {
|
. ($isLocalDev ? " http:" : "") . "; ";
|
||||||
$frameSrc .= " https:";
|
$csp .= "connect-src 'self' https://plausible.io https://www.googleapis.com https://www.youtube.com "
|
||||||
}
|
. "https://www.facebook.com https://graph.facebook.com https://connect.facebook.net "
|
||||||
$csp .= "frame-src " . $frameSrc . "; ";
|
. "https://platform.twitter.com https://syndication.twitter.com https://cdn.syndication.twimg.com https://api.twitter.com "
|
||||||
|
. "https://www.instagram.com https://www.tiktok.com"
|
||||||
// Connexions : autoriser Mastodon et PeerTube
|
. ($isLocalDev ? " ws: wss:" : "") . "; ";
|
||||||
$connectSrc = "'self' https://plausible.io " . ($mastodonDomain ? $mastodonDomain : '') . " " . ($peertubeDomain ? $peertubeDomain : '');
|
$csp .= "media-src 'self' https:; ";
|
||||||
if ($isLocalDev) {
|
|
||||||
$connectSrc .= " ws: wss:"; // WebSockets pour le dev
|
|
||||||
}
|
|
||||||
$csp .= "connect-src " . $connectSrc . "; ";
|
|
||||||
|
|
||||||
// Médias : toujours autoriser 'self', Mastodon et PeerTube
|
|
||||||
$mediaSrc = "'self'";
|
|
||||||
|
|
||||||
// Ajouter l'instance Mastodon (pour les médias stockés sur l'instance)
|
|
||||||
if ($mastodonDomain) {
|
|
||||||
$mediaSrc .= " " . $mastodonDomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ajouter PeerTube
|
|
||||||
if ($peertubeDomain) {
|
|
||||||
$mediaSrc .= " " . $peertubeDomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ajouter l'URL S3 Mastodon si configurée (pour les médias externalisés)
|
|
||||||
if (defined('MASTODON_S3_MEDIA_URL') && !empty(MASTODON_S3_MEDIA_URL)) {
|
|
||||||
$s3Parsed = parse_url(MASTODON_S3_MEDIA_URL);
|
|
||||||
if ($s3Parsed && isset($s3Parsed['host'])) {
|
|
||||||
$s3Domain = $s3Parsed['scheme'] . '://' . $s3Parsed['host'];
|
|
||||||
$mediaSrc .= " " . $s3Domain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isLocalDev) {
|
|
||||||
$mediaSrc .= " https: http:";
|
|
||||||
} else {
|
|
||||||
$mediaSrc .= " https:";
|
|
||||||
}
|
|
||||||
$csp .= "media-src " . $mediaSrc . "; ";
|
|
||||||
|
|
||||||
$csp .= "object-src 'none'; ";
|
$csp .= "object-src 'none'; ";
|
||||||
$csp .= "base-uri 'self';";
|
$csp .= "base-uri 'self';";
|
||||||
|
|
||||||
header('Content-Security-Policy: ' . $csp);
|
header('Content-Security-Policy: ' . $csp);
|
||||||
|
|
||||||
// HTTPS strict transport security (seulement si HTTPS)
|
|
||||||
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
|
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
|
||||||
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
|
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-68
@@ -1,88 +1,53 @@
|
|||||||
<!-- Sidebar de navigation -->
|
<!-- Sidebar de navigation -->
|
||||||
<nav class="sidebar" role="navigation" aria-label="Navigation principale">
|
<nav class="sidebar" role="navigation" aria-label="Navigation principale">
|
||||||
<a href="/" class="logo" aria-label="Retour à l'accueil">
|
<a href="/" class="logo" aria-label="Retour à l'accueil">
|
||||||
<img src="img/logo.png" alt="Logo kaubuntu.re">
|
<img src="img/logo.png" alt="Logo <?php echo htmlspecialchars(SITE_NAME); ?>">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Détecter la page courante et ses paramètres
|
|
||||||
$currentPage = basename($_SERVER['PHP_SELF']);
|
$currentPage = basename($_SERVER['PHP_SELF']);
|
||||||
$currentCategoryId = isset($_GET['id']) ? intval($_GET['id']) : null;
|
$isHome = ($currentPage === 'index.php' || $currentPage === '/');
|
||||||
$currentQuery = isset($_GET['q']) ? trim($_GET['q']) : '';
|
|
||||||
$isTagSearch = !empty($currentQuery) && substr($currentQuery, 0, 1) === '#';
|
|
||||||
$currentTag = $isTagSearch ? substr($currentQuery, 1) : '';
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="sidebar-nav">
|
<div class="sidebar-nav">
|
||||||
<a href="/" class="nav-item <?php echo ($currentPage === 'index.php') ? 'active' : ''; ?>" data-title="Accueil" aria-current="<?php echo ($currentPage === 'index.php') ? 'page' : 'false'; ?>">
|
<a href="/"
|
||||||
|
class="nav-item <?php echo $isHome ? 'active' : ''; ?>"
|
||||||
|
data-title="Accueil"
|
||||||
|
aria-current="<?php echo $isHome ? 'page' : 'false'; ?>">
|
||||||
<i class="fas fa-home" aria-hidden="true"></i> <span>Accueil</span>
|
<i class="fas fa-home" aria-hidden="true"></i> <span>Accueil</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="direct.php" class="nav-item <?php echo ($currentPage === 'direct.php') ? 'active' : ''; ?>" data-title="Direct" aria-current="<?php echo ($currentPage === 'direct.php') ? 'page' : 'false'; ?>">
|
|
||||||
<i class="fas fa-broadcast-tower" aria-hidden="true"></i> <span>Direct</span>
|
<div class="nav-divider"></div>
|
||||||
|
|
||||||
|
<a href="<?php echo $isHome ? '#youtube' : 'index.php#youtube'; ?>"
|
||||||
|
class="nav-item"
|
||||||
|
data-title="YouTube">
|
||||||
|
<i class="fab fa-youtube icon-youtube" aria-hidden="true"></i> <span>YouTube</span>
|
||||||
|
</a>
|
||||||
|
<a href="<?php echo $isHome ? '#instagram' : 'index.php#instagram'; ?>"
|
||||||
|
class="nav-item"
|
||||||
|
data-title="Instagram">
|
||||||
|
<i class="fab fa-instagram icon-instagram" aria-hidden="true"></i> <span>Instagram</span>
|
||||||
|
</a>
|
||||||
|
<a href="<?php echo $isHome ? '#tiktok' : 'index.php#tiktok'; ?>"
|
||||||
|
class="nav-item"
|
||||||
|
data-title="TikTok">
|
||||||
|
<i class="fab fa-tiktok icon-tiktok" aria-hidden="true"></i> <span>TikTok</span>
|
||||||
|
</a>
|
||||||
|
<a href="<?php echo $isHome ? '#actualites' : 'index.php#actualites'; ?>"
|
||||||
|
class="nav-item"
|
||||||
|
data-title="Actualités">
|
||||||
|
<i class="fas fa-newspaper" aria-hidden="true"></i> <span>Actualités</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<?php if (defined('DONATIONS_ENABLED') && DONATIONS_ENABLED && !empty(PAYPAL_ME_URL)): ?>
|
<?php if (defined('DONATIONS_ENABLED') && DONATIONS_ENABLED && !empty(PAYPAL_ME_URL)): ?>
|
||||||
<a href="dons.php" class="nav-item donation-nav-link <?php echo ($currentPage === 'dons.php') ? 'active' : ''; ?>" data-title="Soutenir" aria-current="<?php echo ($currentPage === 'dons.php') ? 'page' : 'false'; ?>">
|
<div class="nav-divider"></div>
|
||||||
|
<a href="dons.php"
|
||||||
|
class="nav-item donation-nav-link <?php echo ($currentPage === 'dons.php') ? 'active' : ''; ?>"
|
||||||
|
data-title="Soutenir"
|
||||||
|
aria-current="<?php echo ($currentPage === 'dons.php') ? 'page' : 'false'; ?>">
|
||||||
<i class="fas fa-heart" aria-hidden="true"></i> <span>Soutenir</span>
|
<i class="fas fa-heart" aria-hidden="true"></i> <span>Soutenir</span>
|
||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="nav-divider"></div>
|
|
||||||
|
|
||||||
<?php
|
|
||||||
// Afficher les catégories prioritaires
|
|
||||||
if (defined('PRIORITY_CATEGORIES') && !empty(PRIORITY_CATEGORIES)) {
|
|
||||||
// Tableau associatif des icônes pour les catégories
|
|
||||||
$categoryIcons = [
|
|
||||||
1 => 'fas fa-music', // Musique
|
|
||||||
2 => 'fas fa-film', // Films
|
|
||||||
3 => 'fas fa-car', // Véhicules
|
|
||||||
4 => 'fas fa-palette', // Jeux
|
|
||||||
5 => 'fas fa-running', // Sport
|
|
||||||
6 => 'fas fa-laugh', // Humour
|
|
||||||
7 => 'fas fa-gamepad', // Art
|
|
||||||
8 => 'fas fa-person', // Personnalités
|
|
||||||
9 => 'fas fa-face-grin-tears', // Comédie
|
|
||||||
10 => 'fas fa-tv', // Divertissement
|
|
||||||
11 => 'fas fa-globe', // Actualité & Politique
|
|
||||||
12 => 'fas fa-chalkboard-user', // Tutoriel
|
|
||||||
13 => 'fas fa-graduation-cap', // Education
|
|
||||||
14 => 'fas fa-fist-raised', // Activisme
|
|
||||||
15 => 'fas fa-microscope', // Science & Technologie
|
|
||||||
16 => 'fas fa-paw', // Animaux
|
|
||||||
17 => 'fas fa-child', // Enfants
|
|
||||||
18 => 'fas fa-utensils' // Cuisine
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach (PRIORITY_CATEGORIES as $id => $name) {
|
|
||||||
$isActive = ($currentPage === 'categories.php' && $currentCategoryId === $id);
|
|
||||||
$icon = isset($categoryIcons[$id]) ? $categoryIcons[$id] : 'fas fa-folder';
|
|
||||||
echo '<a href="categories.php?id=' . $id . '" class="nav-item ' . ($isActive ? 'active' : '') . '" data-title="' . htmlspecialchars($name) . '" aria-current="' . ($isActive ? 'page' : 'false') . '">';
|
|
||||||
echo '<i class="' . $icon . '" aria-hidden="true"></i> <span>' . htmlspecialchars($name) . '</span>';
|
|
||||||
echo '</a>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
|
|
||||||
<div class="nav-divider"></div>
|
|
||||||
|
|
||||||
<div class="category-title" role="heading" aria-level="2">
|
|
||||||
<i class="fas fa-hashtag" aria-hidden="true"></i> <span>Hashtags</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
|
||||||
if (defined('IMPORTANT_TAGS') && !empty(IMPORTANT_TAGS)):
|
|
||||||
foreach (IMPORTANT_TAGS as $tag):
|
|
||||||
$encodedTag = urlencode('#' . $tag);
|
|
||||||
$isActive = ($isTagSearch && strtolower($currentTag) === strtolower($tag));
|
|
||||||
?>
|
|
||||||
<a href="recherche.php?q=<?php echo $encodedTag; ?>" class="tag-item <?php echo $isActive ? 'active' : ''; ?>" data-title="<?php echo htmlspecialchars($tag); ?>" aria-current="<?php echo $isActive ? 'page' : 'false'; ?>">
|
|
||||||
<i class="fas fa-hashtag tag-icon" aria-hidden="true"></i> <span><?php echo htmlspecialchars($tag); ?></span>
|
|
||||||
</a>
|
|
||||||
<?php
|
|
||||||
endforeach;
|
|
||||||
endif;
|
|
||||||
?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Récupération des statistiques de followers pour chaque plateforme sociale.
|
||||||
|
* Toutes les fonctions retournent null en cas d'échec — l'affichage reste optionnel.
|
||||||
|
* Cache 24h pour limiter les appels externes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function formatFollowerCount(int $n): string {
|
||||||
|
if ($n >= 1_000_000) return number_format($n / 1_000_000, 1, '.', '') . 'M';
|
||||||
|
if ($n >= 1_000) return number_format($n / 1_000, 1, '.', '') . 'K';
|
||||||
|
return (string) $n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _statsCurlFetch(string $url, array $headers = []): ?string {
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 8,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 5,
|
||||||
|
CURLOPT_FOLLOWLOCATION => false,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => 2,
|
||||||
|
CURLOPT_USERAGENT => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/124.0 Safari/537.36',
|
||||||
|
CURLOPT_HTTPHEADER => $headers,
|
||||||
|
]);
|
||||||
|
$body = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
return ($body && $httpCode === 200) ? $body : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYouTubeSubscriberCount(): ?int {
|
||||||
|
// Priority 1: YouTube Data API v3 (exact count)
|
||||||
|
if (defined('YOUTUBE_API_KEY') && !empty(YOUTUBE_API_KEY)) {
|
||||||
|
$channelId = getYouTubeChannelId();
|
||||||
|
if ($channelId) {
|
||||||
|
$cacheKey = 'stat_yt_' . $channelId;
|
||||||
|
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||||
|
if ($cached !== null) return (int) $cached ?: null;
|
||||||
|
|
||||||
|
$data = callYouTubeApi('channels', ['part' => 'statistics', 'id' => $channelId]);
|
||||||
|
$count = isset($data['items'][0]['statistics']['subscriberCount'])
|
||||||
|
? (int) $data['items'][0]['statistics']['subscriberCount']
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if ($count !== null) {
|
||||||
|
$GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400);
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 2: parse ytInitialData from public channel page
|
||||||
|
$handle = '';
|
||||||
|
if (defined('YOUTUBE_HANDLE') && !empty(YOUTUBE_HANDLE)) {
|
||||||
|
$handle = ltrim(YOUTUBE_HANDLE, '@');
|
||||||
|
} elseif (defined('YOUTUBE_CHANNEL_HANDLE') && !empty(YOUTUBE_CHANNEL_HANDLE)) {
|
||||||
|
$handle = ltrim(YOUTUBE_CHANNEL_HANDLE, '@');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageUrl = $handle
|
||||||
|
? 'https://www.youtube.com/@' . urlencode($handle)
|
||||||
|
: (defined('YOUTUBE_CHANNEL_ID') && YOUTUBE_CHANNEL_ID
|
||||||
|
? 'https://www.youtube.com/channel/' . urlencode(YOUTUBE_CHANNEL_ID)
|
||||||
|
: null);
|
||||||
|
|
||||||
|
if (!$pageUrl) return null;
|
||||||
|
|
||||||
|
$cacheKey = 'stat_yt_page_' . preg_replace('/[^a-z0-9]/i', '_', $handle ?: YOUTUBE_CHANNEL_ID);
|
||||||
|
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||||
|
if ($cached !== null) return (int) $cached ?: null;
|
||||||
|
|
||||||
|
$body = _statsCurlFetch($pageUrl, ['Accept-Language: fr-FR,fr;q=0.9,en;q=0.8']);
|
||||||
|
if (!$body) return null;
|
||||||
|
|
||||||
|
// ytInitialData embeds exact subscriberCount as a quoted integer string
|
||||||
|
if (preg_match('/"subscriberCount"\s*:\s*"(\d+)"/', $body, $m)) {
|
||||||
|
$count = (int) $m[1];
|
||||||
|
$GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400);
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInstagramFollowers(): ?int {
|
||||||
|
if (!defined('INSTAGRAM_HANDLE') || empty(INSTAGRAM_HANDLE)) return null;
|
||||||
|
|
||||||
|
$handle = ltrim(INSTAGRAM_HANDLE, '@');
|
||||||
|
$cacheKey = 'stat_ig_' . $handle;
|
||||||
|
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||||
|
if ($cached !== null) return (int) $cached ?: null;
|
||||||
|
|
||||||
|
$body = _statsCurlFetch(
|
||||||
|
'https://www.instagram.com/api/v1/users/web_profile_info/?username=' . urlencode($handle),
|
||||||
|
['X-IG-App-ID: 936619743392459', 'Accept: application/json']
|
||||||
|
);
|
||||||
|
if (!$body) return null;
|
||||||
|
|
||||||
|
$data = json_decode($body, true);
|
||||||
|
$count = $data['data']['user']['edge_followed_by']['count'] ?? null;
|
||||||
|
|
||||||
|
if ($count !== null) {
|
||||||
|
$count = (int) $count;
|
||||||
|
$GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400);
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTikTokFollowers(): ?int {
|
||||||
|
if (!defined('TIKTOK_HANDLE') || empty(TIKTOK_HANDLE)) return null;
|
||||||
|
|
||||||
|
$handle = ltrim(TIKTOK_HANDLE, '@');
|
||||||
|
$cacheKey = 'stat_tt_' . $handle;
|
||||||
|
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||||
|
if ($cached !== null) return (int) $cached ?: null;
|
||||||
|
|
||||||
|
$body = _statsCurlFetch('https://www.tiktok.com/embed/@' . urlencode($handle));
|
||||||
|
if (!$body) return null;
|
||||||
|
|
||||||
|
preg_match('/"followerCount"\s*:\s*(\d+)/', $body, $m);
|
||||||
|
$count = isset($m[1]) ? (int) $m[1] : null;
|
||||||
|
|
||||||
|
if ($count !== null) {
|
||||||
|
$GLOBALS['simple_api_cache']->set($cacheKey, [], $count, 86400);
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Intégration YouTube Data API v3
|
||||||
|
*/
|
||||||
|
|
||||||
|
function callYouTubeApi(string $endpoint, array $params = []): array {
|
||||||
|
if (!defined('YOUTUBE_API_KEY') || empty(YOUTUBE_API_KEY)) return [];
|
||||||
|
|
||||||
|
$params['key'] = YOUTUBE_API_KEY;
|
||||||
|
$url = 'https://www.googleapis.com/youtube/v3/' . $endpoint . '?' . http_build_query($params);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 15,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 8,
|
||||||
|
CURLOPT_FOLLOWLOCATION => false,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => 2,
|
||||||
|
CURLOPT_USERAGENT => 'KaubuntuRe/2.0',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$error = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false || !empty($error) || $httpCode < 200 || $httpCode >= 300) {
|
||||||
|
error_log('YouTube API error: ' . $error . ' (HTTP ' . $httpCode . ')');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode($response, true) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYouTubeChannelId(): string {
|
||||||
|
if (defined('YOUTUBE_CHANNEL_ID') && !empty(YOUTUBE_CHANNEL_ID)) {
|
||||||
|
return YOUTUBE_CHANNEL_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined('YOUTUBE_API_KEY') || empty(YOUTUBE_API_KEY)) return '';
|
||||||
|
if (!defined('YOUTUBE_CHANNEL_HANDLE') || empty(YOUTUBE_CHANNEL_HANDLE)) return '';
|
||||||
|
|
||||||
|
$handle = ltrim(YOUTUBE_CHANNEL_HANDLE, '@');
|
||||||
|
$cached = $GLOBALS['simple_api_cache']->get('yt_channel_id_' . $handle, []);
|
||||||
|
if ($cached !== null) return (string) $cached;
|
||||||
|
|
||||||
|
$data = callYouTubeApi('channels', ['part' => 'snippet', 'forHandle' => $handle]);
|
||||||
|
$channelId = $data['items'][0]['id'] ?? '';
|
||||||
|
|
||||||
|
if ($channelId) {
|
||||||
|
$GLOBALS['simple_api_cache']->set('yt_channel_id_' . $handle, [], $channelId, 86400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $channelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isYouTubeShort(string $isoDuration): bool {
|
||||||
|
preg_match('/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/', $isoDuration, $m);
|
||||||
|
$seconds = (int)($m[1] ?? 0) * 3600 + (int)($m[2] ?? 0) * 60 + (int)($m[3] ?? 0);
|
||||||
|
return $seconds > 0 && $seconds <= 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYouTubeLatestVideos(int $count = 6): array {
|
||||||
|
// Priorité 1 : YouTube Data API v3
|
||||||
|
if (defined('YOUTUBE_API_KEY') && !empty(YOUTUBE_API_KEY)) {
|
||||||
|
$channelId = getYouTubeChannelId();
|
||||||
|
if ($channelId) {
|
||||||
|
$shortsTarget = min(3, $count - 1);
|
||||||
|
$regularTarget = $count - $shortsTarget;
|
||||||
|
|
||||||
|
$cacheKey = 'yt_videos2_' . $channelId . '_' . $count;
|
||||||
|
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||||
|
if ($cached !== null) return $cached;
|
||||||
|
|
||||||
|
// On récupère 50 vidéos pour avoir suffisamment de chaque type
|
||||||
|
$searchData = callYouTubeApi('search', [
|
||||||
|
'part' => 'snippet',
|
||||||
|
'channelId' => $channelId,
|
||||||
|
'order' => 'date',
|
||||||
|
'type' => 'video',
|
||||||
|
'maxResults' => 50,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($searchData['items'])) {
|
||||||
|
$videoIds = array_map(fn($i) => $i['id']['videoId'], $searchData['items']);
|
||||||
|
|
||||||
|
// videos.list coûte 1 unité quota (vs 100 pour search)
|
||||||
|
$detailData = callYouTubeApi('videos', [
|
||||||
|
'part' => 'contentDetails',
|
||||||
|
'id' => implode(',', $videoIds),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$durations = [];
|
||||||
|
foreach ($detailData['items'] ?? [] as $item) {
|
||||||
|
$durations[$item['id']] = $item['contentDetails']['duration'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$shorts = [];
|
||||||
|
$regular = [];
|
||||||
|
|
||||||
|
foreach ($searchData['items'] as $item) {
|
||||||
|
$videoId = $item['id']['videoId'];
|
||||||
|
$isShort = isset($durations[$videoId])
|
||||||
|
? isYouTubeShort($durations[$videoId])
|
||||||
|
: false;
|
||||||
|
|
||||||
|
$video = [
|
||||||
|
'id' => $videoId,
|
||||||
|
'title' => html_entity_decode($item['snippet']['title'], ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||||
|
'thumbnail' => $item['snippet']['thumbnails']['high']['url']
|
||||||
|
?? $item['snippet']['thumbnails']['default']['url'],
|
||||||
|
'publishedAt' => $item['snippet']['publishedAt'],
|
||||||
|
'url' => $isShort
|
||||||
|
? 'https://www.youtube.com/shorts/' . $videoId
|
||||||
|
: 'https://www.youtube.com/watch?v=' . $videoId,
|
||||||
|
'isShort' => $isShort,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($isShort && count($shorts) < $shortsTarget) {
|
||||||
|
$shorts[] = $video;
|
||||||
|
} elseif (!$isShort && count($regular) < $regularTarget) {
|
||||||
|
$regular[] = $video;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($shorts) >= $shortsTarget && count($regular) >= $regularTarget) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shorts en premier, puis vidéos normales
|
||||||
|
$videos = array_merge($shorts, $regular);
|
||||||
|
|
||||||
|
if (!empty($videos)) {
|
||||||
|
$ttl = defined('CACHE_DURATION') ? CACHE_DURATION : 900;
|
||||||
|
$GLOBALS['simple_api_cache']->set($cacheKey, [], $videos, $ttl);
|
||||||
|
return $videos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priorité 2 : flux RSS public YouTube (sans clé API, nécessite YOUTUBE_CHANNEL_ID)
|
||||||
|
return getYouTubeVideosFromRSS($count);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYouTubeVideosFromRSS(int $count = 6): array {
|
||||||
|
if (!defined('YOUTUBE_CHANNEL_ID') || empty(YOUTUBE_CHANNEL_ID)) return [];
|
||||||
|
|
||||||
|
$cacheKey = 'yt_rss_' . YOUTUBE_CHANNEL_ID . '_' . $count;
|
||||||
|
$cached = $GLOBALS['simple_api_cache']->get($cacheKey, []);
|
||||||
|
if ($cached !== null) return $cached;
|
||||||
|
|
||||||
|
$feedUrl = 'https://www.youtube.com/feeds/videos.xml?channel_id=' . urlencode(YOUTUBE_CHANNEL_ID);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $feedUrl,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 10,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 5,
|
||||||
|
CURLOPT_FOLLOWLOCATION => false,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => 2,
|
||||||
|
CURLOPT_USERAGENT => 'KaubuntuRe/2.0',
|
||||||
|
]);
|
||||||
|
$xmlString = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if (!$xmlString || $httpCode !== 200) {
|
||||||
|
error_log('YouTube RSS error: HTTP ' . $httpCode);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$xml = @simplexml_load_string($xmlString);
|
||||||
|
if (!$xml) return [];
|
||||||
|
|
||||||
|
$videos = [];
|
||||||
|
$ytNs = 'http://www.youtube.com/xml/schemas/2015';
|
||||||
|
|
||||||
|
foreach ($xml->entry as $entry) {
|
||||||
|
if (count($videos) >= $count) break;
|
||||||
|
$yt = $entry->children($ytNs);
|
||||||
|
$videoId = (string) $yt->videoId;
|
||||||
|
if (!$videoId) continue;
|
||||||
|
|
||||||
|
$videos[] = [
|
||||||
|
'id' => $videoId,
|
||||||
|
'title' => html_entity_decode((string) $entry->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||||
|
'thumbnail' => "https://i.ytimg.com/vi/{$videoId}/hqdefault.jpg",
|
||||||
|
'publishedAt' => (string) $entry->published,
|
||||||
|
'url' => "https://www.youtube.com/watch?v={$videoId}",
|
||||||
|
'isShort' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($videos)) {
|
||||||
|
$ttl = defined('CACHE_DURATION') ? CACHE_DURATION : 3600;
|
||||||
|
$GLOBALS['simple_api_cache']->set($cacheKey, [], $videos, $ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $videos;
|
||||||
|
}
|
||||||
+41
-1
@@ -111,6 +111,46 @@ function isValidWordPressUrl($url) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le nombre total d'articles publiés sur WordPress.
|
||||||
|
* Lit le header X-WP-Total renvoyé par l'API REST.
|
||||||
|
*/
|
||||||
|
function getWordPressPostCount(): ?int {
|
||||||
|
if (!defined('WORDPRESS_ENABLED') || !WORDPRESS_ENABLED || empty(WORDPRESS_URL)) return null;
|
||||||
|
if (!isValidWordPressUrl(WORDPRESS_URL)) return null;
|
||||||
|
|
||||||
|
$cached = $GLOBALS['simple_api_cache']->get('wp_post_count', []);
|
||||||
|
if ($cached !== null) return (int) $cached ?: null;
|
||||||
|
|
||||||
|
$url = WORDPRESS_URL . '/wp-json/wp/v2/posts?per_page=1&_fields=id';
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HEADER => true,
|
||||||
|
CURLOPT_TIMEOUT => 10,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 5,
|
||||||
|
CURLOPT_FOLLOWLOCATION => false,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => 2,
|
||||||
|
CURLOPT_USERAGENT => 'KaubuntuRe-WordPress-Integration/1.0',
|
||||||
|
]);
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if (!$response || $httpCode < 200 || $httpCode >= 300) return null;
|
||||||
|
|
||||||
|
$headers = substr($response, 0, $headerSize);
|
||||||
|
if (!preg_match('/X-WP-Total:\s*(\d+)/i', $headers, $m)) return null;
|
||||||
|
|
||||||
|
$count = (int) $m[1];
|
||||||
|
$GLOBALS['simple_api_cache']->set('wp_post_count', [], $count, 86400);
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les articles WordPress récents
|
* Récupère les articles WordPress récents
|
||||||
*
|
*
|
||||||
@@ -157,7 +197,7 @@ function formatWordPressPosts($posts) {
|
|||||||
// Nettoyer l'extrait HTML
|
// Nettoyer l'extrait HTML
|
||||||
$excerpt = '';
|
$excerpt = '';
|
||||||
if (isset($post['excerpt']['rendered'])) {
|
if (isset($post['excerpt']['rendered'])) {
|
||||||
$excerpt = wp_strip_all_tags($post['excerpt']['rendered']);
|
$excerpt = html_entity_decode(wp_strip_all_tags($post['excerpt']['rendered']), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
$excerpt = trim($excerpt);
|
$excerpt = trim($excerpt);
|
||||||
// Limiter à 150 caractères
|
// Limiter à 150 caractères
|
||||||
if (strlen($excerpt) > 150) {
|
if (strlen($excerpt) > 150) {
|
||||||
|
|||||||
Reference in New Issue
Block a user