2990 lines
152 KiB
PHP
2990 lines
152 KiB
PHP
@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 ? route('media.thumbnail', $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 ? route('media.thumbnail', $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 ? route('media.thumbnail', $v->thumbnail) : '' }}"
|
||
onclick="togglePickerCard(this)">
|
||
<div class="ch-vp-card-thumb">
|
||
@if($v->thumbnail)
|
||
<img src="{{ route('media.thumbnail', $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
|