1008 lines
49 KiB
Vue
1008 lines
49 KiB
Vue
<script setup lang="ts">
|
||
import { Head, router } from '@inertiajs/vue3';
|
||
import { ref } from 'vue';
|
||
import CasinoAdminLayout from '@/layouts/admin/CasinoAdminLayout.vue';
|
||
import {
|
||
ArrowLeft, Flag, Hash, Clock, CheckCircle2, XCircle,
|
||
Ban, MessageSquareOff, AlertTriangle, History,
|
||
Calendar, Mail, Shield, Gavel, Loader2, ChevronRight,
|
||
UserRound, ShieldAlert, Star, Crown, ArrowRight, Image, ExternalLink
|
||
} from 'lucide-vue-next';
|
||
|
||
interface Restriction {
|
||
id: number;
|
||
type: 'chat_ban' | 'account_ban';
|
||
reason: string | null;
|
||
active: boolean;
|
||
starts_at: string | null;
|
||
ends_at: string | null;
|
||
created_at: string;
|
||
}
|
||
|
||
interface UserProfile {
|
||
id: number;
|
||
username: string;
|
||
email: string;
|
||
avatar: string | null;
|
||
avatar_url: string | null;
|
||
role: string;
|
||
vip_level: number;
|
||
is_banned: boolean;
|
||
created_at: string;
|
||
restrictions?: Restriction[];
|
||
}
|
||
|
||
interface SnapshotComment {
|
||
id: number;
|
||
content: string;
|
||
created_at: string;
|
||
user: { id: number; username: string; avatar: string | null };
|
||
}
|
||
|
||
interface ProfileSnapshot {
|
||
id: number;
|
||
username: string;
|
||
avatar: string | null;
|
||
banner: string | null;
|
||
bio: string | null;
|
||
role: string;
|
||
vip_level: number;
|
||
clan_tag: string | null;
|
||
stats: Record<string, any>;
|
||
best_wins: any[];
|
||
comments: SnapshotComment[];
|
||
captured_at: string;
|
||
}
|
||
|
||
interface CurrentProfile {
|
||
id: number;
|
||
username: string;
|
||
avatar: string | null;
|
||
banner: string | null;
|
||
bio: string | null;
|
||
role: string;
|
||
vip_level: number;
|
||
clan_tag: string | null;
|
||
stats: Record<string, any>;
|
||
comments: SnapshotComment[];
|
||
}
|
||
|
||
interface Report {
|
||
id: number;
|
||
reporter_id: number;
|
||
profile_id: number;
|
||
reason: string;
|
||
details: string | null;
|
||
snapshot: ProfileSnapshot | null;
|
||
screenshot_path: string | null;
|
||
status: 'pending' | 'reviewed' | 'dismissed';
|
||
admin_note: string | null;
|
||
created_at: string;
|
||
}
|
||
|
||
const props = defineProps<{
|
||
report: Report;
|
||
reporterUser: UserProfile | null;
|
||
profileUser: UserProfile | null;
|
||
screenshotUrl: string | null;
|
||
currentProfile: CurrentProfile | null;
|
||
flash?: string | null;
|
||
}>();
|
||
|
||
// ── Restriction management ────────────────────────────────────
|
||
const extendHours = ref<Record<number, number>>({});
|
||
const flashMsg = ref(props.flash || '');
|
||
|
||
function liftRestriction(id: number) {
|
||
router.post(`/admin/restrictions/${id}/lift`, {}, {
|
||
preserveScroll: true,
|
||
onSuccess: () => { flashMsg.value = 'Sperre wurde aufgehoben.'; },
|
||
});
|
||
}
|
||
function extendRestriction(id: number) {
|
||
const h = extendHours.value[id];
|
||
if (!h || h < 1) return;
|
||
router.post(`/admin/restrictions/${id}/extend`, { hours: h }, {
|
||
preserveScroll: true,
|
||
onSuccess: () => { flashMsg.value = `Sperre um ${h}h verlängert.`; extendHours.value[id] = 0; },
|
||
});
|
||
}
|
||
|
||
// ── Punishment ────────────────────────────────────────────────
|
||
const punishType = ref<'chat_ban' | 'account_ban'>('chat_ban');
|
||
const punishReason = ref('');
|
||
const punishHours = ref<number | null>(null);
|
||
const submitting = ref(false);
|
||
|
||
const chatTemplates = [
|
||
{ label: '1 Tag', hours: 24, reason: 'Spam' },
|
||
{ label: '3 Tage', hours: 72, reason: 'Beleidigung' },
|
||
{ label: '7 Tage', hours: 168, reason: 'Belästigung' },
|
||
{ label: '30 Tage', hours: 720, reason: 'Schwerer Verstoß' },
|
||
{ label: 'Permanent',hours: null, reason: 'Dauerhafter Ausschluss' },
|
||
];
|
||
const banTemplates = [
|
||
{ label: '1 Tag', hours: 24, reason: 'Betrug / Scam' },
|
||
{ label: '7 Tage', hours: 168, reason: 'Schwerer Verstoß' },
|
||
{ label: '30 Tage', hours: 720, reason: 'Wiederholte Verstöße' },
|
||
{ label: 'Permanent',hours: null, reason: 'Dauerhafter Ausschluss' },
|
||
];
|
||
|
||
function applyTemplate(reason: string, hours: number | null) {
|
||
punishReason.value = reason;
|
||
punishHours.value = hours;
|
||
}
|
||
|
||
function submitPunish() {
|
||
if (!punishReason.value || submitting.value) return;
|
||
submitting.value = true;
|
||
router.post(`/admin/reports/profiles/${props.report.id}/punish`, {
|
||
type: punishType.value, reason: punishReason.value, hours: punishHours.value,
|
||
}, {
|
||
preserveScroll: true,
|
||
onSuccess: () => { flashMsg.value = 'Strafe erfolgreich verhängt!'; },
|
||
onFinish: () => { submitting.value = false; },
|
||
});
|
||
}
|
||
|
||
function updateStatus(status: string) {
|
||
router.post(`/admin/reports/profiles/${props.report.id}`, { status }, { preserveScroll: true });
|
||
}
|
||
|
||
// ── Helpers ───────────────────────────────────────────────────
|
||
const statusMeta: Record<string, { color: string; bg: string; border: string; label: string }> = {
|
||
pending: { color: '#f59e0b', bg: 'rgba(245,158,11,0.12)', border: 'rgba(245,158,11,0.35)', label: 'Ausstehend' },
|
||
reviewed: { color: '#22c55e', bg: 'rgba(34,197,94,0.12)', border: 'rgba(34,197,94,0.35)', label: 'Bearbeitet' },
|
||
dismissed: { color: '#6b7280', bg: 'rgba(107,114,128,0.1)', border: 'rgba(107,114,128,0.3)', label: 'Abgelehnt' },
|
||
};
|
||
const reasonLabels: Record<string, string> = {
|
||
spam: 'Spam', harassment: 'Belästigung', inappropriate: 'Unangemessen',
|
||
fake: 'Fake', other: 'Sonstiges',
|
||
};
|
||
function rl(r: string | null) { return r ? (reasonLabels[r] ?? r) : null; }
|
||
|
||
function avUrl(u: UserProfile | null) { return u?.avatar_url || u?.avatar || null; }
|
||
function ini(name?: string | null) { return (name || '?')[0].toUpperCase(); }
|
||
|
||
function fmt(d: string | null) {
|
||
if (!d) return '–';
|
||
return new Date(d).toLocaleString('de-DE', { day:'2-digit', month:'2-digit', year:'2-digit', hour:'2-digit', minute:'2-digit' });
|
||
}
|
||
function fmtDur(h: number | null) {
|
||
if (h === null) return 'Permanent';
|
||
if (h < 24) return `${h}h`;
|
||
return `${h/24}d`;
|
||
}
|
||
function timeLeft(d: string | null) {
|
||
if (!d) return 'Permanent';
|
||
const ms = new Date(d).getTime() - Date.now();
|
||
if (ms <= 0) return 'Abgelaufen';
|
||
const days = Math.floor(ms / 86400000);
|
||
const hrs = Math.floor((ms % 86400000) / 3600000);
|
||
return days > 0 ? `${days}T ${hrs}h` : `${hrs}h`;
|
||
}
|
||
|
||
function activeR(u: UserProfile | null) {
|
||
return (u?.restrictions ?? []).filter(r => r.active && (!r.ends_at || new Date(r.ends_at) > new Date()));
|
||
}
|
||
function allR(u: UserProfile | null) { return u?.restrictions ?? []; }
|
||
</script>
|
||
|
||
<template>
|
||
<CasinoAdminLayout>
|
||
<Head :title="`Profil Case #${report.id}`" />
|
||
<template #title>
|
||
<div class="pt">
|
||
<a href="/admin/reports/profiles" class="back"><ArrowLeft :size="14" /> Profil Reports</a>
|
||
<span class="ptdiv">/</span>
|
||
<span class="pt-case"><Hash :size="12" />{{ report.id }}</span>
|
||
<span class="status-chip" :style="{ color: statusMeta[report.status].color, background: statusMeta[report.status].bg, borderColor: statusMeta[report.status].border }">
|
||
{{ statusMeta[report.status].label }}
|
||
</span>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- Flash banner -->
|
||
<transition name="fade">
|
||
<div v-if="flashMsg" class="flash">
|
||
<CheckCircle2 :size="15" />
|
||
<span>{{ flashMsg }}</span>
|
||
<button @click="flashMsg = ''"><XCircle :size="14" /></button>
|
||
</div>
|
||
</transition>
|
||
|
||
<!-- Case summary bar -->
|
||
<div class="summary-bar">
|
||
<div class="sb-item">
|
||
<span class="sb-label">Melder</span>
|
||
<span class="sb-val blue">@{{ reporterUser?.username || '–' }}</span>
|
||
</div>
|
||
<ArrowRight :size="14" class="sb-arrow" />
|
||
<div class="sb-item">
|
||
<span class="sb-label">Gemeldet</span>
|
||
<span class="sb-val pink">@{{ profileUser?.username || '–' }}</span>
|
||
</div>
|
||
<div class="sb-sep" />
|
||
<div class="sb-item" v-if="rl(report.reason)">
|
||
<span class="sb-label">Grund</span>
|
||
<span class="sb-val"><Flag :size="11" /> {{ rl(report.reason) }}</span>
|
||
</div>
|
||
<div class="sb-item">
|
||
<span class="sb-label">Datum</span>
|
||
<span class="sb-val">{{ fmt(report.created_at) }}</span>
|
||
</div>
|
||
<div class="sb-status-btns">
|
||
<button :class="['ssb', { active: report.status === 'reviewed' }]" @click="updateStatus('reviewed')">
|
||
<CheckCircle2 :size="12" /> Erledigt
|
||
</button>
|
||
<button :class="['ssb dismiss', { active: report.status === 'dismissed' }]" @click="updateStatus('dismissed')">
|
||
<XCircle :size="12" /> Ablehnen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main grid -->
|
||
<div class="main-grid">
|
||
|
||
<!-- ══ LEFT col ══════════════════════════════════════ -->
|
||
<div class="col-left">
|
||
|
||
<!-- Reporter -->
|
||
<div class="user-card blue-top">
|
||
<div class="uc-label blue"><UserRound :size="10" /> Melder</div>
|
||
<div class="uc-row">
|
||
<div class="uc-av blue">
|
||
<img v-if="avUrl(reporterUser)" :src="avUrl(reporterUser)!" />
|
||
<span v-else>{{ ini(reporterUser?.username) }}</span>
|
||
</div>
|
||
<div class="uc-info">
|
||
<div class="uc-name">@{{ reporterUser?.username || '–' }}</div>
|
||
<div class="uc-email"><Mail :size="9" /> {{ reporterUser?.email || '–' }}</div>
|
||
<div class="uc-since"><Calendar :size="9" /> seit {{ reporterUser?.created_at ? new Date(reporterUser.created_at).toLocaleDateString('de-DE') : '–' }}</div>
|
||
</div>
|
||
<a v-if="reporterUser" :href="`/admin/users/${reporterUser.id}`" class="uc-open" title="Admin öffnen"><ChevronRight :size="14" /></a>
|
||
</div>
|
||
<div class="uc-badges">
|
||
<span class="badge role"><Crown :size="8" /> {{ reporterUser?.role || 'user' }}</span>
|
||
<span v-if="reporterUser?.vip_level && reporterUser.vip_level > 0" class="badge vip"><Star :size="8" /> VIP {{ reporterUser.vip_level }}</span>
|
||
<span v-if="reporterUser?.is_banned" class="badge banned"><Ban :size="8" /> Gebannt</span>
|
||
<span v-if="activeR(reporterUser).some(r => r.type==='chat_ban')" class="badge cbanned"><MessageSquareOff :size="8" /> Chat-Bann</span>
|
||
</div>
|
||
<div v-if="allR(reporterUser).length" class="mini-hist">
|
||
<div class="mh-title"><History :size="9" /> Historie <span class="mh-count">{{ allR(reporterUser).length }}</span></div>
|
||
<div v-for="r in allR(reporterUser).slice(0,3)" :key="r.id" class="mh-row" :class="{ active: r.active }">
|
||
<span class="mh-type" :class="r.type==='chat_ban'?'chat':'ban'">{{ r.type==='chat_ban'?'Chat':'Acc' }}</span>
|
||
<span class="mh-reason">{{ r.reason || '–' }}</span>
|
||
<span v-if="r.active" class="mh-live">●</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="reported-divider"><Flag :size="11" class="df" /> hat gemeldet</div>
|
||
|
||
<!-- Reported user -->
|
||
<div class="user-card pink-top">
|
||
<div class="uc-label pink"><ShieldAlert :size="10" /> Gemeldet</div>
|
||
<div class="uc-row">
|
||
<div class="uc-av pink">
|
||
<img v-if="avUrl(profileUser)" :src="avUrl(profileUser)!" />
|
||
<span v-else>{{ ini(profileUser?.username) }}</span>
|
||
</div>
|
||
<div class="uc-info">
|
||
<div class="uc-name">@{{ profileUser?.username || '–' }}</div>
|
||
<div class="uc-email"><Mail :size="9" /> {{ profileUser?.email || '–' }}</div>
|
||
<div v-if="profileUser" class="uc-since"><Calendar :size="9" /> seit {{ new Date(profileUser.created_at).toLocaleDateString('de-DE') }}</div>
|
||
</div>
|
||
<a v-if="profileUser" :href="`/admin/users/${profileUser.id}`" class="uc-open" title="Admin öffnen"><ChevronRight :size="14" /></a>
|
||
</div>
|
||
<div class="uc-badges">
|
||
<span v-if="profileUser" class="badge role"><Crown :size="8" /> {{ profileUser.role }}</span>
|
||
<span v-if="profileUser?.vip_level && profileUser.vip_level > 0" class="badge vip"><Star :size="8" /> VIP {{ profileUser.vip_level }}</span>
|
||
<span v-if="profileUser?.is_banned" class="badge banned"><Ban :size="8" /> Gebannt</span>
|
||
<span v-if="activeR(profileUser).some(r => r.type==='chat_ban')" class="badge cbanned"><MessageSquareOff :size="8" /> Chat-Bann</span>
|
||
</div>
|
||
|
||
<div v-if="activeR(profileUser).length" class="active-alert">
|
||
<AlertTriangle :size="13" />
|
||
<div>
|
||
<div class="aa-title">{{ activeR(profileUser).length }} aktive Sperre(n)</div>
|
||
<div class="aa-list">
|
||
<div v-for="r in activeR(profileUser)" :key="r.id" class="aa-row">
|
||
<span class="mh-type" :class="r.type==='chat_ban'?'chat':'ban'">{{ r.type==='chat_ban'?'Chat-Bann':'Acc-Bann' }}</span>
|
||
<span class="aa-until">{{ r.ends_at ? timeLeft(r.ends_at) : 'Permanent' }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="allR(profileUser).length" class="restrict-hist">
|
||
<div class="mh-title"><History :size="9" /> Sperr-Historie <span class="mh-count">{{ allR(profileUser).length }}</span></div>
|
||
<div v-for="r in allR(profileUser)" :key="r.id" class="rh-item" :class="{ active: r.active }">
|
||
<div class="rh-row">
|
||
<span class="mh-type" :class="r.type==='chat_ban'?'chat':'ban'">{{ r.type==='chat_ban'?'Chat':'Acc' }}</span>
|
||
<span class="rh-reason">{{ r.reason || '–' }}</span>
|
||
<span v-if="r.active" class="mh-live">AKTIV</span>
|
||
<span class="rh-date">{{ fmt(r.created_at) }}</span>
|
||
</div>
|
||
<div v-if="r.ends_at || r.active" class="rh-until">
|
||
<Clock :size="9" />
|
||
{{ r.ends_at ? fmt(r.ends_at) : 'Permanent' }}
|
||
<span v-if="r.active && r.ends_at" class="rh-left">{{ timeLeft(r.ends_at) }}</span>
|
||
</div>
|
||
<div v-if="r.active" class="rh-actions">
|
||
<button class="rha lift" @click="liftRestriction(r.id)">
|
||
<CheckCircle2 :size="10" /> Aufheben
|
||
</button>
|
||
<div class="rha-extend">
|
||
<input v-model.number="extendHours[r.id]" type="number" min="1" placeholder="Std." class="rha-input" />
|
||
<button class="rha extend" @click="extendRestriction(r.id)">
|
||
<Clock :size="10" /> Verlängern
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="no-hist">Keine Sperr-Historie</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══ CENTER: Evidence + Current Profile ════════════ -->
|
||
<div class="col-center">
|
||
|
||
<!-- ── EVIDENCE BLOCK ───────────────────────────── -->
|
||
<div class="block-header evidence">
|
||
<Image :size="13" />
|
||
<span>Beweise zum Zeitpunkt des Reports</span>
|
||
<span class="ct-sub">{{ fmt(report.snapshot?.captured_at ?? report.created_at) }}</span>
|
||
</div>
|
||
|
||
<!-- Screenshot image -->
|
||
<div v-if="screenshotUrl" class="screenshot-wrap">
|
||
<img :src="screenshotUrl" alt="Profil Screenshot" class="screenshot-img" />
|
||
</div>
|
||
<div v-else class="no-screenshot-hint">
|
||
<Image :size="18" /> Kein Screenshot (älterer Report)
|
||
</div>
|
||
|
||
<!-- Snapshot data -->
|
||
<div v-if="report.snapshot" class="profile-snapshot evidence-snap">
|
||
|
||
<!-- Banner -->
|
||
<div class="snap-banner" :style="report.snapshot.banner ? `background-image: url(${report.snapshot.banner})` : ''">
|
||
<div class="snap-banner-overlay"></div>
|
||
<!-- Avatar overlapping banner -->
|
||
<div class="snap-avatar-abs">
|
||
<img v-if="report.snapshot.avatar" :src="report.snapshot.avatar" class="snap-av-img" />
|
||
<div v-else class="snap-av-fallback">{{ ini(report.snapshot.username) }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Profile header info -->
|
||
<div class="snap-header">
|
||
<div class="snap-name-row">
|
||
<span class="snap-username">@{{ report.snapshot.username }}</span>
|
||
<div class="snap-badges">
|
||
<span class="snap-tag role">{{ report.snapshot.role }}</span>
|
||
<span class="snap-tag vip" v-if="report.snapshot.vip_level > 0">★ VIP {{ report.snapshot.vip_level }}</span>
|
||
<span class="snap-tag clan" v-if="report.snapshot.clan_tag">[{{ report.snapshot.clan_tag }}]</span>
|
||
</div>
|
||
</div>
|
||
<p class="snap-bio" v-if="report.snapshot.bio">{{ report.snapshot.bio }}</p>
|
||
<p class="snap-bio empty" v-else>Keine Beschreibung</p>
|
||
</div>
|
||
|
||
<!-- Stats row -->
|
||
<div class="snap-stats-row" v-if="report.snapshot.stats">
|
||
<div class="snap-stat">
|
||
<span class="snap-stat-val">{{ report.snapshot.stats.likes_count ?? 0 }}</span>
|
||
<span class="snap-stat-label">Likes</span>
|
||
</div>
|
||
<div class="snap-stat">
|
||
<span class="snap-stat-val">{{ report.snapshot.stats.wins ?? 0 }}</span>
|
||
<span class="snap-stat-label">Wins</span>
|
||
</div>
|
||
<div class="snap-stat">
|
||
<span class="snap-stat-val">{{ Number(report.snapshot.stats.wagered ?? 0).toFixed(2) }}</span>
|
||
<span class="snap-stat-label">Wagered</span>
|
||
</div>
|
||
<div class="snap-stat">
|
||
<span class="snap-stat-val">{{ Number(report.snapshot.stats.biggest_win ?? 0).toFixed(2) }}</span>
|
||
<span class="snap-stat-label">Biggest Win</span>
|
||
</div>
|
||
<div class="snap-stat" v-if="report.snapshot.stats.join_date">
|
||
<span class="snap-stat-val">{{ report.snapshot.stats.join_date }}</span>
|
||
<span class="snap-stat-label">Joined</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Best wins -->
|
||
<div v-if="report.snapshot.best_wins?.length" class="snap-section">
|
||
<div class="snap-section-title">Top Wins</div>
|
||
<div class="snap-wins">
|
||
<div v-for="(w, i) in report.snapshot.best_wins" :key="i" class="snap-win-item">
|
||
<span class="win-rank">#{{ i + 1 }}</span>
|
||
<span class="win-game">{{ w.game_name || w.game || '–' }}</span>
|
||
<span class="win-amount">{{ Number(w.payout_amount ?? w.payout ?? 0).toFixed(2) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Comments -->
|
||
<div class="snap-section">
|
||
<div class="snap-section-title">
|
||
Kommentare
|
||
<span class="snap-count">{{ report.snapshot.comments?.length ?? 0 }}</span>
|
||
</div>
|
||
<div v-if="report.snapshot.comments?.length" class="snap-comments">
|
||
<div v-for="c in report.snapshot.comments" :key="c.id" class="snap-comment">
|
||
<div class="sc-avatar">
|
||
<img v-if="c.user?.avatar" :src="c.user.avatar" class="sc-av-img" />
|
||
<span v-else class="sc-av-fallback">{{ ini(c.user?.username) }}</span>
|
||
</div>
|
||
<div class="sc-body">
|
||
<div class="sc-meta">
|
||
<span class="sc-name">@{{ c.user?.username || '?' }}</span>
|
||
<span class="sc-ts">{{ fmt(c.created_at) }}</span>
|
||
</div>
|
||
<div class="sc-text">{{ c.content }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="snap-empty">Keine Kommentare</div>
|
||
</div>
|
||
|
||
<div class="snap-captured">
|
||
Snapshot aufgenommen: {{ fmt(report.snapshot.captured_at) }}
|
||
</div>
|
||
</div>
|
||
<!-- Details text -->
|
||
<div v-if="report.details" class="details-block">
|
||
<div class="db-label">Beschreibung vom Melder</div>
|
||
<div class="db-text">{{ report.details }}</div>
|
||
</div>
|
||
|
||
<!-- ── CURRENT LIVE PROFILE BLOCK ───────────────── -->
|
||
<div class="block-header current">
|
||
<ExternalLink :size="13" />
|
||
<span>Aktuelles Profil (Live aus DB)</span>
|
||
<span class="ct-sub diff-hint">Vergleiche mit den Beweisen oben</span>
|
||
<a v-if="profileUser" :href="`/profile/${profileUser.username}`" target="_blank" class="ct-open-link">
|
||
<ExternalLink :size="12" /> Öffnen
|
||
</a>
|
||
</div>
|
||
|
||
<div v-if="currentProfile" class="profile-snapshot current-snap">
|
||
|
||
<!-- Banner -->
|
||
<div class="snap-banner" :style="currentProfile.banner ? `background-image: url(${currentProfile.banner})` : ''">
|
||
<div class="snap-banner-overlay"></div>
|
||
<div class="snap-avatar-abs">
|
||
<img v-if="currentProfile.avatar" :src="currentProfile.avatar" class="snap-av-img" />
|
||
<div v-else class="snap-av-fallback">{{ ini(currentProfile.username) }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Header -->
|
||
<div class="snap-header">
|
||
<div class="snap-name-row">
|
||
<span class="snap-username">@{{ currentProfile.username }}</span>
|
||
<div class="snap-badges">
|
||
<span class="snap-tag role">{{ currentProfile.role }}</span>
|
||
<span class="snap-tag vip" v-if="currentProfile.vip_level > 0">★ VIP {{ currentProfile.vip_level }}</span>
|
||
<span class="snap-tag clan" v-if="currentProfile.clan_tag">[{{ currentProfile.clan_tag }}]</span>
|
||
</div>
|
||
</div>
|
||
<p class="snap-bio" v-if="currentProfile.bio">{{ currentProfile.bio }}</p>
|
||
<p class="snap-bio empty" v-else>Keine Beschreibung</p>
|
||
</div>
|
||
|
||
<!-- Stats -->
|
||
<div class="snap-stats-row">
|
||
<div class="snap-stat">
|
||
<span class="snap-stat-val">{{ currentProfile.stats?.likes_count ?? 0 }}</span>
|
||
<span class="snap-stat-label">Likes</span>
|
||
</div>
|
||
<div class="snap-stat">
|
||
<span class="snap-stat-val">{{ currentProfile.stats?.wins ?? 0 }}</span>
|
||
<span class="snap-stat-label">Wins</span>
|
||
</div>
|
||
<div class="snap-stat">
|
||
<span class="snap-stat-val">{{ Number(currentProfile.stats?.wagered ?? 0).toFixed(2) }}</span>
|
||
<span class="snap-stat-label">Wagered</span>
|
||
</div>
|
||
<div class="snap-stat">
|
||
<span class="snap-stat-val">{{ Number(currentProfile.stats?.biggest_win ?? 0).toFixed(2) }}</span>
|
||
<span class="snap-stat-label">Biggest Win</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Comments -->
|
||
<div class="snap-section">
|
||
<div class="snap-section-title">
|
||
Aktuelle Kommentare
|
||
<span class="snap-count">{{ currentProfile.comments?.length ?? 0 }}</span>
|
||
</div>
|
||
<div v-if="currentProfile.comments?.length" class="snap-comments">
|
||
<div v-for="c in currentProfile.comments" :key="c.id" class="snap-comment">
|
||
<div class="sc-avatar">
|
||
<img v-if="c.user?.avatar" :src="c.user.avatar" class="sc-av-img" />
|
||
<span v-else class="sc-av-fallback">{{ ini(c.user?.username) }}</span>
|
||
</div>
|
||
<div class="sc-body">
|
||
<div class="sc-meta">
|
||
<span class="sc-name">@{{ c.user?.username || '?' }}</span>
|
||
<span class="sc-ts">{{ fmt(c.created_at) }}</span>
|
||
</div>
|
||
<div class="sc-text">{{ c.content }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="snap-empty">Keine Kommentare</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="no-screenshot">
|
||
<ExternalLink :size="24" />
|
||
<span>Profil nicht gefunden (gelöscht?)</span>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ══ RIGHT: Punishment ══════════════════════════════ -->
|
||
<div class="col-right">
|
||
<div class="pun-head">
|
||
<Gavel :size="15" class="pun-icon" />
|
||
<span>Strafe verhängen</span>
|
||
<span class="ct-sub" v-if="profileUser">@{{ profileUser.username }}</span>
|
||
<span class="ct-sub warn" v-else>Kein Nutzer verknüpft</span>
|
||
</div>
|
||
|
||
<div class="pun-form" :class="{ locked: !profileUser }">
|
||
<div class="type-row">
|
||
<button :class="['type-btn', { on: punishType==='chat_ban' }]" @click="punishType='chat_ban'">
|
||
<MessageSquareOff :size="12" /> Chat-Bann
|
||
</button>
|
||
<button :class="['type-btn ban', { on: punishType==='account_ban' }]" @click="punishType='account_ban'">
|
||
<Ban :size="12" /> Account-Bann
|
||
</button>
|
||
</div>
|
||
|
||
<div class="tpl-block">
|
||
<div class="tpl-label">Schnell-Vorlagen</div>
|
||
<div class="tpl-list">
|
||
<button
|
||
v-for="t in (punishType==='chat_ban' ? chatTemplates : banTemplates)"
|
||
:key="t.label"
|
||
class="tpl-btn"
|
||
:class="{ on: punishReason===t.reason && punishHours===t.hours, ban: punishType==='account_ban' }"
|
||
@click="applyTemplate(t.reason, t.hours)"
|
||
>
|
||
<span class="tpl-name">{{ t.label }}</span>
|
||
<span class="tpl-dur">{{ fmtDur(t.hours) }}</span>
|
||
<span class="tpl-reason">{{ t.reason }}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="or-line"><span>oder anpassen</span></div>
|
||
|
||
<div class="custom-block">
|
||
<label class="fl">
|
||
<span><Clock :size="10" /> Dauer (Stunden)</span>
|
||
<input v-model.number="punishHours" type="number" min="1" placeholder="leer = permanent" class="fi" />
|
||
</label>
|
||
<label class="fl">
|
||
<span><Shield :size="10" /> Grund</span>
|
||
<textarea v-model="punishReason" rows="2" placeholder="Begründung..." class="fi ta"></textarea>
|
||
</label>
|
||
|
||
<div v-if="punishReason" class="pun-preview">
|
||
<span class="pp-type" :class="punishType==='account_ban'?'ban':'chat'">
|
||
{{ punishType==='chat_ban' ? 'Chat-Bann' : 'Account-Bann' }}
|
||
</span>
|
||
<span class="pp-dur">{{ fmtDur(punishHours) }}</span>
|
||
<span v-if="punishHours" class="pp-until">
|
||
bis {{ new Date(Date.now() + (punishHours??0)*3600000).toLocaleDateString('de-DE') }}
|
||
</span>
|
||
</div>
|
||
|
||
<button
|
||
class="pun-btn"
|
||
:class="{ ban: punishType==='account_ban' }"
|
||
:disabled="!punishReason || !profileUser || submitting"
|
||
@click="submitPunish"
|
||
>
|
||
<Loader2 v-if="submitting" :size="13" class="spin" />
|
||
<Ban v-else-if="punishType==='account_ban'" :size="13" />
|
||
<MessageSquareOff v-else :size="13" />
|
||
{{ punishType==='chat_ban' ? 'Chat-Bann verhängen' : 'Account sperren' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /main-grid -->
|
||
</CasinoAdminLayout>
|
||
</template>
|
||
|
||
<style scoped>
|
||
/* ─ Page title ─ */
|
||
.pt { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
|
||
.back { display:flex; align-items:center; gap:4px; color:#555; font-size:12px; text-decoration:none; }
|
||
.back:hover { color:#ccc; }
|
||
.ptdiv { color:#2a2a2a; }
|
||
.pt-case { display:flex; align-items:center; gap:3px; font-weight:800; color:#fff; font-size:14px; }
|
||
.status-chip {
|
||
font-size:10px; font-weight:800; padding:3px 10px; border-radius:20px;
|
||
border:1px solid; text-transform:uppercase; letter-spacing:.5px;
|
||
}
|
||
|
||
/* ─ Flash ─ */
|
||
.flash {
|
||
display:flex; align-items:center; gap:8px; margin-bottom:16px;
|
||
background:rgba(34,197,94,0.1); border:1px solid rgba(34,197,94,0.3);
|
||
color:#22c55e; padding:10px 14px; border-radius:10px; font-size:13px; font-weight:600;
|
||
}
|
||
.flash button { margin-left:auto; background:none; border:none; color:inherit; cursor:pointer; display:flex; }
|
||
.fade-enter-active,.fade-leave-active { transition:opacity .3s; }
|
||
.fade-enter-from,.fade-leave-to { opacity:0; }
|
||
|
||
/* ─ Summary bar ─ */
|
||
.summary-bar {
|
||
display:flex; align-items:center; flex-wrap:wrap;
|
||
background:#111113; border:1px solid #1e1e21; border-radius:12px;
|
||
padding:12px 18px; margin-bottom:18px; gap:16px;
|
||
}
|
||
.sb-item { display:flex; flex-direction:column; gap:2px; }
|
||
.sb-label { font-size:9px; font-weight:700; text-transform:uppercase; letter-spacing:.6px; color:#444; }
|
||
.sb-val { font-size:12px; font-weight:700; color:#ccc; display:flex; align-items:center; gap:4px; }
|
||
.sb-val.blue { color:#60a5fa; }
|
||
.sb-val.pink { color:#f472b6; }
|
||
.sb-arrow { color:#333; flex-shrink:0; }
|
||
.sb-sep { width:1px; height:28px; background:#1e1e21; }
|
||
.sb-status-btns { margin-left:auto; display:flex; gap:6px; }
|
||
.ssb {
|
||
display:flex; align-items:center; gap:5px; padding:6px 12px;
|
||
border-radius:7px; border:1px solid #252528; background:#161618;
|
||
color:#555; font-size:11px; font-weight:700; cursor:pointer; transition:.15s;
|
||
}
|
||
.ssb:hover { border-color:#3a3a3f; color:#aaa; }
|
||
.ssb.active { border-color:rgba(34,197,94,0.4); background:rgba(34,197,94,0.08); color:#22c55e; }
|
||
.ssb.dismiss.active { border-color:rgba(107,114,128,0.4); background:rgba(107,114,128,0.08); color:#6b7280; }
|
||
|
||
/* ─ Main grid ─ */
|
||
.main-grid {
|
||
display:grid;
|
||
grid-template-columns: 270px 1fr 290px;
|
||
gap:16px;
|
||
align-items:start;
|
||
}
|
||
@media(max-width:1300px){ .main-grid { grid-template-columns: 250px 1fr 270px; } }
|
||
@media(max-width:1000px){ .main-grid { grid-template-columns:1fr; } }
|
||
|
||
/* ─ User cards ─ */
|
||
.col-left { display:flex; flex-direction:column; gap:8px; }
|
||
.user-card {
|
||
background:#111113; border:1px solid #1e1e21; border-radius:12px; padding:14px;
|
||
display:flex; flex-direction:column; gap:9px; position:relative;
|
||
}
|
||
.blue-top { border-top:2px solid #3b82f6; }
|
||
.pink-top { border-top:2px solid #df006a; }
|
||
|
||
.uc-label {
|
||
font-size:9px; font-weight:800; text-transform:uppercase; letter-spacing:.6px;
|
||
display:flex; align-items:center; gap:3px; padding:2px 8px; border-radius:20px;
|
||
width:fit-content; border:1px solid;
|
||
}
|
||
.uc-label.blue { color:#3b82f6; background:rgba(59,130,246,.08); border-color:rgba(59,130,246,.2); }
|
||
.uc-label.pink { color:#df006a; background:rgba(223,0,106,.08); border-color:rgba(223,0,106,.2); }
|
||
|
||
.uc-row { display:flex; align-items:center; gap:10px; }
|
||
.uc-av {
|
||
width:40px; height:40px; border-radius:10px; flex-shrink:0;
|
||
display:flex; align-items:center; justify-content:center;
|
||
font-size:16px; font-weight:900; overflow:hidden;
|
||
}
|
||
.uc-av img { width:100%; height:100%; object-fit:cover; }
|
||
.uc-av.blue { background:rgba(59,130,246,.15); color:#3b82f6; }
|
||
.uc-av.pink { background:rgba(223,0,106,.15); color:#df006a; }
|
||
.uc-info { flex:1; min-width:0; }
|
||
.uc-name { font-size:13px; font-weight:800; color:#e0e0e0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||
.uc-email, .uc-since { display:flex; align-items:center; gap:4px; font-size:10px; color:#555; }
|
||
.uc-open {
|
||
color:#666; background:#161618; border:1px solid #252528; border-radius:7px;
|
||
padding:5px; display:flex; cursor:pointer; text-decoration:none; transition:.15s; flex-shrink:0;
|
||
}
|
||
.uc-open:hover { color:#ccc; border-color:#3a3a3f; }
|
||
|
||
.uc-badges { display:flex; gap:5px; flex-wrap:wrap; }
|
||
.badge {
|
||
display:flex; align-items:center; gap:3px;
|
||
font-size:9px; font-weight:800; padding:2px 6px; border-radius:5px; border:1px solid;
|
||
text-transform:uppercase;
|
||
}
|
||
.badge.role { color:#555; border-color:#252528; background:#161618; }
|
||
.badge.vip { color:#fcd34d; border-color:rgba(252,211,77,.25); background:rgba(252,211,77,.06); }
|
||
.badge.banned { color:#ef4444; border-color:rgba(239,68,68,.3); background:rgba(239,68,68,.08); }
|
||
.badge.cbanned { color:#f97316; border-color:rgba(249,115,22,.3); background:rgba(249,115,22,.08); }
|
||
|
||
.reported-divider {
|
||
display:flex; align-items:center; justify-content:center; gap:6px;
|
||
color:#444; font-size:11px; padding:2px 0;
|
||
}
|
||
.df { color:#df006a; }
|
||
|
||
/* Mini history (reporter) */
|
||
.mini-hist { border-top:1px solid #1a1a1c; padding-top:8px; }
|
||
.mh-title { display:flex; align-items:center; gap:4px; font-size:9px; font-weight:700; text-transform:uppercase; color:#444; letter-spacing:.5px; margin-bottom:5px; }
|
||
.mh-count { margin-left:auto; background:#1a1a1c; border:1px solid #252528; border-radius:8px; padding:0 5px; color:#666; font-size:9px; }
|
||
.mh-row { display:flex; align-items:center; gap:6px; padding:4px 7px; border-radius:5px; background:#0e0e10; border:1px solid #1a1a1c; margin-bottom:3px; font-size:10px; }
|
||
.mh-row.active { border-color:rgba(239,68,68,.2); background:rgba(239,68,68,.04); }
|
||
.mh-type { font-size:9px; font-weight:800; padding:1px 5px; border-radius:3px; }
|
||
.mh-type.chat { color:#f97316; background:rgba(249,115,22,.1); }
|
||
.mh-type.ban { color:#ef4444; background:rgba(239,68,68,.1); }
|
||
.mh-reason { flex:1; color:#666; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||
.mh-live { color:#ef4444; font-size:12px; }
|
||
|
||
/* Active alert */
|
||
.active-alert {
|
||
display:flex; align-items:flex-start; gap:8px;
|
||
background:rgba(239,68,68,.06); border:1px solid rgba(239,68,68,.2);
|
||
border-radius:8px; padding:8px 10px; color:#ef4444; font-size:12px; font-weight:600;
|
||
}
|
||
.aa-title { font-size:11px; font-weight:700; color:#ef4444; margin-bottom:4px; }
|
||
.aa-list { display:flex; flex-direction:column; gap:3px; }
|
||
.aa-row { display:flex; align-items:center; justify-content:space-between; gap:8px; }
|
||
.aa-until { font-size:10px; color:#888; font-weight:500; }
|
||
|
||
/* Full restriction history */
|
||
.restrict-hist { border-top:1px solid #1a1a1c; padding-top:8px; }
|
||
.rh-item {
|
||
border:1px solid #1a1a1c; border-radius:8px; padding:7px 9px;
|
||
margin-bottom:5px; display:flex; flex-direction:column; gap:4px;
|
||
background:#0e0e10;
|
||
}
|
||
.rh-item.active { border-color:rgba(239,68,68,.25); background:rgba(239,68,68,.03); }
|
||
.rh-row { display:flex; align-items:center; gap:6px; font-size:11px; }
|
||
.rh-reason { flex:1; color:#777; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||
.rh-date { color:#444; font-size:10px; white-space:nowrap; }
|
||
.rh-until { display:flex; align-items:center; gap:4px; font-size:10px; color:#555; }
|
||
.rh-left { color:#f97316; font-weight:700; }
|
||
.no-hist { font-size:11px; color:#444; font-style:italic; padding-top:4px; }
|
||
|
||
.rh-actions { display:flex; align-items:center; gap:6px; padding-top:2px; }
|
||
.rha {
|
||
display:flex; align-items:center; gap:4px; font-size:10px; font-weight:700;
|
||
padding:3px 8px; border-radius:5px; cursor:pointer; border:1px solid; background:transparent;
|
||
}
|
||
.rha.lift { color:#22c55e; border-color:rgba(34,197,94,.3); }
|
||
.rha.lift:hover { background:rgba(34,197,94,.1); }
|
||
.rha.extend { color:#60a5fa; border-color:rgba(96,165,250,.3); }
|
||
.rha.extend:hover { background:rgba(96,165,250,.1); }
|
||
.rha-extend { display:flex; align-items:center; gap:4px; margin-left:auto; }
|
||
.rha-input {
|
||
width:56px; background:#0e0e10; border:1px solid #252528; border-radius:5px;
|
||
color:#ccc; font-size:10px; padding:3px 6px; outline:none;
|
||
}
|
||
|
||
/* ─ Center: Evidence + Current ─ */
|
||
.col-center { display:flex; flex-direction:column; gap:12px; }
|
||
|
||
.block-header {
|
||
display:flex; align-items:center; gap:8px;
|
||
border-radius:10px; padding:10px 14px;
|
||
font-size:12px; font-weight:700; color:#ccc;
|
||
}
|
||
.block-header.evidence {
|
||
background:rgba(245,158,11,0.06); border:1px solid rgba(245,158,11,0.2);
|
||
color:#f59e0b;
|
||
}
|
||
.block-header.current {
|
||
background:rgba(34,197,94,0.06); border:1px solid rgba(34,197,94,0.2);
|
||
color:#22c55e; margin-top:8px;
|
||
}
|
||
.ct-sub { font-size:10px; color:#666; font-weight:500; }
|
||
.diff-hint { color:#555; }
|
||
.ct-open-link {
|
||
margin-left:auto; display:flex; align-items:center; gap:4px;
|
||
color:#888; font-size:11px; text-decoration:none; font-weight:600;
|
||
}
|
||
.ct-open-link:hover { color:#fff; }
|
||
|
||
.no-screenshot-hint {
|
||
display:flex; align-items:center; gap:8px;
|
||
color:#444; font-size:12px; padding:10px 14px;
|
||
background:#111113; border:1px solid #1e1e21; border-radius:10px;
|
||
}
|
||
|
||
.screenshot-wrap {
|
||
background:#0a0a0c; border:1px solid #1a1a1c; border-radius:12px;
|
||
overflow:hidden; position:relative;
|
||
}
|
||
.screenshot-img {
|
||
width:100%; display:block;
|
||
border-radius:12px;
|
||
}
|
||
|
||
/* ─ Profile data snapshot ─ */
|
||
.profile-snapshot {
|
||
background:#111113; border:1px solid #1e1e21; border-radius:12px;
|
||
overflow:hidden; display:flex; flex-direction:column;
|
||
}
|
||
.evidence-snap { border-color:rgba(245,158,11,0.2); }
|
||
.current-snap { border-color:rgba(34,197,94,0.2); }
|
||
|
||
.snap-banner {
|
||
height:120px; background:#0a0a0a; background-size:cover; background-position:center;
|
||
position:relative; flex-shrink:0;
|
||
}
|
||
.snap-banner-overlay {
|
||
position:absolute; inset:0;
|
||
background:linear-gradient(to bottom, transparent 30%, #111113 100%);
|
||
}
|
||
.snap-avatar-abs {
|
||
position:absolute; bottom:-28px; left:18px; z-index:2;
|
||
}
|
||
.snap-av-img {
|
||
width:60px; height:60px; border-radius:12px; object-fit:cover;
|
||
border:3px solid #111113;
|
||
}
|
||
.snap-av-fallback {
|
||
width:60px; height:60px; border-radius:12px; background:#1a1a1a;
|
||
border:3px solid #111113; display:flex; align-items:center; justify-content:center;
|
||
font-size:22px; font-weight:900; color:#444;
|
||
}
|
||
|
||
.snap-header { padding:38px 18px 14px; border-bottom:1px solid #1e1e21; }
|
||
.snap-name-row { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:6px; }
|
||
.snap-username { font-size:16px; font-weight:900; color:#fff; }
|
||
.snap-badges { display:flex; gap:5px; flex-wrap:wrap; }
|
||
.snap-tag { font-size:10px; font-weight:700; padding:2px 8px; border-radius:20px; border:1px solid; }
|
||
.snap-tag.role { color:#888; border-color:#333; background:#1a1a1a; }
|
||
.snap-tag.vip { color:#ffd700; border-color:rgba(255,215,0,.3); background:rgba(255,215,0,.05); }
|
||
.snap-tag.clan { color:#00f2ff; border-color:rgba(0,242,255,.3); background:rgba(0,242,255,.05); }
|
||
.snap-bio { font-size:13px; color:#888; line-height:1.55; margin:0; }
|
||
.snap-bio.empty { color:#444; font-style:italic; }
|
||
|
||
.snap-stats-row {
|
||
display:flex; gap:0; border-bottom:1px solid #1e1e21;
|
||
}
|
||
.snap-stat {
|
||
flex:1; display:flex; flex-direction:column; align-items:center;
|
||
padding:12px 8px; border-right:1px solid #1e1e21;
|
||
}
|
||
.snap-stat:last-child { border-right:none; }
|
||
.snap-stat-val { font-size:13px; font-weight:800; color:#fff; }
|
||
.snap-stat-label { font-size:9px; color:#555; text-transform:uppercase; letter-spacing:.5px; margin-top:2px; }
|
||
|
||
.snap-section { padding:14px 18px; border-bottom:1px solid #1e1e21; }
|
||
.snap-section-title {
|
||
display:flex; align-items:center; gap:6px;
|
||
font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.6px; color:#444;
|
||
margin-bottom:10px;
|
||
}
|
||
.snap-count {
|
||
background:#1a1a1c; border:1px solid #252528; border-radius:8px;
|
||
padding:0 6px; color:#666; font-size:9px;
|
||
}
|
||
.snap-empty { font-size:12px; color:#444; font-style:italic; }
|
||
|
||
.snap-wins { display:flex; flex-direction:column; gap:5px; }
|
||
.snap-win-item {
|
||
display:flex; align-items:center; gap:10px; padding:7px 10px;
|
||
background:#0e0e10; border:1px solid #1a1a1c; border-radius:8px; font-size:12px;
|
||
}
|
||
.win-rank { color:#555; font-weight:700; min-width:20px; }
|
||
.win-game { flex:1; color:#aaa; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||
.win-amount { color:#22c55e; font-weight:800; }
|
||
|
||
.snap-comments { display:flex; flex-direction:column; gap:8px; }
|
||
.snap-comment { display:flex; gap:10px; align-items:flex-start; }
|
||
.sc-avatar { flex-shrink:0; }
|
||
.sc-av-img { width:28px; height:28px; border-radius:8px; object-fit:cover; border:1px solid #222; }
|
||
.sc-av-fallback {
|
||
width:28px; height:28px; border-radius:8px; background:#1a1a1a; border:1px solid #1e1e21;
|
||
display:flex; align-items:center; justify-content:center; font-size:10px; font-weight:900; color:#555;
|
||
}
|
||
.sc-body { flex:1; min-width:0; }
|
||
.sc-meta { display:flex; align-items:center; gap:8px; margin-bottom:3px; }
|
||
.sc-name { font-size:11px; font-weight:700; color:#ccc; }
|
||
.sc-ts { font-size:10px; color:#444; }
|
||
.sc-text { font-size:12px; color:#888; line-height:1.5; word-break:break-word; }
|
||
|
||
.snap-captured { padding:10px 18px; font-size:10px; color:#333; }
|
||
|
||
.no-screenshot {
|
||
display:flex; flex-direction:column; align-items:center; justify-content:center;
|
||
gap:12px; padding:48px 24px; color:#333;
|
||
background:#111113; border:1px solid #1e1e21; border-radius:12px;
|
||
font-size:13px;
|
||
}
|
||
|
||
.details-block {
|
||
background:#111113; border:1px solid #1e1e21; border-radius:10px; padding:12px 14px;
|
||
}
|
||
.db-label { font-size:10px; color:#555; text-transform:uppercase; letter-spacing:.5px; font-weight:700; margin-bottom:6px; }
|
||
.db-text { font-size:13px; color:#999; line-height:1.6; }
|
||
|
||
/* ─ Right: Punishment ─ */
|
||
.col-right { display:flex; flex-direction:column; gap:0; }
|
||
|
||
.pun-head {
|
||
display:flex; align-items:center; gap:8px;
|
||
background:#111113; border:1px solid #1e1e21; border-radius:10px 10px 0 0;
|
||
border-bottom:none; padding:10px 14px; font-size:12px; font-weight:700; color:#ccc;
|
||
}
|
||
.pun-icon { color:#f59e0b; flex-shrink:0; }
|
||
.ct-sub.warn { color:#ef4444; }
|
||
|
||
.pun-form {
|
||
background:#111113; border:1px solid #1e1e21; border-radius:0 0 12px 12px;
|
||
padding:14px; display:flex; flex-direction:column; gap:12px;
|
||
}
|
||
.pun-form.locked { opacity:.5; pointer-events:none; }
|
||
|
||
.type-row { display:flex; gap:6px; }
|
||
.type-btn {
|
||
flex:1; display:flex; align-items:center; justify-content:center; gap:5px;
|
||
padding:7px; border-radius:7px; border:1px solid #252528; background:#161618;
|
||
color:#555; font-size:11px; font-weight:700; cursor:pointer; transition:.15s;
|
||
}
|
||
.type-btn.on { border-color:rgba(249,115,22,.4); background:rgba(249,115,22,.08); color:#f97316; }
|
||
.type-btn.ban.on { border-color:rgba(239,68,68,.4); background:rgba(239,68,68,.08); color:#ef4444; }
|
||
.type-btn:not(.on):hover { border-color:#3a3a3f; color:#aaa; }
|
||
|
||
.tpl-block { display:flex; flex-direction:column; gap:6px; }
|
||
.tpl-label { font-size:9px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; color:#444; }
|
||
.tpl-list { display:flex; flex-direction:column; gap:4px; }
|
||
.tpl-btn {
|
||
display:grid; grid-template-columns:auto 1fr auto;
|
||
align-items:center; gap:8px; padding:7px 10px;
|
||
border-radius:7px; border:1px solid #1a1a1c; background:#0e0e10;
|
||
cursor:pointer; text-align:left; transition:.15s;
|
||
}
|
||
.tpl-btn:hover { border-color:#2a2a2e; background:#111113; }
|
||
.tpl-btn.on { border-color:rgba(249,115,22,.3); background:rgba(249,115,22,.06); }
|
||
.tpl-btn.ban.on { border-color:rgba(239,68,68,.3); background:rgba(239,68,68,.06); }
|
||
.tpl-name { font-size:11px; font-weight:800; color:#ccc; }
|
||
.tpl-dur { font-size:10px; color:#555; text-align:right; }
|
||
.tpl-reason { grid-column:1/-1; font-size:10px; color:#555; font-style:italic; margin-top:-4px; }
|
||
|
||
.or-line {
|
||
display:flex; align-items:center; gap:8px; color:#333; font-size:10px;
|
||
}
|
||
.or-line::before,.or-line::after { content:''; flex:1; height:1px; background:#1a1a1c; }
|
||
|
||
.custom-block { display:flex; flex-direction:column; gap:8px; }
|
||
.fl { display:flex; flex-direction:column; gap:4px; font-size:10px; color:#555; font-weight:600; }
|
||
.fi {
|
||
background:#0e0e10; border:1px solid #1e1e21; border-radius:7px;
|
||
color:#ccc; font-size:12px; padding:7px 10px; outline:none; transition:.15s;
|
||
font-family:inherit;
|
||
}
|
||
.fi:focus { border-color:#3a3a3f; }
|
||
.ta { resize:vertical; min-height:56px; }
|
||
|
||
.pun-preview {
|
||
display:flex; align-items:center; gap:8px; padding:8px 10px;
|
||
background:#0e0e10; border:1px solid #1a1a1c; border-radius:7px; flex-wrap:wrap;
|
||
}
|
||
.pp-type { font-size:10px; font-weight:800; padding:2px 7px; border-radius:4px; }
|
||
.pp-type.chat { color:#f97316; background:rgba(249,115,22,.1); }
|
||
.pp-type.ban { color:#ef4444; background:rgba(239,68,68,.1); }
|
||
.pp-dur { font-size:11px; font-weight:700; color:#ccc; }
|
||
.pp-until { font-size:10px; color:#555; }
|
||
|
||
.pun-btn {
|
||
display:flex; align-items:center; justify-content:center; gap:7px;
|
||
width:100%; padding:10px; border-radius:8px;
|
||
border:1px solid rgba(249,115,22,.4); background:rgba(249,115,22,.1); color:#f97316;
|
||
font-size:12px; font-weight:800; cursor:pointer; transition:.2s;
|
||
}
|
||
.pun-btn:hover:not(:disabled) { background:rgba(249,115,22,.2); }
|
||
.pun-btn.ban { border-color:rgba(239,68,68,.4); background:rgba(239,68,68,.1); color:#ef4444; }
|
||
.pun-btn.ban:hover:not(:disabled) { background:rgba(239,68,68,.2); }
|
||
.pun-btn:disabled { opacity:.4; cursor:default; }
|
||
.spin { animation:spin 1s linear infinite; }
|
||
@keyframes spin { to { transform:rotate(360deg); } }
|
||
</style>
|