Files
GoTunnel/web/src/components/PageShell.vue
2026-03-19 20:21:05 +08:00

155 lines
2.8 KiB
Vue

<script setup lang="ts">
defineProps<{
title: string
subtitle?: string
eyebrow?: string
}>()
</script>
<template>
<section class="page-shell">
<div class="page-shell__glow page-shell__glow--primary"></div>
<div class="page-shell__glow page-shell__glow--secondary"></div>
<header class="page-shell__header">
<div class="page-shell__heading">
<span v-if="eyebrow" class="page-shell__eyebrow">{{ eyebrow }}</span>
<h1>{{ title }}</h1>
<p v-if="subtitle">{{ subtitle }}</p>
</div>
<div v-if="$slots.actions" class="page-shell__actions">
<slot name="actions" />
</div>
</header>
<div v-if="$slots.metrics" class="page-shell__metrics">
<slot name="metrics" />
</div>
<div class="page-shell__content">
<slot />
</div>
</section>
</template>
<style scoped>
.page-shell {
position: relative;
padding: 32px;
overflow: hidden;
}
.page-shell__glow {
position: absolute;
border-radius: 999px;
filter: blur(80px);
opacity: 0.18;
pointer-events: none;
}
.page-shell__glow--primary {
width: 320px;
height: 320px;
top: -120px;
right: -80px;
background: var(--color-accent);
}
.page-shell__glow--secondary {
width: 280px;
height: 280px;
bottom: -120px;
left: -40px;
background: #8b5cf6;
}
.page-shell__header,
.page-shell__metrics,
.page-shell__content {
position: relative;
z-index: 1;
}
.page-shell__header {
display: flex;
justify-content: space-between;
gap: 20px;
align-items: flex-start;
margin-bottom: 24px;
}
.page-shell__heading {
max-width: 720px;
}
.page-shell__eyebrow {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
margin-bottom: 12px;
border-radius: 999px;
background: rgba(59, 130, 246, 0.12);
border: 1px solid rgba(59, 130, 246, 0.18);
color: var(--color-accent);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.04em;
}
.page-shell__heading h1 {
margin: 0;
font-size: clamp(28px, 4vw, 40px);
font-weight: 700;
letter-spacing: -0.03em;
color: var(--color-text-primary);
}
.page-shell__heading p {
margin: 10px 0 0;
max-width: 640px;
color: var(--color-text-secondary);
font-size: 15px;
line-height: 1.7;
}
.page-shell__actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 10px;
}
.page-shell__metrics {
display: grid;
gap: 16px;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
margin-bottom: 24px;
}
.page-shell__content {
display: flex;
flex-direction: column;
gap: 20px;
}
@media (max-width: 768px) {
.page-shell {
padding: 20px;
}
.page-shell__header {
flex-direction: column;
}
.page-shell__actions {
width: 100%;
justify-content: stretch;
}
.page-shell__actions :deep(*) {
flex: 1;
}
}
</style>