<template>
  <view class="page">
    <view class="header" :class="{ 'header--sk': pageLoading }">
      <image class="header-bg" :src="userHeaderBg" mode="aspectFill" />
      <view class="header-mask" />
      <view :style="{ paddingTop: statusBarHeight + 'px' }" class="header-body">
        <view class="settings-tr" :style="settingsTrackStyle">
          <view class="settings-btn" @click="onSettingsTap">
            <image
              class="settings-ico-img"
              :src="settingsIcon"
              mode="aspectFit"
            />
          </view>
        </view>
        <view v-if="pageLoading" class="header-skeleton">
          <view class="user-sk-avatar user-sk-breathe" />
          <view class="user-sk-stats">
            <view class="user-sk-stat-col">
              <view class="user-sk-stat-num user-sk-breathe" />
              <view class="user-sk-stat-lb user-sk-breathe user-sk-breathe--d1" />
            </view>
            <view class="user-sk-stats-div" />
            <view class="user-sk-stat-col">
              <view class="user-sk-stat-num user-sk-breathe user-sk-breathe--d1" />
              <view class="user-sk-stat-lb user-sk-breathe user-sk-breathe--d2" />
            </view>
          </view>
        </view>
        <template v-else>
          <view class="profile" @click="goUserProfile">
            <view class="big-avatar">
              <image
                v-if="profileAvatarSrc && !avatarLoadFailed"
                class="big-avatar-img"
                :src="profileAvatarSrc"
                mode="aspectFill"
                @error="onProfileAvatarError"
              />
              <text v-else class="big-av-txt">{{
                profileAvatarLetter(profile.nickname)
              }}</text>
            </view>
            <text class="nickname">{{ profile.nickname || "用户" }}</text>
            <text class="bio">{{ profile.bio || "完善资料，让更多人认识你" }}</text>
          </view>
          <view class="stats-card">
            <view class="stat">
              <text class="stat-num">{{ userStats.photos }}</text>
              <text class="stat-label">作品</text>
            </view>
            <view class="stat-div" />
            <view class="stat">
              <text class="stat-num">{{ userStats.likes }}</text>
              <text class="stat-label">获赞</text>
            </view>
          </view>
        </template>
      </view>
      <view class="wave-fake" />
    </view>

    <view class="sheet">
      <view v-if="pageLoading" class="sheet-skeleton">
        <view class="user-sk-tabs">
          <view class="user-sk-tab user-sk-tab--on user-sk-breathe" />
          <view class="user-sk-tab user-sk-breathe user-sk-breathe--d1" />
        </view>
        <view class="user-sk-panel-hd">
          <view class="user-sk-ph-title user-sk-breathe" />
          <view class="user-sk-ph-more user-sk-breathe user-sk-breathe--d1" />
        </view>
        <view class="user-sk-grid">
          <view
            v-for="n in 4"
            :key="'usk' + n"
            class="user-sk-thumb-wrap"
          >
            <view
              class="user-sk-thumb-inner user-sk-breathe"
              :class="'user-sk-breathe--d' + (n % 3)"
            />
          </view>
        </view>
        <view class="user-sk-menu">
          <view v-for="n in 3" :key="'um' + n" class="user-sk-menu-row">
            <view class="user-sk-menu-ico user-sk-breathe" />
            <view class="user-sk-menu-lines">
              <view class="user-sk-menu-line user-sk-breathe user-sk-breathe--d1" />
            <view
              v-if="n <= 2"
              class="user-sk-menu-line user-sk-menu-line--sub user-sk-breathe user-sk-breathe--d2"
            />
            </view>
            <view class="user-sk-menu-tag user-sk-breathe" />
          </view>
        </view>
      </view>
      <template v-else>
      <view class="tabs">
        <view
          :class="['tab', activeTab === 'photos' ? 'tab-on' : '']"
          @click="activeTab = 'photos'"
        >
          <text>我的作品</text>
        </view>
        <view
          :class="['tab', activeTab === 'certificates' ? 'tab-on' : '']"
          @click="activeTab = 'certificates'"
        >
          <text>电子证书</text>
        </view>
      </view>

      <view v-if="activeTab === 'photos'" class="panel">
        <view class="panel-hd">
          <text class="panel-title">我的作品</text>
          <text class="panel-more" @click="goMyWorksList">查看更多 →</text>
        </view>
        <view class="grid-4">
          <view
            v-for="p in userPhotos"
            :key="p.id"
            class="thumb"
            @click="openUserWork(p.id)"
          >
            <image class="thumb-img" :src="p.url" mode="aspectFill" />
            <view class="thumb-badge">
              <text
                :class="[
                  'badge',
                  p.status === 'approved'
                    ? 'badge-ok'
                    : p.status === 'rejected'
                      ? 'badge-reject'
                      : 'badge-wait',
                ]"
                >{{ workStatusShort(p.status) }}</text
              >
            </view>
            <view v-if="p.status === 'approved'" class="thumb-likes">
              <view class="thumb-likes-inner">
                <image
                  class="thumb-cang"
                  :src="p.likes > 0 ? likeOnIcon : likeOffIcon"
                  mode="aspectFit"
                />
                <text class="thumb-likes-txt">{{ p.likes }}</text>
              </view>
            </view>
          </view>
        </view>
      </view>

      <view v-else class="panel">
        <view
          v-for="c in certificates"
          :key="c.id"
          class="cert-row"
        >
          <view class="cert-icon">
            <text class="cert-star">★</text>
          </view>
          <view class="cert-mid">
            <text class="cert-name">{{ c.name }}</text>
            <text class="cert-meta">{{ c.date }} · {{ c.size }}</text>
          </view>
          <text class="cert-arrow">›</text>
        </view>
      </view>

      <view class="menu">
        <view class="menu-row" @click="handleCertificateDownload">
          <view class="menu-ico-wrap menu-ico-blue">
            <image class="menu-down-ico" :src="down1Icon" mode="aspectFit" />
          </view>
          <view class="menu-txt-wrap">
            <text class="menu-txt">下载证书</text>
            <text v-if="!certificateUnlocked" class="menu-sub"
              >需10张作品+100赞</text
            >
          </view>
          <text
            :class="[
              'menu-tag',
              certificateUnlocked ? 'menu-tag-ok' : 'menu-tag-off',
            ]"
            >{{ certificateUnlocked ? "已达成" : "未解锁" }}</text
          >
        </view>

        <view class="menu-row" @click="handleGuideDownload">
          <view class="menu-ico-wrap menu-ico-green">
            <image class="menu-down-ico" :src="down2Icon" mode="aspectFit" />
          </view>
          <view class="menu-txt-wrap">
            <text class="menu-txt">下载攻略</text>
            <text v-if="!guideUnlocked" class="menu-sub">需5张作品</text>
          </view>
          <text
            :class="['menu-tag', guideUnlocked ? 'menu-tag-green' : 'menu-tag-off']"
            >{{ guideUnlocked ? "已达成" : "未解锁" }}</text
          >
        </view>

        <view class="menu-row menu-row-last" @click="goUserProfile">
          <view class="menu-ico-wrap menu-ico-green">
            <image
              class="menu-down-ico"
              :src="profileIcon"
              mode="aspectFit"
            />
          </view>
          <view class="menu-txt-wrap">
            <text class="menu-txt">我的资料</text>
          </view>
          <text class="cert-arrow">›</text>
        </view>
      </view>
      </template>
    </view>

    <ImageTabBar current="user" />

    <view v-if="showCertificateDialog" class="modal-mask" @click="showCertificateDialog = false">
      <view class="modal-box" @click.stop>
        <view class="cert-modal-inner">
          <image
            class="cert-bg"
            :src="certificateDialogSrc"
            mode="widthFix"
          />
          <view class="cert-dl-wrap">
            <view class="cert-dl" @click="handleDownloadCertificate">
              <image class="cert-dl-ico" :src="down1Icon" mode="aspectFit" />
              <text>下载证书</text>
            </view>
          </view>
        </view>
      </view>
    </view>

    <view v-if="showGuideDialog" class="modal-mask" @click="showGuideDialog = false">
      <view class="modal-box modal-guide" @click.stop>
        <image
          class="guide-img"
          :src="guideDialogSrc"
          mode="widthFix"
        />
        <view class="guide-bar">
          <view class="guide-dl" @click="handleDownloadGuideFile">
            <image class="guide-dl-ico" :src="down2Icon" mode="aspectFit" />
            <text>下载攻略</text>
          </view>
        </view>
      </view>
    </view>
    <!-- #ifdef MP-WEIXIN -->
    <canvas
      canvas-id="certificateCanvas"
      id="certificateCanvas"
      class="cert-hidden-canvas"
      :style="{ width: certCanvasW + 'px', height: certCanvasH + 'px' }"
    />
    <!-- #endif -->
  </view>
