upload project source code

This commit is contained in:
2026-04-30 18:49:43 +08:00
commit 9b394ba682
2277 changed files with 660945 additions and 0 deletions

View File

@@ -0,0 +1,823 @@
<template>
<view class="company-desktop-detail">
<view class="detail-header">
<view class="detail-header-back" @click="emit('back')">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<text class="detail-header-title">公司测名详解</text>
<view class="detail-header-placeholder" />
</view>
<scroll-view scroll-y class="detail-content">
<view class="report-body">
<!-- header -->
<view
class="section"
:class="{ 'section--click': hasNodes(header.details?.nodes) }"
@click="hasNodes(header.details?.nodes) && openDetail(header.details?.title || '总分详解', header.details?.nodes)"
>
<text class="section-label">header · 总分</text>
<view class="score-row">
<text class="name">{{ toText(header.name) || '—' }}</text>
<text class="score">{{ num(header.score, 0) }}</text>
</view>
<view class="tag-row">
<text class="pill">{{ toText(header.tagLeft) }}</text>
<text class="pill">{{ toText(header.tagRight) }}</text>
</view>
<text class="body-text">{{ toText(header.quote) }}</text>
<text v-if="hasNodes(header.details?.nodes)" class="section-hint">点击本段查看{{ toText(header.details?.title) || '详解' }}</text>
</view>
<!-- characterAnalysis -->
<view
class="section"
:class="{ 'section--click': hasNodes(characterAnalysis.details?.nodes) }"
@click="
hasNodes(characterAnalysis.details?.nodes) &&
openDetail(characterAnalysis.details?.title || '字义数理详解', characterAnalysis.details?.nodes)
"
>
<text class="section-label">characterAnalysis · 字义数理</text>
<view class="char-grid">
<view v-for="(it, idx) in arr(characterAnalysis.characters)" :key="idx" class="char-box">
<text class="char-single">{{ toText(it?.char) }}</text>
<text class="char-meta">{{ toText(it?.element) }} · {{ num(it?.stroke, 0) }}</text>
<text class="body-text small">{{ toText(it?.meaning) }}</text>
</view>
</view>
<text class="body-text">{{ toText(characterAnalysis.analysis) }}</text>
<text v-if="hasNodes(characterAnalysis.details?.nodes)" class="section-hint">
点击本段查看{{ toText(characterAnalysis.details?.title) || '详细拆解' }}
</text>
</view>
<!-- businessPattern -->
<view class="section">
<text class="section-label">businessPattern · 商业格局</text>
<SixDimensionRadarDesktopEchart
:labels="arr(businessPattern.radar?.labels)"
:values="arr(businessPattern.radar?.values)"
:remark="summaryText"
/>
<view v-if="arr(businessPattern.summary).length" class="kv-inline">
<view v-for="(s, si) in arr(businessPattern.summary)" :key="si" class="summary-chip">
<text class="chip-k">{{ toText(s?.label) }}</text>
<text class="chip-v">{{ toText(s?.value) }}</text>
</view>
</view>
<view
v-if="hasNodes(businessPattern.details?.nodes)"
class="section-link"
:class="{ 'section--click': true }"
@click="openDetail(businessPattern.details?.title || '商业六维详解', businessPattern.details?.nodes)"
>
<text>{{ toText(businessPattern.details?.title) || '商业六维 · 解释与建议' }} </text>
</view>
</view>
<!-- gua -->
<view
class="section"
:class="{ 'section--click': hasNodes(gua.details?.nodes) }"
@click="hasNodes(gua.details?.nodes) && openDetail(gua.details?.title || '卦象解读', gua.details?.nodes)"
>
<text class="section-label">gua · 卦象</text>
<text v-if="toText(gua.bg)" class="gua-bg">卦字{{ toText(gua.bg) }}</text>
<text class="headline">{{ toText(gua.name) }} · {{ toText(gua.badge) }}</text>
<text class="body-text">{{ toText(gua.desc) }}</text>
<view v-if="arr(gua.tags).length" class="tag-list">
<text v-for="(t, ti) in arr(gua.tags)" :key="ti" class="pill pill--soft">{{ toText(t) }}</text>
</view>
<text class="body-text">{{ toText(gua.insight) }}</text>
<text v-if="hasNodes(gua.details?.nodes)" class="section-hint">点击本段查看卦象详解</text>
</view>
<!-- team -->
<view
class="section"
:class="{ 'section--click': hasNodes(team.details?.nodes) }"
@click="hasNodes(team.details?.nodes) && openDetail(team.details?.title || '团队契合', team.details?.nodes)"
>
<text class="section-label">team · 团队契合</text>
<view v-for="(m, mi) in arr(team.members)" :key="mi" class="member-block">
<text class="member-line">
{{ toText(m?.role) }} · {{ num(m?.score, 0) }} · {{ toText(m?.match) }}
</text>
<text v-if="toText(m?.desc)" class="body-text small">{{ toText(m?.desc) }}</text>
</view>
<text v-if="toText(team.note)" class="body-text note">{{ toText(team.note) }}</text>
<text v-if="hasNodes(team.details?.nodes)" class="section-hint">点击本段查看团队说明</text>
</view>
<!-- years -->
<view
class="section"
:class="{ 'section--click': hasNodes(years.details?.nodes) }"
@click="hasNodes(years.details?.nodes) && openDetail(years.details?.title || '流年运势', years.details?.nodes)"
>
<text class="section-label">years · 流年</text>
<view v-for="(y, yi) in arr(years.items)" :key="yi" class="year-row">
<text class="year-key">{{ toText(y?.year) }}</text>
<text class="year-luck">{{ toText(y?.luck) }}</text>
<text class="year-text">{{ toText(y?.text) }}</text>
</view>
<text v-if="hasNodes(years.details?.nodes)" class="section-hint">点击本段查看流年预警/建议</text>
</view>
<!-- wealthTrend -->
<view
class="section"
:class="{ 'section--click': hasNodes(wealthTrend.details?.nodes) }"
@click="hasNodes(wealthTrend.details?.nodes) && openDetail(wealthTrend.details?.title || '财运走势', wealthTrend.details?.nodes)"
>
<text class="section-label">wealthTrend · 财运走势</text>
<view class="bars">
<view v-for="(v, vi) in arr(wealthTrend.bars)" :key="vi" class="bar-wrap">
<view class="bar" :style="{ height: `${Math.max(8, Math.min(100, num(v, 0)))}%` }" />
</view>
</view>
<text class="body-text">{{ toText(wealthTrend.note) }}</text>
<text v-if="hasNodes(wealthTrend.details?.nodes)" class="section-hint">点击本段查看走势建议</text>
</view>
<!-- direction -->
<view
class="section"
:class="{ 'section--click': hasNodes(direction.details?.nodes) }"
@click="hasNodes(direction.details?.nodes) && openDetail(direction.details?.title || '吉凶方位', direction.details?.nodes)"
>
<text class="section-label">direction · 方位</text>
<text class="body-text">{{ toText(direction.note) }}</text>
<text v-if="direction.goodDot && (direction.goodDot.x != null || direction.goodDot.y != null)" class="body-text small">
吉位参考点相对坐标x {{ num(direction.goodDot?.x, 0) }}y {{ num(direction.goodDot?.y, 0) }}
</text>
<text v-if="hasNodes(direction.details?.nodes)" class="section-hint">点击本段查看方位说明</text>
</view>
<!-- layout -->
<view
class="section"
:class="{ 'section--click': hasNodes(layout.details?.nodes) }"
@click="hasNodes(layout.details?.nodes) && openDetail(layout.details?.title || '办公布局', layout.details?.nodes)"
>
<text class="section-label">layout · 办公布局</text>
<view v-for="(it, li) in arr(layout.items)" :key="li" class="layout-line">
<text class="layout-strong">{{ toText(it?.strong) }}</text>
<view class="layout-flow">
<text class="body-text small">{{ toText(it?.textBefore) }}</text>
<text v-for="(h, hi) in arr(it?.highlights)" :key="hi" class="highlight">{{ toText(h) }}</text>
<text class="body-text small">{{ toText(it?.textAfter) }}</text>
</view>
</view>
<text v-if="hasNodes(layout.details?.nodes)" class="section-hint">点击本段查看布局建议</text>
</view>
<!-- execution -->
<view
class="section section--wide"
:class="{ 'section--click': hasNodes(execution.details?.nodes) }"
@click="hasNodes(execution.details?.nodes) && openDetail(execution.details?.title || '执行建议', execution.details?.nodes)"
>
<text class="section-label">execution · 执行建议</text>
<text class="body-text">{{ toText(execution.text) }}</text>
<text v-if="hasNodes(execution.details?.nodes)" class="section-hint">点击本段查看执行条目</text>
</view>
<!-- liuyao -->
<view
v-if="hasLiuyao"
class="section"
:class="{ 'section--click': hasNodes(liuyao.details?.nodes) }"
@click="hasNodes(liuyao.details?.nodes) && openDetail('六爻', liuyao.details?.nodes)"
>
<text class="section-label">liuyao · 六爻</text>
<text class="headline">{{ toText(liuyao.hexagram_title) }}</text>
<text class="body-text">{{ toText(liuyao.changing_summary) }}</text>
<text class="body-text">{{ toText(liuyao.interpretation) }}</text>
<text v-for="(yl, yi) in arr(liuyao.yao_lines)" :key="yi" class="body-text small mono">· {{ toText(yl) }}</text>
<text v-if="hasNodes(liuyao.details?.nodes)" class="section-hint">点击本段查看六爻详解</text>
</view>
<!-- wuxing_bagua -->
<view
v-if="hasWuxingBagua"
class="section"
:class="{ 'section--click': hasNodes(wuxingBagua.details?.nodes) }"
@click="hasNodes(wuxingBagua.details?.nodes) && openDetail('五行八卦', wuxingBagua.details?.nodes)"
>
<text class="section-label">wuxing_bagua · 五行八卦</text>
<text class="body-text">{{ toText(wuxingBagua.wuxing_sketch) }}</text>
<text class="body-text">{{ toText(wuxingBagua.bagua_profile) }}</text>
<text class="body-text">{{ toText(wuxingBagua.mutual_sketch) }}</text>
<text class="body-text strong-end">{{ toText(wuxingBagua.summary) }}</text>
<text v-if="hasNodes(wuxingBagua.details?.nodes)" class="section-hint">点击本段查看详解</text>
</view>
<!-- zodiac_sign -->
<view
v-if="hasZodiac"
class="section"
:class="{ 'section--click': hasNodes(zodiacSign.details?.nodes) }"
@click="hasNodes(zodiacSign.details?.nodes) && openDetail('属相', zodiacSign.details?.nodes)"
>
<text class="section-label">zodiac_sign · 属相</text>
<text class="headline">
{{ toText(zodiacSign.animal_icon) }} {{ toText(zodiacSign.animal) }}{{ toText(zodiacSign.earthly_branch) }}
</text>
<text class="body-text">{{ toText(zodiacSign.trait_summary) }}</text>
<text class="body-text">{{ toText(zodiacSign.name_harmony) }}</text>
<text v-if="hasNodes(zodiacSign.details?.nodes)" class="section-hint">点击本段查看属相详解</text>
</view>
<!-- career_plan -->
<view
v-if="hasCareerPlan"
class="section"
:class="{ 'section--click': hasNodes(careerPlan.details?.nodes) }"
@click="hasNodes(careerPlan.details?.nodes) && openDetail('事业规划', careerPlan.details?.nodes)"
>
<text class="section-label">career_plan · 事业规划</text>
<text class="body-text">{{ toText(careerPlan.summary) }}</text>
<view v-for="(ms, msi) in arr(careerPlan.milestones)" :key="msi" class="milestone">
<text class="milestone-title">{{ toText(ms?.phase) }}{{ toText(ms?.period) ? ' · ' + toText(ms.period) : '' }}</text>
<text v-if="toText(ms?.focus)" class="body-text small">重点{{ toText(ms.focus) }}</text>
<text v-if="toText(ms?.advice)" class="body-text small">建议{{ toText(ms.advice) }}</text>
</view>
<text v-if="hasNodes(careerPlan.details?.nodes)" class="section-hint">点击本段查看规划详解</text>
</view>
<!-- lucky_numbers -->
<view
v-if="hasLuckyNumbers"
class="section"
:class="{ 'section--click': hasNodes(luckyNumbers.details?.nodes) }"
@click="hasNodes(luckyNumbers.details?.nodes) && openDetail('幸运数字', luckyNumbers.details?.nodes)"
>
<text class="section-label">lucky_numbers · 幸运数字</text>
<text class="headline">首推{{ toText(luckyNumbers.primary) }}</text>
<text class="body-text">{{ arr(luckyNumbers.numbers).join('、') }}</text>
<text class="body-text">{{ toText(luckyNumbers.meaning) }}</text>
<text v-if="hasNodes(luckyNumbers.details?.nodes)" class="section-hint">点击本段查看数字详解</text>
</view>
<!-- lucky_colors -->
<view
v-if="hasLuckyColors"
class="section"
:class="{ 'section--click': hasNodes(luckyColors.details?.nodes) }"
@click="hasNodes(luckyColors.details?.nodes) && openDetail('幸运色', luckyColors.details?.nodes)"
>
<text class="section-label">lucky_colors · 幸运色</text>
<text class="headline">主推{{ toText(luckyColors.primary) }}</text>
<view class="color-row">
<view v-for="(c, ci) in arr(luckyColors.colors)" :key="ci" class="color-item">
<view class="color-swatch" :style="{ backgroundColor: toText(c?.hex) || '#333' }" />
<text class="body-text small">{{ toText(c?.name) }}{{ toText(c?.note) ? ' · ' + toText(c.note) : '' }}</text>
</view>
</view>
<text class="body-text">{{ toText(luckyColors.meaning) }}</text>
<text v-if="hasNodes(luckyColors.details?.nodes)" class="section-hint">点击本段查看用色详解</text>
</view>
</view>
</scroll-view>
<view v-if="props.showBusinessFortune !== false" class="footer-action">
<button class="fortune-btn" type="button" @click="emit('businessFortune', detailData)">查看商业运势</button>
</view>
<view v-if="showModal" class="modal-mask" @click="closeModal">
<view class="detail-modal" @click.stop>
<view class="detail-modal-card">
<text class="modal-title">{{ modalTitle }}</text>
<text class="close" @click="closeModal">×</text>
</view>
<scroll-view scroll-y class="detail-modal-body">
<view v-for="(node, idx) in modalNodes" :key="idx" class="node">
<text v-if="node?.type === 'text'" class="line">{{ toText(node.text) }}</text>
<view v-else-if="node?.type === 'list'">
<text v-for="(item, j) in arr(node.items)" :key="j" class="line">- {{ toText(item) }}</text>
</view>
<view v-else-if="node?.type === 'kv'">
<text v-for="(item, j) in arr(node.items)" :key="j" class="line">{{ toText(item?.label) }}{{ toText(item?.value) }}</text>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import SixDimensionRadarDesktopEchart from "../SixDimensionRadarDesktopEchart.vue";
const props = defineProps<{
data: any;
showBusinessFortune?: boolean;
}>();
const emit = defineEmits<{
back: [];
businessFortune: [any];
}>();
const parseMaybeJson = (value: any) => {
if (value && typeof value === "object") return value;
if (typeof value !== "string") return {};
try {
return JSON.parse(value);
} catch {
return {};
}
};
const toText = (v: any) => String(v ?? "").trim();
const num = (v: any, fallback = 0) => {
const n = Number(v);
return Number.isFinite(n) ? n : fallback;
};
const arr = (v: any) => (Array.isArray(v) ? v : []);
const hasNodes = (nodes: any) => arr(nodes).length > 0;
const detailData = computed(() => parseMaybeJson(props.data));
const header = computed(() => detailData.value?.header || {});
const characterAnalysis = computed(() => detailData.value?.characterAnalysis || {});
const businessPattern = computed(() => detailData.value?.businessPattern || {});
const gua = computed(() => detailData.value?.gua || {});
const team = computed(() => detailData.value?.team || {});
const years = computed(() => detailData.value?.years || {});
const wealthTrend = computed(() => detailData.value?.wealthTrend || {});
const direction = computed(() => detailData.value?.direction || {});
const layout = computed(() => detailData.value?.layout || {});
const execution = computed(() => detailData.value?.execution || {});
const liuyao = computed(() => detailData.value?.liuyao || {});
const wuxingBagua = computed(() => detailData.value?.wuxing_bagua || {});
const zodiacSign = computed(() => detailData.value?.zodiac_sign || {});
const careerPlan = computed(() => detailData.value?.career_plan || {});
const luckyNumbers = computed(() => detailData.value?.lucky_numbers || {});
const luckyColors = computed(() => detailData.value?.lucky_colors || {});
const summaryText = computed(() =>
arr(businessPattern.value?.summary)
.map((x: any) => `${toText(x?.label)} ${toText(x?.value)}`)
.filter(Boolean)
.join(" · "),
);
const hasLiuyao = computed(
() =>
!!(
toText(liuyao.value?.hexagram_title) ||
toText(liuyao.value?.changing_summary) ||
toText(liuyao.value?.interpretation) ||
arr(liuyao.value?.yao_lines).length ||
hasNodes(liuyao.value?.details?.nodes)
),
);
const hasWuxingBagua = computed(
() =>
!!(
toText(wuxingBagua.value?.wuxing_sketch) ||
toText(wuxingBagua.value?.bagua_profile) ||
toText(wuxingBagua.value?.mutual_sketch) ||
toText(wuxingBagua.value?.summary) ||
hasNodes(wuxingBagua.value?.details?.nodes)
),
);
const hasZodiac = computed(
() =>
!!(
toText(zodiacSign.value?.animal) ||
toText(zodiacSign.value?.trait_summary) ||
toText(zodiacSign.value?.name_harmony) ||
hasNodes(zodiacSign.value?.details?.nodes)
),
);
const hasCareerPlan = computed(
() =>
!!(
toText(careerPlan.value?.summary) ||
arr(careerPlan.value?.milestones).length ||
hasNodes(careerPlan.value?.details?.nodes)
),
);
const hasLuckyNumbers = computed(
() =>
!!(toText(luckyNumbers.value?.primary) || arr(luckyNumbers.value?.numbers).length || toText(luckyNumbers.value?.meaning)),
);
const hasLuckyColors = computed(
() =>
!!(
toText(luckyColors.value?.primary) ||
arr(luckyColors.value?.colors).length ||
toText(luckyColors.value?.meaning)
),
);
const showModal = ref(false);
const modalTitle = ref("");
const modalNodes = ref<any[]>([]);
const openDetail = (title: string, nodes: any[]) => {
const list = arr(nodes);
if (!list.length) return;
modalTitle.value = toText(title) || "详情";
modalNodes.value = list;
showModal.value = true;
};
const closeModal = () => {
showModal.value = false;
};
</script>
<style scoped>
.company-desktop-detail {
min-height: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(165deg, #070a12 0%, #12102a 42%, #0a1628 100%);
color: #e8e4dc;
}
.detail-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: rgba(15, 23, 42, 0.72);
border-bottom: 1px solid rgba(212, 175, 55, 0.2);
flex-shrink: 0;
}
.detail-header-back {
display: inline-flex;
align-items: center;
gap: 6px;
color: #d4af37;
}
.detail-header-title {
font-size: 16px;
font-weight: 700;
color: #f2e6d8;
}
.detail-header-placeholder {
width: 48px;
}
.detail-content {
flex: 1;
min-height: 0;
box-sizing: border-box;
}
.report-body {
max-width: 820px;
margin: 0 auto;
padding: 16px 18px 20px;
box-sizing: border-box;
}
.section {
margin-bottom: 16px;
padding: 14px 16px;
border-radius: 12px;
background: rgba(15, 23, 42, 0.52);
border: 1px solid rgba(212, 175, 55, 0.14);
border-left: 3px solid rgba(212, 175, 55, 0.45);
backdrop-filter: blur(8px);
}
.section--wide {
max-width: 100%;
}
.section--click {
cursor: pointer;
}
.section--click:active {
opacity: 0.92;
}
.section-label {
display: block;
font-size: 11px;
letter-spacing: 0.06em;
color: rgba(212, 175, 55, 0.75);
margin-bottom: 10px;
font-weight: 600;
}
/* 商业六维内嵌 ECharts 组件自带外边距,报告式布局里收紧 */
:deep(.sixdim-section) {
margin-bottom: 0 !important;
}
.score-row {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 8px;
}
.name {
font-size: 22px;
font-weight: 800;
color: #f2e6d8;
}
.score {
font-size: 32px;
font-weight: 800;
color: #d4af37;
}
.tag-row,
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 10px;
}
.pill {
font-size: 11px;
padding: 4px 10px;
border-radius: 999px;
background: rgba(212, 175, 55, 0.12);
border: 1px solid rgba(212, 175, 55, 0.28);
color: #ede6d8;
}
.pill--soft {
background: rgba(255, 255, 255, 0.06);
border-color: rgba(148, 163, 184, 0.28);
color: rgba(232, 228, 220, 0.9);
}
.body-text {
display: block;
font-size: 13px;
line-height: 1.55;
color: rgba(232, 228, 220, 0.92);
margin-bottom: 8px;
}
.body-text.small {
font-size: 12px;
color: rgba(232, 228, 220, 0.82);
}
.body-text.note {
font-style: italic;
color: rgba(212, 175, 55, 0.65);
}
.mono {
font-family: ui-monospace, monospace;
}
.strong-end {
font-weight: 600;
color: #f0dba9;
}
.headline {
display: block;
font-size: 15px;
font-weight: 700;
color: #f4e5c4;
margin-bottom: 8px;
}
.gua-bg {
display: block;
font-size: 12px;
color: rgba(212, 175, 55, 0.8);
margin-bottom: 6px;
}
.section-hint {
display: block;
margin-top: 8px;
font-size: 11px;
color: rgba(148, 163, 184, 0.85);
}
.section-link {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba(212, 175, 55, 0.12);
font-size: 12px;
color: #d4af37;
}
.char-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 10px;
margin-bottom: 12px;
}
.char-box {
padding: 10px;
border-radius: 10px;
background: rgba(2, 6, 23, 0.4);
border: 1px solid rgba(212, 175, 55, 0.12);
}
.char-single {
font-size: 22px;
font-weight: 800;
}
.char-meta {
display: block;
font-size: 11px;
color: rgba(203, 213, 225, 0.8);
margin: 4px 0 6px;
}
.kv-inline {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.summary-chip {
padding: 6px 10px;
border-radius: 8px;
background: rgba(2, 6, 23, 0.45);
border: 1px solid rgba(148, 163, 184, 0.22);
}
.chip-k {
font-size: 11px;
color: rgba(203, 213, 225, 0.85);
margin-right: 6px;
}
.chip-v {
font-size: 12px;
font-weight: 700;
color: #f0dba9;
}
.member-block {
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.member-block:last-of-type {
border-bottom: none;
}
.member-line {
font-size: 13px;
font-weight: 600;
color: #f2e6d8;
}
.year-row {
display: grid;
grid-template-columns: 56px 52px 1fr;
gap: 8px;
align-items: start;
margin-bottom: 8px;
font-size: 12px;
}
.year-key {
color: #d4af37;
font-weight: 700;
}
.year-luck {
color: #a7f3d0;
}
.layout-line {
margin-bottom: 10px;
}
.layout-strong {
display: block;
font-size: 13px;
font-weight: 700;
color: #f0dba9;
margin-bottom: 4px;
}
.highlight {
color: #fde68a;
margin: 0 2px;
font-size: 12px;
}
.layout-flow {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 2px 6px;
}
.bars {
height: 96px;
display: flex;
align-items: flex-end;
gap: 6px;
margin: 12px 0;
}
.bar-wrap {
flex: 1;
height: 100%;
border-radius: 6px;
background: rgba(255, 255, 255, 0.06);
overflow: hidden;
display: flex;
align-items: flex-end;
}
.bar {
width: 100%;
background: linear-gradient(180deg, rgba(255, 205, 96, 0.9), rgba(230, 129, 38, 0.78));
}
.milestone {
margin-top: 10px;
padding: 10px;
border-radius: 10px;
background: rgba(2, 6, 23, 0.35);
border: 1px solid rgba(212, 175, 55, 0.1);
}
.milestone-title {
display: block;
font-size: 13px;
font-weight: 700;
color: #f4e5c4;
margin-bottom: 6px;
}
.color-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 10px 0;
}
.color-item {
display: flex;
align-items: center;
gap: 8px;
}
.color-swatch {
width: 28px;
height: 28px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.2);
flex-shrink: 0;
}
.footer-action {
padding: 10px 18px 14px;
flex-shrink: 0;
max-width: 820px;
width: 100%;
margin: 0 auto;
box-sizing: border-box;
}
.fortune-btn {
width: 100%;
border-radius: 10px;
padding: 10px 12px;
background: linear-gradient(135deg, rgba(139, 35, 35, 0.95), rgba(90, 20, 20, 0.98));
color: #fdfbf7;
border: 1px solid rgba(212, 175, 55, 0.35);
}
.modal-mask {
position: fixed;
inset: 0;
z-index: 3200;
background: rgba(2, 6, 23, 0.72);
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
box-sizing: border-box;
}
.detail-modal {
width: min(640px, 100%);
max-height: min(82vh, 760px);
background: rgba(10, 12, 20, 0.96);
border: 1px solid rgba(212, 175, 55, 0.2);
border-radius: 12px;
overflow: hidden;
}
.detail-modal-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
border-bottom: 1px solid rgba(212, 175, 55, 0.16);
}
.modal-title {
color: #d4af37;
font-size: 14px;
font-weight: 700;
}
.close {
color: #d4af37;
font-size: 20px;
}
.detail-modal-body {
max-height: calc(min(82vh, 760px) - 48px);
padding: 12px;
box-sizing: border-box;
}
.node {
margin-bottom: 8px;
}
.line {
display: block;
font-size: 13px;
line-height: 1.5;
color: rgba(232, 228, 220, 0.9);
margin-bottom: 6px;
}
</style>