ghassan 0b2e95ea65 Add NAS file manager integration and all pending platform changes
- Installed p7h/nas-file-manager package via private VCS repo
- Published config/nas-file-manager.php with super_admin middleware restriction
- Added NAS env vars to .env.example
- Created admin/nas-storage page with connection info panel and file browser widget
- Added NAS Storage link to admin sidebar (super_admin only)
- Added SuperAdminController@nasStorage method and admin.nas-storage route
- Includes all accumulated branch changes: profile wall, 2FA, audit logs,
  settings panel, country/phone/timezone components, posts, slideshow,
  playlist shares, video downloads/shares, comment likes, notifications,
  social links, and more

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:24:32 +03:00

2990 lines
152 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('layouts.app')
@section('title', $user->name . ' — Channel | ' . config('app.name'))
@section('main_class', 'no-pad')
@section('extra_styles')
<style>
@php
$hue = (crc32($user->name) % 360 + 360) % 360;
$hue2 = ($hue + 45) % 360;
$hue3 = ($hue + 200) % 360;
@endphp
.yt-main.no-pad { padding: 0; }
/* ══════════════════════════════════════════════════
BANNER
══════════════════════════════════════════════════ */
.ch-banner {
width: 100%;
height: 200px;
position: relative;
overflow: hidden;
background:
radial-gradient(ellipse 80% 120% at 20% 50%, hsl({{ $hue }}, 60%, 18%) 0%, transparent 70%),
radial-gradient(ellipse 60% 100% at 80% 30%, hsl({{ $hue2 }}, 55%, 14%) 0%, transparent 65%),
radial-gradient(ellipse 50% 80% at 55% 80%, hsl({{ $hue3 }}, 40%, 10%) 0%, transparent 60%),
#0a0a0a;
}
.ch-banner::after {
content: '';
position: absolute; inset: 0;
background: linear-gradient(to bottom, transparent 40%, rgba(0,0,0,.55) 100%);
}
.ch-banner-noise {
position: absolute; inset: 0;
opacity: .035;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
background-size: 200px 200px;
}
@media (min-width: 768px) { .ch-banner { height: 230px; } }
@media (min-width: 1280px) { .ch-banner { height: 260px; } }
/* ══════════════════════════════════════════════════
HEADER BLOCK
══════════════════════════════════════════════════ */
.ch-header {
background: var(--bg-dark);
padding: 0 32px 24px;
}
.ch-header-inner {
display: flex;
align-items: flex-end;
gap: 28px;
margin-top: -60px;
position: relative;
z-index: 1;
}
/* Avatar */
.ch-avatar-wrap { flex-shrink: 0; position: relative; }
.ch-avatar {
width: 120px; height: 120px; border-radius: 14px;
object-fit: cover; display: block;
border: 3px solid var(--bg-dark); background: #222;
box-shadow: 0 0 0 2px hsl({{ $hue }}, 50%, 38%),
0 12px 40px rgba(0,0,0,.65),
0 0 0 8px rgba(0,0,0,.35);
transition: box-shadow .3s, transform .2s;
}
.ch-avatar:hover { box-shadow: 0 0 0 2px hsl({{ $hue }}, 70%, 52%),
0 16px 48px rgba(0,0,0,.7),
0 0 0 8px rgba(0,0,0,.35); transform: translateY(-2px); }
/* Avatar & banner edit overlays */
.ch-avatar-edit-btn {
position: absolute; inset: 0; border-radius: 14px;
background: rgba(0,0,0,.58); border: none; cursor: pointer;
color: #fff; font-size: 22px; opacity: 0;
display: flex; align-items: center; justify-content: center;
transition: opacity .18s;
}
.ch-avatar-wrap:hover .ch-avatar-edit-btn { opacity: 1; }
.ch-banner-img {
position: absolute; inset: 0;
width: 100%; height: 100%; object-fit: cover; z-index: 0;
}
.ch-banner-edit-btn {
position: absolute; bottom: 14px; right: 14px; z-index: 2;
display: inline-flex; align-items: center; gap: 7px;
height: 34px; padding: 0 14px; border-radius: 8px;
background: rgba(0,0,0,.65); backdrop-filter: blur(6px);
border: 1px solid rgba(255,255,255,.2); color: #fff;
font-size: 13px; font-weight: 600; cursor: pointer;
transition: background .15s, opacity .18s;
opacity: 0;
}
.ch-banner:hover .ch-banner-edit-btn { opacity: 1; }
.ch-banner-edit-btn:hover { background: rgba(20,20,20,.85); }
.ch-banner-actions {
position: absolute; top: 14px; right: 14px; z-index: 2;
display: flex; align-items: center; gap: 8px;
}
.ch-banner-subscribe-btn {
display: inline-flex; align-items: center; gap: 7px;
height: 36px; padding: 0 18px; border-radius: 8px;
background: rgba(0,0,0,.55); backdrop-filter: blur(8px);
border: 1px solid rgba(255,255,255,.25); color: #fff;
font-size: 13px; font-weight: 700; cursor: pointer;
transition: background .15s, border-color .15s;
white-space: nowrap; text-decoration: none;
}
.ch-banner-subscribe-btn:hover { background: rgba(0,0,0,.75); border-color: rgba(255,255,255,.45); color: #fff; }
.ch-banner-subscribe-btn.subscribed-ch { background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.3); }
.ch-banner-subscribe-btn.subscribed-ch:hover { background: rgba(255,255,255,.18); }
.ch-banner-share-btn {
display: inline-flex; align-items: center; justify-content: center;
width: 36px; height: 36px; border-radius: 8px;
background: rgba(0,0,0,.55); backdrop-filter: blur(8px);
border: 1px solid rgba(255,255,255,.25); color: #fff;
font-size: 15px; cursor: pointer;
transition: background .15s, border-color .15s;
text-decoration: none;
}
.ch-banner-share-btn:hover { background: rgba(0,0,0,.75); border-color: rgba(255,255,255,.45); color: #fff; }
/* Info */
.ch-info { flex: 1; min-width: 0; padding-top: 12px; }
.ch-name-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 3px; }
.ch-name { font-size: 26px; font-weight: 800; line-height: 1.15; color: var(--text-primary); margin: 0; letter-spacing: -.4px; }
.ch-verified-badge {
display: inline-flex; align-items: center; justify-content: center;
width: 20px; height: 20px; border-radius: 50%;
background: hsl({{ $hue }}, 60%, 45%); color: #fff; font-size: 11px; flex-shrink: 0;
}
.ch-handle { font-size: 13px; color: var(--text-secondary); margin-bottom: 10px; opacity: .7; }
/* Flat stats row — no pills */
.ch-meta-row {
display: flex; align-items: center; flex-wrap: wrap;
gap: 0; margin-bottom: 12px; font-size: 13px; color: var(--text-secondary);
}
.ch-meta-item { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.ch-meta-item i { font-size: 11px; color: hsl({{ $hue }}, 55%, 55%); }
.ch-meta-sep { margin: 0 10px; opacity: .3; font-size: 12px; }
/* Bio */
.ch-bio { font-size: 13.5px; color: var(--text-secondary); line-height: 1.65; max-width: 580px; margin-bottom: 14px; }
.ch-bio-short { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.ch-bio-full { display: none; }
.ch-bio-toggle { background: none; border: none; color: var(--text-primary); font-size: 13px; font-weight: 600; cursor: pointer; padding: 0; margin-left: 2px; }
/* Horoscope + compat strip */
.ch-horo-strip {
display: flex; align-items: center; flex-wrap: wrap; gap: 6px 14px;
margin-bottom: 14px; padding: 10px 14px;
background: rgba(255,255,255,.03);
border: 1px solid rgba(255,255,255,.07);
border-radius: 10px;
width: fit-content; max-width: 100%;
}
.ch-horo-sign-part { display: flex; align-items: center; gap: 8px; }
.ch-horo-emoji { font-size: 22px; line-height: 1; cursor: default;
filter: drop-shadow(0 0 6px hsl({{ $hue }}, 60%, 45%)); }
.ch-horo-name { font-size: 14px; font-weight: 700; color: var(--text-primary); }
.ch-horo-el {
font-size: 11px; font-weight: 600; padding: 2px 8px;
border-radius: 4px;
background: rgba(255,255,255,.07); color: var(--text-secondary);
border: 1px solid rgba(255,255,255,.1);
}
.ch-horo-divider { width: 1px; height: 18px; background: rgba(255,255,255,.12); flex-shrink: 0; }
.ch-compat-mini { display: flex; align-items: center; gap: 8px; }
.ch-compat-mini-pct { font-size: 13px; font-weight: 800; }
.ch-compat-mini-track {
width: 72px; height: 4px; border-radius: 4px;
background: rgba(255,255,255,.1); overflow: hidden;
}
.ch-compat-mini-fill {
height: 100%; border-radius: 4px;
transition: width 1.2s cubic-bezier(.22,.61,.36,1);
}
.ch-compat-mini-label { font-size: 11px; color: var(--text-secondary); white-space: nowrap; }
/* Social icon buttons row */
.ch-social-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
.ch-social-btn {
display: inline-flex; align-items: center; justify-content: center;
width: 44px; height: 44px; border-radius: 8px;
background: rgba(255,255,255,.06);
border: 1px solid rgba(255,255,255,.12);
color: rgba(255,255,255,.65); font-size: 16px;
text-decoration: none; cursor: pointer;
transition: background .15s, transform .1s, border-color .15s, color .15s;
flex-shrink: 0;
}
.ch-social-btn:hover {
background: rgba(255,255,255,.12);
border-color: rgba(255,255,255,.3);
color: #fff;
transform: translateY(-1px);
}
/* Actions */
.ch-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ch-btn-primary {
display: inline-flex; align-items: center; gap: 7px;
background: var(--text-primary); color: var(--bg-dark);
border: none; height: 36px; padding: 0 18px;
border-radius: 8px; font-size: 14px; font-weight: 700;
cursor: pointer; transition: background .15s, transform .1s;
text-decoration: none; white-space: nowrap; flex-shrink: 0;
}
.ch-btn-primary:hover { background: #d4d4d4; color: var(--bg-dark); transform: translateY(-1px); }
.ch-btn-primary.subscribed-ch { background: rgba(255,255,255,.08); color: var(--text-primary); border: 1px solid rgba(255,255,255,.18); }
.ch-btn-primary.subscribed-ch:hover { background: rgba(255,255,255,.13); }
.ch-btn-ghost {
display: inline-flex; align-items: center; gap: 7px;
background: rgba(255,255,255,.07); color: var(--text-primary);
border: 1px solid rgba(255,255,255,.12);
height: 36px; padding: 0 16px; border-radius: 8px;
font-size: 14px; font-weight: 500; cursor: pointer;
transition: background .15s; text-decoration: none; white-space: nowrap;
}
.ch-btn-ghost:hover { background: rgba(255,255,255,.13); color: var(--text-primary); }
.ch-btn-icon {
display: inline-flex; align-items: center; justify-content: center;
width: 36px; height: 36px; border-radius: 8px;
background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.12);
color: var(--text-primary); font-size: 15px; cursor: pointer; transition: background .15s;
}
.ch-btn-icon:hover { background: rgba(255,255,255,.15); }
/* Video search bar */
.ch-vid-search-wrap {
position: relative; margin-bottom: 20px; max-width: 340px;
}
.ch-vid-search-wrap i {
position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
color: var(--text-secondary); font-size: 14px; pointer-events: none;
}
.ch-vid-search {
width: 100%; height: 38px; padding: 0 14px 0 36px;
background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.1);
border-radius: 8px; color: var(--text-primary); font-size: 14px;
outline: none; transition: border-color .2s, background .2s;
}
.ch-vid-search:focus { border-color: hsl({{ $hue }}, 55%, 45%); background: rgba(255,255,255,.08); }
.ch-vid-search::placeholder { color: var(--text-secondary); }
.ch-vid-no-results {
display: none; text-align: center; padding: 60px 20px;
color: var(--text-secondary); grid-column: 1/-1;
}
.ch-vid-no-results i { font-size: 40px; display: block; margin-bottom: 12px; opacity: .4; }
/* ══════════════════════════════════════════════════
TABS
══════════════════════════════════════════════════ */
.ch-tabs-wrap {
position: sticky; top: 56px; z-index: 80;
background: var(--bg-dark); border-bottom: 1px solid var(--border-color); padding: 0 32px;
}
.ch-tabs { display: flex; gap: 0; overflow-x: auto; scrollbar-width: none; }
.ch-tabs::-webkit-scrollbar { display: none; }
.ch-tab {
background: none; border: none; color: var(--text-secondary);
font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: .6px;
padding: 0 20px; height: 46px; cursor: pointer; position: relative; white-space: nowrap;
transition: color .15s; flex-shrink: 0; display: inline-flex; align-items: center; gap: 7px;
}
.ch-tab:hover { color: var(--text-primary); }
.ch-tab.active { color: var(--text-primary); }
.ch-tab.active::after {
content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 2px;
background: hsl({{ $hue }}, 65%, 55%); border-radius: 2px 2px 0 0;
}
.ch-tab-badge {
background: rgba(255,255,255,.1); color: var(--text-secondary);
font-size: 11px; font-weight: 600; padding: 2px 7px; border-radius: 10px; line-height: 1.4;
}
.ch-tab.active .ch-tab-badge { background: hsl({{ $hue }}, 55%, 30%); color: hsl({{ $hue }}, 80%, 75%); }
/* ══════════════════════════════════════════════════
TAB CONTENT
══════════════════════════════════════════════════ */
.ch-tab-content { display: none; padding: 28px 32px; }
.ch-tab-content.active { display: block; }
/* Sort bar */
.ch-sort-bar { display: flex; align-items: center; gap: 10px; margin-bottom: 24px; flex-wrap: wrap; }
.ch-sort-label { font-size: 13px; font-weight: 600; color: var(--text-secondary); }
.ch-sort-pills { display: flex; gap: 6px; flex-wrap: wrap; }
.ch-sort-pill {
background: transparent; border: 1px solid rgba(255,255,255,.1); color: var(--text-secondary);
font-size: 13px; font-weight: 500; padding: 5px 14px; border-radius: 20px;
cursor: pointer; text-decoration: none; transition: all .15s; display: inline-block;
}
.ch-sort-pill:hover { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.2); color: var(--text-primary); }
.ch-sort-pill.active { background: hsl({{ $hue }}, 55%, 25%); border-color: hsl({{ $hue }}, 60%, 40%); color: hsl({{ $hue }}, 80%, 80%); }
/* ══════════════════════════════════════════════════
VIDEO GRID
══════════════════════════════════════════════════ */
.yt-video-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
@media (max-width: 1100px) { .yt-video-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .yt-video-grid { grid-template-columns: 1fr; } }
@media (min-width: 1500px) { .yt-video-grid { grid-template-columns: repeat(4, 1fr); } }
/* ══════════════════════════════════════════════════
SHORTS GRID
══════════════════════════════════════════════════ */
.ch-shorts-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(155px, 1fr)); gap: 10px; }
.ch-short-card { cursor: pointer; text-decoration: none; color: inherit; display: block; }
.ch-short-thumb { position: relative; aspect-ratio: 9/16; border-radius: 14px; overflow: hidden; background: #1a1a1a; transition: transform .2s; }
.ch-short-card:hover .ch-short-thumb { transform: scale(1.02); }
.ch-short-thumb img { width: 100%; height: 100%; object-fit: cover; }
.ch-short-thumb::after { content: ''; position: absolute; inset: 0; background: linear-gradient(to bottom, transparent 55%, rgba(0,0,0,.7) 100%); }
.ch-short-play {
position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%);
width: 40px; height: 40px; border-radius: 50%;
background: rgba(255,255,255,.2); backdrop-filter: blur(6px);
display: flex; align-items: center; justify-content: center;
color: #fff; font-size: 16px; z-index: 1; opacity: 0; transition: opacity .2s;
}
.ch-short-card:hover .ch-short-play { opacity: 1; }
.ch-short-duration { position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,.75); color: #fff; font-size: 11px; font-weight: 600; padding: 2px 7px; border-radius: 6px; z-index: 1; }
.ch-short-visibility-badge { position: absolute; bottom: 10px; left: 8px; font-size: 10px; font-weight: 600; padding: 2px 7px; border-radius: 4px; z-index: 2; display: flex; align-items: center; gap: 3px; pointer-events: none; }
.ch-vis-private { background: rgba(220,38,38,.88); color: #fff; }
.ch-vis-unlisted { background: rgba(20,20,20,.82); color: #facc15; border: 1px solid rgba(250,204,21,.4); }
.ch-short-title { font-size: 13px; font-weight: 500; margin: 9px 0 2px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.35; }
.ch-short-meta { font-size: 12px; color: var(--text-secondary); }
/* ══════════════════════════════════════════════════
PLAYLISTS GRID
══════════════════════════════════════════════════ */
.ch-playlists-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 18px; }
@media (max-width: 600px) { .ch-playlists-grid { grid-template-columns: repeat(2, 1fr); } }
.ch-playlist-card { text-decoration: none; color: inherit; display: block; transition: transform .2s; }
.ch-playlist-card:hover { transform: translateY(-3px); }
.ch-playlist-thumb { position: relative; aspect-ratio: 16/9; border-radius: 12px; overflow: hidden; background: var(--bg-secondary); margin-bottom: 10px; box-shadow: 0 4px 20px rgba(0,0,0,.4); }
.ch-playlist-thumb img { width: 100%; height: 100%; object-fit: cover; }
.ch-pl-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #1a1a1a, #252525); color: var(--text-secondary); font-size: 36px; }
.ch-playlist-overlay { position: absolute; inset: 0; background: linear-gradient(to bottom, transparent 40%, rgba(0,0,0,.75) 100%); }
.ch-playlist-count { position: absolute; bottom: 0; right: 0; background: rgba(0,0,0,.82); color: #fff; font-size: 12px; font-weight: 600; padding: 8px 12px; width: 100%; display: flex; align-items: center; justify-content: flex-end; gap: 6px; }
.ch-playlist-count i { color: hsl({{ $hue }}, 70%, 65%); }
.ch-playlist-name { font-size: 14px; font-weight: 600; margin-bottom: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-playlist-meta { font-size: 12px; color: var(--text-secondary); }
/* ══════════════════════════════════════════════════
WALL / POSTS TAB
══════════════════════════════════════════════════ */
.ch-wall-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: 28px;
}
@media (max-width: 860px) { .ch-wall-layout { grid-template-columns: 1fr; } }
/* ── Composer ────────────────────────────────────── */
.ch-composer {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-top: 3px solid hsl({{ $hue }}, 55%, 50%);
border-radius: 18px;
padding: 0;
margin-bottom: 22px;
box-shadow: 0 4px 24px rgba(0,0,0,.18);
overflow: hidden;
transition: box-shadow .2s;
}
.ch-composer:focus-within { box-shadow: 0 6px 32px rgba(0,0,0,.28), 0 0 0 1px hsl({{ $hue }}, 50%, 40%); }
.ch-composer-top {
display: flex; gap: 14px; align-items: flex-start;
padding: 18px 18px 14px;
}
.ch-composer-avatar {
width: 44px; height: 44px; border-radius: 50%;
object-fit: cover; flex-shrink: 0;
border: 2px solid hsl({{ $hue }}, 45%, 35%);
box-shadow: 0 0 0 3px hsl({{ $hue }}, 45%, 18%);
}
.ch-composer-right { flex: 1; min-width: 0; }
.ch-composer-name { font-size: 13px; font-weight: 700; color: var(--text-primary); margin-bottom: 7px; }
.ch-composer textarea {
width: 100%;
background: rgba(255,255,255,.04);
border: 1px solid rgba(255,255,255,.09);
border-radius: 14px;
color: var(--text-primary);
font-size: 15px;
padding: 13px 16px;
resize: none;
line-height: 1.6;
font-family: inherit;
min-height: 68px;
max-height: 280px;
transition: border-color .2s, min-height .2s;
}
.ch-composer textarea:focus {
outline: none;
border-color: hsl({{ $hue }}, 55%, 45%);
min-height: 110px;
background: rgba(255,255,255,.06);
}
.ch-composer textarea::placeholder { color: var(--text-secondary); font-size: 15px; }
/* ── Multi-image preview grid ── */
.ch-composer-img-grid {
display: grid;
gap: 6px;
margin: 0 18px 10px;
}
.ch-composer-img-grid.count-1 { grid-template-columns: 1fr; }
.ch-composer-img-grid.count-2 { grid-template-columns: 1fr 1fr; }
.ch-composer-img-grid.count-3 { grid-template-columns: 1fr 1fr 1fr; }
.ch-composer-img-grid.count-4,
.ch-composer-img-grid.count-more { grid-template-columns: 1fr 1fr; }
.ch-composer-img-item {
position: relative;
border-radius: 10px;
overflow: hidden;
background: rgba(255,255,255,.06);
aspect-ratio: 4/3;
}
.ch-composer-img-item img {
width: 100%; height: 100%; object-fit: cover; display: block;
}
.ch-composer-img-remove {
position: absolute; top: 6px; right: 6px;
width: 26px; height: 26px; border-radius: 50%;
background: rgba(0,0,0,.75); backdrop-filter: blur(4px);
color: #fff; border: none; font-size: 14px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: background .15s;
}
.ch-composer-img-remove:hover { background: rgba(200,30,30,.85); }
/* ── Selected video chips ── */
.ch-composer-video-chips {
display: flex; flex-wrap: wrap; gap: 8px;
margin: 0 18px 10px;
}
.ch-composer-video-chip {
display: inline-flex; align-items: center; gap: 8px;
background: rgba(96,165,250,.1); border: 1px solid rgba(96,165,250,.25);
border-radius: 20px; padding: 5px 5px 5px 8px;
font-size: 12px; font-weight: 600; color: #93c5fd; max-width: 220px;
}
.ch-composer-video-chip-thumb {
width: 32px; height: 20px; border-radius: 4px; object-fit: cover;
background: #111; flex-shrink: 0;
}
.ch-composer-video-chip span {
flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ch-composer-video-chip-remove {
flex-shrink: 0; background: rgba(96,165,250,.2); border: none;
border-radius: 50%; width: 20px; height: 20px; color: #93c5fd;
font-size: 12px; cursor: pointer; display: flex; align-items: center; justify-content: center;
transition: background .15s;
}
.ch-composer-video-chip-remove:hover { background: rgba(239,68,68,.3); color: #fca5a5; }
/* ── Video picker modal ── */
.ch-vp-backdrop {
position: fixed; inset: 0; background: rgba(0,0,0,.75);
z-index: 9100; display: flex; align-items: center; justify-content: center;
padding: 16px; animation: chVpIn .15s ease;
}
@keyframes chVpIn { from{opacity:0} to{opacity:1} }
.ch-vp-modal {
background: var(--bg-secondary); border: 1px solid var(--border-color);
border-radius: 18px; width: 100%; max-width: 680px;
max-height: 82vh; display: flex; flex-direction: column;
overflow: hidden; animation: chVpSlide .2s cubic-bezier(.34,1.4,.64,1);
}
@keyframes chVpSlide { from{opacity:0;transform:scale(.9) translateY(16px)} to{opacity:1;transform:none} }
.ch-vp-head {
display: flex; align-items: center; justify-content: space-between;
padding: 16px 18px 13px; border-bottom: 1px solid var(--border-color); flex-shrink: 0;
}
.ch-vp-head h3 { font-size: 16px; font-weight: 700; color: var(--text-primary); margin: 0; }
.ch-vp-close {
background: rgba(255,255,255,.06); border: none; color: var(--text-secondary);
border-radius: 50%; width: 30px; height: 30px; cursor: pointer;
display: flex; align-items: center; justify-content: center; font-size: 16px;
transition: background .15s, color .15s;
}
.ch-vp-close:hover { background: rgba(239,68,68,.2); color: #fca5a5; }
.ch-vp-search {
padding: 12px 18px; border-bottom: 1px solid var(--border-color); flex-shrink: 0;
}
.ch-vp-search input {
width: 100%; background: rgba(255,255,255,.06); border: 1px solid var(--border-color);
border-radius: 10px; color: var(--text-primary); padding: 9px 14px; font-size: 13px;
font-family: inherit;
}
.ch-vp-search input:focus { outline: none; border-color: rgba(96,165,250,.5); }
.ch-vp-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 10px; padding: 14px 18px; overflow-y: auto; flex: 1;
}
@media(max-width:500px){ .ch-vp-grid { grid-template-columns: repeat(2,1fr); } }
.ch-vp-card {
position: relative; border-radius: 12px; overflow: hidden;
background: rgba(255,255,255,.04); border: 2px solid transparent;
cursor: pointer; transition: border-color .15s, transform .12s;
user-select: none;
}
.ch-vp-card:hover { border-color: rgba(96,165,250,.4); transform: translateY(-2px); }
.ch-vp-card.selected { border-color: #60a5fa; background: rgba(96,165,250,.08); }
.ch-vp-card-thumb {
position: relative; aspect-ratio: 16/9; background: #111; overflow: hidden;
}
.ch-vp-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ch-vp-card-check {
position: absolute; top: 6px; right: 6px;
width: 22px; height: 22px; border-radius: 50%;
background: #60a5fa; display: none;
align-items: center; justify-content: center; font-size: 12px; color: #fff;
}
.ch-vp-card.selected .ch-vp-card-check { display: flex; }
.ch-vp-card-title {
font-size: 12px; font-weight: 600; color: var(--text-primary);
padding: 8px 9px 9px; line-height: 1.35;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ch-vp-footer {
display: flex; align-items: center; justify-content: space-between;
padding: 12px 18px 14px; border-top: 1px solid var(--border-color); flex-shrink: 0;
gap: 12px;
}
.ch-vp-count { font-size: 13px; color: var(--text-secondary); font-weight: 600; }
.ch-vp-confirm {
background: #60a5fa; color: #fff; border: none;
padding: 0 22px; height: 36px; border-radius: 18px;
font-size: 14px; font-weight: 700; cursor: pointer; font-family: inherit;
transition: background .15s; flex-shrink: 0;
}
.ch-vp-confirm:hover { background: #3b82f6; }
.ch-vp-confirm:disabled { background: rgba(255,255,255,.12); color: var(--text-secondary); cursor: not-allowed; }
/* ── Post image grid (feed) ── */
.ch-post-img-grid {
display: grid; gap: 3px; margin-top: 12px; overflow: hidden;
}
.ch-post-img-grid.count-1 { grid-template-columns: 1fr; }
.ch-post-img-grid.count-2 { grid-template-columns: 1fr 1fr; }
.ch-post-img-grid.count-3 { grid-template-columns: 1fr 1fr 1fr; }
.ch-post-img-grid.count-4,
.ch-post-img-grid.count-more { grid-template-columns: 1fr 1fr; }
.ch-post-img-item {
position: relative; overflow: hidden; cursor: zoom-in;
background: #111;
}
.ch-post-img-grid.count-1 .ch-post-img-item { max-height: 480px; }
.ch-post-img-grid.count-2 .ch-post-img-item { aspect-ratio: 4/3; }
.ch-post-img-grid.count-3 .ch-post-img-item { aspect-ratio: 1/1; }
.ch-post-img-grid.count-4 .ch-post-img-item,
.ch-post-img-grid.count-more .ch-post-img-item { aspect-ratio: 4/3; }
.ch-post-img-item img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .25s; }
.ch-post-img-item:hover img { transform: scale(1.03); }
.ch-post-img-more {
position: absolute; inset: 0; background: rgba(0,0,0,.55);
display: flex; align-items: center; justify-content: center;
font-size: 28px; font-weight: 900; color: #fff; pointer-events: none;
}
.ch-composer-bar {
display: flex; align-items: center; justify-content: space-between;
padding: 12px 18px 14px;
border-top: 1px solid rgba(255,255,255,.05);
background: rgba(255,255,255,.02);
flex-wrap: wrap; gap: 8px;
}
.ch-composer-media { display: flex; gap: 4px; }
.ch-composer-media-btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 7px 14px; border-radius: 20px;
background: none; border: none;
color: var(--text-secondary); font-size: 13px; font-weight: 500;
cursor: pointer; font-family: inherit;
transition: background .15s, color .15s;
}
.ch-composer-media-btn:hover { background: rgba(255,255,255,.08); color: var(--text-primary); }
.ch-composer-media-btn i { font-size: 16px; }
.ch-composer-media-btn.photo i { color: #4ade80; }
.ch-composer-media-btn.video i { color: #60a5fa; }
.ch-composer-post-btn {
display: inline-flex; align-items: center; gap: 7px;
background: hsl({{ $hue }}, 60%, 45%);
color: #fff; border: none;
height: 36px; padding: 0 22px;
border-radius: 18px; font-size: 14px; font-weight: 700;
cursor: pointer; font-family: inherit;
transition: background .15s, transform .1s, box-shadow .15s;
box-shadow: 0 2px 12px hsl({{ $hue }}, 60%, 30%);
}
.ch-composer-post-btn:hover {
background: hsl({{ $hue }}, 70%, 52%);
transform: translateY(-1px);
box-shadow: 0 4px 18px hsl({{ $hue }}, 60%, 25%);
}
/* ── Post card ───────────────────────────────────── */
.ch-post {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 18px;
margin-bottom: 18px;
overflow: hidden;
transition: box-shadow .2s, border-color .2s;
box-shadow: 0 2px 12px rgba(0,0,0,.12);
}
.ch-post:hover {
border-color: rgba(255,255,255,.12);
box-shadow: 0 4px 24px rgba(0,0,0,.22);
}
.ch-post-header {
display: flex; align-items: center; gap: 12px;
padding: 16px 18px 0;
}
.ch-post-avatar-link { flex-shrink: 0; display: block; }
.ch-post-avatar {
width: 44px; height: 44px; border-radius: 50%;
object-fit: cover; display: block;
border: 2px solid rgba(255,255,255,.1);
}
.ch-post-meta { flex: 1; min-width: 0; }
.ch-post-name {
font-size: 15px; font-weight: 700; color: var(--text-primary);
line-height: 1.2;
}
.ch-post-time {
font-size: 12px; color: var(--text-secondary);
margin-top: 2px; display: flex; align-items: center; gap: 4px;
}
.ch-post-time i { font-size: 11px; }
.ch-post-menu-wrap { position: relative; flex-shrink: 0; }
.ch-post-menu-btn {
background: none; border: none;
width: 34px; height: 34px; border-radius: 50%;
color: var(--text-secondary); font-size: 18px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: background .15s, color .15s;
}
.ch-post-menu-btn:hover { background: rgba(255,255,255,.08); color: var(--text-primary); }
.ch-post-dropdown {
display: none; position: absolute; right: 0; top: 38px;
background: var(--bg-secondary); border: 1px solid var(--border-color);
border-radius: 12px; min-width: 160px; z-index: 50;
box-shadow: 0 8px 32px rgba(0,0,0,.4); padding: 6px;
}
.ch-post-dropdown.open { display: block; }
.ch-post-dropdown form { margin: 0; }
.ch-post-dropdown button, .ch-post-dropdown a {
display: flex; align-items: center; gap: 10px;
width: 100%; padding: 8px 12px; border-radius: 8px;
background: none; border: none; text-align: left;
font-size: 14px; color: var(--text-secondary); cursor: pointer; font-family: inherit;
text-decoration: none; transition: background .12s, color .12s;
}
.ch-post-dropdown button:hover, .ch-post-dropdown a:hover { background: rgba(255,255,255,.07); color: var(--text-primary); }
.ch-post-dropdown .danger:hover { background: rgba(255,60,60,.12); color: #f87171; }
.ch-post-dropdown i { font-size: 14px; width: 16px; text-align: center; }
.ch-post-body {
font-size: 15px; color: var(--text-primary);
line-height: 1.7; white-space: pre-wrap;
padding: 14px 18px 4px;
}
.ch-post-body-lg { font-size: 22px; line-height: 1.45; font-weight: 500; padding: 16px 18px 4px; }
.ch-post-image-wrap {
margin-top: 12px;
cursor: zoom-in;
}
.ch-post-image-wrap img {
width: 100%; max-height: 480px; object-fit: cover; display: block;
}
.ch-post-video-wrap { padding: 12px 18px 0; }
.ch-post-video-card {
display: flex; gap: 14px; align-items: center;
background: rgba(255,255,255,.04);
border: 1px solid rgba(255,255,255,.08);
border-radius: 14px; padding: 12px 14px;
text-decoration: none; color: var(--text-primary);
transition: background .15s, border-color .15s;
overflow: hidden;
}
.ch-post-video-card:hover { background: rgba(255,255,255,.07); border-color: rgba(255,255,255,.15); color: var(--text-primary); }
.ch-post-video-thumb-wrap {
position: relative; flex-shrink: 0;
width: 100px; height: 58px; border-radius: 9px; overflow: hidden;
background: #111;
}
.ch-post-video-thumb-wrap img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ch-post-video-play {
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
background: rgba(0,0,0,.35);
transition: background .15s;
}
.ch-post-video-card:hover .ch-post-video-play { background: rgba(0,0,0,.5); }
.ch-post-video-play i { font-size: 22px; color: #fff; filter: drop-shadow(0 2px 6px rgba(0,0,0,.5)); }
.ch-post-video-info { flex: 1; min-width: 0; }
.ch-post-video-title { font-size: 14px; font-weight: 600; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.ch-post-video-meta { font-size: 12px; color: var(--text-secondary); margin-top: 5px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ch-post-video-badge {
display: inline-flex; align-items: center; gap: 4px;
background: rgba(255,255,255,.07); border-radius: 6px; padding: 2px 7px; font-size: 11px;
}
.ch-post-actions {
display: flex; align-items: center;
padding: 10px 10px 12px;
margin-top: 12px;
border-top: 1px solid rgba(255,255,255,.05);
gap: 4px;
}
.ch-post-action-btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 7px 14px; border-radius: 20px;
background: none; border: none;
color: var(--text-secondary); font-size: 13px; font-weight: 500;
cursor: pointer; font-family: inherit;
transition: background .15s, color .15s, transform .1s;
text-decoration: none;
}
.ch-post-action-btn:hover { background: rgba(255,255,255,.07); color: var(--text-primary); transform: scale(1.03); }
.ch-post-action-btn.liked { color: #f87171; }
.ch-post-action-btn.liked:hover { background: rgba(248,113,113,.1); }
.ch-post-action-btn i { font-size: 16px; }
.ch-post-action-sep { width: 1px; height: 20px; background: rgba(255,255,255,.07); margin: 0 2px; }
.ch-post-like-anim { display: inline-block; }
.ch-post-like-anim.pop { animation: likePop .3s ease; }
@keyframes likePop {
0% { transform: scale(1); }
50% { transform: scale(1.5); }
100% { transform: scale(1); }
}
/* ── Wall sidebar ─────────────────────────────────── */
.ch-wall-sidebar {}
.ch-sidebar-widget {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 18px;
overflow: hidden;
margin-bottom: 18px;
box-shadow: 0 2px 16px rgba(0,0,0,.12);
}
/* ── Compact horoscope + compat card ── */
.ch-sw-card {
background: var(--bg-secondary);
border: 1px solid rgba(255,255,255,.08);
border-radius: 14px;
padding: 14px 16px;
box-shadow: 0 0 0 1px var(--sw-h, transparent) inset,
0 4px 24px rgba(0,0,0,.18);
}
.ch-sw-sign-row { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.ch-sw-emoji { font-size: 32px; line-height: 1; flex-shrink: 0; }
.ch-sw-sign-info { min-width: 0; }
.ch-sw-sign-name { font-size: 15px; font-weight: 800; color: var(--text-primary); line-height: 1.2; }
.ch-sw-element { font-size: 11px; font-weight: 600; color: var(--sw-h, var(--text-secondary)); margin-top: 2px; text-transform: uppercase; letter-spacing: .05em; }
.ch-sw-traits { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 12px; }
.ch-sw-trait {
font-size: 11px; font-weight: 500; padding: 3px 9px;
border-radius: 4px;
background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.09);
color: var(--text-secondary);
}
.ch-sw-compat {
border-top: 1px solid rgba(255,255,255,.07);
padding-top: 10px;
}
.ch-sw-compat-signs {
display: flex; align-items: center; gap: 6px;
margin-bottom: 7px; font-size: 20px;
}
.ch-sw-compat-sign { display: flex; flex-direction: column; align-items: center; line-height: 1; }
.ch-sw-compat-sign small { font-size: 10px; color: var(--text-secondary); margin-top: 2px; font-style: normal; }
.ch-sw-compat-heart { font-size: 14px; }
.ch-sw-compat-pct { font-size: 15px; font-weight: 900; line-height: 1; }
.ch-sw-compat-lbl { font-size: 11px; color: var(--text-secondary); white-space: nowrap; }
.ch-sw-compat-bar {
height: 4px; border-radius: 4px;
background: rgba(255,255,255,.08); overflow: hidden;
}
.ch-sw-compat-fill { height: 100%; border-radius: 4px; transition: width 1.3s cubic-bezier(.22,.61,.36,1); }
.ch-sw-compat-prompt {
border-top: 1px solid rgba(255,255,255,.07);
padding-top: 10px;
font-size: 12px; color: var(--text-secondary); line-height: 1.6;
}
.ch-sw-compat-prompt a { color: var(--sw-h, #a78bfa); text-decoration: none; font-weight: 600; }
.ch-sw-compat-prompt a:hover { text-decoration: underline; }
.ch-sidebar-widget-header {
padding: 16px 18px 12px;
border-bottom: 1px solid rgba(255,255,255,.05);
font-size: 12px; font-weight: 700;
text-transform: uppercase; letter-spacing: .07em;
color: var(--text-secondary);
display: flex; align-items: center; gap: 7px;
}
.ch-sidebar-widget-header i { font-size: 13px; }
.ch-sidebar-widget-body { padding: 16px 18px 18px; }
/* Horoscope widget */
.ch-horoscope-hero {
display: flex; align-items: center; gap: 14px; margin-bottom: 14px;
}
.ch-horoscope-orb {
width: 64px; height: 64px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 30px; flex-shrink: 0;
border: 2px solid var(--h-color, rgba(255,255,255,.15));
background: rgba(255,255,255,.05);
box-shadow: 0 0 28px var(--h-color, transparent), inset 0 1px 0 rgba(255,255,255,.12);
position: relative;
transition: box-shadow .3s;
}
.ch-horoscope-sign-info { flex: 1; min-width: 0; }
.ch-horoscope-sign-name {
font-size: 18px; font-weight: 800; color: var(--text-primary); line-height: 1.1; margin-bottom: 4px;
}
.ch-horoscope-sign-dates { font-size: 12px; color: var(--text-secondary); }
.ch-horoscope-element-pill {
display: inline-flex; align-items: center; gap: 5px;
font-size: 12px; font-weight: 600; padding: 3px 11px;
border-radius: 20px; margin-top: 5px;
border: 1px solid var(--h-color, rgba(255,255,255,.15));
background: rgba(255,255,255,.06);
color: var(--h-color, var(--text-secondary));
}
.ch-horoscope-divider { height: 1px; background: rgba(255,255,255,.06); margin: 12px 0; }
.ch-horoscope-traits { display: flex; flex-wrap: wrap; gap: 6px; }
.ch-horoscope-trait {
font-size: 12px; padding: 4px 11px; border-radius: 20px;
background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.08);
color: var(--text-secondary); font-weight: 500;
}
/* Compatibility widget */
.ch-compat-hero {
display: flex; align-items: center; justify-content: center;
gap: 0; margin-bottom: 18px;
}
.ch-compat-sign-block { text-align: center; flex: 1; }
.ch-compat-sign-orb {
width: 52px; height: 52px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 24px; margin: 0 auto 6px;
background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1);
}
.ch-compat-sign-label { font-size: 11px; color: var(--text-secondary); font-weight: 500; line-height: 1.5; }
.ch-compat-vs {
padding: 0 12px;
display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.ch-compat-hearts { font-size: 20px; line-height: 1; }
.ch-compat-vs-text { font-size: 10px; color: var(--text-secondary); font-weight: 600; letter-spacing: .05em; }
.ch-compat-score-wrap { text-align: center; margin-bottom: 12px; }
.ch-compat-score {
font-size: 42px; font-weight: 900; line-height: 1;
color: var(--compat-color, #22c55e);
}
.ch-compat-label-text { font-size: 13px; color: var(--text-secondary); margin-top: 4px; font-weight: 500; }
.ch-compat-track {
height: 6px; background: rgba(255,255,255,.07); border-radius: 6px; overflow: hidden;
}
.ch-compat-fill {
height: 100%; border-radius: 6px;
background: var(--compat-color, #22c55e);
transition: width 1.4s cubic-bezier(.22,.61,.36,1);
}
/* Profile mini-card in sidebar */
.ch-wall-profile-mini {
display: flex; flex-direction: column; align-items: center;
text-align: center; padding: 20px 18px 18px;
}
.ch-wall-profile-mini-avatar {
width: 72px; height: 72px; border-radius: 50%; object-fit: cover;
border: 3px solid hsl({{ $hue }}, 45%, 35%);
box-shadow: 0 0 0 4px hsl({{ $hue }}, 45%, 14%), 0 8px 24px rgba(0,0,0,.3);
margin-bottom: 12px;
}
.ch-wall-profile-mini-name { font-size: 16px; font-weight: 800; color: var(--text-primary); margin-bottom: 2px; }
.ch-wall-profile-mini-handle { font-size: 13px; color: var(--text-secondary); margin-bottom: 12px; }
.ch-wall-profile-mini-stats {
display: flex; gap: 0; width: 100%;
background: rgba(255,255,255,.04); border-radius: 12px;
border: 1px solid rgba(255,255,255,.07); overflow: hidden;
margin-bottom: 14px;
}
.ch-wall-profile-mini-stat {
flex: 1; padding: 10px 6px; text-align: center;
border-right: 1px solid rgba(255,255,255,.07);
}
.ch-wall-profile-mini-stat:last-child { border-right: none; }
.ch-wall-profile-mini-stat-val { font-size: 16px; font-weight: 800; color: var(--text-primary); line-height: 1; }
.ch-wall-profile-mini-stat-key { font-size: 10px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: .05em; margin-top: 3px; }
/* No-birthday prompt */
.ch-no-birthday-card {
text-align: center; padding: 20px 18px;
}
.ch-no-birthday-card .nb-icon { font-size: 32px; margin-bottom: 10px; }
.ch-no-birthday-card p { font-size: 13px; color: var(--text-secondary); line-height: 1.6; margin-bottom: 14px; }
/* ── Empty wall state ─────────────────────────────── */
.ch-wall-empty {
text-align: center; padding: 56px 24px 48px;
background: var(--bg-secondary); border: 1px solid var(--border-color);
border-radius: 18px;
}
.ch-wall-empty-icon {
width: 88px; height: 88px; border-radius: 50%; margin: 0 auto 20px;
background: radial-gradient(circle at 35% 35%, hsl({{ $hue }}, 55%, 22%), hsl({{ $hue }}, 40%, 12%));
border: 1px solid hsl({{ $hue }}, 45%, 22%);
display: flex; align-items: center; justify-content: center;
font-size: 36px;
box-shadow: 0 0 40px hsl({{ $hue }}, 55%, 15%);
}
.ch-wall-empty h3 { font-size: 20px; font-weight: 700; color: var(--text-primary); margin-bottom: 8px; }
.ch-wall-empty p { font-size: 14px; color: var(--text-secondary); line-height: 1.65; max-width: 300px; margin: 0 auto 22px; }
/* ══════════════════════════════════════════════════
ABOUT TAB
══════════════════════════════════════════════════ */
.ch-about-layout { display: grid; grid-template-columns: 1fr 320px; gap: 24px; }
@media (max-width: 820px) { .ch-about-layout { grid-template-columns: 1fr; } }
.ch-about-card { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 16px; padding: 24px; margin-bottom: 16px; }
.ch-about-card:last-child { margin-bottom: 0; }
.ch-about-card-title { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-secondary); margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
.ch-about-card-title i { color: hsl({{ $hue }}, 60%, 55%); }
.ch-about-desc { font-size: 14px; color: var(--text-secondary); line-height: 1.75; white-space: pre-wrap; margin: 0; }
/* Stat cards */
.ch-stat-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
.ch-stat-card { background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.07); border-radius: 12px; padding: 14px 16px; text-align: center; }
.ch-stat-card-val { font-size: 22px; font-weight: 800; color: var(--text-primary); line-height: 1; margin-bottom: 4px; }
.ch-stat-card-key { font-size: 11px; color: var(--text-secondary); font-weight: 500; text-transform: uppercase; letter-spacing: .05em; }
.ch-stat-item { display: flex; align-items: center; gap: 12px; padding: 9px 0; font-size: 14px; color: var(--text-secondary); border-bottom: 1px solid rgba(255,255,255,.05); }
.ch-stat-item:last-child { border-bottom: none; }
.ch-stat-item-icon { width: 32px; height: 32px; border-radius: 8px; flex-shrink: 0; background: hsl({{ $hue }}, 40%, 16%); border: 1px solid hsl({{ $hue }}, 40%, 22%); display: flex; align-items: center; justify-content: center; color: hsl({{ $hue }}, 65%, 60%); font-size: 14px; }
/* Social links */
.ch-link-card { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-radius: 12px; background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.07); text-decoration: none; color: var(--text-secondary); margin-bottom: 8px; transition: all .15s; font-size: 14px; }
.ch-link-card:last-child { margin-bottom: 0; }
.ch-link-card:hover { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.14); color: var(--text-primary); transform: translateX(3px); }
.ch-link-card-icon { width: 34px; height: 34px; border-radius: 9px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 16px; }
.ch-link-card-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-link-card i.bi-chevron-right { font-size: 12px; opacity: .4; flex-shrink: 0; }
/* ══════════════════════════════════════════════════
HOROSCOPE & COMPATIBILITY CARDS
══════════════════════════════════════════════════ */
.ch-horoscope-card {
background: var(--bg-secondary); border: 1px solid var(--border-color);
border-radius: 16px; padding: 22px; margin-bottom: 16px;
position: relative; overflow: hidden;
}
.ch-horoscope-glow {
position: absolute; top: -30px; right: -30px;
width: 120px; height: 120px; border-radius: 50%;
background: var(--horoscope-color, hsl({{ $hue }}, 60%, 45%));
opacity: .08; filter: blur(30px); pointer-events: none;
}
.ch-horoscope-sign-row { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.ch-horoscope-symbol {
font-size: 42px; line-height: 1;
filter: drop-shadow(0 0 12px var(--horoscope-color, hsl({{ $hue }}, 60%, 55%)));
}
.ch-horoscope-name { font-size: 20px; font-weight: 800; color: var(--text-primary); line-height: 1.2; }
.ch-horoscope-element {
display: inline-flex; align-items: center; gap: 5px;
font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 20px;
background: rgba(255,255,255,.07); color: var(--text-secondary);
margin-top: 3px;
}
.ch-horoscope-traits { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.ch-horoscope-trait {
font-size: 12px; padding: 4px 11px; border-radius: 20px;
background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.08);
color: var(--text-secondary);
}
/* Compatibility card */
.ch-compat-card {
background: var(--bg-secondary); border: 1px solid var(--border-color);
border-radius: 16px; padding: 22px; margin-bottom: 16px;
position: relative; overflow: hidden;
}
.ch-compat-label { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-secondary); margin-bottom: 14px; }
.ch-compat-faces { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 16px; }
.ch-compat-sign-block { text-align: center; }
.ch-compat-sign-sym { font-size: 30px; line-height: 1.2; }
.ch-compat-sign-name { font-size: 11px; color: var(--text-secondary); margin-top: 2px; }
.ch-compat-heart { font-size: 20px; color: #e33; }
.ch-compat-bar-wrap { background: rgba(255,255,255,.07); border-radius: 8px; height: 8px; overflow: hidden; margin-bottom: 8px; }
.ch-compat-bar { height: 100%; border-radius: 8px; transition: width 1.2s cubic-bezier(.25,.1,.25,1); }
.ch-compat-pct { font-size: 28px; font-weight: 900; text-align: center; line-height: 1; margin-bottom: 4px; }
.ch-compat-desc { font-size: 12px; color: var(--text-secondary); text-align: center; }
/* ══════════════════════════════════════════════════
EMPTY STATE
══════════════════════════════════════════════════ */
.ch-empty { text-align: center; padding: 80px 24px; color: var(--text-secondary); }
.ch-empty-icon { width: 80px; height: 80px; border-radius: 50%; background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.08); display: flex; align-items: center; justify-content: center; font-size: 32px; margin: 0 auto 20px; color: var(--text-secondary); }
.ch-empty h3 { font-size: 20px; color: var(--text-primary); margin-bottom: 6px; }
.ch-empty p { font-size: 14px; margin-bottom: 20px; max-width: 320px; margin-left: auto; margin-right: auto; }
/* ══════════════════════════════════════════════════
MOBILE
══════════════════════════════════════════════════ */
@media (max-width: 768px) {
.ch-banner { height: 110px; }
.ch-header { padding: 0 16px 20px; }
.ch-header-inner { gap: 14px; margin-top: -40px; }
.ch-avatar { width: 76px; height: 76px; border-radius: 10px; }
.ch-name { font-size: 18px; }
/* sticky doesn't work inside .yt-main on mobile — use fixed instead */
.ch-tabs-wrap {
position: fixed !important;
top: 56px;
left: 0; right: 0;
z-index: 80;
padding: 0 16px;
}
/* push tab content below the fixed tabs bar (46px height + 1px border) */
.ch-tabs-spacer { display: block; height: 47px; }
.ch-tab { padding: 0 12px; font-size: 11px; }
.ch-tab-content { padding: 16px; }
.ch-sort-bar { gap: 8px; }
.ch-about-card { padding: 18px; }
.ch-stat-cards { grid-template-columns: 1fr 1fr; }
.ch-wall-layout { grid-template-columns: 1fr; }
.ch-horo-strip { padding: 8px 12px; gap: 5px 10px; }
.ch-vid-search-wrap { max-width: 100%; }
}
@media (max-width: 480px) {
.ch-avatar { width: 60px; height: 60px; border-radius: 8px; }
.ch-name { font-size: 16px; }
.ch-header-inner { flex-wrap: nowrap; }
.ch-actions { gap: 6px; }
.ch-btn-ghost span { display: none; }
.ch-stat-cards { grid-template-columns: 1fr; }
.ch-horo-divider { display: none; }
}
/* ══════════════════════════════════════════════════
SETTINGS TAB
══════════════════════════════════════════════════ */
.ch-settings-layout {
display: grid;
grid-template-columns: 1fr 360px;
gap: 24px;
}
@media (max-width: 860px) { .ch-settings-layout { grid-template-columns: 1fr; } }
.ch-settings-card {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 16px;
padding: 24px;
margin-bottom: 20px;
}
.ch-settings-card:last-child { margin-bottom: 0; }
.ch-settings-card-title {
font-size: 13px; font-weight: 700;
text-transform: uppercase; letter-spacing: .06em;
color: var(--text-secondary); margin-bottom: 20px;
display: flex; align-items: center; gap: 8px;
padding-bottom: 14px; border-bottom: 1px solid rgba(255,255,255,.06);
}
.ch-settings-card-title i { color: hsl({{ $hue }}, 60%, 55%); }
.ch-settings-avatar-row {
display: flex; align-items: center; gap: 20px; margin-bottom: 22px;
padding: 16px; background: rgba(255,255,255,.03); border-radius: 12px;
border: 1px solid rgba(255,255,255,.06);
}
.ch-settings-avatar-wrap { position: relative; flex-shrink: 0; }
.ch-settings-avatar {
width: 80px; height: 80px; border-radius: 50%; object-fit: cover;
border: 3px solid rgba(255,255,255,.12); display: block;
}
.ch-settings-avatar-btn {
position: absolute; bottom: 0; right: 0;
width: 28px; height: 28px; border-radius: 50%;
background: hsl({{ $hue }}, 60%, 40%); border: 2px solid var(--bg-secondary);
color: #fff; font-size: 12px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: background .15s;
}
.ch-settings-avatar-btn:hover { background: hsl({{ $hue }}, 70%, 50%); }
.ch-settings-2col { display: flex; gap: 14px; }
.ch-settings-2col > .ch-settings-field { flex: 1; min-width: 0; }
@media (max-width: 600px) { .ch-settings-2col { flex-direction: column; } }
.ch-settings-field { margin-bottom: 16px; }
.ch-settings-field:last-of-type { margin-bottom: 0; }
.ch-settings-label { display: block; font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 7px; }
.ch-settings-input {
width: 100%; background: rgba(255,255,255,.04);
border: 1px solid rgba(255,255,255,.1);
border-radius: 10px; padding: 11px 14px;
color: var(--text-primary); font-size: 14px; font-family: inherit;
transition: border-color .2s;
}
.ch-settings-input:focus { outline: none; border-color: hsl({{ $hue }}, 60%, 45%); }
.ch-settings-input:disabled { opacity: .45; cursor: not-allowed; }
.ch-settings-textarea { min-height: 90px; resize: vertical; line-height: 1.55; }
.ch-settings-hint { font-size: 12px; color: var(--text-secondary); margin-top: 5px; }
.ch-settings-footer { margin-top: 20px; padding-top: 16px; border-top: 1px solid rgba(255,255,255,.06); display: flex; justify-content: flex-end; }
/* Make country/timezone/date-picker match ch-settings-input sizing */
#tab-settings .csd-btn,
#tab-settings .dp-btn { padding: 11px 14px; min-height: 0; background: rgba(255,255,255,.04); border-color: rgba(255,255,255,.1); border-radius: 10px; }
</style>
@endsection
@section('content')
{{-- ─────────────── PREVIEW BANNER ─────────────────── --}}
@if($preview)
<div id="chPreviewBar" style="
position: sticky; top: 56px; z-index: 200;
background: #1a1200; border-bottom: 2px solid #f59e0b;
display: flex; align-items: center; justify-content: space-between;
padding: 10px 24px; gap: 12px;">
<div style="display:flex; align-items:center; gap:10px; color:#fbbf24; font-size:14px; font-weight:600;">
<i class="bi bi-eye-fill" style="font-size:16px;"></i>
You're viewing your profile as others see it
</div>
<a href="{{ route('channel', $user->channel) }}"
style="display:inline-flex; align-items:center; gap:6px; background:#f59e0b; color:#000;
font-size:13px; font-weight:700; padding:6px 14px; border-radius:6px; text-decoration:none;">
<i class="bi bi-arrow-left"></i> Back to my channel
</a>
</div>
@endif
{{-- ───────────────────── BANNER ───────────────────── --}}
<div class="ch-banner" id="chBanner">
@if($user->banner)
<img src="{{ $user->banner_url }}" id="chBannerImg" class="ch-banner-img" alt="">
@endif
<div class="ch-banner-noise"></div>
@if($isOwner && !$preview)
<button class="ch-banner-edit-btn" onclick="openCropperModal_banner()" title="Change banner">
<i class="bi bi-camera-fill"></i>
<span>Change banner</span>
</button>
@else
<div class="ch-banner-actions">
@auth
<button type="button"
class="ch-banner-subscribe-btn subscribe-toggle-btn {{ $isSubscriber ? 'subscribed-ch' : '' }}"
data-channel-id="{{ $user->id }}"
data-subscribe-url="{{ route('channel.subscribe', $user->id) }}"
data-subscribed="{{ $isSubscriber ? 'true' : 'false' }}">
<i class="bi {{ $isSubscriber ? 'bi-bell-fill' : 'bi-person-plus-fill' }}"></i>
<span class="subscribe-label">{{ $isSubscriber ? 'Subscribed' : 'Subscribe' }}</span>
</button>
@else
<a href="{{ route('login') }}" class="ch-banner-subscribe-btn">
<i class="bi bi-person-plus-fill"></i>
Subscribe
</a>
@endauth
<button class="ch-banner-share-btn" title="Share channel"
onclick="navigator.clipboard.writeText(window.location.href).then(()=>showToast('Channel link copied!','success'))">
<i class="bi bi-share"></i>
</button>
</div>
@endif
</div>
{{-- ─────────────────── CHANNEL HEADER ─────────────── --}}
@php
$headerSocialMap = [
'twitter' => ['icon'=>'bi-twitter-x', 'color'=>'#38bdf8', 'bg'=>'rgba(56,189,248,.1)', 'hover'=>'#38bdf8', 'href'=>fn($v)=>"https://twitter.com/{$v}"],
'instagram' => ['icon'=>'bi-instagram', 'color'=>'#818cf8', 'bg'=>'rgba(129,140,248,.1)', 'hover'=>'#818cf8', 'href'=>fn($v)=>"https://instagram.com/{$v}"],
'facebook' => ['icon'=>'bi-facebook', 'color'=>'#60a5fa', 'bg'=>'rgba(96,165,250,.1)', 'hover'=>'#60a5fa', 'href'=>fn($v)=>"https://facebook.com/{$v}"],
'youtube' => ['icon'=>'bi-youtube', 'color'=>'#94a3b8', 'bg'=>'rgba(148,163,184,.1)', 'hover'=>'#94a3b8', 'href'=>fn($v)=>"https://youtube.com/@{$v}"],
'tiktok' => ['icon'=>'bi-tiktok', 'color'=>'#67e8f9', 'bg'=>'rgba(103,232,249,.1)', 'hover'=>'#67e8f9', 'href'=>fn($v)=>"https://tiktok.com/@{$v}"],
'linkedin' => ['icon'=>'bi-linkedin', 'color'=>'#7dd3fc', 'bg'=>'rgba(125,211,252,.1)', 'hover'=>'#7dd3fc', 'href'=>fn($v)=>"https://linkedin.com/in/{$v}"],
'whatsapp' => ['icon'=>'bi-whatsapp', 'color'=>'#4ade80', 'bg'=>'rgba(74,222,128,.1)', 'hover'=>'#4ade80', 'href'=>fn($v)=>"https://wa.me/".preg_replace('/\D/','',$v)],
'website' => ['icon'=>'bi-globe2', 'color'=>'#a78bfa', 'bg'=>'rgba(167,139,250,.1)', 'hover'=>'#a78bfa', 'href'=>fn($v)=>preg_match('/^https?:\/\//',$v)?$v:"https://{$v}"],
'social_phone' => ['icon'=>'bi-telephone-fill', 'color'=>'#34d399', 'bg'=>'rgba(52,211,153,.1)', 'hover'=>'#34d399', 'href'=>fn($v)=>"tel:{$v}"],
'social_email' => ['icon'=>'bi-envelope-fill', 'color'=>'#fbbf24', 'bg'=>'rgba(251,191,36,.1)', 'hover'=>'#fbbf24', 'href'=>fn($v)=>"mailto:{$v}"],
'google_location'=> ['icon'=>'bi-geo-alt-fill', 'color'=>'#6ee7b7', 'bg'=>'rgba(110,231,183,.1)', 'hover'=>'#6ee7b7', 'href'=>fn($v)=>$v],
];
@endphp
<div class="ch-header">
<div class="ch-header-inner">
{{-- Avatar --}}
<div class="ch-avatar-wrap">
<img src="{{ $user->avatar_url }}" alt="{{ $user->name }}" class="ch-avatar" id="chAvatarImg">
@if($isOwner && !$preview)
<button class="ch-avatar-edit-btn" onclick="openCropperModal_avatar()" title="Change photo">
<i class="bi bi-camera-fill"></i>
</button>
@endif
</div>
{{-- Info --}}
<div class="ch-info">
{{-- Name row --}}
<div class="ch-name-row">
<h1 class="ch-name">{{ $user->name }}</h1>
@if($user->email_verified_at)
<span class="ch-verified-badge" title="Verified"><i class="bi bi-check-lg"></i></span>
@endif
</div>
{{-- Flat stats row no pills --}}
<div class="ch-meta-row">
<span class="ch-meta-item">
<i class="bi bi-camera-video-fill"></i>
{{ number_format($videos->count() + $shorts->count()) }} videos
</span>
<span class="ch-meta-sep">·</span>
<span class="ch-meta-item">
<i class="bi bi-eye-fill"></i>
{{ number_format($totalViews) }} views
</span>
<span class="ch-meta-sep">·</span>
<span class="ch-meta-item channel-subs"
data-channel-id="{{ $user->id }}"
data-count="{{ $user->subscriber_count }}">
<i class="bi bi-people-fill"></i>
{{ number_format($user->subscriber_count) }} {{ Str::plural('subscriber', $user->subscriber_count) }}
</span>
@if($user->location)
<span class="ch-meta-sep">·</span>
<span class="ch-meta-item">
<i class="bi bi-geo-alt-fill"></i>
{{ $user->location }}
</span>
@endif
<span class="ch-meta-sep">·</span>
<span class="ch-meta-item">
<i class="bi bi-calendar3"></i>
Joined {{ $user->created_at->format('M Y') }}
</span>
@if($user->birthday)
@php $age = \Carbon\Carbon::parse($user->birthday)->age; @endphp
<span class="ch-meta-sep">·</span>
<span class="ch-meta-item">
<i class="bi bi-person-fill"></i>
{{ $age }} yrs
</span>
@endif
@if($user->gender === 'male')
<span class="ch-meta-sep">·</span>
<span class="ch-meta-item" style="color:#3b82f6;font-weight:700;">
<i class="bi bi-gender-male" style="font-size:14px;-webkit-text-stroke:.4px #3b82f6;filter:drop-shadow(0 0 6px #3b82f640);"></i>
Male
</span>
@elseif($user->gender === 'female')
<span class="ch-meta-sep">·</span>
<span class="ch-meta-item" style="color:#ec4899;font-weight:700;">
<i class="bi bi-gender-female" style="font-size:14px;-webkit-text-stroke:.4px #ec4899;filter:drop-shadow(0 0 6px #ec489940);"></i>
Female
</span>
@endif
</div>
@if($user->bio)
<div class="ch-bio" id="ch-bio">
<span class="ch-bio-short" id="ch-bio-short">{{ $user->bio }}</span>
<span class="ch-bio-full" id="ch-bio-full" style="display:none;">{{ $user->bio }}</span>
@if(strlen($user->bio) > 120)
<button class="ch-bio-toggle" id="ch-bio-btn" onclick="toggleBio()">…more</button>
@endif
</div>
@endif
{{-- Horoscope + Compatibility strip & Social buttons side by side --}}
@if($horoscope || $socialLinks->isNotEmpty())
@php
$hColor = $horoscope ? \App\Helpers\Horoscope::elementColor($horoscope['element']) : null;
$showCompat = $horoscope && ($compatibility !== null && $viewerSign && !$isOwner);
if ($showCompat) {
$compatColor = $compatibility >= 75 ? '#22c55e' : ($compatibility >= 55 ? '#eab308' : '#ef4444');
$compatLabel = $compatibility >= 85 ? 'Soulmates' : ($compatibility >= 70 ? 'Great match' : ($compatibility >= 55 ? 'Good vibes' : ($compatibility >= 40 ? 'Some friction' : 'Tough combo')));
}
@endphp
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:14px;">
@if($horoscope)
<div class="ch-horo-strip" style="margin-bottom:0; cursor:pointer;"
onclick="switchTab('about',document.querySelector('[data-tab=about]'))" title="Click to view horoscope">
<div class="ch-horo-sign-part">
<span class="ch-horo-emoji" style="filter:drop-shadow(0 0 8px {{ $hColor }});">{{ $horoscope['emoji'] }}</span>
<span class="ch-horo-name">{{ $horoscope['name'] }}</span>
<span class="ch-horo-el" style="color:{{ $hColor }};border-color:{{ $hColor }}20;background:{{ $hColor }}18;">{{ $horoscope['element'] }}</span>
</div>
@if($showCompat)
<span class="ch-horo-divider"></span>
<div class="ch-compat-mini">
<span style="font-size:13px;">❤️</span>
<span class="ch-compat-mini-pct" style="color:{{ $compatColor }};">{{ $compatibility }}%</span>
<div class="ch-compat-mini-track">
<div class="ch-compat-mini-fill" id="chCompatMiniBar" style="width:0%;background:{{ $compatColor }};"></div>
</div>
<span class="ch-compat-mini-label">{{ $compatLabel }}</span>
</div>
@endif
</div>
@endif
@if($socialLinks->isNotEmpty())
<div class="ch-social-row" style="margin-bottom:0;">
@foreach($socialLinks as $slink)
@if(isset($headerSocialMap[$slink->platform]))
@php $sm = $headerSocialMap[$slink->platform]; @endphp
<a href="{{ $sm['href']($slink->value) }}"
target="{{ in_array($slink->platform, ['social_phone','social_email']) ? '_self' : '_blank' }}"
rel="noopener"
class="ch-social-btn"
style="--sc:{{ $sm['color'] }};--sb:{{ $sm['bg'] }};--sh:{{ $sm['hover'] }};"
title="{{ ucfirst(str_replace(['social_','google_'],['',''],$slink->platform)) }}">
<i class="bi {{ $sm['icon'] }}"></i>
</a>
@endif
@endforeach
</div>
@endif
</div>
@endif
{{-- Action buttons (owner only) --}}
@if($isOwner && !$preview)
<div class="ch-actions">
<button type="button" class="ch-btn-ghost" onclick="switchTab('settings', null)">
<i class="bi bi-pencil"></i>
<span>Edit channel</span>
</button>
<button class="ch-btn-ghost" data-bs-toggle="modal" data-bs-target="#uploadModal">
<i class="bi bi-camera-video"></i>
<span>Upload</span>
</button>
<button type="button" class="ch-btn-ghost" title="{{ $user->two_factor_enabled ? '2FA Enabled' : 'Enable 2FA' }}"
onclick="switchTab('settings', document.querySelector('[data-tab=settings]')); setTimeout(()=>document.getElementById('twoFactorCard')?.scrollIntoView({behavior:'smooth',block:'center'}),400);"
style="{{ $user->two_factor_enabled ? 'color:#4ade80;border-color:rgba(74,222,128,.4);' : 'color:#fbbf24;border-color:rgba(251,191,36,.4);' }}">
<i class="bi {{ $user->two_factor_enabled ? 'bi-shield-check-fill' : 'bi-shield-exclamation' }}"></i>
<span>{{ $user->two_factor_enabled ? '2FA On' : '2FA Off' }}</span>
</button>
<button type="button" class="ch-btn-ghost" title="Log out all other devices"
onclick="openLogoutAllModal()"
style="color:#f87171;border-color:rgba(248,113,113,.4);">
<i class="bi bi-box-arrow-right"></i>
<span>Log Out All</span>
</button>
<a href="{{ route('channel', $user->channel) }}?preview=1" class="ch-btn-icon" title="Preview as visitor">
<i class="bi bi-eye"></i>
</a>
</div>
{{-- Logout All Devices Modal appended to body on open to escape stacking context --}}
<template id="logoutAllModalTpl">
<div id="logoutAllModal" style="position:fixed;inset:0;z-index:10100;background:rgba(0,0,0,.7);display:flex;align-items:center;justify-content:center;padding:20px 20px 80px;" onclick="if(event.target===this)closeLogoutAllModal()">
<div style="background:var(--bg-secondary,#1a1a1a);border:1px solid var(--border-color,#333);border-radius:16px;padding:28px;max-width:400px;width:100%;box-shadow:0 24px 64px rgba(0,0,0,.6);" onclick="event.stopPropagation()">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px;">
<div style="width:44px;height:44px;border-radius:50%;background:rgba(248,113,113,.15);display:flex;align-items:center;justify-content:center;flex-shrink:0;">
<i class="bi bi-box-arrow-right" style="color:#f87171;font-size:20px;"></i>
</div>
<div>
<div style="font-size:16px;font-weight:700;color:var(--text-primary,#fff);">Log Out All Other Devices</div>
<div style="font-size:12px;color:var(--text-secondary,#aaa);margin-top:2px;">Your current session will stay active</div>
</div>
</div>
<p style="font-size:13px;color:var(--text-secondary,#aaa);margin-bottom:20px;line-height:1.6;">
This will immediately end all other active sessions on phones, tablets, and other browsers. Enter your password to confirm.
</p>
<form method="POST" action="{{ route('settings.logoutAllDevices') }}">
@csrf
<div style="margin-bottom:16px;">
<label style="display:block;font-size:12px;font-weight:600;color:var(--text-secondary,#aaa);margin-bottom:7px;letter-spacing:.5px;text-transform:uppercase;">Password</label>
<input type="password" name="password" required placeholder="••••••••" id="logoutAllPwInput"
style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border-color,#333);border-radius:10px;padding:11px 14px;color:var(--text-primary,#fff);font-size:14px;">
@error('logout_password')
<div style="color:#f87171;font-size:12px;margin-top:5px;">{{ $message }}</div>
@enderror
</div>
<div style="display:flex;gap:10px;justify-content:flex-end;">
<button type="button" class="action-btn" onclick="closeLogoutAllModal()">Cancel</button>
<button type="submit" class="action-btn action-btn-danger">
<i class="bi bi-box-arrow-right"></i> <span>Log Out All Devices</span>
</button>
</div>
</form>
</div>
</div>
</template>
@endif
</div>
</div>
</div>
{{-- ──────────────────────── TABS ───────────────────── --}}
<div class="ch-tabs-wrap">
<div class="ch-tabs" role="tablist">
<button class="ch-tab active" data-tab="wall" onclick="switchTab('wall', this)">
<i class="bi bi-layout-text-sidebar-reverse"></i>
Wall
@if($posts->count() > 0)
<span class="ch-tab-badge">{{ $posts->count() }}</span>
@endif
</button>
<button class="ch-tab" data-tab="videos" onclick="switchTab('videos', this)">
Videos
@if($videos->count() > 0)
<span class="ch-tab-badge">{{ $videos->count() }}</span>
@endif
</button>
@if($shorts->count() > 0)
<button class="ch-tab" data-tab="shorts" onclick="switchTab('shorts', this)">
Shorts
<span class="ch-tab-badge">{{ $shorts->count() }}</span>
</button>
@endif
@if(($playlists && $playlists->count() > 0) || $isOwner)
<button class="ch-tab" data-tab="playlists" onclick="switchTab('playlists', this)">
Playlists
@if($playlists && $playlists->count() > 0)
<span class="ch-tab-badge">{{ $playlists->count() }}</span>
@endif
</button>
@endif
<button class="ch-tab" data-tab="about" onclick="switchTab('about', this)">About</button>
@if($isOwner)
<button class="ch-tab" data-tab="settings" onclick="switchTab('settings', this)">
<i class="bi bi-gear" style="font-size:13px;"></i> Settings
</button>
@endif
</div>
</div>
{{-- spacer pushes content below the fixed tabs bar on mobile --}}
<div class="ch-tabs-spacer" style="display:none;"></div>
{{-- ─────────────────────── WALL TAB ────────────────── --}}
<div class="ch-tab-content active" id="tab-wall">
<div class="ch-wall-layout">
{{-- ── LEFT: composer + feed ── --}}
<div>
{{-- Composer (owner only) --}}
@auth
@if(Auth::id() === $user->id)
<div class="ch-composer">
<form action="{{ route('posts.store', $user->id) }}" method="POST" enctype="multipart/form-data" id="postForm">
@csrf
<div class="ch-composer-top">
<img src="{{ Auth::user()->avatar_url }}" alt="" class="ch-composer-avatar">
<div class="ch-composer-right">
<div class="ch-composer-name">{{ Auth::user()->name }}</div>
<textarea name="body" id="postBody"
placeholder="What's on your mind? Share with your community…"
maxlength="2000"
oninput="this.style.height='auto';this.style.height=this.scrollHeight+'px'"></textarea>
</div>
</div>
{{-- Multi-image preview grid --}}
<div id="imagePreviewGrid" class="ch-composer-img-grid" style="display:none;"></div>
<input type="file" name="images[]" id="postImages" accept="image/*" multiple style="display:none;" onchange="handlePostImages(this)">
{{-- Selected video chips --}}
<div id="postVideoChips" class="ch-composer-video-chips" style="display:none;"></div>
<div class="ch-composer-bar">
<div class="ch-composer-media">
<button type="button" class="ch-composer-media-btn photo" title="Add photos" onclick="document.getElementById('postImages').click()">
<i class="bi bi-image-fill"></i> Photo
</button>
@if($user->videos->count() > 0)
<button type="button" class="ch-composer-media-btn video" onclick="openVideoPicker()" title="Share videos">
<i class="bi bi-play-btn-fill"></i> Video
</button>
@endif
</div>
<button type="submit" class="ch-composer-post-btn">
<i class="bi bi-send-fill"></i> Post
</button>
</div>
</form>
</div>
@endif
@endauth
{{-- Posts feed --}}
@forelse($posts as $post)
@php
$postLiked = Auth::check() && $post->isLikedBy(Auth::user());
$bodyLen = strlen($post->body ?? '');
$isShort = $bodyLen > 0 && $bodyLen <= 80 && !$post->image && !$post->video_id && !$post->postImages->count() && !$post->postVideos->count();
@endphp
<div class="ch-post" id="post-{{ $post->id }}">
{{-- Header --}}
<div class="ch-post-header">
<a href="{{ $post->user ? route('channel', $post->user->channel) : '#' }}" class="ch-post-avatar-link">
<img src="{{ $post->user->avatar_url }}" alt="{{ $post->user->name }}" class="ch-post-avatar">
</a>
<div class="ch-post-meta">
<div class="ch-post-name">{{ $post->user->name }}</div>
<div class="ch-post-time">
<i class="bi bi-clock"></i>
<span title="{{ $post->created_at->format('D, M j Y — g:i A') }}">{{ $post->created_at->diffForHumans() }}</span>
</div>
</div>
@auth
@if(Auth::id() === $post->user_id || Auth::user()->isAdmin())
<div class="ch-post-menu-wrap">
<button class="ch-post-menu-btn" onclick="togglePostMenu(this)" title="More options">
<i class="bi bi-three-dots"></i>
</button>
<div class="ch-post-dropdown">
<form action="{{ route('posts.destroy', $post->id) }}" method="POST">
@csrf @method('DELETE')
<button type="submit" class="danger">
<i class="bi bi-trash3"></i> Delete post
</button>
</form>
</div>
</div>
@endif
@endauth
</div>
{{-- Body --}}
@if($post->body)
<div class="{{ $isShort ? 'ch-post-body-lg' : 'ch-post-body' }}">{{ $post->body }}</div>
@endif
{{-- Images (new multi-image) --}}
@php
$postImgs = $post->postImages;
$legacyImg = (!$postImgs->count() && $post->image) ? $post->image_url : null;
$imgCount = $postImgs->count() ?: ($legacyImg ? 1 : 0);
$countClass = $imgCount === 1 ? 'count-1' : ($imgCount === 2 ? 'count-2' : ($imgCount === 3 ? 'count-3' : ($imgCount === 4 ? 'count-4' : ($imgCount > 4 ? 'count-more' : ''))));
@endphp
@if($imgCount > 0)
<div class="ch-post-img-grid {{ $countClass }}">
@if($legacyImg)
<div class="ch-post-img-item" onclick="openLightbox('{{ $legacyImg }}')">
<img src="{{ $legacyImg }}" alt="" loading="lazy">
</div>
@else
@foreach($postImgs->take(4) as $idx => $img)
<div class="ch-post-img-item" onclick="openLightbox('{{ $img->image_url }}')">
<img src="{{ $img->image_url }}" alt="" loading="lazy">
@if($idx === 3 && $imgCount > 4)
<div class="ch-post-img-more">+{{ $imgCount - 4 }}</div>
@endif
</div>
@endforeach
@endif
</div>
@endif
{{-- Videos (new multi-video) --}}
@php
$postVids = $post->postVideos->filter(fn($pv) => $pv->video?->exists);
$legacyVid = (!$postVids->count() && $post->video_id && $post->video?->exists) ? $post->video : null;
$allVids = $legacyVid ? collect([$legacyVid]) : $postVids->map(fn($pv) => $pv->video);
@endphp
@if($allVids->count())
<div class="ch-post-video-wrap">
@foreach($allVids as $v)
<a href="{{ route('videos.show', $v) }}" class="ch-post-video-card" style="{{ !$loop->first ? 'margin-top:8px;' : '' }}">
<div class="ch-post-video-thumb-wrap">
<img src="{{ $v->thumbnail ? asset('storage/thumbnails/'.$v->thumbnail) : '' }}"
alt="" onerror="this.style.display='none'">
<div class="ch-post-video-play"><i class="bi bi-play-circle-fill"></i></div>
</div>
<div class="ch-post-video-info">
<div class="ch-post-video-title">{{ $v->title }}</div>
<div class="ch-post-video-meta">
<span class="ch-post-video-badge"><i class="bi bi-eye"></i> {{ \Illuminate\Support\Number::abbreviate($v->view_count, precision: 1) }}</span>
@if($v->duration)<span class="ch-post-video-badge"><i class="bi bi-clock"></i> {{ $v->formatted_duration }}</span>@endif
@if($v->is_shorts)<span class="ch-post-video-badge" style="color:#e33;"><i class="bi bi-phone"></i> Short</span>@endif
</div>
</div>
</a>
@endforeach
</div>
@endif
{{-- Actions --}}
<div class="ch-post-actions">
@auth
<button class="ch-post-action-btn {{ $postLiked ? 'liked' : '' }}"
data-post-id="{{ $post->id }}"
data-react-url="{{ route('posts.react', $post->id) }}"
onclick="reactPost(this)">
<span class="ch-post-like-anim">
<i class="bi {{ $postLiked ? 'bi-heart-fill' : 'bi-heart' }}"></i>
</span>
<span class="post-like-count">{{ $post->reaction_count > 0 ? $post->reaction_count : '' }}</span>
<span style="margin-left:2px;">{{ $postLiked ? 'Liked' : 'Like' }}</span>
</button>
@else
<span class="ch-post-action-btn" style="cursor:default;">
<i class="bi bi-heart"></i>
{{ $post->reaction_count > 0 ? $post->reaction_count : '' }}
<span style="margin-left:2px;">Like</span>
</span>
@endauth
<div class="ch-post-action-sep"></div>
<button class="ch-post-action-btn"
onclick="navigator.clipboard.writeText('{{ url()->current() }}').then(()=>showToast('Link copied!','success'))">
<i class="bi bi-share"></i> Share
</button>
</div>
</div>
@empty
{{-- Empty state --}}
<div class="ch-wall-empty">
<div class="ch-wall-empty-icon"></div>
<h3>Nothing here yet</h3>
@auth
@if(Auth::id() === $user->id)
<p>This is your wall. Share a thought, a photo, or one of your videos with your community!</p>
@else
<p>{{ $user->name }} hasn't shared anything yet — check back soon!</p>
@endif
@else
<p>{{ $user->name }} hasn't shared anything yet. Sign in to be notified when they do.</p>
@endauth
</div>
@endforelse
</div>
{{-- ── RIGHT: sidebar ── --}}
<div class="ch-wall-sidebar">
@if(!$horoscope && $isOwner)
<div class="ch-sw-card" style="text-align:center;padding:22px 16px;">
<div style="font-size:32px;margin-bottom:8px;">🔮</div>
<div style="font-size:14px;font-weight:700;color:var(--text-primary);margin-bottom:8px;">Your Horoscope Awaits</div>
<p style="font-size:12px;color:var(--text-secondary);line-height:1.6;margin-bottom:14px;">Add your birthday to unlock your zodiac sign, age, personality traits, and compatibility with visitors.</p>
<button type="button" class="action-btn action-btn-primary" style="width:100%;" onclick="switchTab('settings', document.querySelector('[data-tab=settings]')); setTimeout(()=>document.querySelector('[name=birthday]')?.closest('.ch-settings-field')?.scrollIntoView({behavior:'smooth',block:'center'}),400);">
<i class="bi bi-calendar-heart"></i> <span>Add Birthday</span>
</button>
</div>
@elseif($horoscope)
@php
$swHColor = \App\Helpers\Horoscope::elementColor($horoscope['element']);
$swPct = $compatibility;
$swCColor = $swPct !== null ? ($swPct >= 75 ? '#4ade80' : ($swPct >= 55 ? '#fbbf24' : '#f87171')) : null;
$swLabel = $swPct !== null ? ($swPct >= 85 ? 'Soulmates' : ($swPct >= 70 ? 'Great Match' : ($swPct >= 55 ? 'Good Vibes' : ($swPct >= 40 ? 'Some Friction' : 'Tough Combo')))) : null;
$swEmoji = $swPct !== null ? ($swPct >= 85 ? '✨' : ($swPct >= 70 ? '💛' : ($swPct >= 55 ? '🙂' : ($swPct >= 40 ? '🤔' : '⚡')))) : null;
@endphp
<div class="ch-sw-card" style="--sw-h:{{ $swHColor }};">
{{-- Sign row --}}
<div class="ch-sw-sign-row">
<span class="ch-sw-emoji" style="filter:drop-shadow(0 0 10px {{ $swHColor }});">{{ $horoscope['emoji'] }}</span>
<div class="ch-sw-sign-info">
<div class="ch-sw-sign-name">{{ $horoscope['name'] }} <span style="opacity:.5;font-weight:500;">{{ $horoscope['symbol'] }}</span></div>
<div class="ch-sw-element" style="color:{{ $swHColor }};">{{ $horoscope['element'] }} sign</div>
</div>
</div>
{{-- Traits --}}
<div class="ch-sw-traits">
@foreach($horoscope['traits'] as $trait)
<span class="ch-sw-trait">{{ $trait }}</span>
@endforeach
</div>
{{-- Compat section --}}
@if($swPct !== null && $viewerSign)
<div class="ch-sw-compat">
<div class="ch-sw-compat-signs">
<span class="ch-sw-compat-sign">{{ $viewerSign['emoji'] }}<small>You</small></span>
<span class="ch-sw-compat-heart">❤️</span>
<span class="ch-sw-compat-sign">{{ $horoscope['emoji'] }}<small>{{ explode(' ', $user->name)[0] }}</small></span>
<div style="flex:1;"></div>
<span class="ch-sw-compat-pct" style="color:{{ $swCColor }};">{{ $swPct }}%</span>
<span class="ch-sw-compat-lbl">{{ $swEmoji }} {{ $swLabel }}</span>
</div>
<div class="ch-sw-compat-bar">
<div class="ch-sw-compat-fill" id="compatBar" style="width:0%;background:{{ $swCColor }};"></div>
</div>
</div>
@elseif(auth()->check() && !auth()->user()->birthday && auth()->id() !== $user->id)
<div class="ch-sw-compat-prompt">
🔮 <a href="{{ route('channel') }}#settings">Add your birthday</a> to see your cosmic match with {{ explode(' ', $user->name)[0] }}
</div>
@endif
</div>
@endif
</div>{{-- .ch-wall-sidebar --}}
</div>
</div>
{{-- ─────────────────────── VIDEOS TAB ──────────────── --}}
<div class="ch-tab-content" id="tab-videos">
@if($videos->count() > 0)
<div style="display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:20px;">
<div class="ch-vid-search-wrap" style="margin-bottom:0; flex:1; min-width:180px;">
<i class="bi bi-search"></i>
<input type="search" class="ch-vid-search" id="chVidSearch" placeholder="Search videos…" autocomplete="off">
</div>
<div class="ch-sort-bar" style="margin-bottom:0; flex-shrink:0;">
<span class="ch-sort-label">Sort</span>
<div class="ch-sort-pills">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'latest']) }}"
class="ch-sort-pill {{ $sort === 'latest' ? 'active' : '' }}">Latest</a>
<a href="{{ request()->fullUrlWithQuery(['sort' => 'popular']) }}"
class="ch-sort-pill {{ $sort === 'popular' ? 'active' : '' }}">Popular</a>
<a href="{{ request()->fullUrlWithQuery(['sort' => 'oldest']) }}"
class="ch-sort-pill {{ $sort === 'oldest' ? 'active' : '' }}">Oldest</a>
</div>
</div>
</div>
<div class="yt-video-grid" id="chVidGrid">
@foreach($videos as $video)
@include('components.video-card', ['video' => $video])
@endforeach
<div class="ch-vid-no-results" id="chVidNoResults">
<i class="bi bi-search"></i>
<span>No videos match your search</span>
</div>
</div>
@else
<div class="ch-empty">
<div class="ch-empty-icon"><i class="bi bi-camera-video"></i></div>
<h3>No videos yet</h3>
<p>This channel hasn't uploaded any videos.</p>
@auth
@if(Auth::id() === $user->id)
<button class="ch-btn-primary" data-bs-toggle="modal" data-bs-target="#uploadModal">
<i class="bi bi-cloud-upload"></i> Upload your first video
</button>
@endif
@endauth
</div>
@endif
</div>
{{-- ─────────────────────── SHORTS TAB ──────────────── --}}
@if($shorts->count() > 0)
<div class="ch-tab-content" id="tab-shorts">
<div class="ch-shorts-grid">
@foreach($shorts as $short)
<a href="{{ route('videos.show', $short) }}" class="ch-short-card">
<div class="ch-short-thumb">
<img src="{{ $short->thumbnail ? asset('storage/thumbnails/' . $short->thumbnail) : 'https://picsum.photos/seed/' . $short->id . '/360/640' }}"
alt="{{ $short->title }}" loading="lazy">
@if($short->duration)
<span class="ch-short-duration">{{ gmdate('i:s', $short->duration) }}</span>
@endif
@if($isOwner && $short->visibility === 'private')
<span class="ch-short-visibility-badge ch-vis-private"><i class="bi bi-lock-fill"></i> Private</span>
@elseif($isOwner && $short->visibility === 'unlisted')
<span class="ch-short-visibility-badge ch-vis-unlisted"><i class="bi bi-link-45deg"></i> Unlisted</span>
@endif
<div class="ch-short-play"><i class="bi bi-play-fill"></i></div>
</div>
<div class="ch-short-title">{{ $short->title }}</div>
<div class="ch-short-meta">{{ number_format($short->view_count) }} views</div>
</a>
@endforeach
</div>
</div>
@endif
{{-- ─────────────────────── PLAYLISTS TAB ───────────── --}}
@if(($playlists && $playlists->count() > 0) || $isOwner)
<div class="ch-tab-content" id="tab-playlists">
@if($isOwner)
<div style="display:flex; justify-content:flex-end; margin-bottom:16px;">
<button class="action-btn action-btn-primary" onclick="openChannelCreatePlaylistModal()">
<i class="bi bi-plus-lg"></i> <span>New Playlist</span>
</button>
</div>
@endif
@if($playlists && $playlists->count() > 0)
<div class="ch-playlists-grid">
@foreach($playlists as $playlist)
<a href="{{ route('playlists.show', $playlist->id) }}" class="ch-playlist-card">
<div class="ch-playlist-thumb">
@if($playlist->thumbnail_url)
<img src="{{ $playlist->thumbnail_url }}" alt="{{ $playlist->name }}" loading="lazy">
@else
<div class="ch-pl-placeholder"><i class="bi bi-collection-play"></i></div>
@endif
<div class="ch-playlist-overlay"></div>
<div class="ch-playlist-count">
<i class="bi bi-play-fill"></i>
{{ $playlist->video_count }} videos
</div>
</div>
<div class="ch-playlist-name">{{ $playlist->name }}</div>
<div class="ch-playlist-meta">
@if($playlist->is_default)
Watch Later
@elseif($playlist->visibility === 'private')
<i class="bi bi-lock-fill"></i> Private
@elseif($playlist->visibility === 'unlisted')
<i class="bi bi-link-45deg"></i> Unlisted
@else
<i class="bi bi-globe"></i> Public
@endif
</div>
</a>
@endforeach
</div>
@else
<div style="text-align:center; padding:60px 20px; color:var(--text-secondary);">
<i class="bi bi-collection-play" style="font-size:48px; margin-bottom:12px; display:block;"></i>
<p style="margin:0 0 16px;">No playlists yet. Create your first one!</p>
</div>
@endif
</div>
@endif
{{-- ─── Create Playlist Modal (channel page) ─────────── --}}
@if($isOwner)
<div id="chCreatePlaylistModal" style="display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:9999; align-items:center; justify-content:center; backdrop-filter:blur(2px);">
<div style="background:#282828; border-radius:12px; width:90%; max-width:480px; box-shadow:0 8px 32px rgba(0,0,0,.5); overflow:hidden; animation:chPlModalIn .2s ease;">
<div style="display:flex; justify-content:space-between; align-items:center; padding:20px 24px; border-bottom:1px solid #3f3f3f;">
<h2 style="font-size:18px; font-weight:600; margin:0; color:#fff;">Create new playlist</h2>
<button type="button" onclick="closeChannelCreatePlaylistModal()" style="background:transparent; border:none; color:#aaa; cursor:pointer; font-size:22px; line-height:1; border-radius:50%; width:32px; height:32px; display:flex; align-items:center; justify-content:center;">
<i class="bi bi-x-lg"></i>
</button>
</div>
<div style="padding:24px;">
<form id="chCreatePlaylistForm" enctype="multipart/form-data">
@csrf
<!-- Thumbnail -->
<div style="margin-bottom:20px;">
<label style="display:block; margin-bottom:8px; font-weight:500; font-size:14px; color:#fff;">Thumbnail</label>
<div id="chPlThumbZone" style="border:2px dashed #3f3f3f; border-radius:12px; padding:20px; text-align:center; cursor:pointer; background:#1a1a1a; transition:all .2s;"
onmouseover="this.style.borderColor='#e61e1e'; this.style.background='rgba(230,30,30,0.05)'"
onmouseout="this.style.borderColor='#3f3f3f'; this.style.background='#1a1a1a'">
<input type="file" name="thumbnail" id="chPlThumbInput" accept="image/*" style="display:none;">
<div id="chPlThumbDefault">
<div style="font-size:36px; color:#666; margin-bottom:8px;"><i class="bi bi-card-image"></i></div>
<p style="color:#aaa; font-size:13px; margin:0 0 4px;">Click to upload thumbnail</p>
<p style="color:#555; font-size:11px; margin:0;">JPG, PNG, GIF, WebP (max 5MB)</p>
</div>
<div id="chPlThumbPreview" style="display:none; position:relative;">
<img id="chPlThumbImg" src="" alt="" style="max-width:100%; max-height:160px; border-radius:8px; object-fit:cover;">
<button type="button" onclick="chRemovePlThumb(event)" style="position:absolute; top:-8px; right:-8px; width:24px; height:24px; background:#e61e1e; color:#fff; border:none; border-radius:50%; cursor:pointer; display:flex; align-items:center; justify-content:center; font-size:14px;"><i class="bi bi-x"></i></button>
</div>
</div>
</div>
<!-- Name -->
<div style="margin-bottom:20px;">
<label style="display:block; margin-bottom:8px; font-weight:500; font-size:14px; color:#fff;">Name *</label>
<input type="text" name="name" required placeholder="Enter playlist name"
style="width:100%; padding:12px 14px; border:1px solid #3f3f3f; border-radius:8px; background:#121212; color:#fff; font-size:14px; outline:none; box-sizing:border-box;"
onfocus="this.style.borderColor='#e61e1e'" onblur="this.style.borderColor='#3f3f3f'">
</div>
<!-- Description -->
<div style="margin-bottom:20px;">
<label style="display:block; margin-bottom:8px; font-weight:500; font-size:14px; color:#fff;">Description</label>
<textarea name="description" rows="3" placeholder="Add a description (optional)"
style="width:100%; padding:12px 14px; border:1px solid #3f3f3f; border-radius:8px; background:#121212; color:#fff; font-size:14px; outline:none; resize:none; box-sizing:border-box;"
onfocus="this.style.borderColor='#e61e1e'" onblur="this.style.borderColor='#3f3f3f'"></textarea>
</div>
<!-- Visibility -->
<div style="margin-bottom:24px;">
<label style="display:block; margin-bottom:10px; font-weight:500; font-size:14px; color:#fff;">Visibility</label>
<div style="display:flex; flex-direction:column; gap:8px;">
<label style="display:flex; align-items:center; gap:12px; cursor:pointer; padding:10px 12px; border-radius:8px; border:1px solid #3f3f3f; background:#1a1a1a;">
<input type="radio" name="visibility" value="public" style="accent-color:#e61e1e; width:16px; height:16px;">
<div>
<div style="display:flex; align-items:center; gap:6px; color:#fff; font-size:14px; font-weight:500;"><i class="bi bi-globe"></i> Public</div>
<div style="color:#aaa; font-size:12px; margin-top:2px;">Anyone can search for and view this playlist</div>
</div>
</label>
<label style="display:flex; align-items:center; gap:12px; cursor:pointer; padding:10px 12px; border-radius:8px; border:1px solid #3f3f3f; background:#1a1a1a;">
<input type="radio" name="visibility" value="unlisted" style="accent-color:#e61e1e; width:16px; height:16px;">
<div>
<div style="display:flex; align-items:center; gap:6px; color:#fff; font-size:14px; font-weight:500;"><i class="bi bi-link-45deg"></i> Unlisted</div>
<div style="color:#aaa; font-size:12px; margin-top:2px;">Anyone with the link can view, but won't appear in search</div>
</div>
</label>
<label style="display:flex; align-items:center; gap:12px; cursor:pointer; padding:10px 12px; border-radius:8px; border:1px solid #3f3f3f; background:#1a1a1a;">
<input type="radio" name="visibility" value="private" checked style="accent-color:#e61e1e; width:16px; height:16px;">
<div>
<div style="display:flex; align-items:center; gap:6px; color:#fff; font-size:14px; font-weight:500;"><i class="bi bi-lock-fill"></i> Private</div>
<div style="color:#aaa; font-size:12px; margin-top:2px;">Only you can view this playlist</div>
</div>
</label>
</div>
</div>
<div style="display:flex; gap:12px; justify-content:flex-end;">
<button type="button" onclick="closeChannelCreatePlaylistModal()" class="action-btn">Cancel</button>
<button type="submit" class="action-btn action-btn-primary"><i class="bi bi-plus-lg"></i> <span>Create</span></button>
</div>
</form>
</div>
</div>
</div>
<style>
@keyframes chPlModalIn {
from { opacity:0; transform:translateY(-20px) scale(.95); }
to { opacity:1; transform:translateY(0) scale(1); }
}
</style>
<script>
(function() {
const modal = document.getElementById('chCreatePlaylistModal');
const form = document.getElementById('chCreatePlaylistForm');
const thumbZone = document.getElementById('chPlThumbZone');
const thumbInput = document.getElementById('chPlThumbInput');
const thumbDef = document.getElementById('chPlThumbDefault');
const thumbPrev = document.getElementById('chPlThumbPreview');
const thumbImg = document.getElementById('chPlThumbImg');
thumbZone.addEventListener('click', function(e) { if (!e.target.closest('button')) thumbInput.click(); });
thumbInput.addEventListener('change', function() { chHandleThumb(this); });
thumbZone.addEventListener('dragover', function(e) { e.preventDefault(); this.style.borderColor='#e61e1e'; });
thumbZone.addEventListener('dragleave', function(e) { e.preventDefault(); this.style.borderColor='#3f3f3f'; });
thumbZone.addEventListener('drop', function(e) {
e.preventDefault(); this.style.borderColor='#3f3f3f';
if (e.dataTransfer.files.length) { thumbInput.files = e.dataTransfer.files; chHandleThumb(thumbInput); }
});
function chHandleThumb(input) {
if (!input.files || !input.files[0]) return;
const file = input.files[0];
if (!['image/jpeg','image/png','image/gif','image/webp'].includes(file.type)) {
showToast('Invalid image format. Use JPG, PNG, GIF or WebP.', 'error'); return;
}
if (file.size > 5 * 1024 * 1024) { showToast('Image exceeds 5 MB limit.', 'error'); return; }
const reader = new FileReader();
reader.onload = function(e) {
thumbImg.src = e.target.result;
thumbDef.style.display = 'none';
thumbPrev.style.display = 'block';
};
reader.readAsDataURL(file);
}
window.chRemovePlThumb = function(e) {
e.preventDefault(); e.stopPropagation();
thumbInput.value = '';
thumbDef.style.display = 'block';
thumbPrev.style.display = 'none';
thumbImg.src = '';
};
window.openChannelCreatePlaylistModal = function() { modal.style.display = 'flex'; };
window.closeChannelCreatePlaylistModal = function() {
modal.style.display = 'none';
form.reset();
thumbDef.style.display = 'block';
thumbPrev.style.display = 'none';
thumbImg.src = '';
thumbInput.value = '';
};
modal.addEventListener('click', function(e) { if (e.target === modal) closeChannelCreatePlaylistModal(); });
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && modal.style.display === 'flex') closeChannelCreatePlaylistModal();
});
form.addEventListener('submit', function(e) {
e.preventDefault();
const btn = form.querySelector('[type=submit]');
btn.disabled = true;
fetch('{{ route("playlists.store") }}', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Accept': 'application/json' },
body: new FormData(form)
})
.then(r => r.json())
.then(data => {
if (data.success) {
window.location.href = '{{ route("playlists.show", "") }}/' + data.playlist.id;
} else {
showToast(data.message || 'Failed to create playlist.', 'error');
btn.disabled = false;
}
})
.catch(() => { showToast('Failed to create playlist. Please try again.', 'error'); btn.disabled = false; });
});
})();
</script>
@endif
{{-- ─────────────────────── ABOUT TAB ───────────────── --}}
<div class="ch-tab-content" id="tab-about">
<div class="ch-about-layout">
{{-- Left column --}}
<div>
@if($user->bio)
<div class="ch-about-card">
<div class="ch-about-card-title"><i class="bi bi-person-lines-fill"></i> About</div>
<p class="ch-about-desc">{{ $user->bio }}</p>
</div>
@endif
@if(!$horoscope && $isOwner)
<div class="ch-about-card" style="text-align:center;padding:24px 20px;">
<div style="font-size:36px;margin-bottom:10px;">🔮</div>
<div style="font-size:15px;font-weight:700;color:var(--text-primary);margin-bottom:8px;">Unlock Your Horoscope</div>
<p style="font-size:13px;color:var(--text-secondary);line-height:1.6;margin-bottom:16px;">Add your birthday to see your zodiac sign, age, traits, and compatibility with visitors.</p>
<button type="button" class="action-btn action-btn-primary" onclick="switchTab('settings', document.querySelector('[data-tab=settings]')); setTimeout(()=>document.querySelector('[name=birthday]')?.closest('.ch-settings-field')?.scrollIntoView({behavior:'smooth',block:'center'}),400);">
<i class="bi bi-calendar-heart"></i> <span>Add Birthday</span>
</button>
</div>
@elseif($horoscope)
@php $hColor = \App\Helpers\Horoscope::elementColor($horoscope['element']); @endphp
<div class="ch-about-card" style="--horoscope-color:{{ $hColor }};">
<div class="ch-about-card-title"><i class="bi bi-stars"></i> Horoscope</div>
<div class="ch-horoscope-sign-row" style="margin-bottom:12px;">
<div class="ch-horoscope-symbol" style="font-size:36px;">{{ $horoscope['emoji'] }}</div>
<div>
<div class="ch-horoscope-name" style="font-size:17px;">{{ $horoscope['name'] }} {{ $horoscope['symbol'] }}</div>
<div class="ch-horoscope-element" style="color:{{ $hColor }};margin-top:4px;">
<i class="bi bi-gem"></i> {{ $horoscope['element'] }} sign
</div>
</div>
</div>
<div class="ch-horoscope-traits">
@foreach($horoscope['traits'] as $trait)
<span class="ch-horoscope-trait">{{ $trait }}</span>
@endforeach
</div>
</div>
@endif
@if($socialLinks->isNotEmpty())
@php
$chLinkMap = [
'twitter' => ['icon' => 'bi-twitter-x', 'color' => '#1d9bf0', 'bg' => 'rgba(29,155,240,.15)',
'href' => fn($v) => "https://twitter.com/{$v}",
'label' => fn($v) => "@{$v}"],
'instagram' => ['icon' => 'bi-instagram', 'color' => '#e1306c', 'bg' => 'rgba(225,48,108,.15)',
'href' => fn($v) => "https://instagram.com/{$v}",
'label' => fn($v) => "@{$v}"],
'facebook' => ['icon' => 'bi-facebook', 'color' => '#1877f2', 'bg' => 'rgba(24,119,242,.15)',
'href' => fn($v) => "https://facebook.com/{$v}",
'label' => fn($v) => $v],
'youtube' => ['icon' => 'bi-youtube', 'color' => '#ff0000', 'bg' => 'rgba(255,0,0,.15)',
'href' => fn($v) => "https://youtube.com/@{$v}",
'label' => fn($v) => "@{$v}"],
'linkedin' => ['icon' => 'bi-linkedin', 'color' => '#0a66c2', 'bg' => 'rgba(10,102,194,.15)',
'href' => fn($v) => "https://linkedin.com/in/{$v}",
'label' => fn($v) => $v],
'tiktok' => ['icon' => 'bi-tiktok', 'color' => '#ff0050', 'bg' => 'rgba(255,0,80,.15)',
'href' => fn($v) => "https://tiktok.com/@{$v}",
'label' => fn($v) => "@{$v}"],
'whatsapp' => ['icon' => 'bi-whatsapp', 'color' => '#25d366', 'bg' => 'rgba(37,211,102,.15)',
'href' => fn($v) => "https://wa.me/" . preg_replace('/\D/','',$v),
'label' => fn($v) => $v],
'website' => ['icon' => 'bi-globe2', 'color' => '#a78bfa', 'bg' => 'rgba(167,139,250,.15)',
'href' => fn($v) => preg_match('/^https?:\/\//', $v) ? $v : "https://{$v}",
'label' => fn($v) => parse_url((str_starts_with($v,'http') ? $v : "https://{$v}"), PHP_URL_HOST) ?: $v],
'google_location' => ['icon' => 'bi-geo-alt-fill', 'color' => '#ea4335', 'bg' => 'rgba(234,67,53,.15)',
'href' => fn($v) => $v,
'label' => fn($v) => 'View on Maps'],
'social_phone' => ['icon' => 'bi-telephone-fill', 'color' => '#34d399', 'bg' => 'rgba(52,211,153,.15)',
'href' => fn($v) => "tel:{$v}",
'label' => fn($v) => $v],
'social_email' => ['icon' => 'bi-envelope-fill', 'color' => '#fb923c', 'bg' => 'rgba(251,146,60,.15)',
'href' => fn($v) => "mailto:{$v}",
'label' => fn($v) => $v],
];
@endphp
<div class="ch-about-card">
<div class="ch-about-card-title"><i class="bi bi-link-45deg"></i> Links</div>
@foreach($socialLinks as $link)
@if(isset($chLinkMap[$link->platform]))
@php $m = $chLinkMap[$link->platform]; @endphp
<a href="{{ $m['href']($link->value) }}"
target="{{ in_array($link->platform, ['social_phone','social_email']) ? '_self' : '_blank' }}"
rel="noopener" class="ch-link-card">
<span class="ch-link-card-icon" style="background:{{ $m['bg'] }};color:{{ $m['color'] }};">
<i class="bi {{ $m['icon'] }}"></i>
</span>
<span class="ch-link-card-label">{{ $m['label']($link->value) }}</span>
<i class="bi bi-chevron-right"></i>
</a>
@endif
@endforeach
</div>
@endif
</div>
{{-- Right column: Stats + Compatibility --}}
<div>
<div class="ch-about-card">
<div class="ch-about-card-title"><i class="bi bi-bar-chart-fill"></i> Stats</div>
<div class="ch-stat-cards">
<div class="ch-stat-card">
<div class="ch-stat-card-val">{{ \Illuminate\Support\Number::abbreviate($totalViews, precision: 1) }}</div>
<div class="ch-stat-card-key">Total Views</div>
</div>
<div class="ch-stat-card">
<div class="ch-stat-card-val">{{ $videos->count() + $shorts->count() }}</div>
<div class="ch-stat-card-key">Videos</div>
</div>
</div>
<div class="ch-stat-item">
<div class="ch-stat-item-icon"><i class="bi bi-calendar3"></i></div>
<span>Joined {{ $user->created_at->format('F j, Y') }}</span>
</div>
@if($user->location)
<div class="ch-stat-item">
<div class="ch-stat-item-icon"><i class="bi bi-geo-alt-fill"></i></div>
<span>{{ $user->location }}</span>
</div>
@endif
@if($playlists && $playlists->count() > 0)
<div class="ch-stat-item">
<div class="ch-stat-item-icon"><i class="bi bi-collection-play-fill"></i></div>
<span>{{ $playlists->count() }} playlists</span>
</div>
@endif
@if($shorts->count() > 0)
<div class="ch-stat-item">
<div class="ch-stat-item-icon"><i class="bi bi-phone-fill"></i></div>
<span>{{ $shorts->count() }} shorts</span>
</div>
@endif
</div>
@if($compatibility !== null && $viewerSign)
@php
$pct = $compatibility;
$compatColor = $pct >= 75 ? '#22c55e' : ($pct >= 55 ? '#eab308' : '#ef4444');
$compatLabel = $pct >= 85 ? 'Soulmates' : ($pct >= 70 ? 'Great match' : ($pct >= 55 ? 'Good vibes' : ($pct >= 40 ? 'Some friction' : 'Tough combo')));
@endphp
<div class="ch-compat-card">
<div class="ch-compat-label"><i class="bi bi-hearts" style="color:#e33;"></i> Compatibility</div>
<div class="ch-compat-faces">
<div class="ch-compat-sign-block">
<div class="ch-compat-sign-sym">{{ $viewerSign['emoji'] }}</div>
<div class="ch-compat-sign-name">You</div>
</div>
<div class="ch-compat-heart">❤️</div>
<div class="ch-compat-sign-block">
<div class="ch-compat-sign-sym">{{ $horoscope['emoji'] }}</div>
<div class="ch-compat-sign-name">{{ explode(' ', $user->name)[0] }}</div>
</div>
</div>
<div class="ch-compat-pct" style="color:{{ $compatColor }};">{{ $pct }}%</div>
<div class="ch-compat-bar-wrap" style="margin:8px 0;">
<div class="ch-compat-bar" id="compatBarAbout" style="width:0%;background:{{ $compatColor }};"></div>
</div>
<div class="ch-compat-desc">{{ $compatLabel }}</div>
</div>
@endif
</div>
</div>
</div>
{{-- ─────────────────────── SETTINGS TAB ────────────── --}}
@if($isOwner)
@php
$oldLinks = old('slink');
if ($oldLinks) {
$socialExisting = collect($oldLinks)
->filter(fn($e) => !empty($e['platform']) && !empty($e['value']))
->map(fn($e) => ['platform' => $e['platform'], 'value' => $e['value'], 'visibility' => $e['visibility'] ?? 'public'])
->values()->all();
} else {
$socialExisting = $user->socialLinks()->orderBy('sort_order')->get()
->map(fn($l) => ['platform' => $l->platform, 'value' => $l->value, 'visibility' => $l->visibility])
->all();
}
@endphp
<div class="ch-tab-content" id="tab-settings">
<div class="ch-settings-layout">
{{-- Profile Edit Form --}}
<div>
<div class="ch-settings-card">
<div class="ch-settings-card-title"><i class="bi bi-person-fill"></i> Profile Info</div>
<form method="POST" action="{{ route('profile.update') }}" enctype="multipart/form-data">
@csrf @method('PUT')
<input type="hidden" name="_edit_user_id" value="{{ $user->id }}">
{{-- Avatar --}}
<div class="ch-settings-avatar-row">
<div class="ch-settings-avatar-wrap">
<img src="{{ $user->avatar_url }}" alt="" class="ch-settings-avatar" id="chSettingsAvatarPreview">
<label class="ch-settings-avatar-btn" title="Change photo">
<i class="bi bi-camera-fill"></i>
<input type="file" name="avatar" accept="image/*" style="display:none;"
onchange="previewSettingsAvatar(this)">
</label>
</div>
<div style="font-size:13px;color:var(--text-secondary);line-height:1.7;">
<strong style="color:var(--text-primary);">Profile Photo</strong><br>
JPG, PNG or WebP · Max 5 MB<br>
Recommended: 200×200 px minimum
</div>
</div>
<div class="ch-settings-2col">
<div class="ch-settings-field">
<label class="ch-settings-label">Name</label>
<input type="text" name="name" class="ch-settings-input" value="{{ old('name', $user->name) }}" required>
</div>
<div class="ch-settings-field">
<label class="ch-settings-label">Gender</label>
<select name="gender" class="ch-settings-input" style="cursor:pointer;">
<option value="">Prefer not to say</option>
<option value="male" {{ old('gender', $user->gender) === 'male' ? 'selected' : '' }}>Male</option>
<option value="female" {{ old('gender', $user->gender) === 'female' ? 'selected' : '' }}>Female</option>
<option value="prefer_not_to_say" {{ old('gender', $user->gender) === 'prefer_not_to_say' ? 'selected' : '' }}>Prefer not to say</option>
</select>
</div>
</div>
<div class="ch-settings-field">
<label class="ch-settings-label">Bio</label>
<textarea name="bio" class="ch-settings-input ch-settings-textarea" placeholder="Tell the world about yourself…">{{ old('bio', $user->bio) }}</textarea>
</div>
<div class="ch-settings-2col">
<div class="ch-settings-field">
<x-date-picker
name="birthday"
label="Birthday"
value="{{ old('birthday', $user->birthday) }}"
:max-year="(int) date('Y')"
:min-year="1900"
/>
</div>
<div class="ch-settings-field">
<label class="ch-settings-label">Location</label>
<input type="text" name="location" class="ch-settings-input" value="{{ old('location', $user->location) }}" maxlength="100" placeholder="City, Country">
</div>
</div>
<div class="ch-settings-2col">
<div class="ch-settings-field">
<x-country-select
name="nationality"
label="Nationality"
placeholder="Select nationality"
value="{{ old('nationality', $user->nationality) }}"
/>
</div>
<div class="ch-settings-field">
<x-timezone-select
name="timezone"
label="Timezone"
placeholder="Select timezone"
value="{{ old('timezone', $user->timezone) }}"
/>
</div>
</div>
<div class="ch-settings-footer">
<button type="submit" class="action-btn action-btn-primary"><i class="bi bi-check-lg"></i> <span>Save Profile</span></button>
</div>
</form>
</div>
{{-- Social Links --}}
<div class="ch-settings-card">
<div class="ch-settings-card-title"><i class="bi bi-link-45deg"></i> Social Links</div>
<form method="POST" action="{{ route('profile.update') }}" enctype="multipart/form-data">
@csrf @method('PUT')
<input type="hidden" name="_edit_user_id" value="{{ $user->id }}">
{{-- Must send name so the controller doesn't clear it --}}
<input type="hidden" name="name" value="{{ $user->name }}">
<x-social-links-editor :existing="$socialExisting" />
<div class="ch-settings-footer">
<button type="submit" class="action-btn action-btn-primary"><i class="bi bi-check-lg"></i> <span>Save Links</span></button>
</div>
</form>
</div>
</div>
{{-- Right: Security --}}
<div>
@if($isOwner)
<div class="ch-settings-card">
<div class="ch-settings-card-title"><i class="bi bi-shield-lock-fill"></i> Change Password</div>
<form method="POST" action="{{ route('settings.update') }}">
@csrf @method('PUT')
<div class="ch-settings-field">
<label class="ch-settings-label">Current Password</label>
<input type="password" name="current_password" class="ch-settings-input" required>
</div>
<div class="ch-settings-field">
<label class="ch-settings-label">New Password</label>
<input type="password" name="new_password" class="ch-settings-input" required minlength="8">
<div class="ch-settings-hint">Minimum 8 characters</div>
</div>
<div class="ch-settings-field">
<label class="ch-settings-label">Confirm New Password</label>
<input type="password" name="new_password_confirmation" class="ch-settings-input" required>
</div>
<div class="ch-settings-footer">
<button type="submit" class="action-btn action-btn-primary"><i class="bi bi-key-fill"></i> <span>Update Password</span></button>
</div>
</form>
</div>
@endif
@if($isOwner)
{{-- 2FA Card --}}
<div class="ch-settings-card" id="twoFactorCard">
<div class="ch-settings-card-title"><i class="bi bi-shield-check-fill"></i> Two-Factor Authentication</div>
@if($user->two_factor_enabled)
{{-- Enabled state --}}
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;">
<span style="background:rgba(34,197,94,.15);border:1px solid #22c55e;color:#22c55e;border-radius:20px;padding:3px 12px;font-size:12px;font-weight:600;">
<i class="bi bi-check-circle-fill"></i> Enabled
</span>
<span style="font-size:13px;color:var(--text-secondary);">Your account is protected.</span>
</div>
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:16px;">
To disable 2FA, confirm your password below.
</p>
<form method="POST" action="{{ route('2fa.disable') }}">
@csrf
<div class="ch-settings-field">
<label class="ch-settings-label">Password</label>
<input type="password" name="password" class="ch-settings-input" required placeholder="••••••••">
</div>
<div class="ch-settings-footer">
<button type="submit" class="action-btn action-btn-danger"><i class="bi bi-shield-x"></i> <span>Disable 2FA</span></button>
</div>
</form>
@else
{{-- Disabled state --}}
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:20px;">
Add an extra layer of security. Once enabled, you'll need your authenticator app each time you sign in.
</p>
{{-- Step 1: Show QR --}}
<div id="tfa-step1" style="display:none;">
<p style="font-size:13px;font-weight:600;margin-bottom:8px;">Step 1 Scan this QR code with your authenticator app</p>
<div id="tfa-qr" style="background:#fff;display:inline-block;padding:10px;border-radius:8px;margin-bottom:12px;">
<img id="tfa-qr-img" src="" alt="QR Code" style="display:block;width:180px;height:180px;">
</div>
<p style="font-size:12px;color:var(--text-secondary);margin-bottom:4px;">Or enter the key manually:</p>
<code id="tfa-secret" style="font-size:13px;letter-spacing:.1em;background:rgba(255,255,255,.06);padding:4px 10px;border-radius:6px;display:inline-block;margin-bottom:20px;word-break:break-all;"></code>
<p style="font-size:13px;font-weight:600;margin-bottom:8px;">Step 2 Enter the 6-digit code to confirm</p>
<form method="POST" action="{{ route('2fa.enable') }}" id="tfa-confirm-form">
@csrf
<input type="hidden" name="code" id="tfa-code-hidden">
<div style="display:flex;gap:10px;align-items:center;">
<input type="text" id="tfa-code-input" inputmode="numeric" pattern="[0-9]*" maxlength="6"
placeholder="000000" autocomplete="one-time-code"
style="width:140px;background:rgba(255,255,255,.04);border:1px solid var(--border-color);border-radius:8px;padding:10px 14px;color:var(--text-primary);font-size:18px;letter-spacing:.3em;text-align:center;">
<button type="submit" class="action-btn action-btn-primary"><i class="bi bi-check-lg"></i> <span>Enable 2FA</span></button>
</div>
@if($errors->has('code'))
<p style="color:#ef4444;font-size:12px;margin-top:6px;">{{ $errors->first('code') }}</p>
@endif
</form>
</div>
<div id="tfa-step0">
<button type="button" class="action-btn action-btn-primary" id="tfa-start-btn" onclick="tfaStart()">
<i class="bi bi-shield-plus"></i> <span>Set Up 2FA</span>
</button>
</div>
@endif
</div>
@endif
<div class="ch-settings-card">
<div class="ch-settings-card-title"><i class="bi bi-info-circle-fill"></i> Account</div>
<div class="ch-settings-field">
<label class="ch-settings-label">Email</label>
<input type="email" class="ch-settings-input" value="{{ $user->email }}" disabled>
<div class="ch-settings-hint">Email cannot be changed</div>
</div>
<div class="ch-settings-field" style="margin-bottom:0;">
<label class="ch-settings-label">Member Since</label>
<input type="text" class="ch-settings-input" value="{{ $user->created_at->format('F d, Y') }}" disabled>
</div>
</div>
@if($isOwner)
<div class="ch-settings-card">
<div class="ch-settings-card-title"><i class="bi bi-devices"></i> Active Sessions</div>
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:16px;line-height:1.6;">
Sign out of all other browsers and devices where your account is currently logged in.
Your current session will remain active.
</p>
<form method="POST" action="{{ route('settings.logoutAllDevices') }}">
@csrf
<div class="ch-settings-field">
<label class="ch-settings-label">Confirm Password</label>
<input type="password" name="password" class="ch-settings-input" required placeholder="••••••••">
@error('logout_password')
<div style="color:#ef4444;font-size:12px;margin-top:5px;">{{ $message }}</div>
@enderror
</div>
<div class="ch-settings-footer">
<button type="submit" class="action-btn action-btn-danger"><i class="bi bi-box-arrow-right"></i> <span>Log Out All Other Devices</span></button>
</div>
</form>
</div>
@endif
</div>
</div>
</div>
@endif
{{-- Video picker modal (wall composer) --}}
@if(Auth::check() && Auth::id() === $user->id && $user->videos->count() > 0)
@php $pickerVideos = $user->videos()->latest()->get(); @endphp
<div id="videoPickerModal" class="ch-vp-backdrop" style="display:none;" onclick="if(event.target===this)closeVideoPicker()">
<div class="ch-vp-modal" onclick="event.stopPropagation()">
<div class="ch-vp-head">
<h3><i class="bi bi-collection-play-fill" style="color:#60a5fa;margin-right:8px;"></i>Select Videos</h3>
<button class="ch-vp-close" onclick="closeVideoPicker()"><i class="bi bi-x-lg"></i></button>
</div>
<div class="ch-vp-search">
<input type="text" id="vpSearchInput" placeholder="Search your videos…" oninput="filterPickerVideos(this.value)">
</div>
<div class="ch-vp-grid" id="videoPickerGrid">
@foreach($pickerVideos as $v)
<div class="ch-vp-card"
data-id="{{ $v->id }}"
data-title="{{ $v->title }}"
data-thumb="{{ $v->thumbnail ? asset('storage/thumbnails/'.$v->thumbnail) : '' }}"
onclick="togglePickerCard(this)">
<div class="ch-vp-card-thumb">
@if($v->thumbnail)
<img src="{{ asset('storage/thumbnails/'.$v->thumbnail) }}" alt="" loading="lazy">
@else
<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:rgba(255,255,255,.2);font-size:22px;"><i class="bi bi-film"></i></div>
@endif
<div class="ch-vp-card-check"><i class="bi bi-check"></i></div>
</div>
<div class="ch-vp-card-title">{{ $v->title }}</div>
</div>
@endforeach
</div>
<div class="ch-vp-footer">
<span class="ch-vp-count" id="vpSelectedCount">0 videos selected</span>
<button class="ch-vp-confirm" id="vpConfirmBtn" onclick="confirmVideoPicker()" disabled>Add to Post</button>
</div>
</div>
</div>
@endif
@endsection
@section('scripts')
<script>
/* ── Logout All Devices modal ── */
function openLogoutAllModal() {
if (document.getElementById('logoutAllModal')) return;
const tpl = document.getElementById('logoutAllModalTpl');
const node = tpl.content.cloneNode(true);
document.body.appendChild(node);
setTimeout(() => document.getElementById('logoutAllPwInput')?.focus(), 50);
}
function closeLogoutAllModal() {
const el = document.getElementById('logoutAllModal');
if (el) el.remove();
}
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeLogoutAllModal(); });
/* ── Tab switching ── */
function switchTab(tabId, btn) {
document.querySelectorAll('.ch-tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.ch-tab-content').forEach(c => c.classList.remove('active'));
if (!btn) btn = document.querySelector('.ch-tab[data-tab="' + tabId + '"]');
if (btn) btn.classList.add('active');
const content = document.getElementById('tab-' + tabId);
if (content) content.classList.add('active');
history.replaceState(null, '', '#' + tabId);
}
window.addEventListener('hashchange', function () {
const h = location.hash.replace('#', '');
if (h) {
const btn = document.querySelector('.ch-tab[data-tab="' + h + '"]');
if (btn) switchTab(h, btn);
}
});
document.addEventListener('DOMContentLoaded', function () {
const hash = location.hash.replace('#', '');
const urlParams = new URLSearchParams(location.search);
if (hash) {
const btn = document.querySelector('.ch-tab[data-tab="' + hash + '"]');
if (btn) switchTab(hash, btn);
} else if (urlParams.has('sort')) {
const btn = document.querySelector('.ch-tab[data-tab="videos"]');
if (btn) switchTab('videos', btn);
}
@if($errors->any() && $isOwner)
switchTab('settings', null);
@elseif(session('_open_tab') === 'settings' && $isOwner)
switchTab('settings', null);
@endif
// Animate compat bars
setTimeout(function () {
@if($compatibility !== null)
var bar = document.getElementById('compatBar');
if (bar) bar.style.width = '{{ $compatibility }}%';
var bar2 = document.getElementById('compatBarAbout');
if (bar2) bar2.style.width = '{{ $compatibility }}%';
var mini = document.getElementById('chCompatMiniBar');
if (mini) mini.style.width = '{{ $compatibility }}%';
@endif
}, 300);
// Video search filter
var vidSearch = document.getElementById('chVidSearch');
var vidGrid = document.getElementById('chVidGrid');
var noResults = document.getElementById('chVidNoResults');
if (vidSearch && vidGrid) {
vidSearch.addEventListener('input', function () {
var q = this.value.trim().toLowerCase();
var cards = vidGrid.querySelectorAll('.yt-video-card');
var visible = 0;
cards.forEach(function (card) {
var titleEl = card.querySelector('.yt-video-title');
var title = titleEl ? titleEl.textContent.toLowerCase() : '';
var show = !q || title.includes(q);
card.style.display = show ? '' : 'none';
if (show) visible++;
});
if (noResults) noResults.style.display = visible === 0 ? 'block' : 'none';
});
}
});
/* ── 2FA setup ── */
function tfaStart() {
const btn = document.getElementById('tfa-start-btn');
if (btn) { btn.disabled = true; btn.innerHTML = '<i class="bi bi-hourglass-split"></i> <span>Loading…</span>'; }
fetch('{{ route('2fa.setup') }}', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json',
},
})
.then(r => r.json())
.then(data => {
document.getElementById('tfa-qr-img').src = 'data:image/svg+xml;base64,' + data.qr;
document.getElementById('tfa-secret').textContent = data.secret;
document.getElementById('tfa-step0').style.display = 'none';
document.getElementById('tfa-step1').style.display = '';
document.getElementById('tfa-code-input').focus();
})
.catch(() => {
if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-shield-plus"></i> <span>Set Up 2FA</span>'; }
});
}
document.addEventListener('DOMContentLoaded', function () {
const codeInput = document.getElementById('tfa-code-input');
const codeHidden = document.getElementById('tfa-code-hidden');
if (codeInput && codeHidden) {
document.getElementById('tfa-confirm-form').addEventListener('submit', function () {
codeHidden.value = codeInput.value;
});
}
});
/* ── Bio expand/collapse ── */
function toggleBio() {
const short = document.getElementById('ch-bio-short');
const full = document.getElementById('ch-bio-full');
const btn = document.getElementById('ch-bio-btn');
const isShort = full.style.display === 'none' || !full.style.display;
short.style.display = isShort ? 'none' : '';
full.style.display = isShort ? 'inline' : 'none';
btn.textContent = isShort ? ' Show less' : '…more';
}
/* ── Post composer — multi-image & video picker ── */
let _postImages = []; // array of {file, dataUrl}
function handlePostImages(input) {
const files = Array.from(input.files || []);
if (!files.length) return;
const remaining = 10 - _postImages.length;
files.slice(0, remaining).forEach(file => {
const reader = new FileReader();
reader.onload = e => {
_postImages.push({ file, dataUrl: e.target.result });
renderImageGrid();
};
reader.readAsDataURL(file);
});
input.value = '';
}
function renderImageGrid() {
const grid = document.getElementById('imagePreviewGrid');
if (!grid) return;
if (!_postImages.length) { grid.style.display = 'none'; grid.innerHTML = ''; return; }
const n = _postImages.length;
const cls = n === 1 ? 'count-1' : n === 2 ? 'count-2' : n === 3 ? 'count-3' : n === 4 ? 'count-4' : 'count-more';
grid.className = 'ch-composer-img-grid ' + cls;
grid.style.display = '';
grid.innerHTML = _postImages.map((img, i) => `
<div class="ch-composer-img-item">
<img src="${img.dataUrl}" alt="">
<button type="button" class="ch-composer-img-remove" onclick="removePostImage(${i})">
<i class="bi bi-x"></i>
</button>
</div>`).join('');
}
function removePostImage(idx) {
_postImages.splice(idx, 1);
renderImageGrid();
}
/* ── Video picker ── */
let _pickerSelected = {}; // {id: {id, title, thumb}}
function openVideoPicker() {
_pickerSelected = {};
document.querySelectorAll('.ch-vp-card').forEach(c => c.classList.remove('selected'));
document.getElementById('vpSelectedCount').textContent = '0 videos selected';
document.getElementById('vpConfirmBtn').disabled = true;
document.getElementById('vpSearchInput').value = '';
filterPickerVideos('');
document.getElementById('videoPickerModal').style.display = 'flex';
document.body.style.overflow = 'hidden';
// restore already-selected chips
document.querySelectorAll('#postVideoChips [data-vid]').forEach(chip => {
const id = chip.dataset.vid;
const card = document.querySelector(`.ch-vp-card[data-id="${id}"]`);
if (card) {
card.classList.add('selected');
_pickerSelected[id] = { id, title: card.dataset.title, thumb: card.dataset.thumb };
}
});
updatePickerCount();
}
function closeVideoPicker() {
document.getElementById('videoPickerModal').style.display = 'none';
document.body.style.overflow = '';
}
function togglePickerCard(card) {
const id = card.dataset.id;
if (card.classList.contains('selected')) {
card.classList.remove('selected');
delete _pickerSelected[id];
} else {
card.classList.add('selected');
_pickerSelected[id] = { id, title: card.dataset.title, thumb: card.dataset.thumb };
}
updatePickerCount();
}
function updatePickerCount() {
const n = Object.keys(_pickerSelected).length;
document.getElementById('vpSelectedCount').textContent = n === 0 ? '0 videos selected' : `${n} video${n > 1 ? 's' : ''} selected`;
document.getElementById('vpConfirmBtn').disabled = n === 0;
}
function filterPickerVideos(q) {
const lower = q.toLowerCase();
document.querySelectorAll('.ch-vp-card').forEach(card => {
card.style.display = card.dataset.title.toLowerCase().includes(lower) ? '' : 'none';
});
}
function confirmVideoPicker() {
closeVideoPicker();
renderVideoChips();
}
function renderVideoChips() {
const wrap = document.getElementById('postVideoChips');
if (!wrap) return;
const ids = Object.values(_pickerSelected);
if (!ids.length) { wrap.style.display = 'none'; wrap.innerHTML = ''; return; }
wrap.style.display = '';
wrap.innerHTML = ids.map(v => `
<div class="ch-composer-video-chip" data-vid="${v.id}">
${v.thumb ? `<img class="ch-composer-video-chip-thumb" src="${v.thumb}" alt="">` : '<i class="bi bi-film" style="font-size:14px;flex-shrink:0;"></i>'}
<span>${v.title}</span>
<button type="button" class="ch-composer-video-chip-remove" onclick="removeVideoChip('${v.id}')">
<i class="bi bi-x"></i>
</button>
</div>`).join('');
}
function removeVideoChip(id) {
delete _pickerSelected[id];
renderVideoChips();
}
/* Intercept form submit — use fetch/FormData to send files + video ids */
(function () {
const form = document.getElementById('postForm');
if (!form) return;
form.addEventListener('submit', function (e) {
e.preventDefault();
const fd = new FormData(this);
// Remove placeholder inputs we don't need
fd.delete('images[]');
fd.delete('video_ids[]');
// Add staged images
_postImages.forEach(img => fd.append('images[]', img.file));
// Add selected video ids
Object.keys(_pickerSelected).forEach(id => fd.append('video_ids[]', id));
const btn = this.querySelector('.ch-composer-post-btn');
if (btn) { btn.disabled = true; btn.innerHTML = '<i class="bi bi-hourglass-split"></i> Posting…'; }
fetch(this.action, {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
body: fd,
credentials: 'same-origin',
})
.then(r => {
if (r.redirected) { window.location.href = r.url; return; }
if (!r.ok) return r.text().then(t => { throw new Error(t); });
window.location.reload();
})
.catch(err => {
if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-send-fill"></i> Post'; }
if (typeof showToast === 'function') showToast('Failed to post. Please try again.', 'error');
console.error(err);
});
});
})();
/* Escape closes video picker */
document.addEventListener('keydown', e => {
if (e.key === 'Escape') {
const modal = document.getElementById('videoPickerModal');
if (modal && modal.style.display !== 'none') closeVideoPicker();
}
});
/* ── Post three-dot menu ── */
function togglePostMenu(btn) {
const dropdown = btn.nextElementSibling;
const isOpen = dropdown.classList.contains('open');
// close all
document.querySelectorAll('.ch-post-dropdown.open').forEach(d => d.classList.remove('open'));
if (!isOpen) dropdown.classList.add('open');
}
document.addEventListener('click', function(e) {
if (!e.target.closest('.ch-post-menu-wrap')) {
document.querySelectorAll('.ch-post-dropdown.open').forEach(d => d.classList.remove('open'));
}
});
/* ── Post like ── */
var csrfToken = '{{ csrf_token() }}';
function reactPost(btn) {
var url = btn.dataset.reactUrl;
var liked = btn.classList.contains('liked');
var anim = btn.querySelector('.ch-post-like-anim');
var icon = btn.querySelector('i');
var count = btn.querySelector('.post-like-count');
var label = btn.querySelector('span:last-child');
var newLiked = !liked;
// Optimistic update
btn.classList.toggle('liked', newLiked);
if (icon) icon.className = newLiked ? 'bi bi-heart-fill' : 'bi bi-heart';
if (label) label.textContent = newLiked ? 'Liked' : 'Like';
if (count) {
var n = parseInt(count.textContent) || 0;
count.textContent = (newLiked ? n + 1 : Math.max(0, n - 1)) || '';
}
if (anim && newLiked) { anim.classList.remove('pop'); void anim.offsetWidth; anim.classList.add('pop'); }
fetch(url, {
method: 'POST',
headers: { 'X-CSRF-TOKEN': csrfToken, 'Accept': 'application/json' },
credentials: 'same-origin',
})
.then(r => r.json())
.then(data => {
btn.classList.toggle('liked', data.liked);
if (icon) icon.className = data.liked ? 'bi bi-heart-fill' : 'bi bi-heart';
if (label) label.textContent = data.liked ? 'Liked' : 'Like';
if (count) count.textContent = data.count || '';
})
.catch(() => {
btn.classList.toggle('liked', liked);
if (icon) icon.className = liked ? 'bi bi-heart-fill' : 'bi bi-heart';
if (label) label.textContent = liked ? 'Liked' : 'Like';
});
}
/* ── Image lightbox ── */
function openLightbox(src) {
var lb = document.getElementById('wallLightbox');
if (!lb) {
lb = document.createElement('div');
lb.id = 'wallLightbox';
lb.style.cssText = 'position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.92);display:flex;align-items:center;justify-content:center;cursor:zoom-out;backdrop-filter:blur(6px);';
lb.innerHTML = '<img style="max-width:92vw;max-height:92vh;border-radius:8px;box-shadow:0 32px 80px rgba(0,0,0,.7);object-fit:contain;" /><button style="position:absolute;top:20px;right:24px;background:rgba(255,255,255,.15);border:none;color:#fff;font-size:22px;width:44px;height:44px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;" onclick="this.parentElement.remove()"><i class="bi bi-x-lg"></i></button>';
lb.addEventListener('click', function(e) { if (e.target === lb) lb.remove(); });
document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { var l = document.getElementById('wallLightbox'); if(l) l.remove(); } }, { once: true });
document.body.appendChild(lb);
}
lb.querySelector('img').src = src;
lb.style.display = 'flex';
}
/* ── Video hover-preview ── */
document.querySelectorAll('.yt-video-card').forEach(card => {
card.addEventListener('mouseenter', () => {
if ('ontouchstart' in window) return;
const v = card.querySelector('video');
if (v) { v.currentTime = 0; v.volume = 0.1; v.play().catch(()=>{}); v.classList.add('active'); }
});
card.addEventListener('mouseleave', () => {
const v = card.querySelector('video');
if (v) { v.pause(); v.currentTime = 0; v.classList.remove('active'); }
});
});
/* ── Subscribe toggle ── */
(function () {
document.addEventListener('click', function (e) {
var btn = e.target.closest('.subscribe-toggle-btn');
if (!btn) return;
var url = btn.dataset.subscribeUrl;
var subscribed = btn.dataset.subscribed === 'true';
var channelId = btn.dataset.channelId;
var nowSub = !subscribed;
btn.dataset.subscribed = nowSub ? 'true' : 'false';
btn.classList.toggle('subscribed-ch', nowSub);
var icon = btn.querySelector('i');
var label = btn.querySelector('.subscribe-label');
if (icon) icon.className = 'bi ' + (nowSub ? 'bi-bell-fill' : 'bi-person-plus-fill');
if (label) label.textContent = nowSub ? 'Subscribed' : 'Subscribe';
document.querySelectorAll('.channel-subs[data-channel-id="' + channelId + '"]').forEach(function (el) {
var n = parseInt(el.dataset.count || 0);
el.dataset.count = nowSub ? n + 1 : Math.max(0, n - 1);
el.innerHTML = '<i class="bi bi-people-fill"></i> ' + Number(el.dataset.count).toLocaleString() + ' ' + (el.dataset.count == 1 ? 'subscriber' : 'subscribers');
});
fetch(url, {
method: 'POST',
headers: { 'X-CSRF-TOKEN': csrfToken, 'Accept': 'application/json' },
credentials: 'same-origin',
})
.then(function (r) { if (!r.ok) throw new Error(r.status); return r.json(); })
.then(function (data) {
btn.dataset.subscribed = data.subscribed ? 'true' : 'false';
btn.classList.toggle('subscribed-ch', data.subscribed);
if (icon) icon.className = 'bi ' + (data.subscribed ? 'bi-bell-fill' : 'bi-person-plus-fill');
if (label) label.textContent = data.subscribed ? 'Subscribed' : 'Subscribe';
document.querySelectorAll('.channel-subs[data-channel-id="' + channelId + '"]').forEach(function (el) {
el.dataset.count = data.subscriber_count;
el.innerHTML = '<i class="bi bi-people-fill"></i> ' + Number(data.subscriber_count).toLocaleString() + ' ' + (data.subscriber_count == 1 ? 'subscriber' : 'subscribers');
});
})
.catch(function () {
btn.dataset.subscribed = subscribed ? 'true' : 'false';
btn.classList.toggle('subscribed-ch', subscribed);
if (icon) icon.className = 'bi ' + (subscribed ? 'bi-bell-fill' : 'bi-person-plus-fill');
if (label) label.textContent = subscribed ? 'Subscribed' : 'Subscribe';
if (typeof showToast === 'function') showToast('Could not update subscription. Please try again.', 'error');
});
});
})();
/* ── Settings avatar preview ── */
function previewSettingsAvatar(input) {
if (!input.files || !input.files[0]) return;
var reader = new FileReader();
reader.onload = function(e) {
var prev = document.getElementById('chSettingsAvatarPreview');
if (prev) prev.src = e.target.result;
// Also update the channel header avatar
var chAvatar = document.querySelector('.ch-avatar');
if (chAvatar) chAvatar.src = e.target.result;
};
reader.readAsDataURL(input.files[0]);
}
/* ── Delete video (own channel) ── */
function deleteVideo(videoId, videoTitle) {
showConfirm('Delete "' + videoTitle + '"? This cannot be undone.', function () {
fetch('/videos/' + videoId, {
method: 'DELETE',
headers: { 'X-CSRF-TOKEN': csrfToken, 'Accept': 'application/json' }
})
.then(r => r.json())
.then(data => {
if (data.success || data.redirect) window.location.reload();
else showToast('Failed to delete video.', 'error');
})
.catch(() => showToast('Failed to delete video.', 'error'));
}, 'Delete');
}
/* ── Cropper callbacks ── */
function onAvatarSaved(url) {
var img = document.getElementById('chAvatarImg');
if (img) img.src = url + '?t=' + Date.now();
// Also update header avatar in settings tab
var prev = document.getElementById('chSettingsAvatarPreview');
if (prev) prev.src = url + '?t=' + Date.now();
}
function onBannerSaved(url) {
var img = document.getElementById('chBannerImg');
if (img) {
img.src = url + '?t=' + Date.now();
} else {
var newImg = document.createElement('img');
newImg.id = 'chBannerImg';
newImg.className = 'ch-banner-img';
newImg.alt = '';
newImg.src = url + '?t=' + Date.now();
var banner = document.getElementById('chBanner');
if (banner) banner.insertBefore(newImg, banner.firstChild);
}
}
</script>
@if($isOwner && !$preview)
<x-image-cropper
id="avatar"
:width="300"
:height="300"
shape="circle"
folder="avatars"
:filename="'avatar_' . $user->id"
callback="onAvatarSaved"
update-url="{{ route('profile.updateAvatar') }}"
title="Change Profile Photo"
output-width="600"
/>
<x-image-cropper
id="banner"
:width="500"
:height="160"
shape="square"
folder="banners"
:filename="'banner_' . $user->id"
callback="onBannerSaved"
update-url="{{ route('profile.updateBanner') }}"
title="Change Banner Image"
output-width="1500"
/>
@endif
@endsection