</template>

<script setup lang="ts">
import { computed, getCurrentInstance, nextTick, onMounted, ref, watch } from "vue";
import { onShow } from "@dcloudio/uni-app";
import ImageTabBar from "@/components/ImageTabBar.vue";
import { useHideNativeTabBar } from "@/composables/useHideNativeTabBar";
import { localGalleryByIndex } from "@/utils/localGalleryImages";
import { staticAsset } from "@/utils/staticAsset";
import {
  loadUserProfile,
  profileAvatarLetter,
  saveUserProfile,
  type UserProfile,
} from "@/utils/userProfileStorage";
import { apiEnabled } from "@/config/api";
import {
  get,
  getMpToken,
  clearMpSession,
  isMpSessionExpiredError,
} from "@/utils/api/http";
import { ensureMpSession, getMpLoginChannel } from "@/utils/api/mpSession";
import { reLaunchLoginIfNoBoundPhone, redirectToProfileSetupIfNeeded } from "@/utils/guestTabGuard";
import { resolveMediaUrl } from "@/utils/mediaUrl";

const statusBarHeight = ref(20);
const settingsRightSafePx = ref(16);
const settingsTopExtraPx = ref(0);
const apiOn = apiEnabled();


const emptyProfileWhileLoading: UserProfile = {
  nickname: "",
  bio: "",
  avatarUrl: "",
  gender: "secret",
  phone: "",
};

