adding details
This commit is contained in:
231
frontend/src/views/ProductDetailView.vue
Normal file
231
frontend/src/views/ProductDetailView.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, RouterLink } from 'vue-router'
|
||||
import { api } from '@/api/client'
|
||||
|
||||
const route = useRoute()
|
||||
const product = ref(null)
|
||||
const loading = ref(true)
|
||||
const notFound = ref(false)
|
||||
|
||||
async function load(slug) {
|
||||
loading.value = true
|
||||
notFound.value = false
|
||||
try {
|
||||
product.value = await api.getProduct(slug)
|
||||
if (product.value?.name) document.title = `${product.value.name} — RisingCompute`
|
||||
} catch (e) {
|
||||
notFound.value = true
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => load(route.params.slug))
|
||||
watch(() => route.params.slug, (s) => s && load(s))
|
||||
|
||||
const interestMap = {
|
||||
ai: 'ai-ip',
|
||||
security: 'security-ip',
|
||||
comms: 'comms-ip',
|
||||
asic: 'custom-asic',
|
||||
}
|
||||
const contactInterest = computed(() => interestMap[product.value?.category] || 'other')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="loading" class="container section dim mono">Loading…</div>
|
||||
|
||||
<section v-else-if="notFound" class="section text-center">
|
||||
<div class="container">
|
||||
<h1>Product not found.</h1>
|
||||
<p class="muted">It may have moved, or never existed.</p>
|
||||
<RouterLink to="/products" class="btn btn--ghost">Back to products</RouterLink>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<template v-else>
|
||||
<!-- HERO -->
|
||||
<section class="product-hero">
|
||||
<div class="container">
|
||||
<RouterLink to="/products" class="back-link">← All products</RouterLink>
|
||||
<span class="pill">{{ product.category_label }}</span>
|
||||
<h1>{{ product.name }}</h1>
|
||||
<p class="product-hero__tagline">{{ product.tagline }}</p>
|
||||
<div class="product-hero__cta">
|
||||
<RouterLink :to="{ path: '/contact', query: { interest: contactInterest } }" class="btn btn--primary">
|
||||
{{ product.primary_cta_label }}
|
||||
</RouterLink>
|
||||
<RouterLink to="/contact" class="btn btn--ghost">Request datasheet →</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- OVERVIEW + BENEFITS -->
|
||||
<section class="section">
|
||||
<div class="container product-grid">
|
||||
<div class="product-grid__copy">
|
||||
<span class="eyebrow">Overview</span>
|
||||
<p class="product-grid__lead">{{ product.summary }}</p>
|
||||
<p>{{ product.description }}</p>
|
||||
</div>
|
||||
|
||||
<aside class="product-grid__side">
|
||||
<h3 class="aside-h">Benefits</h3>
|
||||
<ul class="check-list">
|
||||
<li v-for="b in product.benefits" :key="b">
|
||||
<span class="check">✓</span><span>{{ b }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FEATURES -->
|
||||
<section class="section section--tight features-section" v-if="product.features?.length">
|
||||
<div class="container">
|
||||
<span class="eyebrow">What's included</span>
|
||||
<h2>Features</h2>
|
||||
<div class="grid grid--3 features">
|
||||
<div v-for="f in product.features" :key="f" class="feature">
|
||||
<span class="feature__dot mono">+</span>
|
||||
<p>{{ f }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SPECS -->
|
||||
<section class="section" v-if="product.spec_table?.length">
|
||||
<div class="container">
|
||||
<span class="eyebrow">Spec sheet</span>
|
||||
<h2>At a glance</h2>
|
||||
<div class="specs">
|
||||
<div v-for="row in product.spec_table" :key="row.label" class="spec-row">
|
||||
<span class="spec-row__label mono">{{ row.label }}</span>
|
||||
<span class="spec-row__value">{{ row.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="dim mono spec-note">
|
||||
Full numbers, gate counts, and target-node performance disclosed under NDA.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="section cta">
|
||||
<div class="container cta__inner">
|
||||
<h2>Evaluate {{ product.name }}.</h2>
|
||||
<p class="muted">Get a datasheet, a reference design, and a 30-minute architect call.</p>
|
||||
<RouterLink :to="{ path: '/contact', query: { interest: contactInterest } }" class="btn btn--primary">
|
||||
{{ product.primary_cta_label }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
color: var(--color-text-dim);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.back-link:hover { color: var(--color-accent); }
|
||||
|
||||
.product-hero {
|
||||
padding: var(--space-8) 0 var(--space-6);
|
||||
border-bottom: 1px solid var(--color-border-soft);
|
||||
background: radial-gradient(800px 360px at 90% 0%, rgba(0, 229, 255, 0.08), transparent 60%);
|
||||
}
|
||||
.product-hero h1 { margin-top: var(--space-3); }
|
||||
.product-hero__tagline { color: var(--color-accent); font-size: 1.2rem; max-width: 720px; }
|
||||
.product-hero__cta { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-top: var(--space-5); }
|
||||
|
||||
.product-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.6fr 1fr;
|
||||
gap: var(--space-7);
|
||||
}
|
||||
@media (max-width: 900px) { .product-grid { grid-template-columns: 1fr; } }
|
||||
.product-grid__lead { font-size: 1.15rem; color: var(--color-text); }
|
||||
.product-grid__side {
|
||||
background: linear-gradient(180deg, var(--color-surface), var(--color-surface-2));
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-5);
|
||||
position: sticky;
|
||||
top: calc(var(--nav-height) + var(--space-4));
|
||||
}
|
||||
.aside-h {
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-dim);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.check-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.check-list li {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
align-items: flex-start;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.check {
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.features-section { background: linear-gradient(180deg, transparent, var(--color-bg-soft), transparent); border-block: 1px solid var(--color-border-soft); }
|
||||
.feature {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
align-items: flex-start;
|
||||
padding: var(--space-3) 0;
|
||||
}
|
||||
.feature__dot {
|
||||
flex: 0 0 28px;
|
||||
width: 28px; height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-accent-soft);
|
||||
color: var(--color-accent);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.feature p { margin: 0; color: var(--color-text); }
|
||||
|
||||
.specs {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
.spec-row {
|
||||
display: grid;
|
||||
grid-template-columns: 200px 1fr;
|
||||
padding: var(--space-4) var(--space-5);
|
||||
border-top: 1px solid var(--color-border-soft);
|
||||
}
|
||||
.spec-row:first-child { border-top: 0; }
|
||||
.spec-row__label { color: var(--color-text-dim); font-size: 0.85rem; letter-spacing: 0.05em; text-transform: uppercase; }
|
||||
.spec-row__value { color: var(--color-text); }
|
||||
@media (max-width: 640px) {
|
||||
.spec-row { grid-template-columns: 1fr; gap: var(--space-1); }
|
||||
}
|
||||
.spec-note { margin-top: var(--space-3); font-size: 0.78rem; }
|
||||
|
||||
.cta {
|
||||
text-align: center;
|
||||
background: radial-gradient(600px 240px at 50% 50%, rgba(0, 229, 255, 0.1), transparent 70%);
|
||||
}
|
||||
.cta__inner { max-width: 640px; margin: 0 auto; }
|
||||
.cta p { margin-bottom: var(--space-5); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user