232 lines
7.1 KiB
Vue
Executable File
232 lines
7.1 KiB
Vue
Executable File
<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>
|