/** 接接口：首屏不展示演示用作品与统计，等请求结束 */
const pageLoading = ref(apiOn);
const profile = ref<UserProfile>(
  apiOn ? { ...emptyProfileWhileLoading } : loadUserProfile()
);
/** 服务端头像多为 /uploads/...，需拼 API 根地址才能显示 */
const profileAvatarSrc = computed(() =>
  resolveMediaUrl(profile.value.avatarUrl.trim())
);
/** 头像 URL 无效或网络失败时回退为昵称首字 */
const avatarLoadFailed = ref(false);
watch(profileAvatarSrc, () => {
  avatarLoadFailed.value = false;
});
function onProfileAvatarError() {
  avatarLoadFailed.value = true;
}
const activeTab = ref<"photos" | "certificates">("photos");
const showCertificateDialog = ref(false);
const showGuideDialog = ref(false);
const userHeaderBg = staticAsset("user-center-header.png");
const settingsIcon = staticAsset("set.png");
const likeOnIcon = staticAsset("cang_on.png");
const likeOffIcon = staticAsset("cang.png");
const down1Icon = staticAsset("down_1.png");
const down2Icon = staticAsset("down_2.png");
const profileIcon = staticAsset("zl.png");
const certificateTemplateUrl = ref(staticAsset("certificate-dialog-bg.png"));
const guideTemplateUrl = ref(staticAsset("daocheng-yading-map.png"));
const vueInstance = getCurrentInstance()?.proxy ?? null;
const certCanvasW = ref(1200);
const certCanvasH = ref(800);

const certificateDialogSrc = computed(() => resolveMediaUrl(certificateTemplateUrl.value));
const guideDialogSrc = computed(() => resolveMediaUrl(guideTemplateUrl.value));

const userStats = ref(
  apiOn ? { photos: 0, likes: 0 } : { photos: 12, likes: 458 }
);

const userPhotos = ref<
  Array<{
    id: number;
    url: string;
    status: "approved" | "pending" | "rejected";
    likes: number;
  }>
>(
  apiOn
    ? []
    : [
        { id: 1, url: localGalleryByIndex(1), status: "approved", likes: 156 },
        { id: 2, url: localGalleryByIndex(2), status: "pending", likes: 0 },
        { id: 3, url: localGalleryByIndex(3), status: "approved", likes: 89 },
        { id: 4, url: localGalleryByIndex(4), status: "approved", likes: 203 },
      ]
);

const certificates = [
  { id: 1, name: "稻城摄影大赛证书.pdf", date: "2026-03-15", size: "2.3 MB" },
  { id: 2, name: "亚丁风光奖状.pdf", date: "2026-02-20", size: "1.8 MB" },
];

const certificateUnlocked = computed(
  () => userStats.value.photos >= 10 && userStats.value.likes >= 100
);
const guideUnlocked = computed(() => userStats.value.photos >= 5);

useHideNativeTabBar();

/** 与当前 token 一致且已成功拉过「我的」数据时，再次进入 tab 不铺满骨架屏，仅静默刷新 */
let lastSuccessMpTokenForMe = "";

onMounted(() => {
  statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight || 20;
  // #ifdef MP-WEIXIN
  const info = uni.getSystemInfoSync();
  const cap = uni.getMenuButtonBoundingClientRect?.();
  if (cap && info?.screenWidth) {
    const rightNeed = Math.max(0, info.screenWidth - cap.left + 8);
    settingsRightSafePx.value = rightNeed;
    settingsTopExtraPx.value = Math.max(0, cap.top - statusBarHeight.value);
  }
  // #endif
});

const settingsTrackStyle = computed(() => ({
  paddingRight: `${settingsRightSafePx.value}px`,
  paddingTop: `${settingsTopExtraPx.value}px`,
}));

