786 lines
22 KiB
Vue
786 lines
22 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref, watch } from 'vue';
|
||
import { RDMS_OBJECT_DIRECTION_DICT_CODE, RDMS_PROJECT_TYPE_DICT_CODE } from '@/constants/dict';
|
||
import { fetchGetProject, fetchGetProjectMembers, fetchGetProjectSettings } from '@/service/api';
|
||
import { useDict } from '@/hooks/business/dict';
|
||
import { useCurrentProject } from '../../shared/use-current-project';
|
||
import {
|
||
buildProjectHomepageBanner,
|
||
buildProjectHomepageTimeline,
|
||
buildProjectScheduleOverview,
|
||
buildProjectTeamOverview,
|
||
getProjectHomepageExtensionModules
|
||
} from './homepage';
|
||
import { projectHomepageExtensionMock } from './mock';
|
||
|
||
defineOptions({ name: 'ProjectOverview' });
|
||
|
||
const { currentObjectId, currentProject } = useCurrentProject();
|
||
const { getLabel: getDirectionLabel } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE);
|
||
const { getLabel: getProjectTypeLabel } = useDict(RDMS_PROJECT_TYPE_DICT_CODE);
|
||
|
||
const pageLoading = ref(false);
|
||
const projectDetail = ref<Api.Project.Project | null>(null);
|
||
const settings = ref<Api.Project.ProjectSettings | null>(null);
|
||
const members = ref<Api.Project.ProjectMember[]>([]);
|
||
const latestActivityTime = ref('');
|
||
|
||
const timelineItems = computed(() => buildProjectHomepageTimeline(projectDetail.value, settings.value, members.value));
|
||
const scheduleOverview = computed(() => buildProjectScheduleOverview(projectDetail.value));
|
||
const teamOverview = computed(() => buildProjectTeamOverview(members.value));
|
||
const homepageBanner = computed(() =>
|
||
buildProjectHomepageBanner({
|
||
project: projectDetail.value,
|
||
settings: settings.value,
|
||
members: members.value,
|
||
latestActivityTime: latestActivityTime.value
|
||
})
|
||
);
|
||
const extensionModules = computed(() => getProjectHomepageExtensionModules(projectHomepageExtensionMock));
|
||
const directionLabel = computed(() => getDirectionLabel(homepageBanner.value.identity.directionCode, '--'));
|
||
const projectTypeLabel = computed(() => getProjectTypeLabel(homepageBanner.value.identity.projectType, '--'));
|
||
const bannerFacts = computed(() => [
|
||
{
|
||
label: '项目方向',
|
||
value: directionLabel.value,
|
||
fullWidth: false
|
||
},
|
||
{
|
||
label: '项目类型',
|
||
value: projectTypeLabel.value,
|
||
fullWidth: false
|
||
},
|
||
...homepageBanner.value.identity.facts
|
||
]);
|
||
const progressValue = computed(() => projectDetail.value?.progressRate ?? 0);
|
||
const bannerStatusClass = computed(() => {
|
||
const statusCode = homepageBanner.value.identity.statusCode;
|
||
|
||
return statusCode ? `project-homepage-banner--${statusCode}` : 'project-homepage-banner--default';
|
||
});
|
||
const bannerStatusWordClass = computed(() => {
|
||
const statusCode = homepageBanner.value.identity.statusCode;
|
||
|
||
return statusCode
|
||
? `project-homepage-banner__status-word--${statusCode}`
|
||
: 'project-homepage-banner__status-word--default';
|
||
});
|
||
|
||
async function loadOverviewData(objectId: string) {
|
||
pageLoading.value = true;
|
||
|
||
try {
|
||
const [projectResult, settingsResult, membersResult] = await Promise.all([
|
||
fetchGetProject(objectId),
|
||
fetchGetProjectSettings(objectId),
|
||
fetchGetProjectMembers(objectId)
|
||
]);
|
||
|
||
projectDetail.value = projectResult.error ? null : projectResult.data || null;
|
||
settings.value = settingsResult.error ? null : settingsResult.data || null;
|
||
members.value = membersResult.error ? [] : membersResult.data || [];
|
||
latestActivityTime.value = timelineItems.value[0]?.time || '';
|
||
} finally {
|
||
pageLoading.value = false;
|
||
}
|
||
}
|
||
|
||
watch(
|
||
() => currentObjectId.value,
|
||
async objectId => {
|
||
if (!objectId) {
|
||
projectDetail.value = null;
|
||
settings.value = null;
|
||
members.value = [];
|
||
latestActivityTime.value = '';
|
||
return;
|
||
}
|
||
|
||
await loadOverviewData(objectId);
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
</script>
|
||
|
||
<template>
|
||
<div v-loading="pageLoading" class="project-homepage">
|
||
<section class="project-homepage-banner" :class="bannerStatusClass">
|
||
<div class="project-homepage-banner__identity">
|
||
<div class="project-homepage-banner__title-group">
|
||
<div class="project-homepage-banner__title-main min-w-0">
|
||
<div class="project-homepage-banner__title-row">
|
||
<h1 class="project-homepage-banner__title">
|
||
{{ homepageBanner.identity.name || currentProject?.projectName || '--' }}
|
||
</h1>
|
||
<span class="project-homepage-banner__status-word" :class="bannerStatusWordClass">
|
||
{{ homepageBanner.identity.statusLabel }}
|
||
</span>
|
||
</div>
|
||
<div class="project-homepage-banner__subtitle">
|
||
<span class="project-homepage-banner__code">编号 {{ homepageBanner.identity.code }}</span>
|
||
<p v-if="homepageBanner.identity.description" class="project-homepage-banner__description">
|
||
{{ homepageBanner.identity.description }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="project-homepage-banner__facts">
|
||
<div
|
||
v-for="item in bannerFacts"
|
||
:key="item.label"
|
||
class="project-homepage-banner__fact"
|
||
:class="{ 'project-homepage-banner__fact--full': item.fullWidth }"
|
||
>
|
||
<span class="project-homepage-banner__fact-label">{{ item.label }}</span>
|
||
<strong class="project-homepage-banner__fact-value">{{ item.value }}</strong>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="project-homepage-banner__metrics">
|
||
<article v-for="item in homepageBanner.metrics" :key="item.label" class="project-homepage-banner__metric">
|
||
<span class="project-homepage-banner__metric-label">{{ item.label }}</span>
|
||
<strong class="project-homepage-banner__metric-value">{{ item.value }}</strong>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="project-homepage-main">
|
||
<ElCard class="project-homepage-panel card-wrapper">
|
||
<template #header>
|
||
<div>
|
||
<h3 class="project-homepage-panel__title">项目动态时间线</h3>
|
||
<p class="project-homepage-panel__desc">
|
||
先展示项目创建、状态动作、实际日期和团队变化,后续可替换为专用动态接口。
|
||
</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div v-if="timelineItems.length" class="project-homepage-timeline">
|
||
<article v-for="item in timelineItems" :key="item.key" class="project-homepage-timeline__item">
|
||
<div class="project-homepage-timeline__rail">
|
||
<span class="project-homepage-timeline__dot" :class="`project-homepage-timeline__dot--${item.tone}`" />
|
||
<span class="project-homepage-timeline__line" />
|
||
</div>
|
||
|
||
<div class="project-homepage-timeline__content">
|
||
<div class="project-homepage-timeline__meta">
|
||
<ElTag effect="plain" size="small">{{ item.tag }}</ElTag>
|
||
<span class="project-homepage-timeline__time">{{ item.time }}</span>
|
||
</div>
|
||
<p class="project-homepage-timeline__sentence">
|
||
<strong class="project-homepage-timeline__headline">{{ item.title }}</strong>
|
||
<span>{{ item.content }}</span>
|
||
</p>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
|
||
<ElEmpty v-else description="当前暂无可展示的项目动态" :image-size="88" />
|
||
</ElCard>
|
||
|
||
<div class="project-homepage-main__aside">
|
||
<ElCard class="project-homepage-panel card-wrapper">
|
||
<template #header>
|
||
<div>
|
||
<h3 class="project-homepage-panel__title">计划进展概览</h3>
|
||
<p class="project-homepage-panel__desc">先看当前进度、计划周期和实际执行日期是否已经闭环。</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="project-homepage-schedule">
|
||
<div class="project-homepage-schedule__progress">
|
||
<strong>{{ progressValue }}%</strong>
|
||
<ElProgress
|
||
:percentage="progressValue"
|
||
:stroke-width="8"
|
||
:show-text="false"
|
||
:color="progressValue >= 100 ? '#10b981' : progressValue >= 50 ? '#3b82f6' : '#6366f1'"
|
||
/>
|
||
</div>
|
||
|
||
<div class="project-homepage-summary-metrics">
|
||
<article
|
||
v-for="item in scheduleOverview.metrics"
|
||
:key="item.label"
|
||
class="project-homepage-summary-metrics__item"
|
||
>
|
||
<span class="project-homepage-summary-metrics__label">{{ item.label }}</span>
|
||
<strong class="project-homepage-summary-metrics__value">{{ item.value }}</strong>
|
||
</article>
|
||
</div>
|
||
|
||
<div class="project-homepage-schedule__dates">
|
||
<div v-for="item in scheduleOverview.dates" :key="item.label" class="project-homepage-schedule__date">
|
||
<span>{{ item.label }}</span>
|
||
<strong>{{ item.value }}</strong>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ElCard>
|
||
|
||
<ElCard class="project-homepage-panel card-wrapper">
|
||
<template #header>
|
||
<div>
|
||
<h3 class="project-homepage-panel__title">项目团队概览</h3>
|
||
<p class="project-homepage-panel__desc">承接当前成员规模、负责人和角色结构,和设置页团队维护分开表达。</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="project-homepage-team">
|
||
<div class="project-homepage-summary-metrics">
|
||
<article
|
||
v-for="item in teamOverview.metrics"
|
||
:key="item.label"
|
||
class="project-homepage-summary-metrics__item"
|
||
>
|
||
<span class="project-homepage-summary-metrics__label">{{ item.label }}</span>
|
||
<strong class="project-homepage-summary-metrics__value">{{ item.value }}</strong>
|
||
</article>
|
||
</div>
|
||
|
||
<div v-if="teamOverview.roles.length" class="project-homepage-team__roles">
|
||
<div v-for="item in teamOverview.roles" :key="item.label" class="project-homepage-team__role">
|
||
<span>{{ item.label }}</span>
|
||
<strong>{{ item.value }}</strong>
|
||
</div>
|
||
</div>
|
||
|
||
<ElEmpty v-else description="当前暂无有效团队成员" :image-size="72" />
|
||
</div>
|
||
</ElCard>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="project-homepage-extension">
|
||
<ElCard v-for="module in extensionModules" :key="module.key" class="project-homepage-panel card-wrapper">
|
||
<template #header>
|
||
<div>
|
||
<h3 class="project-homepage-panel__title">{{ module.title }}</h3>
|
||
<p class="project-homepage-panel__desc">{{ module.description }}</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="project-homepage-extension__list">
|
||
<div v-for="item in module.items" :key="item" class="project-homepage-extension__item">
|
||
<span class="project-homepage-extension__dot" />
|
||
<span>{{ item }}</span>
|
||
</div>
|
||
</div>
|
||
</ElCard>
|
||
</section>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.project-homepage {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.project-homepage-banner {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.55fr) minmax(320px, 1fr);
|
||
gap: 16px;
|
||
padding: 24px;
|
||
border: 1px solid rgb(226 232 240 / 92%);
|
||
border-radius: 24px;
|
||
background:
|
||
radial-gradient(circle at top left, rgb(14 116 144 / 14%), transparent 34%),
|
||
radial-gradient(circle at bottom right, rgb(15 118 110 / 10%), transparent 28%),
|
||
linear-gradient(135deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 98%));
|
||
}
|
||
|
||
.project-homepage-banner--default {
|
||
border-color: rgb(226 232 240 / 92%);
|
||
background:
|
||
radial-gradient(circle at top left, rgb(14 116 144 / 14%), transparent 34%),
|
||
radial-gradient(circle at bottom right, rgb(15 118 110 / 10%), transparent 28%),
|
||
linear-gradient(135deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 98%));
|
||
}
|
||
|
||
.project-homepage-banner--pending,
|
||
.project-homepage-banner--archived {
|
||
border-color: rgb(203 213 225 / 92%);
|
||
background:
|
||
radial-gradient(circle at top left, rgb(100 116 139 / 14%), transparent 34%),
|
||
radial-gradient(circle at bottom right, rgb(148 163 184 / 10%), transparent 26%),
|
||
linear-gradient(135deg, rgb(248 250 252 / 99%), rgb(255 255 255 / 98%));
|
||
}
|
||
|
||
.project-homepage-banner--active,
|
||
.project-homepage-banner--completed {
|
||
border-color: rgb(167 243 208 / 88%);
|
||
background:
|
||
radial-gradient(circle at top left, rgb(5 150 105 / 16%), transparent 32%),
|
||
radial-gradient(circle at bottom right, rgb(16 185 129 / 14%), transparent 26%),
|
||
linear-gradient(135deg, rgb(236 253 245 / 99%), rgb(255 255 255 / 98%));
|
||
}
|
||
|
||
.project-homepage-banner--paused {
|
||
border-color: rgb(253 230 138 / 90%);
|
||
background:
|
||
radial-gradient(circle at top left, rgb(245 158 11 / 18%), transparent 32%),
|
||
radial-gradient(circle at bottom right, rgb(251 191 36 / 16%), transparent 24%),
|
||
linear-gradient(135deg, rgb(255 251 235 / 99%), rgb(255 255 255 / 98%));
|
||
}
|
||
|
||
.project-homepage-banner--cancelled {
|
||
border-color: rgb(254 205 211 / 92%);
|
||
background:
|
||
radial-gradient(circle at top left, rgb(244 63 94 / 16%), transparent 32%),
|
||
radial-gradient(circle at bottom right, rgb(251 113 133 / 14%), transparent 24%),
|
||
linear-gradient(135deg, rgb(255 241 242 / 99%), rgb(255 255 255 / 98%));
|
||
}
|
||
|
||
.project-homepage-banner__identity {
|
||
display: flex;
|
||
min-width: 0;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.project-homepage-banner__title-group {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 12px;
|
||
}
|
||
|
||
.project-homepage-banner__title-main {
|
||
display: flex;
|
||
min-width: 0;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.project-homepage-banner__title-row {
|
||
display: flex;
|
||
min-width: 0;
|
||
align-items: baseline;
|
||
gap: 14px;
|
||
}
|
||
|
||
.project-homepage-banner__code {
|
||
margin: 0;
|
||
color: rgb(14 116 144 / 92%);
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.project-homepage-banner__title {
|
||
margin: 0;
|
||
color: rgb(15 23 42 / 98%);
|
||
font-size: 34px;
|
||
line-height: 1.15;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.project-homepage-banner__subtitle {
|
||
display: flex;
|
||
min-width: 0;
|
||
flex-wrap: wrap;
|
||
align-items: baseline;
|
||
gap: 10px 14px;
|
||
}
|
||
|
||
.project-homepage-banner__description {
|
||
margin: 0;
|
||
min-width: 0;
|
||
color: rgb(71 85 105 / 94%);
|
||
font-size: 14px;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
.project-homepage-banner__status-word {
|
||
flex-shrink: 0;
|
||
font-size: 26px;
|
||
font-weight: 800;
|
||
line-height: 1;
|
||
letter-spacing: 0.18em;
|
||
text-transform: uppercase;
|
||
user-select: none;
|
||
}
|
||
|
||
.project-homepage-banner__status-word--default {
|
||
color: rgb(148 163 184 / 48%);
|
||
}
|
||
|
||
.project-homepage-banner__status-word--pending,
|
||
.project-homepage-banner__status-word--archived {
|
||
color: transparent;
|
||
background: linear-gradient(180deg, rgb(71 85 105 / 92%), rgb(148 163 184 / 64%));
|
||
background-clip: text;
|
||
text-shadow: 0 10px 24px rgb(100 116 139 / 14%);
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
|
||
.project-homepage-banner__status-word--active,
|
||
.project-homepage-banner__status-word--completed {
|
||
color: transparent;
|
||
background: linear-gradient(180deg, rgb(5 150 105 / 94%), rgb(16 185 129 / 70%));
|
||
background-clip: text;
|
||
text-shadow: 0 10px 24px rgb(5 150 105 / 16%);
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
|
||
.project-homepage-banner__status-word--paused {
|
||
color: transparent;
|
||
background: linear-gradient(180deg, rgb(217 119 6 / 94%), rgb(245 158 11 / 70%));
|
||
background-clip: text;
|
||
text-shadow: 0 10px 24px rgb(245 158 11 / 16%);
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
|
||
.project-homepage-banner__status-word--cancelled {
|
||
color: transparent;
|
||
background: linear-gradient(180deg, rgb(244 63 94 / 94%), rgb(251 113 133 / 68%));
|
||
background-clip: text;
|
||
text-shadow: 0 10px 24px rgb(244 63 94 / 16%);
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
|
||
.project-homepage-banner__facts {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.project-homepage-banner__fact {
|
||
display: flex;
|
||
min-height: 58px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
padding: 14px 16px;
|
||
border: 1px solid rgb(226 232 240 / 88%);
|
||
border-radius: 18px;
|
||
background-color: rgb(255 255 255 / 78%);
|
||
}
|
||
|
||
.project-homepage-banner__fact--full {
|
||
grid-column: 1 / -1;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.project-homepage-banner__fact-label {
|
||
color: rgb(100 116 139 / 94%);
|
||
font-size: 13px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.project-homepage-banner__fact-value {
|
||
color: rgb(15 23 42 / 96%);
|
||
font-size: 15px;
|
||
line-height: 1.6;
|
||
text-align: right;
|
||
}
|
||
|
||
.project-homepage-banner__fact--full .project-homepage-banner__fact-value {
|
||
max-width: 72%;
|
||
text-align: left;
|
||
}
|
||
|
||
.project-homepage-banner__metrics {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.project-homepage-banner__metric {
|
||
display: flex;
|
||
min-height: 112px;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
gap: 16px;
|
||
padding: 18px;
|
||
border: 1px solid rgb(226 232 240 / 88%);
|
||
border-radius: 20px;
|
||
background: linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(241 245 249 / 98%));
|
||
}
|
||
|
||
.project-homepage-banner__metric-label {
|
||
color: rgb(100 116 139 / 92%);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.project-homepage-banner__metric-value {
|
||
color: rgb(15 23 42 / 98%);
|
||
font-size: 28px;
|
||
line-height: 1.1;
|
||
letter-spacing: 0;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.project-homepage-main {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.project-homepage-main__aside {
|
||
display: flex;
|
||
min-width: 0;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.project-homepage-panel {
|
||
overflow: hidden;
|
||
}
|
||
|
||
.project-homepage-panel__title {
|
||
margin: 0;
|
||
color: rgb(15 23 42 / 98%);
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.project-homepage-panel__desc {
|
||
margin: 4px 0 0;
|
||
color: rgb(100 116 139 / 92%);
|
||
font-size: 13px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.project-homepage-timeline {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.project-homepage-timeline__item {
|
||
display: grid;
|
||
grid-template-columns: 20px minmax(0, 1fr);
|
||
gap: 12px;
|
||
}
|
||
|
||
.project-homepage-timeline__rail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.project-homepage-timeline__dot {
|
||
width: 12px;
|
||
height: 12px;
|
||
margin-top: 6px;
|
||
border-radius: 999px;
|
||
box-shadow: 0 0 0 4px rgb(255 255 255 / 96%);
|
||
}
|
||
|
||
.project-homepage-timeline__dot--sky {
|
||
background-color: rgb(14 165 233 / 88%);
|
||
}
|
||
|
||
.project-homepage-timeline__dot--emerald {
|
||
background-color: rgb(5 150 105 / 88%);
|
||
}
|
||
|
||
.project-homepage-timeline__dot--amber {
|
||
background-color: rgb(217 119 6 / 88%);
|
||
}
|
||
|
||
.project-homepage-timeline__dot--rose {
|
||
background-color: rgb(225 29 72 / 88%);
|
||
}
|
||
|
||
.project-homepage-timeline__dot--slate {
|
||
background-color: rgb(100 116 139 / 88%);
|
||
}
|
||
|
||
.project-homepage-timeline__line {
|
||
flex: 1;
|
||
width: 2px;
|
||
min-height: 30px;
|
||
margin-top: 4px;
|
||
background: linear-gradient(180deg, rgb(203 213 225 / 96%), rgb(226 232 240 / 28%));
|
||
}
|
||
|
||
.project-homepage-timeline__item:last-child .project-homepage-timeline__line {
|
||
opacity: 0;
|
||
}
|
||
|
||
.project-homepage-timeline__content {
|
||
padding: 12px 14px;
|
||
border: 1px solid rgb(226 232 240 / 92%);
|
||
border-radius: 16px;
|
||
background-color: rgb(255 255 255 / 98%);
|
||
}
|
||
|
||
.project-homepage-timeline__meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
}
|
||
|
||
.project-homepage-timeline__time {
|
||
color: rgb(100 116 139 / 90%);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.project-homepage-timeline__sentence {
|
||
margin: 6px 0 0;
|
||
color: rgb(71 85 105 / 94%);
|
||
font-size: 13px;
|
||
line-height: 1.65;
|
||
}
|
||
|
||
.project-homepage-timeline__headline {
|
||
margin-right: 6px;
|
||
color: rgb(15 23 42 / 98%);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.project-homepage-schedule,
|
||
.project-homepage-team {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.project-homepage-schedule__progress {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
padding: 18px;
|
||
border-radius: 18px;
|
||
background: linear-gradient(180deg, rgb(248 250 252 / 98%), rgb(241 245 249 / 94%));
|
||
}
|
||
|
||
.project-homepage-schedule__progress strong {
|
||
color: rgb(15 23 42 / 98%);
|
||
font-size: 36px;
|
||
line-height: 1.1;
|
||
}
|
||
|
||
.project-homepage-summary-metrics {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.project-homepage-summary-metrics__item {
|
||
display: flex;
|
||
min-height: 100px;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
gap: 14px;
|
||
padding: 14px 16px;
|
||
border-radius: 18px;
|
||
background: linear-gradient(180deg, rgb(248 250 252 / 98%), rgb(241 245 249 / 94%));
|
||
}
|
||
|
||
.project-homepage-summary-metrics__label {
|
||
color: rgb(100 116 139 / 92%);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.project-homepage-summary-metrics__value {
|
||
color: rgb(15 23 42 / 98%);
|
||
font-size: 22px;
|
||
line-height: 1.2;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.project-homepage-schedule__dates,
|
||
.project-homepage-team__roles {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.project-homepage-schedule__date,
|
||
.project-homepage-team__role {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 13px 14px;
|
||
border: 1px solid rgb(226 232 240 / 88%);
|
||
border-radius: 14px;
|
||
background-color: rgb(255 255 255 / 96%);
|
||
color: rgb(51 65 85 / 95%);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.project-homepage-schedule__date strong,
|
||
.project-homepage-team__role strong {
|
||
color: rgb(15 23 42 / 98%);
|
||
font-size: 18px;
|
||
}
|
||
|
||
.project-homepage-extension {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.project-homepage-extension__list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.project-homepage-extension__item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 13px 14px;
|
||
border-radius: 16px;
|
||
background-color: rgb(248 250 252 / 96%);
|
||
color: rgb(51 65 85 / 95%);
|
||
font-size: 14px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.project-homepage-extension__dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
flex-shrink: 0;
|
||
border-radius: 999px;
|
||
background-color: rgb(14 116 144 / 88%);
|
||
}
|
||
|
||
@media (width <= 1280px) {
|
||
.project-homepage-banner,
|
||
.project-homepage-main,
|
||
.project-homepage-extension {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
@media (width <= 768px) {
|
||
.project-homepage-banner {
|
||
padding: 18px;
|
||
}
|
||
|
||
.project-homepage-banner__title-row {
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.project-homepage-banner__title {
|
||
font-size: 28px;
|
||
}
|
||
|
||
.project-homepage-banner__status-word {
|
||
font-size: 22px;
|
||
}
|
||
|
||
.project-homepage-banner__facts,
|
||
.project-homepage-banner__metrics,
|
||
.project-homepage-summary-metrics {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.project-homepage-banner__fact--full .project-homepage-banner__fact-value {
|
||
max-width: none;
|
||
}
|
||
}
|
||
</style>
|