bstein-dev-home/frontend/src/components/HomeHeroStatus.vue

352 lines
7.5 KiB
Vue
Raw Normal View History

2026-06-29 14:49:49 -03:00
<template>
<section class="hero-section" aria-labelledby="home-title">
<div class="hero-copy">
<p class="eyebrow mono">{{ content.eyebrow }}</p>
<h1 id="home-title">{{ content.professionalName }}</h1>
<h2>{{ content.role }}</h2>
<p class="hero-summary">{{ content.summary }}</p>
<p class="experience-line">{{ content.experienceLine }}</p>
<p class="availability-line">{{ content.availability.label }}</p>
<div class="cta-row" aria-label="Professional actions">
<a class="btn primary-action" :href="mailtoHref">Discuss a Project</a>
<RouterLink class="btn secondary" :to="content.resumeUrl">View R&eacute;sum&eacute;</RouterLink>
<a class="btn tertiary" href="#platform">Explore the Live Platform</a>
</div>
<div class="tech-list" aria-label="Core technologies">
<span v-for="tech in content.techStack" :key="tech" class="pill mono">{{ tech }}</span>
</div>
</div>
<aside class="status-card glass" aria-labelledby="status-title">
<div class="status-head">
<p class="eyebrow mono">Live platform</p>
<h3 id="status-title">Titan Lab Status</h3>
</div>
<div class="status-list">
<div v-for="item in statusItems" :key="item.label" class="status-row">
<span class="status-dot" :class="item.state" aria-hidden="true"></span>
2026-06-29 16:45:06 -03:00
<div class="status-row-copy">
2026-06-29 14:49:49 -03:00
<a v-if="item.labelHref" class="status-label status-title-link" :href="item.labelHref">
{{ item.label }}
</a>
<div v-else class="status-label">{{ item.label }}</div>
<a
v-if="item.href && isExternal(item.href)"
class="status-value mono status-link"
:href="item.href"
target="_blank"
rel="noopener noreferrer"
>
{{ item.value }}
</a>
<RouterLink v-else-if="item.href" class="status-value mono status-link" :to="item.href">
{{ item.value }}
</RouterLink>
<div v-else class="status-value mono">{{ item.value }}</div>
2026-06-29 16:45:06 -03:00
<div v-if="item.children?.length" class="status-children" :aria-label="`${item.label} details`">
<div v-for="child in item.children" :key="child.label" class="status-child">
<span class="status-child-dot" :class="child.state" aria-hidden="true"></span>
<span class="status-child-label">{{ child.label }}</span>
<span class="status-child-value mono">{{ child.value }}</span>
</div>
</div>
2026-06-29 14:49:49 -03:00
</div>
</div>
</div>
<p class="status-note mono">Last checked: {{ lastCheckedLabel }}</p>
<p v-if="error || (!loading && !labStatus?.connected)" class="status-note">
Live status is temporarily unavailable. The platform overview remains available below.
</p>
</aside>
</section>
</template>
<script setup>
import { RouterLink } from "vue-router";
defineProps({
content: Object,
statusItems: Array,
labStatus: Object,
loading: Boolean,
error: String,
lastCheckedLabel: String,
mailtoHref: String,
});
function isExternal(href) {
return typeof href === "string" && /^https?:\/\//.test(href);
}
</script>
<style scoped>
.hero-section {
position: relative;
isolation: isolate;
display: grid;
grid-template-columns: minmax(0, 1.75fr) minmax(320px, 0.85fr);
gap: 26px;
align-items: center;
min-height: calc(100vh - 86px);
padding: 42px 0 30px;
overflow: hidden;
}
.hero-section::before,
.hero-section::after {
content: "";
position: absolute;
pointer-events: none;
z-index: -1;
}
.hero-section::before {
inset: 16% -8vw 8% 50%;
border-radius: 999px;
background:
radial-gradient(ellipse at 68% 42%, rgba(0, 229, 197, 0.17), transparent 32%),
radial-gradient(ellipse at 28% 66%, rgba(120, 180, 255, 0.11), transparent 36%),
radial-gradient(ellipse at center, rgba(11, 24, 46, 0.58), transparent 72%);
opacity: 0.8;
filter: blur(2px);
}
.hero-section::after {
width: min(300px, 24vw);
top: 28%;
right: 6vw;
bottom: 22%;
border-radius: 999px;
background:
radial-gradient(ellipse at center, rgba(0, 229, 197, 0.16), transparent 64%),
linear-gradient(90deg, transparent, rgba(120, 180, 255, 0.08), transparent);
opacity: 0.32;
filter: blur(18px);
}
.hero-copy,
.status-card {
position: relative;
z-index: 1;
}
.eyebrow {
color: var(--accent-cyan);
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.06em;
margin: 0 0 8px;
}
.hero-copy h1 {
font-size: clamp(44px, 7vw, 72px);
line-height: 0.98;
margin: 0;
letter-spacing: 0;
}
.hero-copy h2 {
font-size: clamp(26px, 3.2vw, 36px);
margin: 12px 0 0;
color: var(--text-primary);
letter-spacing: 0;
}
.hero-summary {
max-width: 760px;
margin: 20px 0 0;
font-size: clamp(18px, 2.2vw, 22px);
line-height: 1.55;
color: var(--text-strong);
}
.experience-line,
.availability-line {
max-width: 760px;
margin: 14px 0 0;
font-size: 17px;
line-height: 1.65;
}
.availability-line {
color: rgba(170, 255, 215, 0.94);
}
.tech-list,
.cta-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.tech-list {
margin-top: 22px;
}
.cta-row {
margin-top: 28px;
}
.primary-action {
background: linear-gradient(135deg, rgba(0, 229, 197, 0.9), rgba(120, 180, 255, 0.82));
color: #02141d;
}
.tertiary {
background: transparent;
color: var(--text-strong);
}
.status-card {
padding: 20px;
min-width: 0;
}
.status-head h3 {
margin: 0 0 14px;
font-size: 28px;
}
.status-list {
display: grid;
gap: 14px;
}
.status-row {
display: grid;
grid-template-columns: 14px 1fr;
gap: 12px;
align-items: start;
padding: 12px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius-sm);
background: rgba(255, 255, 255, 0.03);
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 999px;
margin-top: 4px;
background: var(--text-muted);
}
.status-dot.ok {
background: var(--accent-cyan);
}
2026-06-29 16:45:06 -03:00
.status-dot.warn {
background: #f2c94c;
}
2026-06-29 14:49:49 -03:00
.status-dot.bad {
background: var(--accent-rose);
}
2026-06-29 16:45:06 -03:00
.status-row-copy {
min-width: 0;
}
2026-06-29 14:49:49 -03:00
.status-label {
font-weight: 800;
color: var(--text-strong);
}
.status-title-link {
display: inline-flex;
text-decoration: none;
}
.status-title-link:hover {
color: var(--accent-cyan);
}
.status-value,
.status-note {
color: var(--text-muted);
}
.status-link {
display: inline-flex;
color: var(--accent-cyan);
}
2026-06-29 16:45:06 -03:00
.status-children {
display: grid;
gap: 6px;
margin-top: 10px;
}
.status-child {
display: grid;
grid-template-columns: 9px minmax(0, 1fr) auto;
gap: 8px;
align-items: center;
color: var(--text-muted);
font-size: 13px;
}
.status-child-dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: var(--text-muted);
}
.status-child-dot.ok {
background: var(--accent-cyan);
}
.status-child-dot.warn {
background: #f2c94c;
}
.status-child-dot.bad {
background: var(--accent-rose);
}
.status-child-value {
color: var(--text-strong);
font-size: 12px;
}
2026-06-29 14:49:49 -03:00
.status-note {
margin: 14px 0 0;
}
@media (min-width: 1180px) {
.hero-copy h2 {
white-space: nowrap;
}
}
@media (max-width: 1080px) {
.hero-section {
grid-template-columns: 1fr;
min-height: auto;
}
.hero-section::before {
inset: 96px -22vw 24px 18%;
opacity: 0.34;
}
.hero-section::after {
display: none;
}
}
@media (max-width: 760px) {
.hero-section {
padding-top: 28px;
}
.hero-section::before {
inset: 84px -44vw 18px 8%;
opacity: 0.28;
}
}
</style>