async function loadUserFromApi() {
  if (!apiEnabled()) {
    pageLoading.value = false;
    return;
  }
  const token = getMpToken();
  const showFullSkeleton = !token || token !== lastSuccessMpTokenForMe;
  if (showFullSkeleton) {
    pageLoading.value = true;
  }
  try {
    // #ifndef MP-WEIXIN
    await ensureMpSession();
    // #endif
    const [st, works, prof, assetsRes] = await Promise.all([
      get<{ photos: number; likes: number }>("/api/v1/me/stats"),
      get<{
        list: Array<{
          id: number;
          imageUrl: string;
          status: string;
          likes: number;
        }>;
      }>("/api/v1/me/works"),
      get<{
        nickname: string;
        avatarUrl: string;
        bio: string;
        gender: number;
        phone?: string;
      }>("/api/v1/me/profile"),
      get<{ certificate: string; guide: string }>("/api/v1/download-assets"),
    ]);
    userStats.value = {
      photos: st.data.photos,
      likes: st.data.likes,
    };
    userPhotos.value = works.data.list.slice(0, 4).map((w) => {
      const st = w.status === "approved" ? "approved" : w.status === "rejected" ? "rejected" : "pending";
      return {
        id: w.id,
        url: resolveMediaUrl(w.imageUrl),
        status: st,
        likes: w.likes,
      };
    });
    const serverAvatar =
      typeof prof.data.avatarUrl === "string"
        ? prof.data.avatarUrl.trim()
        : "";
    profile.value = {
      ...profile.value,
      nickname: String(prof.data.nickname || "").trim(),
      avatarUrl: serverAvatar || profile.value.avatarUrl,
      bio: prof.data.bio ?? profile.value.bio,
      gender:
        prof.data.gender === 1
          ? "male"
          : prof.data.gender === 2
            ? "female"
            : "secret",
      phone:
        typeof prof.data.phone === "string"
          ? prof.data.phone.trim()
          : profile.value.phone,
    };
    const certRaw = String(assetsRes?.data?.certificate ?? "").trim();
    const guideRaw = String(assetsRes?.data?.guide ?? "").trim();
    certificateTemplateUrl.value = certRaw || staticAsset("certificate-dialog-bg.png");
    guideTemplateUrl.value = guideRaw || staticAsset("daocheng-yading-map.png");
    saveUserProfile(profile.value);
    lastSuccessMpTokenForMe = getMpToken();
  } catch (e) {
    if (isMpSessionExpiredError(e)) {
      return;
    }
    userStats.value = { photos: 0, likes: 0 };
    userPhotos.value = [];
    profile.value = loadUserProfile();
    const msg =
      e && typeof e === "object" && "msg" in e && typeof (e as { msg: string }).msg === "string"
        ? (e as { msg: string }).msg
        : "加载失败，请稍后重试";
    uni.showToast({
      title: msg.length > 20 ? msg.slice(0, 20) + "…" : msg,
      icon: "none",
    });
  } finally {
    pageLoading.value = false;
    if (!getMpToken()) {
      lastSuccessMpTokenForMe = "";
    }
  }
}

onShow(() => {
  // #ifdef MP-WEIXIN
  if (apiEnabled() && (!getMpToken() || getMpLoginChannel() !== "weixin")) {
    uni.reLaunch({ url: "/pages/login/login?tab=user" });
    return;
  }
  // #endif
  // #ifndef MP-WEIXIN
  void (async () => {
    if (apiEnabled() && !getMpToken()) {
      try {
        await ensureMpSession();
      } catch {
        /* ignore */
      }
    }
  })();
  // #endif
  if (reLaunchLoginIfNoBoundPhone()) {
    return;
  }
  if (redirectToProfileSetupIfNeeded()) {
    return;
  }
  profile.value = loadUserProfile();
  void loadUserFromApi();
});

function onSettingsTap() {
  if (!apiEnabled()) {
    uni.showToast({ title: "离线演示模式", icon: "none" });
    return;
  }
  uni.showActionSheet({
    itemList: ["退出登录"],
    success(res) {
      if (res.tapIndex === 0) {
        lastSuccessMpTokenForMe = "";
        clearMpSession();
        uni.reLaunch({ url: "/pages/login/login" });
      }
    },
  });
}

function workStatusShort(s: "approved" | "pending" | "rejected"): string {
  if (s === "approved") return "已通过";
  if (s === "rejected") return "未通过";
  return "审核中";
}

function openUserWork(id: number) {
  uni.navigateTo({ url: `/pages/photo-detail/photo-detail?id=${id}` });
}

function goMyWorksList() {
  uni.navigateTo({ url: "/pages/my-works/my-works" });
}

function goUserProfile() {
  uni.navigateTo({ url: "/pages/user-profile/user-profile" });
}

