509 lines
24 KiB
Vue
509 lines
24 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed } from 'vue';
|
|
import { useForm, Head } from '@inertiajs/vue3';
|
|
import UserLayout from '@/layouts/user/userlayout.vue';
|
|
import { useNotifications } from '@/composables/useNotifications';
|
|
|
|
// Declare route globally for TypeScript if needed, or assume it's available
|
|
declare function route(name: string, params?: any): string;
|
|
|
|
const props = defineProps<{
|
|
user: any;
|
|
}>();
|
|
|
|
const { notify } = useNotifications();
|
|
|
|
// Helper to access global route function safely with fallback
|
|
const getRoute = (name: string, params?: any) => {
|
|
// @ts-ignore
|
|
if (typeof window.route === 'function') {
|
|
// @ts-ignore
|
|
return window.route(name, params);
|
|
}
|
|
|
|
// Fallback for known routes if Ziggy fails
|
|
if (name === 'profile.update') return '/profile/update';
|
|
if (name === 'profile.upload') return '/profile/upload';
|
|
|
|
console.error('Ziggy route function not found on window and no fallback for:', name);
|
|
return '';
|
|
};
|
|
|
|
const form = useForm({
|
|
is_public: props.user.is_public || false,
|
|
bio: props.user.bio || '',
|
|
avatar: props.user.avatar || '',
|
|
banner: props.user.banner || '',
|
|
});
|
|
|
|
const isEditingBio = ref(false);
|
|
const isEditingAvatar = ref(false);
|
|
const isEditingBanner = ref(false);
|
|
const isUploading = ref(false);
|
|
|
|
const save = () => {
|
|
const url = getRoute('profile.update');
|
|
if (!url) return;
|
|
|
|
form.post(url, {
|
|
preserveScroll: true,
|
|
onSuccess: () => {
|
|
isEditingBio.value = false;
|
|
isEditingAvatar.value = false;
|
|
isEditingBanner.value = false;
|
|
notify({ type: 'green', title: 'Saved', desc: 'Profile updated successfully.', icon: 'check' });
|
|
},
|
|
});
|
|
};
|
|
|
|
const togglePublic = () => {
|
|
form.is_public = !form.is_public;
|
|
save();
|
|
};
|
|
|
|
const handleUpload = async (event: Event, type: 'avatar' | 'banner') => {
|
|
const file = (event.target as HTMLInputElement).files?.[0];
|
|
if (!file) return;
|
|
|
|
isUploading.value = true;
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
formData.append('type', type);
|
|
|
|
try {
|
|
const url = getRoute('profile.upload');
|
|
if (!url) throw new Error('Route not found');
|
|
|
|
const res = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement)?.content || '',
|
|
'Accept': 'application/json',
|
|
},
|
|
body: formData,
|
|
});
|
|
|
|
if (!res.ok) {
|
|
let errorMsg = 'Upload failed';
|
|
try {
|
|
const errorData = await res.json();
|
|
errorMsg = errorData.message || errorMsg;
|
|
} catch {}
|
|
throw new Error(errorMsg);
|
|
}
|
|
|
|
const data = await res.json();
|
|
if (type === 'avatar') form.avatar = data.url;
|
|
else form.banner = data.url;
|
|
notify({ type: 'green', title: 'Uploaded', desc: `${type} updated successfully.`, icon: 'upload' });
|
|
|
|
// Close popover
|
|
if (type === 'avatar') isEditingAvatar.value = false;
|
|
if (type === 'banner') isEditingBanner.value = false;
|
|
} catch (e: any) {
|
|
notify({ type: 'red', title: 'Error', desc: e.message, icon: 'alert-triangle' });
|
|
} finally {
|
|
isUploading.value = false;
|
|
}
|
|
};
|
|
|
|
// Rank Logic
|
|
const vipLevelsConfig = [
|
|
{ name: 'Newbie', color: '#888888' },
|
|
{ name: 'Bronze', color: '#cd7f32' },
|
|
{ name: 'Silver', color: '#c0c0c0' },
|
|
{ name: 'Gold', color: '#ffd700' },
|
|
{ name: 'Platinum', color: '#00f2ff' },
|
|
{ name: 'Diamond', color: '#ff007a' },
|
|
{ name: 'Obsidian', color: '#ff3e3e' }
|
|
];
|
|
|
|
const currentVipStyle = computed(() => {
|
|
const level = props.user.vip_level || 0;
|
|
const idx = Math.min(Math.max(level, 0), vipLevelsConfig.length - 1);
|
|
return vipLevelsConfig[idx];
|
|
});
|
|
|
|
// Role Logic
|
|
const roleConfig = computed(() => {
|
|
const role = props.user.role || 'User';
|
|
let color = '#888';
|
|
let effectClass = '';
|
|
|
|
if (role === 'Admin') {
|
|
color = '#ff3e3e';
|
|
effectClass = 'role-admin';
|
|
} else if (role === 'Mod' || role === 'Staff') {
|
|
color = '#00f2ff';
|
|
effectClass = 'role-staff';
|
|
} else if (role === 'Streamer') {
|
|
color = '#a855f7';
|
|
effectClass = 'role-streamer';
|
|
}
|
|
|
|
return { name: role, color, effectClass };
|
|
});
|
|
|
|
// Profile URL Logic
|
|
const profileUrl = computed(() => {
|
|
return `${window.location.origin}/profile/${props.user.username}`;
|
|
});
|
|
|
|
const copyProfileUrl = () => {
|
|
navigator.clipboard.writeText(profileUrl.value);
|
|
notify({ type: 'green', title: 'Copied', desc: 'Profile URL copied to clipboard.', icon: 'copy' });
|
|
};
|
|
|
|
// Mock stats for preview
|
|
const stats = {
|
|
wagered: 12500.50,
|
|
wins: 450,
|
|
losses: 320,
|
|
favorite_game: 'Gates of Olympus',
|
|
last_played: 'Sweet Bonanza',
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<UserLayout>
|
|
<Head title="Edit Profile" />
|
|
|
|
<div class="edit-profile-page" :class="roleConfig.effectClass">
|
|
<div class="bg-fx"></div>
|
|
|
|
<!-- Banner Editor -->
|
|
<div class="banner-editor group" :style="{ backgroundImage: `url(${form.banner || '/img/default-banner.jpg'})` }">
|
|
<div class="banner-overlay"></div>
|
|
|
|
<div class="edit-trigger" @click="isEditingBanner = !isEditingBanner">
|
|
<i data-lucide="camera"></i>
|
|
<span>Edit Banner</span>
|
|
</div>
|
|
|
|
<div v-if="isEditingBanner" class="edit-popover banner-pop">
|
|
<div class="pop-tabs">
|
|
<label class="pop-tab">
|
|
<input type="file" class="hidden" accept=".jpg,.jpeg,.png,.gif,.webp,.bmp,image/jpeg,image/png,image/gif,image/webp,image/bmp" @change="handleUpload($event, 'banner')">
|
|
<i data-lucide="upload"></i> Upload
|
|
</label>
|
|
<div class="pop-divider">or</div>
|
|
<input type="text" v-model="form.banner" placeholder="Paste URL..." class="edit-input" @keyup.enter="save">
|
|
<button @click="save" class="save-btn"><i data-lucide="check"></i></button>
|
|
</div>
|
|
<p class="upload-hint">✓ JPG ✓ PNG ✓ GIF (animiert) ✓ WebP ✓ BMP · Max. <b style="color:#888">5 MB</b></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content-container">
|
|
<!-- Header Section -->
|
|
<div class="profile-header">
|
|
|
|
<!-- Avatar Editor -->
|
|
<div class="avatar-wrapper group">
|
|
<div class="avatar" :style="{ borderColor: roleConfig.name !== 'User' ? roleConfig.color : currentVipStyle.color, boxShadow: `0 0 20px ${roleConfig.name !== 'User' ? roleConfig.color : currentVipStyle.color}40` }">
|
|
<img v-if="form.avatar" :src="form.avatar" alt="Avatar">
|
|
<span v-else>{{ user.username.charAt(0) }}</span>
|
|
<div v-if="isUploading" class="upload-overlay"><span class="spinner"></span></div>
|
|
</div>
|
|
<div class="avatar-edit-overlay" @click="isEditingAvatar = !isEditingAvatar">
|
|
<i data-lucide="camera"></i>
|
|
</div>
|
|
|
|
<div v-if="isEditingAvatar" class="edit-popover avatar-pop">
|
|
<div class="pop-tabs">
|
|
<label class="pop-tab">
|
|
<input type="file" class="hidden" accept=".jpg,.jpeg,.png,.gif,.webp,.bmp,image/jpeg,image/png,image/gif,image/webp,image/bmp" @change="handleUpload($event, 'avatar')">
|
|
<i data-lucide="upload"></i>
|
|
</label>
|
|
<input type="text" v-model="form.avatar" placeholder="URL..." class="edit-input" @keyup.enter="save">
|
|
<button @click="save" class="save-btn"><i data-lucide="check"></i></button>
|
|
</div>
|
|
<p class="upload-hint">✓ JPG ✓ PNG ✓ GIF (animiert) ✓ WebP ✓ BMP · Max. <b style="color:#888">5 MB</b></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="header-info">
|
|
<div class="name-row">
|
|
<h1 class="username" :class="roleConfig.effectClass" :style="{ color: roleConfig.name !== 'User' ? roleConfig.color : '#fff' }">
|
|
<span v-if="user.clan_tag" class="clan-tag">[{{ user.clan_tag }}]</span>
|
|
{{ user.username }}
|
|
</h1>
|
|
<div class="badges">
|
|
<span class="badge vip" :style="{ color: currentVipStyle.color, borderColor: currentVipStyle.color, background: `${currentVipStyle.color}15` }">
|
|
VIP {{ user.vip_level || 0 }}
|
|
</span>
|
|
<span class="badge role" :class="roleConfig.effectClass" :style="{ color: roleConfig.color, borderColor: roleConfig.color, background: `${roleConfig.color}15` }">
|
|
{{ roleConfig.name }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bio Editor -->
|
|
<div class="bio-editor">
|
|
<div v-if="!isEditingBio" class="bio-display" @click="isEditingBio = true">
|
|
{{ form.bio || 'Click here to add a bio...' }}
|
|
<i data-lucide="edit-2" class="edit-icon"></i>
|
|
</div>
|
|
<div v-else class="bio-input-wrap">
|
|
<textarea v-model="form.bio" rows="2" class="bio-input" placeholder="Tell us about yourself..." maxlength="160"></textarea>
|
|
<div class="bio-actions">
|
|
<span class="char-count">{{ form.bio.length }}/160</span>
|
|
<button @click="save" class="bio-save">Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="header-actions">
|
|
<div class="public-toggle" @click="togglePublic">
|
|
<span class="toggle-label">Public Profile</span>
|
|
<div class="toggle-switch" :class="{ active: form.is_public }">
|
|
<div class="toggle-knob"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Copy Profile URL -->
|
|
<div class="url-copy-box" @click="copyProfileUrl">
|
|
<i data-lucide="link"></i>
|
|
<span>Copy Link</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Grid (Preview) -->
|
|
<div class="stats-grid opacity-50 pointer-events-none grayscale">
|
|
<div class="stat-card highlight">
|
|
<div class="stat-icon"><i data-lucide="coins"></i></div>
|
|
<div class="stat-data">
|
|
<div class="stat-label">Total Wagered</div>
|
|
<div class="stat-value">${{ stats.wagered.toFixed(2) }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon"><i data-lucide="trophy"></i></div>
|
|
<div class="stat-data">
|
|
<div class="stat-label">Total Wins</div>
|
|
<div class="stat-value">{{ stats.wins }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon"><i data-lucide="heart"></i></div>
|
|
<div class="stat-data">
|
|
<div class="stat-label">Favorite Game</div>
|
|
<div class="stat-value text-magenta">{{ stats.favorite_game }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon"><i data-lucide="clock"></i></div>
|
|
<div class="stat-data">
|
|
<div class="stat-label">Last Played</div>
|
|
<div class="stat-value">{{ stats.last_played }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="preview-hint">
|
|
<i data-lucide="info"></i> Stats are just a preview here.
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</UserLayout>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.edit-profile-page { min-height: 100vh; background: #050505; padding-bottom: 100px; position: relative; overflow: hidden; }
|
|
|
|
/* Role Effects & Backgrounds */
|
|
.bg-fx { position: absolute; inset: 0; pointer-events: none; z-index: 0; opacity: 0; transition: opacity 0.5s; }
|
|
|
|
.role-admin .bg-fx {
|
|
opacity: 1;
|
|
background:
|
|
radial-gradient(circle at 20% 30%, rgba(255, 62, 62, 0.08), transparent 50%),
|
|
radial-gradient(circle at 80% 70%, rgba(255, 62, 62, 0.05), transparent 50%);
|
|
animation: pulse-bg-red 5s infinite alternate;
|
|
}
|
|
.role-staff .bg-fx {
|
|
opacity: 1;
|
|
background:
|
|
radial-gradient(circle at 20% 30%, rgba(0, 242, 255, 0.08), transparent 50%),
|
|
radial-gradient(circle at 80% 70%, rgba(0, 242, 255, 0.05), transparent 50%);
|
|
animation: pulse-bg-cyan 5s infinite alternate;
|
|
}
|
|
|
|
@keyframes pulse-bg-red { 0% { opacity: 0.8; } 100% { opacity: 1; } }
|
|
@keyframes pulse-bg-cyan { 0% { opacity: 0.8; } 100% { opacity: 1; } }
|
|
|
|
/* Glitter Text */
|
|
.username.role-admin { text-shadow: 0 0 15px rgba(255, 62, 62, 0.6); animation: glitter-red 3s infinite; }
|
|
.username.role-staff { text-shadow: 0 0 15px rgba(0, 242, 255, 0.6); animation: glitter-cyan 3s infinite; }
|
|
|
|
/* Badge Glitter */
|
|
.badge.role.role-admin { box-shadow: 0 0 10px rgba(255, 62, 62, 0.4); animation: border-pulse-red 2s infinite; }
|
|
.badge.role.role-staff { box-shadow: 0 0 10px rgba(0, 242, 255, 0.4); animation: border-pulse-cyan 2s infinite; }
|
|
|
|
@keyframes glitter-red { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.3); } }
|
|
@keyframes glitter-cyan { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.3); } }
|
|
@keyframes border-pulse-red { 0%, 100% { border-color: rgba(255,62,62,0.3); } 50% { border-color: rgba(255,62,62,0.8); } }
|
|
@keyframes border-pulse-cyan { 0%, 100% { border-color: rgba(0,242,255,0.3); } 50% { border-color: rgba(0,242,255,0.8); } }
|
|
|
|
/* Banner Editor */
|
|
.banner-editor {
|
|
height: 300px; background-size: cover; background-position: center; position: relative;
|
|
border-bottom: 1px solid #222; transition: 0.3s; z-index: 1;
|
|
}
|
|
.banner-editor:hover .banner-overlay { opacity: 0.4; }
|
|
.banner-overlay { position: absolute; inset: 0; background: #000; opacity: 0.2; transition: 0.3s; }
|
|
|
|
.edit-trigger {
|
|
position: absolute; top: 20px; right: 20px; background: rgba(0,0,0,0.6); backdrop-filter: blur(10px);
|
|
padding: 10px 16px; border-radius: 12px; color: #fff; font-weight: 700; font-size: 13px;
|
|
display: flex; align-items: center; gap: 8px; cursor: pointer; border: 1px solid rgba(255,255,255,0.1);
|
|
transition: 0.2s; opacity: 0; transform: translateY(-10px);
|
|
}
|
|
.banner-editor:hover .edit-trigger { opacity: 1; transform: translateY(0); }
|
|
.edit-trigger:hover { background: rgba(255,0,122,0.8); border-color: #ff007a; }
|
|
.edit-trigger i { width: 16px; height: 16px; }
|
|
|
|
/* Edit Popover */
|
|
.edit-popover {
|
|
position: absolute; background: #111; border: 1px solid #333; padding: 8px; border-radius: 12px;
|
|
display: flex; flex-direction: column; gap: 8px; box-shadow: 0 10px 40px rgba(0,0,0,0.5); z-index: 20;
|
|
animation: popIn 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
.banner-pop { top: 70px; right: 20px; width: 320px; }
|
|
.avatar-pop { bottom: -60px; left: 50%; transform: translateX(-50%); width: 280px; }
|
|
|
|
.pop-tabs { display: flex; align-items: center; gap: 8px; }
|
|
.pop-tab {
|
|
background: #222; color: #ccc; padding: 8px 12px; border-radius: 8px; font-size: 12px; font-weight: 700;
|
|
cursor: pointer; display: flex; align-items: center; gap: 6px; transition: 0.2s;
|
|
}
|
|
.pop-tab:hover { background: #333; color: #fff; }
|
|
.pop-tab i { width: 14px; }
|
|
.pop-divider { font-size: 10px; color: #555; font-weight: 700; text-transform: uppercase; }
|
|
.upload-hint { font-size: 10px; color: #555; margin: 0; text-align: center; }
|
|
|
|
.edit-input {
|
|
flex: 1; background: #000; border: 1px solid #222; color: #fff; padding: 8px 12px; border-radius: 8px; font-size: 13px; width: 100%;
|
|
}
|
|
.edit-input:focus { border-color: #ff007a; outline: none; }
|
|
.save-btn {
|
|
background: #ff007a; color: #fff; border: none; width: 36px; height: 36px; border-radius: 8px; cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
|
|
}
|
|
.save-btn:hover { background: #d40065; }
|
|
|
|
/* Content Container */
|
|
.content-container { max-width: 1100px; margin: -80px auto 0; padding: 0 20px; position: relative; z-index: 10; }
|
|
|
|
/* Header */
|
|
.profile-header { display: flex; align-items: flex-end; gap: 30px; margin-bottom: 40px; }
|
|
|
|
/* Avatar Editor */
|
|
.avatar-wrapper { position: relative; cursor: pointer; }
|
|
.avatar {
|
|
width: 160px; height: 160px; border-radius: 50%; border: 6px solid #050505; background: #111;
|
|
overflow: hidden; display: flex; align-items: center; justify-content: center;
|
|
font-size: 48px; font-weight: 900; color: #333; box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
|
transition: 0.3s; position: relative;
|
|
}
|
|
.avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
.upload-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; }
|
|
.spinner { width: 24px; height: 24px; border: 3px solid rgba(255,255,255,0.3); border-top-color: #fff; border-radius: 50%; animation: spin 1s linear infinite; }
|
|
|
|
.avatar-edit-overlay {
|
|
position: absolute; inset: 6px; border-radius: 50%; background: rgba(0,0,0,0.6);
|
|
display: flex; align-items: center; justify-content: center; color: #fff; opacity: 0;
|
|
transition: 0.2s; backdrop-filter: blur(2px);
|
|
}
|
|
.avatar-wrapper:hover .avatar-edit-overlay { opacity: 1; }
|
|
.avatar-edit-overlay i { width: 32px; height: 32px; }
|
|
|
|
/* Info */
|
|
.header-info { flex: 1; padding-bottom: 10px; }
|
|
.name-row { display: flex; align-items: center; gap: 16px; margin-bottom: 12px; flex-wrap: wrap; }
|
|
.username { font-size: 32px; font-weight: 900; color: #fff; display: flex; align-items: center; gap: 10px; }
|
|
.clan-tag { color: inherit; opacity: 0.8; font-size: 0.6em; background: rgba(255,255,255,0.1); padding: 2px 8px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); vertical-align: middle; }
|
|
|
|
.badge.vip { font-size: 11px; font-weight: 900; padding: 4px 10px; border-radius: 6px; text-transform: uppercase; background: rgba(255,0,122,0.1); color: #ff007a; border: 1px solid rgba(255,0,122,0.2); }
|
|
.badges { display: flex; gap: 8px; }
|
|
.badge { font-size: 11px; font-weight: 900; padding: 4px 10px; border-radius: 6px; text-transform: uppercase; border: 1px solid transparent; letter-spacing: 0.5px; }
|
|
|
|
/* Bio Editor */
|
|
.bio-display {
|
|
color: #888; font-size: 15px; max-width: 600px; line-height: 1.5; cursor: pointer;
|
|
padding: 8px 12px; border-radius: 8px; border: 1px dashed transparent; transition: 0.2s;
|
|
display: inline-flex; align-items: center; gap: 8px;
|
|
}
|
|
.bio-display:hover { border-color: #333; background: #111; color: #aaa; }
|
|
.edit-icon { width: 14px; height: 14px; opacity: 0.5; }
|
|
|
|
.bio-input-wrap { max-width: 600px; background: #111; border: 1px solid #333; border-radius: 12px; padding: 12px; }
|
|
.bio-input { width: 100%; background: transparent; border: none; color: #fff; font-size: 15px; resize: none; font-family: inherit; }
|
|
.bio-actions { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; }
|
|
.char-count { font-size: 11px; color: #555; }
|
|
.bio-save { background: #ff007a; color: #fff; border: none; padding: 6px 16px; border-radius: 6px; font-size: 12px; font-weight: 700; cursor: pointer; }
|
|
.bio-save:hover { background: #d40065; }
|
|
|
|
/* Public Toggle & Copy */
|
|
.header-actions { padding-bottom: 20px; display: flex; flex-direction: column; gap: 12px; align-items: flex-end; }
|
|
.public-toggle {
|
|
display: flex; align-items: center; gap: 12px; background: #111; padding: 10px 16px;
|
|
border-radius: 12px; border: 1px solid #222; cursor: pointer; transition: 0.2s;
|
|
}
|
|
.public-toggle:hover { border-color: #333; }
|
|
.toggle-label { font-size: 13px; font-weight: 700; color: #ccc; }
|
|
.toggle-switch { width: 44px; height: 24px; background: #333; border-radius: 99px; position: relative; transition: 0.3s; }
|
|
.toggle-switch.active { background: #ff007a; }
|
|
.toggle-knob {
|
|
width: 18px; height: 18px; background: #fff; border-radius: 50%; position: absolute;
|
|
top: 3px; left: 3px; transition: 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
.toggle-switch.active .toggle-knob { transform: translateX(20px); }
|
|
|
|
.url-copy-box {
|
|
display: flex; align-items: center; gap: 8px; background: #111; padding: 8px 12px;
|
|
border-radius: 8px; border: 1px solid #222; cursor: pointer; transition: 0.2s;
|
|
color: #888; font-size: 12px; font-weight: 700;
|
|
}
|
|
.url-copy-box:hover { color: #fff; border-color: #333; }
|
|
.url-copy-box i { width: 14px; height: 14px; }
|
|
|
|
/* Stats Grid (Preview) */
|
|
.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 20px; }
|
|
.stat-card { background: #0f0f0f; border: 1px solid #1f1f1f; padding: 20px; border-radius: 16px; display: flex; align-items: center; gap: 16px; }
|
|
.stat-card.highlight { background: linear-gradient(135deg, rgba(255,0,122,0.05), transparent); border-color: rgba(255,0,122,0.2); }
|
|
.stat-icon { width: 48px; height: 48px; background: #18181b; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: #666; }
|
|
.stat-card.highlight .stat-icon { color: #ff007a; background: rgba(255,0,122,0.1); }
|
|
.stat-label { font-size: 11px; font-weight: 700; color: #666; text-transform: uppercase; margin-bottom: 4px; }
|
|
.stat-value { font-size: 18px; font-weight: 900; color: #fff; }
|
|
.text-magenta { color: #ff007a; }
|
|
|
|
.preview-hint { text-align: center; color: #444; font-size: 12px; display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 20px; }
|
|
.preview-hint i { width: 14px; }
|
|
|
|
@keyframes popIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
@media (max-width: 768px) {
|
|
.profile-header { flex-direction: column; align-items: center; text-align: center; margin-top: -60px; }
|
|
.header-info { padding-bottom: 0; width: 100%; display: flex; flex-direction: column; align-items: center; }
|
|
.name-row { justify-content: center; }
|
|
.stats-grid { grid-template-columns: 1fr 1fr; }
|
|
.banner-editor { height: 200px; }
|
|
.avatar { width: 120px; height: 120px; }
|
|
}
|
|
|
|
/* Mobile tweaks */
|
|
@media (max-width: 420px) {
|
|
.username { font-size: clamp(18px, 7vw, 24px); word-break: break-word; }
|
|
.stats-grid { grid-template-columns: 1fr 1fr; }
|
|
.banner-pop { width: min(92vw, 320px); right: 12px; }
|
|
.avatar-pop { width: min(92vw, 280px); left: 50%; transform: translateX(-50%); }
|
|
.edit-trigger { padding: 8px 12px; font-size: 12px; top: 12px; right: 12px; }
|
|
.content-container { margin-top: -50px; }
|
|
}
|
|
</style>
|