function handleCertificateDownload() {
  if (certificateUnlocked.value) showCertificateDialog.value = true;
  else {
    uni.showModal({
      title: "提示",
      content: `需要上传${10 - userStats.value.photos}张作品，获得${100 - userStats.value.likes}个赞才能解锁证书下载`,
      showCancel: false,
    });
  }
}

function handleGuideDownload() {
  if (guideUnlocked.value) showGuideDialog.value = true;
  else {
    uni.showModal({
      title: "提示",
      content: `需要上传${5 - userStats.value.photos}张作品才能解锁攻略下载`,
      showCancel: false,
    });
  }
}

function handleDownloadCertificate() {
  void downloadCertificateImage();
}

function handleDownloadGuideFile() {
  void downloadGuideImage();
}

function unlockDateYmd(): string {
  const d = new Date();
  const y = d.getFullYear();
  const m = `${d.getMonth() + 1}`.padStart(2, "0");
  const day = `${d.getDate()}`.padStart(2, "0");
  return `${y}-${m}-${day}`;
}

function downloadFileToTemp(url: string): Promise<string> {
  return new Promise((resolve, reject) => {
    uni.downloadFile({
      url,
      success: (res) => {
        if (res.statusCode === 200 && res.tempFilePath) {
          resolve(res.tempFilePath);
          return;
        }
        reject(new Error("下载失败"));
      },
      fail: () => reject(new Error("下载失败")),
    });
  });
}

function saveImageToAlbum(tempFilePath: string): Promise<void> {
  return new Promise((resolve, reject) => {
    uni.saveImageToPhotosAlbum({
      filePath: tempFilePath,
      success: () => resolve(),
      fail: (e) => reject(e),
    });
  });
}

function getImageInfo(src: string): Promise<{ width: number; height: number; path: string }> {
  return new Promise((resolve, reject) => {
    uni.getImageInfo({
      src,
      success: (r) => resolve({ width: r.width, height: r.height, path: r.path }),
      fail: () => reject(new Error("读取图片失败")),
    });
  });
}

function renderCertificateWithText(src: string, username: string, dateYmd: string): Promise<string> {
  return new Promise(async (resolve, reject) => {
    try {
      const info = await getImageInfo(src);
      const canvasW = Math.max(600, info.width);
      const canvasH = Math.max(800, info.height);
      certCanvasW.value = canvasW;
      certCanvasH.value = canvasH;
      await nextTick();
      const ctx = uni.createCanvasContext("certificateCanvas", vueInstance);
      ctx.clearRect(0, 0, canvasW, canvasH);
      ctx.drawImage(info.path, 0, 0, canvasW, canvasH);

      const name = username.trim() || "用户";
      const dateLine = dateYmd;

      // 按模板固定锚点写字：用户名位于「特此授予：」后，日期位于「颁发日期：」后
      const nameSize = Math.max(30, Math.round(canvasW * 0.036));
      const dateSize = Math.max(26, Math.round(canvasW * 0.030));
      // 基于标注图校准：用户名与日期分属两行红框
      // 用户名：特此授予后方
      const nameX = Math.round(canvasW * 0.266);
      const nameY = Math.round(canvasH * 0.224);
      // 日期：颁发日期后方
      const dateX = Math.round(canvasW * 0.73);
      const dateY = Math.round(canvasH * 0.334);

      ctx.setTextAlign("left");
      ctx.setFillStyle("#374151");
      ctx.setFontSize(nameSize);
      ctx.fillText(name, nameX, nameY);
      ctx.setFillStyle("#4b5563");
      ctx.setFontSize(dateSize);
      ctx.fillText(dateLine, dateX, dateY);
      ctx.draw(false, () => {
        setTimeout(() => {
          uni.canvasToTempFilePath(
            {
              canvasId: "certificateCanvas",
              width: canvasW,
              height: canvasH,
              destWidth: canvasW,
              destHeight: canvasH,
              fileType: "jpg",
              quality: 0.95,
              success: (res) => resolve(res.tempFilePath),
              fail: () => reject(new Error("导出证书失败")),
            },
            vueInstance
          );
        }, 80);
      });
    } catch (e) {
      reject(e);
    }
  });
}

async function downloadCertificateImage() {
  try {
    uni.showLoading({ title: "生成证书中" });
    const srcUrl = certificateDialogSrc.value;
    if (!srcUrl) {
      throw new Error("证书模板未配置");
    }
    const temp = await downloadFileToTemp(srcUrl);
    const withText = await renderCertificateWithText(temp, profile.value.nickname || "用户", unlockDateYmd());
    await saveImageToAlbum(withText);
    uni.hideLoading();
    uni.showToast({ title: "证书已保存到相册", icon: "success" });
    showCertificateDialog.value = false;
  } catch (e) {
    uni.hideLoading();
    const msg = e instanceof Error ? e.message : "证书下载失败";
    uni.showToast({ title: msg, icon: "none" });
  }
}

async function downloadGuideImage() {
  try {
    uni.showLoading({ title: "下载中" });
    const srcUrl = guideDialogSrc.value;
    if (!srcUrl) {
      throw new Error("攻略图片未配置");
    }
    const temp = await downloadFileToTemp(srcUrl);
    await saveImageToAlbum(temp);
    uni.hideLoading();
    uni.showToast({ title: "攻略已保存到相册", icon: "success" });
    showGuideDialog.value = false;
  } catch (e) {
    uni.hideLoading();
    const msg = e instanceof Error ? e.message : "攻略下载失败";
    uni.showToast({ title: msg, icon: "none" });
  }
}

</script>

<style scoped>
.page {
  min-height: 100vh;
  padding-bottom: calc(100rpx + env(safe-area-inset-bottom));
  background: #f9fafb;
}

.header {
  position: relative;
  overflow: hidden;
  border-radius: 0 0 48rpx 48rpx;
}

/* 骨架屏：多露出一段头图区域，避免加载态头图偏矮 */
.header--sk {
  padding-bottom: 96rpx;
}

.header-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.header-mask {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(0, 0, 0, 0.4) 0%,
    rgba(0, 0, 0, 0.2) 50%,
    transparent 100%
  );
}

.header-body {
  position: relative;
  padding: 24rpx 48rpx 48rpx;
}

.settings-tr {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 32rpx;
}

.settings-btn {
  display: flex;
  width: 80rpx;
  height: 80rpx;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.18);
}

.settings-ico-img {
  width: 40rpx;
  height: 40rpx;
}

/* ---------- 骨架屏（对齐真实 profile / stats / sheet 结构） ---------- */
.header-skeleton {
  display: flex;
  width: 100%;
  flex-direction: column;
  align-items: center;
  padding-bottom: 0;
  /* 与 .profile 一致：整块居中 */
  text-align: center;
  /* 相对设置行略上移，避免加载态留白过大 */
  margin-top: -20rpx;
}

.user-sk-avatar {
  flex-shrink: 0;
  width: 192rpx;
  height: 192rpx;
  margin-bottom: 32rpx;
  border: 8rpx solid rgba(255, 255, 255, 0.92);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.2);
  box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
}

.user-sk-stats {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  width: 100%;
  box-sizing: border-box;
  padding: 28rpx 28rpx;
  margin: 0 -12rpx;
  border-radius: 32rpx;
  background: rgba(255, 255, 255, 0.96);
  box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.12);
}

.user-sk-stat-col {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10rpx;
}

.user-sk-stat-num {
  width: 56rpx;
  height: 44rpx;
  border-radius: 10rpx;
  background: #e2e8f0;
}

.user-sk-stat-lb {
  width: 64rpx;
  height: 22rpx;
  border-radius: 6rpx;
  background: #eceff3;
}

.user-sk-stats-div {
  flex-shrink: 0;
  width: 1rpx;
  margin: 8rpx 0;
  background: #e5e7eb;
  align-self: stretch;
  min-height: 56rpx;
}

.sheet-skeleton {
  padding-bottom: 32rpx;
}

.user-sk-tabs {
  display: grid;
  margin-bottom: 32rpx;
  padding: 8rpx;
  border-radius: 24rpx;
  background: #fff;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
  gap: 8rpx;
  grid-template-columns: 1fr 1fr;
}

/* 左格模拟 .tab-on，右格模拟 .tab-off */
.user-sk-tab {
  min-height: 68rpx;
  border-radius: 20rpx;
  background: #f3f4f6;
}

.user-sk-tab--on {
  background: #e8ecf0;
}

.user-sk-panel-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24rpx;
  min-height: 44rpx;
}

.user-sk-ph-title {
  width: 168rpx;
  height: 32rpx;
  border-radius: 8rpx;
  background: #e2e8f0;
}

.user-sk-ph-more {
  width: 148rpx;
  height: 28rpx;
  border-radius: 8rpx;
  background: #e8ecf0;
}

.user-sk-grid {
  display: grid;
  margin-bottom: 32rpx;
  gap: 16rpx;
  grid-template-columns: repeat(4, 1fr);
}

.user-sk-thumb-wrap {
  position: relative;
  overflow: hidden;
  width: 100%;
  height: 0;
  padding-bottom: 100%;
  border-radius: 24rpx;
  background: #f3f4f6;
}

.user-sk-thumb-inner {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border-radius: 24rpx;
  background: linear-gradient(
    145deg,
    #e8ecf0 0%,
    #dce2ea 48%,
    #e8ecf0 100%
  );
}

.user-sk-menu {
  overflow: hidden;
  margin-bottom: 48rpx;
  border-radius: 24rpx;
  background: #fff;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}

.user-sk-menu-row {
  display: flex;
  align-items: center;
  padding: 40rpx 32rpx;
  border-bottom: 1rpx solid #f3f4f6;
  gap: 28rpx;
}

.user-sk-menu-row:last-child {
  border-bottom: none;
}

.user-sk-menu-ico {
  flex-shrink: 0;
  width: 88rpx;
  height: 88rpx;
  border-radius: 50%;
  background: #eef1f5;
}

.user-sk-menu-lines {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 16rpx;
}

.user-sk-menu-line {
  width: 200rpx;
  max-width: 58%;
  height: 32rpx;
  border-radius: 10rpx;
  background: #e2e8f0;
}

.user-sk-menu-line--sub {
  width: 280rpx;
  max-width: 72%;
  height: 26rpx;
  border-radius: 8rpx;
  background: #eceff3;
}

.user-sk-menu-tag {
  flex-shrink: 0;
  width: 100rpx;
  height: 44rpx;
  border-radius: 12rpx;
  background: #e8ecf0;
}

@keyframes user-page-sk-breathe {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.38;
  }
}

.user-sk-breathe {
  animation: user-page-sk-breathe 0.85s ease-in-out infinite;
}

.user-sk-breathe--d0 {
  animation-delay: 0s;
}

.user-sk-breathe--d1 {
  animation-delay: 0.08s;
}

.user-sk-breathe--d2 {
  animation-delay: 0.16s;
}

.profile {
  align-items: center;
  margin-bottom: 32rpx;
  text-align: center;
  transition: opacity 0.15s ease;
}

.profile:active {
  opacity: 0.88;
}

.big-avatar {
  display: flex;
  width: 192rpx;
  height: 192rpx;
  align-items: center;
  justify-content: center;
  margin: 0 auto 24rpx;
  border: 8rpx solid #fff;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.28);
  box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
}

.big-avatar-img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
}

.big-av-txt {
  font-size: 72rpx;
  color: #fff;
}

.nickname {
  display: block;
  margin-bottom: 12rpx;
  font-size: 44rpx;
  color: #fff;
}

.bio {
  font-size: 26rpx;
  color: rgba(255, 255, 255, 0.92);
}

.stats-card {
  display: flex;
  align-items: center;
  justify-content: space-around;
  padding: 32rpx;
  margin: 0 -12rpx;
  border-radius: 32rpx;
  background: rgba(255, 255, 255, 0.95);
  box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.12);
}

.stat {
  flex: 1;
  text-align: center;
}

.stat-num {
  display: block;
  margin-bottom: 6rpx;
  font-size: 44rpx;
  font-weight: 600;
  color: #7fb069;
}

.stat-label {
  font-size: 22rpx;
  color: #4b5563;
}

.stat-div {
  width: 1rpx;
  height: 56rpx;
  background: #e5e7eb;
}

.wave-fake {
  position: relative;
  height: 48rpx;
  margin-top: -48rpx;
  border-radius: 32rpx 32rpx 0 0;
  background: transparent;
}

.sheet {
  max-width: 750rpx;
  padding: 0 32rpx;
  margin: -96rpx auto 0;
}

.tabs {
  display: grid;
  padding: 8rpx;
  margin-bottom: 32rpx;
  border-radius: 24rpx;
  background: #fff;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
  grid-template-columns: 1fr 1fr;
}

.tab {
  padding: 20rpx;
  font-size: 28rpx;
  color: #6b7280;
  text-align: center;
  border-radius: 20rpx;
}

.tab-on {
  color: #111827;
  background: #f3f4f6;
  font-weight: 500;
}

.panel {
  margin-bottom: 32rpx;
}

.panel-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24rpx;
}

.panel-title {
  font-size: 30rpx;
  color: #1f2937;
}

.panel-more {
  font-size: 26rpx;
  color: #4a90e2;
}

.grid-4 {
  display: grid;
  gap: 16rpx;
  grid-template-columns: repeat(4, 1fr);
}

.thumb {
  position: relative;
  overflow: hidden;
  width: 100%;
  height: 0;
  padding-bottom: 100%;
  border-radius: 24rpx;
  background: #f3f4f6;
}

.thumb-img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.thumb-badge {
  position: absolute;
  top: 12rpx;
  right: 12rpx;
}

.badge {
  padding: 6rpx 12rpx;
  font-size: 20rpx;
  color: #fff;
  border-radius: 8rpx;
}

.badge-ok {
  background: #7fb069;
}

.badge-wait {
  background: #eab308;
}

.badge-reject {
  background: #ef4444;
}

.thumb-likes {
  position: absolute;
  right: 12rpx;
  bottom: 12rpx;
  left: 12rpx;
}

.thumb-likes-inner {
  display: inline-flex;
  align-items: center;
  padding: 8rpx 16rpx;
  border-radius: 999rpx;
  background: rgba(0, 0, 0, 0.5);
  gap: 6rpx;
}

.thumb-cang {
  width: 22rpx;
  height: 22rpx;
  flex-shrink: 0;
}

.thumb-likes-txt {
  font-size: 20rpx;
  color: #fff;
}

.cert-row {
  display: flex;
  align-items: center;
  padding: 32rpx;
  margin-bottom: 24rpx;
  border-radius: 24rpx;
  background: #fff;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  gap: 24rpx;
}

.cert-icon {
  display: flex;
  width: 96rpx;
  height: 96rpx;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  border-radius: 20rpx;
  background: linear-gradient(135deg, #4a90e2, #50c8d8);
}

.cert-star {
  font-size: 40rpx;
  color: #fff;
}

.cert-mid {
  flex: 1;
  min-width: 0;
}

.cert-name {
  display: block;
  margin-bottom: 8rpx;
  overflow: hidden;
  font-size: 28rpx;
  color: #111827;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cert-meta {
  font-size: 22rpx;
  color: #6b7280;
}

.cert-arrow {
  flex-shrink: 0;
  font-size: 40rpx;
  color: #9ca3af;
}

.menu {
  overflow: hidden;
  margin-bottom: 48rpx;
  border-radius: 24rpx;
  background: #fff;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}

.menu-row {
  display: flex;
  align-items: center;
  padding: 32rpx;
  border-bottom: 1rpx solid #f3f4f6;
  gap: 24rpx;
}

.menu-row-last {
  border-bottom: none;
}

.menu-ico-wrap {
  display: flex;
  width: 80rpx;
  height: 80rpx;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  font-size: 28rpx;
}

.menu-ico-blue {
  background: rgba(74, 144, 226, 0.12);
  color: #4a90e2;
}

.menu-ico-green {
  background: rgba(127, 176, 105, 0.12);
  color: #7fb069;
}

.menu-ico-gray {
  background: #f3f4f6;
  color: #9ca3af;
}

.menu-down-ico {
  width: 40rpx;
  height: 40rpx;
}

.menu-txt-wrap {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 6rpx;
}

.menu-txt {
  font-size: 28rpx;
  color: #111827;
}

.menu-sub {
  font-size: 22rpx;
  color: #9ca3af;
}

.menu-tag {
  padding: 8rpx 20rpx;
  font-size: 22rpx;
  border-radius: 8rpx;
}

.menu-tag-ok {
  color: #fff;
  background: #7fb069;
}

.menu-tag-green {
  color: #fff;
  background: #7fb069;
}

.menu-tag-off {
  color: #4b5563;
  background: #e5e7eb;
}

.modal-mask {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40rpx;
  background: rgba(0, 0, 0, 0.55);
}

.modal-box {
  position: relative;
  width: 100%;
  max-height: 85vh;
  overflow: auto;
}

.cert-modal-inner {
  position: relative;
  overflow: hidden;
  border-radius: 16rpx;
}

.cert-bg {
  display: block;
  width: 100%;
}

.cert-dl-wrap {
  display: flex;
  justify-content: center;
  padding: 24rpx 0 8rpx;
}

.cert-dl {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16rpx;
  min-width: 360rpx;
  padding: 22rpx 48rpx;
  font-size: 28rpx;
  color: #fff;
  background: linear-gradient(90deg, #4a90e2, #50c8d8);
  border-radius: 999rpx;
}

.cert-dl-ico {
  width: 36rpx;
  height: 36rpx;
  flex-shrink: 0;
}

.modal-guide {
  position: relative;
  max-height: 88vh;
  border-radius: 16rpx;
  background: #fff;
}

.guide-img {
  display: block;
  width: 100%;
}

.cert-hidden-canvas {
  position: fixed;
  top: -3000px;
  left: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.guide-bar {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  justify-content: center;
  padding: 32rpx 48rpx calc(32rpx + env(safe-area-inset-bottom));
  background: linear-gradient(
    180deg,
    transparent,
    rgba(0, 0, 0, 0.45) 40%
  );
}

.guide-dl {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16rpx;
  min-width: 420rpx;
  padding: 22rpx 48rpx;
  font-size: 28rpx;
  color: #fff;
  background: linear-gradient(90deg, #4a90e2, #50c8d8);
  border-radius: 999rpx;
}

.guide-dl-ico {
  width: 36rpx;
  height: 36rpx;
  flex-shrink: 0;
}
</style>
