adding details

This commit is contained in:
abhishekpythons
2026-05-19 04:38:02 +05:30
parent 246b5381ce
commit a16520420d
63 changed files with 6123 additions and 1 deletions

5
frontend/.env.example Normal file
View File

@@ -0,0 +1,5 @@
# Frontend env — copy to `.env` (or `.env.local`).
# Dev: leave empty to use the Vite proxy → http://127.0.0.1:8000
# Prod: set the absolute API base, e.g. https://risingcompute.in
VITE_API_BASE_URL=
VITE_API_PROXY=http://127.0.0.1:8000

31
frontend/index.html Normal file
View File

@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0B1437" />
<meta
name="description"
content="RisingCompute — parallel hardware architectures and IP cores for AI, space, and robotics. Designed in India, flown in orbit."
/>
<meta property="og:title" content="RisingCompute — Accelerating the compute" />
<meta
property="og:description"
content="Parallel hardware architectures and IP cores for AI, space, and robotics."
/>
<meta property="og:type" content="website" />
<meta property="og:url" content="https://risingcompute.in" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
rel="stylesheet"
/>
<title>RisingCompute — Accelerating the compute</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

20
frontend/package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "risingcompute-web",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview --port 4173"
},
"dependencies": {
"axios": "^1.7.2",
"vue": "^3.4.27",
"vue-router": "^4.3.3"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.5",
"vite": "^5.2.13"
}
}

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#0B1437"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
</defs>
<rect width="64" height="64" rx="14" fill="url(#g)"/>
<path d="M16 44 L26 28 L36 38 L48 18" stroke="#F4F6FB" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 452 B

23
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,23 @@
<script setup>
import TheHeader from '@/components/TheHeader.vue'
import TheFooter from '@/components/TheFooter.vue'
</script>
<template>
<TheHeader />
<main>
<RouterView v-slot="{ Component }">
<transition name="route-fade" mode="out-in">
<component :is="Component" />
</transition>
</RouterView>
</main>
<TheFooter />
</template>
<style scoped>
main {
flex: 1;
padding-top: var(--nav-height);
}
</style>

View File

@@ -0,0 +1,32 @@
import axios from 'axios'
// In dev, leave VITE_API_BASE_URL empty and let Vite proxy /api → Django.
// In prod, set VITE_API_BASE_URL=https://api.risingcompute.in (or same origin).
const baseURL = (import.meta.env.VITE_API_BASE_URL || '') + '/api'
const client = axios.create({
baseURL,
timeout: 15000,
headers: { 'Content-Type': 'application/json' },
})
export const api = {
// Content
listProducts: () => client.get('/products/').then(r => r.data),
getProduct: (slug) => client.get(`/products/${slug}/`).then(r => r.data),
listFounders: () => client.get('/founders/').then(r => r.data),
listPosts: () => client.get('/posts/').then(r => r.data),
getPost: (slug) => client.get(`/posts/${slug}/`).then(r => r.data),
listJobs: () => client.get('/jobs/').then(r => r.data),
// Submissions
submitContact: (payload) => client.post('/contact/', payload).then(r => r.data),
subscribeNewsletter: (email, source = '') =>
client.post('/newsletter/', { email, source }).then(r => r.data),
submitApplication: (formData) =>
client.post('/apply/', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
}).then(r => r.data),
}
export default client

View File

@@ -0,0 +1,342 @@
/* ===================================================================
* RisingCompute — Design tokens & base styles
* Brand: Fast · Reliable · Robust
* Dark theme by default. Indigo + Cyan + minimal motion.
* =================================================================== */
:root {
/* Brand palette — see design-document §7.2 */
--color-bg: #0B1437; /* Deep Space Indigo */
--color-bg-soft: #111B47;
--color-surface: #1A2347; /* Slate Navy — cards */
--color-surface-2: #232C57;
--color-border: #2A3463;
--color-border-soft: #1F2750;
--color-text: #F4F6FB; /* Mist */
--color-text-muted: #B6BED2;
--color-text-dim: #8A93A6; /* Steel */
--color-accent: #00E5FF; /* Accelerate Cyan */
--color-accent-soft: rgba(0, 229, 255, 0.16);
--color-accent-glow: rgba(0, 229, 255, 0.35);
--color-success: #22C55E;
--color-warning: #F59E0B;
--color-error: #EF4444;
/* Typography */
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
/* Spacing scale */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.5rem;
--space-6: 2rem;
--space-7: 3rem;
--space-8: 4rem;
--space-9: 6rem;
--space-10: 8rem;
/* Radii */
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 16px;
--radius-xl: 22px;
/* Layout */
--max-content: 1240px;
--nav-height: 72px;
/* Motion */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--transition-fast: 120ms var(--ease-out);
--transition: 220ms var(--ease-out);
--transition-slow: 420ms var(--ease-out);
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
html, body {
margin: 0;
padding: 0;
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-sans);
font-size: 16px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
body {
background:
radial-gradient(1100px 600px at 80% -10%, rgba(0, 229, 255, 0.08), transparent 60%),
radial-gradient(900px 500px at 0% 0%, rgba(100, 120, 255, 0.06), transparent 60%),
var(--color-bg);
min-height: 100vh;
}
#app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* ---------- Typography ---------- */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-sans);
font-weight: 600;
line-height: 1.15;
letter-spacing: -0.02em;
margin: 0 0 var(--space-4);
color: var(--color-text);
}
h1 { font-size: clamp(2.25rem, 5vw, 3.75rem); font-weight: 700; letter-spacing: -0.03em; }
h2 { font-size: clamp(1.75rem, 3vw, 2.5rem); }
h3 { font-size: 1.5rem; }
h4 { font-size: 1.25rem; }
p { margin: 0 0 var(--space-4); color: var(--color-text-muted); }
small { color: var(--color-text-dim); }
a {
color: var(--color-accent);
text-decoration: none;
transition: color var(--transition-fast);
}
a:hover { color: var(--color-text); }
code, pre, kbd {
font-family: var(--font-mono);
font-size: 0.9em;
}
/* ---------- Layout ---------- */
.container {
width: 100%;
max-width: var(--max-content);
margin: 0 auto;
padding: 0 var(--space-5);
}
.section {
padding: var(--space-9) 0;
}
.section--tight { padding: var(--space-7) 0; }
.eyebrow {
display: inline-flex;
align-items: center;
gap: var(--space-2);
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-accent);
}
.eyebrow::before {
content: '';
display: inline-block;
width: 10px;
height: 1px;
background: var(--color-accent);
}
/* ---------- Buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
font-family: var(--font-sans);
font-weight: 500;
font-size: 0.95rem;
line-height: 1;
padding: 0.85rem 1.4rem;
border-radius: var(--radius-md);
border: 1px solid transparent;
cursor: pointer;
text-decoration: none;
transition: all var(--transition);
white-space: nowrap;
}
.btn:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 3px;
}
.btn--primary {
background: var(--color-accent);
color: #001722;
font-weight: 600;
}
.btn--primary:hover {
background: #5BF0FF;
box-shadow: 0 8px 28px -8px var(--color-accent-glow);
color: #001722;
transform: translateY(-1px);
}
.btn--ghost {
background: transparent;
color: var(--color-text);
border-color: var(--color-border);
}
.btn--ghost:hover {
border-color: var(--color-accent);
color: var(--color-accent);
}
.btn--link {
background: transparent;
padding: 0;
color: var(--color-accent);
}
.btn--link:hover { color: var(--color-text); }
.btn--full { width: 100%; }
/* ---------- Cards ---------- */
.card {
background: linear-gradient(180deg, var(--color-surface) 0%, var(--color-surface-2) 100%);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--space-6);
transition: border-color var(--transition), transform var(--transition);
}
.card:hover {
border-color: rgba(0, 229, 255, 0.35);
transform: translateY(-3px);
}
/* ---------- Forms ---------- */
.field {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-bottom: var(--space-4);
}
.field label {
font-size: 0.85rem;
color: var(--color-text-muted);
font-weight: 500;
}
.field input,
.field select,
.field textarea {
width: 100%;
background: var(--color-bg-soft);
color: var(--color-text);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: 0.8rem 1rem;
font-family: var(--font-sans);
font-size: 0.95rem;
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.field input::placeholder,
.field textarea::placeholder { color: var(--color-text-dim); }
.field input:focus,
.field select:focus,
.field textarea:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: 0 0 0 3px var(--color-accent-soft);
}
.field textarea {
min-height: 140px;
resize: vertical;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-4);
}
@media (max-width: 640px) {
.form-row { grid-template-columns: 1fr; }
}
.form-error {
color: var(--color-error);
font-size: 0.85rem;
}
.form-success {
background: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.35);
color: var(--color-success);
padding: var(--space-4);
border-radius: var(--radius-md);
font-size: 0.95rem;
}
/* ---------- Pills / Tags ---------- */
.pill {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: 0.3rem 0.7rem;
border-radius: 999px;
font-family: var(--font-mono);
font-size: 0.72rem;
font-weight: 500;
letter-spacing: 0.05em;
text-transform: uppercase;
background: var(--color-accent-soft);
color: var(--color-accent);
border: 1px solid rgba(0, 229, 255, 0.25);
}
/* ---------- Grids ---------- */
.grid {
display: grid;
gap: var(--space-5);
}
.grid--3 { grid-template-columns: repeat(3, 1fr); }
.grid--2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 900px) {
.grid--3, .grid--2 { grid-template-columns: 1fr; }
}
/* ---------- Page transitions ---------- */
.route-fade-enter-active,
.route-fade-leave-active {
transition: opacity 200ms var(--ease-out);
}
.route-fade-enter-from,
.route-fade-leave-to {
opacity: 0;
}
/* ---------- Utility ---------- */
.text-center { text-align: center; }
.muted { color: var(--color-text-muted); }
.dim { color: var(--color-text-dim); }
.mono { font-family: var(--font-mono); }
.divider {
height: 1px;
background: linear-gradient(90deg, transparent, var(--color-border), transparent);
margin: var(--space-7) 0;
border: 0;
}
/* Reduce motion for users who request it */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
transition-duration: 0.001ms !important;
}
}

View File

@@ -0,0 +1,129 @@
<script setup>
import { reactive, ref } from 'vue'
import { api } from '@/api/client'
const props = defineProps({
defaultInterest: { type: String, default: 'other' },
})
const form = reactive({
name: '',
email: '',
company: '',
role: '',
country: '',
interest: props.defaultInterest,
message: '',
referrer: typeof document !== 'undefined' ? document.referrer : '',
})
const status = ref('idle') // idle | loading | success | error
const errors = ref({})
const errorMessage = ref('')
async function submit() {
status.value = 'loading'
errors.value = {}
errorMessage.value = ''
try {
await api.submitContact(form)
status.value = 'success'
} catch (err) {
status.value = 'error'
if (err.response?.data && typeof err.response.data === 'object') {
errors.value = err.response.data
errorMessage.value = 'Please check the fields below.'
} else {
errorMessage.value = 'Something went wrong. Please try again.'
}
}
}
</script>
<template>
<form class="contact-form" @submit.prevent="submit" novalidate>
<template v-if="status !== 'success'">
<div class="form-row">
<div class="field">
<label for="cf-name">Name</label>
<input id="cf-name" v-model="form.name" type="text" required autocomplete="name" />
<span v-if="errors.name" class="form-error">{{ errors.name[0] }}</span>
</div>
<div class="field">
<label for="cf-email">Work email</label>
<input id="cf-email" v-model="form.email" type="email" required autocomplete="email" />
<span v-if="errors.email" class="form-error">{{ errors.email[0] }}</span>
</div>
</div>
<div class="form-row">
<div class="field">
<label for="cf-company">Company</label>
<input id="cf-company" v-model="form.company" type="text" autocomplete="organization" />
</div>
<div class="field">
<label for="cf-role">Role</label>
<input id="cf-role" v-model="form.role" type="text" />
</div>
</div>
<div class="form-row">
<div class="field">
<label for="cf-country">Country</label>
<input id="cf-country" v-model="form.country" type="text" autocomplete="country-name" />
</div>
<div class="field">
<label for="cf-interest">Interest</label>
<select id="cf-interest" v-model="form.interest">
<option value="ai-ip">AI Inference IP</option>
<option value="security-ip">Cybersecurity IP</option>
<option value="comms-ip">Communication IP</option>
<option value="custom-asic">Custom ASIC</option>
<option value="careers">Careers</option>
<option value="press">Press</option>
<option value="other">Other</option>
</select>
</div>
</div>
<div class="field">
<label for="cf-message">What are you looking for?</label>
<textarea
id="cf-message"
v-model="form.message"
required
placeholder="A few lines about the project, target node / FPGA, and timeline."
></textarea>
<span v-if="errors.message" class="form-error">{{ errors.message[0] }}</span>
</div>
<p v-if="errorMessage" class="form-error">{{ errorMessage }}</p>
<button type="submit" class="btn btn--primary btn--full" :disabled="status === 'loading'">
{{ status === 'loading' ? 'Sending…' : 'Send enquiry' }}
</button>
<p class="dim contact-form__legal">
We typically reply within one business day. By submitting, you agree to be
contacted about your enquiry.
</p>
</template>
<div v-else class="form-success">
<h3>Thanks message received.</h3>
<p>One of the founders will follow up at <strong>{{ form.email }}</strong> within one business day.</p>
</div>
</form>
</template>
<style scoped>
.contact-form {
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-6);
}
.contact-form__legal {
font-size: 0.78rem;
margin: var(--space-3) 0 0;
}
</style>

View File

@@ -0,0 +1,72 @@
<script setup>
defineProps({
founder: {
type: Object,
required: true,
},
})
function initials(name) {
return name.split(' ').map(p => p[0]).slice(0, 2).join('').toUpperCase()
}
</script>
<template>
<article class="founder">
<div class="founder__avatar" :aria-hidden="!!founder.photo_url">
<img v-if="founder.photo_url" :src="founder.photo_url" :alt="founder.name" />
<span v-else>{{ initials(founder.name) }}</span>
</div>
<h3 class="founder__name">{{ founder.name }}</h3>
<p class="founder__role">{{ founder.role }}</p>
<p class="founder__domain mono">{{ founder.domain }}</p>
<p class="founder__bio">{{ founder.bio }}</p>
<a v-if="founder.linkedin_url" :href="founder.linkedin_url" target="_blank" rel="noopener" class="btn--link">
LinkedIn
</a>
</article>
</template>
<style scoped>
.founder {
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-6);
display: flex;
flex-direction: column;
gap: var(--space-3);
transition: border-color var(--transition), transform var(--transition);
}
.founder:hover {
border-color: rgba(0, 229, 255, 0.35);
transform: translateY(-3px);
}
.founder__avatar {
width: 72px;
height: 72px;
border-radius: 50%;
background: linear-gradient(135deg, var(--color-accent-soft), rgba(11, 20, 55, 0.6));
border: 1px solid var(--color-border);
display: flex;
align-items: center;
justify-content: center;
color: var(--color-accent);
font-family: var(--font-mono);
font-size: 1.1rem;
font-weight: 600;
overflow: hidden;
}
.founder__avatar img {
width: 100%; height: 100%; object-fit: cover;
}
.founder__name { margin: 0; font-size: 1.25rem; }
.founder__role { color: var(--color-accent); font-size: 0.9rem; margin: 0; font-weight: 500; }
.founder__domain {
color: var(--color-text-dim);
font-size: 0.78rem;
letter-spacing: 0.04em;
margin: 0;
}
.founder__bio { color: var(--color-text-muted); margin: 0; }
</style>

View File

@@ -0,0 +1,75 @@
<script setup>
import { RouterLink } from 'vue-router'
defineProps({
product: { type: Object, required: true },
})
const iconMap = {
ai: '∿',
security: '⌬',
comms: '',
asic: '◇',
other: '◍',
}
</script>
<template>
<RouterLink :to="`/products/${product.slug}`" class="product">
<div class="product__head">
<span class="product__icon mono">{{ iconMap[product.category] || '◍' }}</span>
<span class="pill">{{ product.category_label }}</span>
</div>
<h3 class="product__name">{{ product.name }}</h3>
<p class="product__tagline">{{ product.tagline }}</p>
<p class="product__summary">{{ product.summary }}</p>
<span class="product__more">Read more </span>
</RouterLink>
</template>
<style scoped>
.product {
display: flex;
flex-direction: column;
gap: var(--space-3);
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-6);
color: var(--color-text);
transition: border-color var(--transition), transform var(--transition);
height: 100%;
}
.product:hover {
border-color: rgba(0, 229, 255, 0.4);
transform: translateY(-3px);
color: var(--color-text);
}
.product__head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-2);
}
.product__icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px; height: 44px;
border-radius: var(--radius-md);
background: var(--color-accent-soft);
color: var(--color-accent);
font-size: 1.5rem;
border: 1px solid rgba(0, 229, 255, 0.25);
}
.product__name { margin: 0; font-size: 1.3rem; }
.product__tagline { color: var(--color-accent); font-size: 0.95rem; margin: 0; }
.product__summary { color: var(--color-text-muted); margin: 0; flex: 1; }
.product__more {
color: var(--color-accent);
font-size: 0.9rem;
font-weight: 500;
margin-top: var(--space-2);
}
</style>

View File

@@ -0,0 +1,32 @@
<script setup>
defineProps({
eyebrow: String,
title: String,
subtitle: String,
})
</script>
<template>
<section class="page-hero">
<div class="container">
<span v-if="eyebrow" class="eyebrow">{{ eyebrow }}</span>
<h1>{{ title }}</h1>
<p v-if="subtitle" class="page-hero__sub">{{ subtitle }}</p>
</div>
</section>
</template>
<style scoped>
.page-hero {
padding: var(--space-9) 0 var(--space-7);
border-bottom: 1px solid var(--color-border-soft);
background:
radial-gradient(800px 360px at 90% 0%, rgba(0, 229, 255, 0.08), transparent 60%);
}
.page-hero__sub {
font-size: 1.15rem;
color: var(--color-text-muted);
max-width: 720px;
margin: var(--space-4) 0 0;
}
</style>

View File

@@ -0,0 +1,187 @@
<script setup>
import { ref } from 'vue'
import { api } from '@/api/client'
const email = ref('')
const status = ref('idle') // idle | loading | success | error
const message = ref('')
async function subscribe() {
if (!email.value) return
status.value = 'loading'
try {
await api.subscribeNewsletter(email.value, 'footer')
status.value = 'success'
message.value = "You're on the list."
email.value = ''
} catch (err) {
status.value = 'error'
message.value = 'Something went wrong. Try again in a moment.'
}
}
const year = new Date().getFullYear()
</script>
<template>
<footer class="footer">
<div class="container footer__grid">
<div class="footer__brand">
<div class="brand-line">
<svg width="28" height="28" viewBox="0 0 64 64" aria-hidden="true">
<defs>
<linearGradient id="fg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#0B1437"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
</defs>
<rect width="64" height="64" rx="14" fill="url(#fg)"/>
<path d="M16 44 L26 28 L36 38 L48 18" stroke="#F4F6FB" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<strong>RisingCompute</strong>
</div>
<p class="footer__tagline">Accelerating the compute IP cores for AI, space, and robotics.</p>
<p class="footer__incub mono">Incubated at STIIC · IIST</p>
</div>
<div>
<h4>Company</h4>
<ul>
<li><RouterLink to="/about">About</RouterLink></li>
<li><RouterLink to="/careers">Careers</RouterLink></li>
<li><RouterLink to="/blog">Insights</RouterLink></li>
<li><RouterLink to="/contact">Contact</RouterLink></li>
</ul>
</div>
<div>
<h4>Products</h4>
<ul>
<li><RouterLink to="/products/ai-inference-ip">AI Inference IP</RouterLink></li>
<li><RouterLink to="/products/cybersecurity-ip">Cybersecurity IP</RouterLink></li>
<li><RouterLink to="/products/communication-ip">Communication IP</RouterLink></li>
<li><RouterLink to="/technology">Technology</RouterLink></li>
</ul>
</div>
<div class="footer__news">
<h4>Newsletter</h4>
<p class="dim">Quarterly technical updates. No spam.</p>
<form class="footer__sub" @submit.prevent="subscribe">
<input
v-model="email"
type="email"
required
placeholder="you@company.com"
aria-label="Email address"
/>
<button type="submit" class="btn btn--primary" :disabled="status === 'loading'">
{{ status === 'loading' ? '…' : 'Subscribe' }}
</button>
</form>
<p v-if="status === 'success'" class="footer__msg footer__msg--ok">{{ message }}</p>
<p v-if="status === 'error'" class="footer__msg footer__msg--err">{{ message }}</p>
</div>
</div>
<div class="container footer__bottom">
<p class="dim">© {{ year }} RisingCompute Pvt Ltd · Surat, Gujarat, India</p>
<p class="dim">
<RouterLink to="/contact">Privacy</RouterLink> ·
<RouterLink to="/contact">Terms</RouterLink> ·
<a href="mailto:contact@risingcompute.in">contact@risingcompute.in</a>
</p>
</div>
</footer>
</template>
<style scoped>
.footer {
border-top: 1px solid var(--color-border-soft);
padding: var(--space-8) 0 var(--space-5);
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.25));
margin-top: var(--space-9);
}
.footer__grid {
display: grid;
grid-template-columns: 1.6fr 1fr 1fr 1.4fr;
gap: var(--space-7);
margin-bottom: var(--space-7);
}
@media (max-width: 900px) {
.footer__grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 540px) {
.footer__grid { grid-template-columns: 1fr; }
}
.brand-line {
display: inline-flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-3);
color: var(--color-text);
}
.footer__tagline { color: var(--color-text-muted); max-width: 280px; }
.footer__incub { color: var(--color-text-dim); font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; }
.footer h4 {
font-size: 0.8rem;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-text-dim);
margin-bottom: var(--space-4);
}
.footer ul {
list-style: none;
padding: 0;
margin: 0;
display: grid;
gap: var(--space-2);
}
.footer ul a {
color: var(--color-text-muted);
font-size: 0.92rem;
}
.footer ul a:hover { color: var(--color-accent); }
.footer__sub {
display: flex;
gap: var(--space-2);
margin-top: var(--space-3);
}
.footer__sub input {
flex: 1;
background: var(--color-bg-soft);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: 0.7rem 0.9rem;
font-family: var(--font-sans);
color: var(--color-text);
font-size: 0.9rem;
}
.footer__sub input:focus {
outline: none;
border-color: var(--color-accent);
}
.footer__sub button { padding: 0.7rem 1rem; font-size: 0.9rem; }
.footer__msg { font-size: 0.85rem; margin-top: var(--space-2); }
.footer__msg--ok { color: var(--color-success); }
.footer__msg--err { color: var(--color-error); }
.footer__bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: var(--space-5);
border-top: 1px solid var(--color-border-soft);
font-size: 0.85rem;
}
@media (max-width: 640px) {
.footer__bottom { flex-direction: column; gap: var(--space-2); }
}
.footer__bottom a { color: var(--color-text-muted); }
.footer__bottom a:hover { color: var(--color-accent); }
</style>

View File

@@ -0,0 +1,160 @@
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { RouterLink } from 'vue-router'
const scrolled = ref(false)
const menuOpen = ref(false)
const onScroll = () => { scrolled.value = window.scrollY > 8 }
onMounted(() => {
window.addEventListener('scroll', onScroll, { passive: true })
onScroll()
})
onBeforeUnmount(() => window.removeEventListener('scroll', onScroll))
const closeMenu = () => { menuOpen.value = false }
</script>
<template>
<header class="nav" :class="{ 'nav--scrolled': scrolled }">
<div class="container nav__inner">
<RouterLink to="/" class="brand" @click="closeMenu">
<svg width="32" height="32" viewBox="0 0 64 64" aria-hidden="true">
<defs>
<linearGradient id="logoGrad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#0B1437"/>
<stop offset="100%" stop-color="#00E5FF"/>
</linearGradient>
</defs>
<rect width="64" height="64" rx="14" fill="url(#logoGrad)"/>
<path d="M16 44 L26 28 L36 38 L48 18" stroke="#F4F6FB" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="brand__name">RisingCompute</span>
</RouterLink>
<button
class="nav__toggle"
:aria-expanded="menuOpen"
aria-label="Toggle menu"
@click="menuOpen = !menuOpen"
>
<span></span><span></span><span></span>
</button>
<nav class="nav__links" :class="{ 'nav__links--open': menuOpen }" @click="closeMenu">
<RouterLink to="/products">Products</RouterLink>
<RouterLink to="/technology">Technology</RouterLink>
<RouterLink to="/about">About</RouterLink>
<RouterLink to="/careers">Careers</RouterLink>
<RouterLink to="/blog">Insights</RouterLink>
<RouterLink to="/contact" class="btn btn--primary nav__cta">Talk to us</RouterLink>
</nav>
</div>
</header>
</template>
<style scoped>
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--nav-height);
z-index: 50;
background: rgba(11, 20, 55, 0.6);
backdrop-filter: saturate(1.4) blur(14px);
-webkit-backdrop-filter: saturate(1.4) blur(14px);
border-bottom: 1px solid transparent;
transition: background var(--transition), border-color var(--transition);
}
.nav--scrolled {
background: rgba(11, 20, 55, 0.88);
border-bottom-color: var(--color-border-soft);
}
.nav__inner {
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-5);
}
.brand {
display: inline-flex;
align-items: center;
gap: var(--space-3);
color: var(--color-text);
font-weight: 600;
letter-spacing: -0.01em;
}
.brand:hover { color: var(--color-text); }
.brand__name { font-size: 1.05rem; }
.nav__links {
display: flex;
align-items: center;
gap: var(--space-5);
}
.nav__links :where(a):not(.btn) {
color: var(--color-text-muted);
font-size: 0.95rem;
font-weight: 500;
padding: 0.4rem 0;
border-bottom: 2px solid transparent;
}
.nav__links :where(a):not(.btn):hover {
color: var(--color-text);
}
.nav__links :where(a.router-link-active):not(.btn) {
color: var(--color-accent);
border-bottom-color: var(--color-accent);
}
.nav__cta { padding: 0.55rem 1rem; font-size: 0.9rem; }
.nav__toggle {
display: none;
background: transparent;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
width: 40px;
height: 36px;
padding: 8px 10px;
flex-direction: column;
justify-content: space-between;
cursor: pointer;
}
.nav__toggle span {
display: block;
width: 100%;
height: 2px;
background: var(--color-text);
border-radius: 2px;
}
@media (max-width: 880px) {
.nav__toggle { display: flex; }
.nav__links {
position: absolute;
top: var(--nav-height);
left: 0;
right: 0;
flex-direction: column;
align-items: stretch;
padding: var(--space-5);
background: rgba(11, 20, 55, 0.98);
border-bottom: 1px solid var(--color-border-soft);
transform: translateY(-12px);
opacity: 0;
pointer-events: none;
transition: all var(--transition);
}
.nav__links--open {
transform: translateY(0);
opacity: 1;
pointer-events: auto;
}
.nav__cta { margin-top: var(--space-2); }
}
</style>

6
frontend/src/main.js Normal file
View File

@@ -0,0 +1,6 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './assets/styles/main.css'
createApp(App).use(router).mount('#app')

View File

@@ -0,0 +1,30 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{ path: '/', name: 'home', component: () => import('@/views/HomeView.vue'), meta: { title: 'RisingCompute — Accelerating the compute' } },
{ path: '/about', name: 'about', component: () => import('@/views/AboutView.vue'), meta: { title: 'About — RisingCompute' } },
{ path: '/products', name: 'products', component: () => import('@/views/ProductsView.vue'), meta: { title: 'Products — RisingCompute' } },
{ path: '/products/:slug', name: 'product-detail', component: () => import('@/views/ProductDetailView.vue'), meta: { title: 'Product — RisingCompute' } },
{ path: '/technology', name: 'technology', component: () => import('@/views/TechnologyView.vue'), meta: { title: 'Technology — RisingCompute' } },
{ path: '/careers', name: 'careers', component: () => import('@/views/CareersView.vue'), meta: { title: 'Careers — RisingCompute' } },
{ path: '/blog', name: 'blog', component: () => import('@/views/BlogView.vue'), meta: { title: 'Insights — RisingCompute' } },
{ path: '/blog/:slug', name: 'blog-post', component: () => import('@/views/BlogPostView.vue'), meta: { title: 'Insights — RisingCompute' } },
{ path: '/contact', name: 'contact', component: () => import('@/views/ContactView.vue'), meta: { title: 'Talk to us — RisingCompute' } },
{ path: '/:pathMatch(.*)*', name: 'not-found', component: () => import('@/views/NotFoundView.vue'), meta: { title: 'Not found — RisingCompute' } },
]
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, saved) {
if (saved) return saved
if (to.hash) return { el: to.hash, behavior: 'smooth' }
return { top: 0 }
},
})
router.afterEach((to) => {
if (to.meta?.title) document.title = to.meta.title
})
export default router

View File

@@ -0,0 +1,159 @@
<script setup>
import { onMounted, ref } from 'vue'
import { RouterLink } from 'vue-router'
import { api } from '@/api/client'
import SectionHero from '@/components/SectionHero.vue'
import FounderCard from '@/components/FounderCard.vue'
const founders = ref([])
const loading = ref(true)
onMounted(async () => {
try { founders.value = await api.listFounders() }
catch (e) {}
finally { loading.value = false }
})
const values = [
{ name: 'Fast', body: 'We measure things. We ship blocks. We don\'t hide behind roadmaps.' },
{ name: 'Reliable', body: 'Verification-first. Documentation that an integrator can actually use.' },
{ name: 'Robust', body: 'Designed for orbits and factory floors — robustness is the baseline, not a feature.' },
]
</script>
<template>
<SectionHero
eyebrow="About"
title="Built by a satellite-software team, for everyone who needs that compute."
subtitle="RisingCompute designs parallel hardware architectures and IP cores for AI, space, and robotics. The founders met in the SSPACE lab at IIST — and brought the same standards out into the open."
/>
<!-- STORY -->
<section class="section section--tight">
<div class="container story">
<div>
<span class="eyebrow">Our story</span>
<h2>Closing the compute gap.</h2>
</div>
<div class="story__body">
<p>
RisingCompute began in 2026 inside the SSPACE lab at IIST, where our founding
team spent two years building computation systems for satellites IP that has
since flown in space.
</p>
<p>
Working at that level taught us something simple: the difference between what a
research team can do with AI and what most engineers and companies can do comes
down to the compute they have access to. We started RisingCompute to close that
gap. By building parallel hardware architectures and reusable IP cores, we make
accelerated compute scalable and easy to integrate so an aerospace OEM, a
robotics startup, or a defense electronics lab can get research-grade
performance without rebuilding the stack from scratch.
</p>
</div>
</div>
</section>
<hr class="divider container" />
<!-- FOUNDERS -->
<section class="section section--tight">
<div class="container">
<div class="section-head">
<span class="eyebrow">Founders</span>
<h2>Three engineers. One full stack.</h2>
<p class="muted">
Every founder owns a clear technical domain. Together we cover system architecture
down to silicon.
</p>
</div>
<div v-if="loading" class="dim mono">Loading</div>
<div v-else class="grid grid--3">
<FounderCard v-for="f in founders" :key="f.name" :founder="f" />
</div>
</div>
</section>
<hr class="divider container" />
<!-- VALUES -->
<section class="section section--tight">
<div class="container">
<div class="section-head">
<span class="eyebrow">Core values</span>
<h2>Fast. Reliable. Robust.</h2>
</div>
<div class="grid grid--3">
<div v-for="v in values" :key="v.name" class="value-card">
<h3>{{ v.name }}</h3>
<p>{{ v.body }}</p>
</div>
</div>
</div>
</section>
<!-- FACTS -->
<section class="section section--tight">
<div class="container facts">
<div>
<span class="eyebrow">Where we are</span>
<h3 class="facts__h">Surat, Gujarat</h3>
<p class="muted">Primary office and engineering team.</p>
</div>
<div>
<span class="eyebrow">Where we're incubated</span>
<h3 class="facts__h">STIIC · IIST</h3>
<p class="muted">Space Technology Incubation & Innovation Cell, IIST.</p>
</div>
<div>
<span class="eyebrow">Stage</span>
<h3 class="facts__h">Pre-revenue</h3>
<p class="muted">Founded 2026. Three IP cores in customer-ready testing.</p>
</div>
</div>
</section>
<!-- CTA -->
<section class="section text-center">
<div class="container">
<h2>Work with us.</h2>
<p class="muted">Talk to the founders directly engineering-first conversations only.</p>
<RouterLink to="/contact" class="btn btn--primary">Talk to us</RouterLink>
</div>
</section>
</template>
<style scoped>
.story {
display: grid;
grid-template-columns: 1fr 1.6fr;
gap: var(--space-7);
}
@media (max-width: 860px) { .story { grid-template-columns: 1fr; } }
.story__body p { font-size: 1.05rem; }
.section-head { margin-bottom: var(--space-6); max-width: 720px; }
.value-card {
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-6);
}
.value-card h3 {
font-family: var(--font-mono);
color: var(--color-accent);
margin-bottom: var(--space-3);
font-size: 1.1rem;
letter-spacing: 0.05em;
}
.facts {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-6);
}
@media (max-width: 860px) { .facts { grid-template-columns: 1fr; } }
.facts__h { margin: var(--space-3) 0 var(--space-2); }
</style>

View File

@@ -0,0 +1,108 @@
<script setup>
import { onMounted, ref, watch } from 'vue'
import { useRoute, RouterLink } from 'vue-router'
import { api } from '@/api/client'
const route = useRoute()
const post = ref(null)
const loading = ref(true)
const notFound = ref(false)
async function load(slug) {
loading.value = true
notFound.value = false
try {
post.value = await api.getPost(slug)
if (post.value?.title) document.title = `${post.value.title} — RisingCompute`
} catch (e) {
notFound.value = true
} finally {
loading.value = false
}
}
onMounted(() => load(route.params.slug))
watch(() => route.params.slug, (s) => s && load(s))
function fmtDate(iso) {
try { return new Date(iso).toLocaleDateString('en-IN', { year: 'numeric', month: 'short', day: 'numeric' }) }
catch { return iso }
}
</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>Post not found.</h1>
<RouterLink to="/blog" class="btn btn--ghost">Back to insights</RouterLink>
</div>
</section>
<article v-else class="post-page">
<header class="post-page__head">
<div class="container">
<RouterLink to="/blog" class="back-link"> All insights</RouterLink>
<span class="pill">{{ post.category_label }}</span>
<h1>{{ post.title }}</h1>
<p class="dim mono post-page__meta">
{{ post.author_name }} · {{ fmtDate(post.published_at) }} · {{ post.read_time_minutes }} min read
</p>
</div>
</header>
<div class="container post-page__body">
<p class="post-page__excerpt">{{ post.excerpt }}</p>
<!-- Body is markdown-ish; for v1 we render as preformatted text with double-newline paragraphing -->
<div class="prose" v-for="(para, idx) in post.body.split(/\n\n+/)" :key="idx">
<h2 v-if="para.startsWith('## ')">{{ para.replace(/^##\s*/, '') }}</h2>
<p v-else>{{ para }}</p>
</div>
</div>
<div class="container post-page__footer">
<RouterLink to="/blog" class="btn btn--ghost"> More insights</RouterLink>
<RouterLink to="/contact" class="btn btn--primary">Talk to us</RouterLink>
</div>
</article>
</template>
<style scoped>
.back-link { color: var(--color-text-dim); font-size: 0.9rem; }
.back-link:hover { color: var(--color-accent); }
.post-page__head {
padding: var(--space-8) 0 var(--space-5);
border-bottom: 1px solid var(--color-border-soft);
background: radial-gradient(800px 360px at 90% 0%, rgba(0, 229, 255, 0.08), transparent 60%);
}
.post-page__head h1 { margin-top: var(--space-3); max-width: 820px; }
.post-page__meta { margin-top: var(--space-2); }
.post-page__body {
max-width: 760px;
padding: var(--space-7) var(--space-5);
}
.post-page__excerpt {
font-size: 1.2rem;
color: var(--color-text);
border-left: 2px solid var(--color-accent);
padding-left: var(--space-4);
margin-bottom: var(--space-6);
}
.prose h2 { margin-top: var(--space-6); }
.prose p { font-size: 1.05rem; }
.post-page__footer {
max-width: 760px;
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-5);
border-top: 1px solid var(--color-border-soft);
margin-top: var(--space-6);
gap: var(--space-4);
}
@media (max-width: 540px) { .post-page__footer { flex-direction: column-reverse; } }
</style>

View File

@@ -0,0 +1,123 @@
<script setup>
import { computed, onMounted, ref } from 'vue'
import { RouterLink } from 'vue-router'
import { api } from '@/api/client'
import SectionHero from '@/components/SectionHero.vue'
const posts = ref([])
const loading = ref(true)
const activeCategory = ref('all')
onMounted(async () => {
try { posts.value = await api.listPosts() }
catch (e) {}
finally { loading.value = false }
})
const categories = computed(() => {
const set = new Map()
set.set('all', 'All')
for (const p of posts.value) set.set(p.category, p.category_label)
return Array.from(set, ([value, label]) => ({ value, label }))
})
const filtered = computed(() =>
activeCategory.value === 'all'
? posts.value
: posts.value.filter(p => p.category === activeCategory.value)
)
function fmtDate(iso) {
try { return new Date(iso).toLocaleDateString('en-IN', { year: 'numeric', month: 'short', day: 'numeric' }) }
catch { return iso }
}
</script>
<template>
<SectionHero
eyebrow="Insights"
title="Notes from the lab."
subtitle="Engineering notes, architecture choices, and the occasional founder essay. Written by the team — not a content desk."
/>
<section class="section">
<div class="container">
<div class="filters" v-if="categories.length > 1">
<button
v-for="c in categories"
:key="c.value"
class="filter"
:class="{ 'filter--active': activeCategory === c.value }"
@click="activeCategory = c.value"
>
{{ c.label }}
</button>
</div>
<div v-if="loading" class="dim mono">Loading</div>
<div v-else class="grid grid--2 posts">
<RouterLink
v-for="post in filtered"
:key="post.slug"
:to="`/blog/${post.slug}`"
class="post"
>
<div class="post__head">
<span class="pill">{{ post.category_label }}</span>
<span class="dim mono">{{ post.read_time_minutes }} min</span>
</div>
<h3>{{ post.title }}</h3>
<p class="muted">{{ post.excerpt }}</p>
<span class="dim mono">{{ post.author_name }} · {{ fmtDate(post.published_at) }}</span>
</RouterLink>
</div>
</div>
</section>
</template>
<style scoped>
.filters {
display: flex;
gap: var(--space-2);
flex-wrap: wrap;
margin-bottom: var(--space-6);
}
.filter {
background: transparent;
border: 1px solid var(--color-border);
color: var(--color-text-muted);
border-radius: 999px;
padding: 0.4rem 0.9rem;
font-family: var(--font-sans);
font-size: 0.85rem;
cursor: pointer;
transition: all var(--transition-fast);
}
.filter:hover { border-color: var(--color-accent); color: var(--color-accent); }
.filter--active {
background: var(--color-accent-soft);
border-color: var(--color-accent);
color: var(--color-accent);
}
.posts { gap: var(--space-5); }
.post {
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-6);
display: flex;
flex-direction: column;
gap: var(--space-3);
color: var(--color-text);
transition: border-color var(--transition), transform var(--transition);
}
.post:hover {
border-color: rgba(0, 229, 255, 0.4);
transform: translateY(-3px);
color: var(--color-text);
}
.post__head { display: flex; justify-content: space-between; align-items: center; }
.post h3 { margin: 0; font-size: 1.3rem; }
.post p { margin: 0; flex: 1; }
</style>

View File

@@ -0,0 +1,141 @@
<script setup>
import { onMounted, ref } from 'vue'
import { RouterLink } from 'vue-router'
import { api } from '@/api/client'
import SectionHero from '@/components/SectionHero.vue'
const jobs = ref([])
const loading = ref(true)
onMounted(async () => {
try { jobs.value = await api.listJobs() }
catch (e) {}
finally { loading.value = false }
})
</script>
<template>
<SectionHero
eyebrow="Careers"
title="Build silicon that flies."
subtitle="We're a small founding team in Surat and at IIST. The bar is high; so is the autonomy. If you want to own real RTL, real verification, and real customer relationships from day one, we should talk."
/>
<section class="section section--tight">
<div class="container">
<div class="why-grid">
<div>
<h3 class="mono mono-head">Why us</h3>
<ul class="reasons">
<li>Founder-led engineering. You sit next to the people who decide what to build.</li>
<li>Real flight-heritage work variants of our IP have flown.</li>
<li>Ownership over a block, not a sliver.</li>
<li>Equity for the first ten people.</li>
<li>Surat HQ + IIST presence both real labs.</li>
</ul>
</div>
<div>
<h3 class="mono mono-head">How we hire</h3>
<ol class="steps">
<li><strong>Intro chat</strong> (30 min) what you've shipped, what you want next.</li>
<li><strong>Technical deep-dive</strong> (90 min) — a real problem from our stack.</li>
<li><strong>Founder round</strong> — values, autonomy, expectations.</li>
<li><strong>Offer</strong> within a week.</li>
</ol>
</div>
</div>
</div>
</section>
<hr class="divider container" />
<section class="section section--tight">
<div class="container">
<span class="eyebrow">Open roles</span>
<h2>What we're hiring for.</h2>
<div v-if="loading" class="dim mono">Loading</div>
<div v-else-if="!jobs.length" class="empty">
<p class="muted">
No open roles posted right now. We always want to hear from VLSI, RTL,
verification, and DSP engineers send us a note.
</p>
<RouterLink :to="{ path: '/contact', query: { interest: 'careers' } }" class="btn btn--primary">
Introduce yourself
</RouterLink>
</div>
<div v-else class="jobs">
<article v-for="j in jobs" :key="j.slug" class="job">
<div>
<h3>{{ j.title }}</h3>
<p class="dim mono job__meta">{{ j.location_label }} · {{ j.employment_type }} · posted {{ j.posted_at }}</p>
</div>
<RouterLink :to="{ path: '/contact', query: { interest: 'careers' } }" class="btn btn--ghost">
Apply
</RouterLink>
</article>
</div>
</div>
</section>
</template>
<style scoped>
.why-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-7);
}
@media (max-width: 760px) { .why-grid { grid-template-columns: 1fr; } }
.mono-head {
color: var(--color-text-dim);
font-size: 0.78rem;
letter-spacing: 0.1em;
text-transform: uppercase;
margin-bottom: var(--space-4);
}
.reasons, .steps {
display: grid;
gap: var(--space-3);
color: var(--color-text-muted);
}
.reasons { list-style: none; padding: 0; }
.reasons li { padding-left: var(--space-5); position: relative; }
.reasons li::before {
content: '+';
position: absolute;
left: 0;
color: var(--color-accent);
font-family: var(--font-mono);
font-weight: 600;
}
.steps { padding-left: var(--space-5); }
.steps li::marker { color: var(--color-accent); font-family: var(--font-mono); }
.jobs {
display: grid;
gap: var(--space-3);
margin-top: var(--space-5);
}
.job {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--space-4);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: var(--space-5);
}
.job h3 { margin: 0 0 var(--space-1); font-size: 1.15rem; }
.job__meta { margin: 0; font-size: 0.8rem; }
.empty {
background: var(--color-surface);
border: 1px dashed var(--color-border);
border-radius: var(--radius-lg);
padding: var(--space-6);
text-align: center;
margin-top: var(--space-5);
}
.empty p { margin: 0 0 var(--space-4); }
</style>

View File

@@ -0,0 +1,89 @@
<script setup>
import { useRoute } from 'vue-router'
import SectionHero from '@/components/SectionHero.vue'
import ContactForm from '@/components/ContactForm.vue'
const route = useRoute()
const defaultInterest = String(route.query.interest || 'other')
</script>
<template>
<SectionHero
eyebrow="Talk to us"
title="Start an evaluation."
subtitle="Conversations land with an engineer, not a sales rep. One business-day reply."
/>
<section class="section">
<div class="container contact-grid">
<div class="contact-info">
<h3>Direct lines</h3>
<ul>
<li>
<span class="dim mono">Email</span>
<a href="mailto:contact@risingcompute.in">contact@risingcompute.in</a>
</li>
<li>
<span class="dim mono">HQ</span>
<p>Surat, Gujarat, India</p>
</li>
<li>
<span class="dim mono">Incubation</span>
<p>STIIC · IIST, Thiruvananthapuram</p>
</li>
<li>
<span class="dim mono">Hours</span>
<p>MonFri · 09:3018:30 IST</p>
</li>
</ul>
<hr class="divider" />
<h3>What to expect</h3>
<ol class="steps">
<li>A short reply from a founder within one business day.</li>
<li>A 30-minute qualification call.</li>
<li>Datasheet + reference design under NDA.</li>
<li>Evaluation licence terms.</li>
</ol>
</div>
<div>
<ContactForm :default-interest="defaultInterest" />
</div>
</div>
</section>
</template>
<style scoped>
.contact-grid {
display: grid;
grid-template-columns: 1fr 1.4fr;
gap: var(--space-7);
}
@media (max-width: 900px) { .contact-grid { grid-template-columns: 1fr; } }
.contact-info ul {
list-style: none;
padding: 0;
margin: 0 0 var(--space-5);
display: grid;
gap: var(--space-4);
}
.contact-info li { display: grid; gap: var(--space-1); }
.contact-info li span.mono {
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.contact-info li p { margin: 0; }
.contact-info a { font-size: 1.05rem; }
.steps {
padding-left: var(--space-5);
display: grid;
gap: var(--space-2);
color: var(--color-text-muted);
}
.steps li::marker { color: var(--color-accent); font-family: var(--font-mono); }
</style>

View File

@@ -0,0 +1,314 @@
<script setup>
import { onMounted, ref } from 'vue'
import { RouterLink } from 'vue-router'
import { api } from '@/api/client'
import ProductCard from '@/components/ProductCard.vue'
const products = ref([])
const posts = ref([])
const loading = ref(true)
onMounted(async () => {
try {
const [p, b] = await Promise.all([api.listProducts(), api.listPosts()])
products.value = p
posts.value = b.slice(0, 3)
} catch (e) { /* surface a friendlier UI in production */ }
finally { loading.value = false }
})
const values = [
{ name: 'Fast', body: 'Parallel architectures, deterministic latency, throughput-per-watt that ships.' },
{ name: 'Reliable', body: 'Verification-first design. Heritage you can audit, not heritage you have to take on faith.' },
{ name: 'Robust', body: 'Built for orbits, factory floors, and defense supply chains — not a controlled bench.' },
]
</script>
<template>
<!-- HERO -->
<section class="hero">
<div class="container hero__inner">
<div class="hero__copy">
<span class="eyebrow">Semiconductor IP · India · Flight-heritage</span>
<h1>Accelerating the compute.</h1>
<p class="hero__sub">
Parallel hardware architectures and IP cores for <strong>AI</strong>, <strong>space</strong>,
and <strong>robotics</strong>. Designed in India, flown in orbit.
</p>
<div class="hero__cta">
<RouterLink to="/contact" class="btn btn--primary">Talk to us</RouterLink>
<RouterLink to="/technology" class="btn btn--ghost">Read the architecture brief </RouterLink>
</div>
<div class="hero__proof">
<span class="pill">IIST · STIIC</span>
<span class="pill">Flight-heritage IP</span>
<span class="pill">Founded 2026</span>
</div>
</div>
<!-- Decorative compute visual -->
<div class="hero__visual" aria-hidden="true">
<svg viewBox="0 0 420 420" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="chipGrad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#00E5FF" stop-opacity="0.55"/>
<stop offset="100%" stop-color="#0B1437" stop-opacity="0"/>
</linearGradient>
<linearGradient id="lineGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#00E5FF" stop-opacity="0"/>
<stop offset="50%" stop-color="#00E5FF" stop-opacity="0.7"/>
<stop offset="100%" stop-color="#00E5FF" stop-opacity="0"/>
</linearGradient>
</defs>
<!-- Outer ring -->
<circle cx="210" cy="210" r="200" fill="none" stroke="#2A3463" stroke-width="1"/>
<circle cx="210" cy="210" r="150" fill="none" stroke="#2A3463" stroke-width="1" stroke-dasharray="2 4"/>
<!-- Chip square -->
<rect x="130" y="130" width="160" height="160" rx="16" fill="url(#chipGrad)" stroke="#00E5FF" stroke-width="1.5" opacity="0.9"/>
<!-- Pins -->
<g stroke="#00E5FF" stroke-width="1.5" opacity="0.7">
<line x1="50" y1="170" x2="130" y2="170"/>
<line x1="50" y1="210" x2="130" y2="210"/>
<line x1="50" y1="250" x2="130" y2="250"/>
<line x1="290" y1="170" x2="370" y2="170"/>
<line x1="290" y1="210" x2="370" y2="210"/>
<line x1="290" y1="250" x2="370" y2="250"/>
<line x1="170" y1="50" x2="170" y2="130"/>
<line x1="210" y1="50" x2="210" y2="130"/>
<line x1="250" y1="50" x2="250" y2="130"/>
<line x1="170" y1="290" x2="170" y2="370"/>
<line x1="210" y1="290" x2="210" y2="370"/>
<line x1="250" y1="290" x2="250" y2="370"/>
</g>
<!-- Inner grid -->
<g stroke="#00E5FF" stroke-width="0.6" opacity="0.5">
<line x1="170" y1="150" x2="170" y2="270"/>
<line x1="210" y1="150" x2="210" y2="270"/>
<line x1="250" y1="150" x2="250" y2="270"/>
<line x1="150" y1="170" x2="270" y2="170"/>
<line x1="150" y1="210" x2="270" y2="210"/>
<line x1="150" y1="250" x2="270" y2="250"/>
</g>
<!-- Animated traveling pulse -->
<rect x="50" y="209" width="320" height="2" fill="url(#lineGrad)">
<animate attributeName="x" from="-120" to="420" dur="3s" repeatCount="indefinite"/>
</rect>
<!-- Core text -->
<text x="210" y="216" text-anchor="middle" fill="#F4F6FB" font-family="JetBrains Mono" font-size="14" letter-spacing="2">RC-01</text>
</svg>
</div>
</div>
</section>
<!-- VALUES -->
<section class="section section--tight">
<div class="container">
<div class="values-grid">
<div v-for="v in values" :key="v.name" class="value">
<h3>{{ v.name }}.</h3>
<p>{{ v.body }}</p>
</div>
</div>
</div>
</section>
<!-- PRODUCTS -->
<section class="section">
<div class="container">
<div class="section-head">
<span class="eyebrow">What we ship</span>
<h2>One vendor for AI, security, and comms IP.</h2>
<p class="muted section-head__lead">
Our IP catalogue covers the three blocks every modern payload, robot, and edge product
ends up integrating usually from three different vendors.
</p>
</div>
<div v-if="loading" class="dim mono">Loading products</div>
<div v-else class="grid grid--3">
<ProductCard v-for="p in products" :key="p.slug" :product="p" />
</div>
</div>
</section>
<!-- PROOF -->
<section class="section proof">
<div class="container proof__inner">
<div>
<span class="eyebrow">Flight heritage</span>
<h2>Our IP has flown.<br/>Yours can too.</h2>
<p class="muted">
The founding team spent two years inside the SSPACE lab at IIST building computation
systems for satellites. The IP that flew on those missions is the same IP we're
productising for the open market.
</p>
<RouterLink to="/about" class="btn btn--ghost">Read our story </RouterLink>
</div>
<div class="proof__stats">
<div><strong>2+</strong><span>years in flight programmes</span></div>
<div><strong>3</strong><span>IP cores in customer-ready testing</span></div>
<div><strong>5</strong><span>engineers across system + RTL</span></div>
</div>
</div>
</section>
<!-- BLOG -->
<section class="section">
<div class="container">
<div class="section-head">
<span class="eyebrow">Insights</span>
<h2>Notes from the lab.</h2>
</div>
<div v-if="loading" class="dim mono">Loading</div>
<div v-else class="grid grid--3">
<RouterLink
v-for="post in posts"
:key="post.slug"
:to="`/blog/${post.slug}`"
class="post-card"
>
<span class="pill">{{ post.category_label }}</span>
<h3>{{ post.title }}</h3>
<p class="muted">{{ post.excerpt }}</p>
<span class="mono dim">{{ post.author_name }} · {{ post.read_time_minutes }} min</span>
</RouterLink>
</div>
</div>
</section>
<!-- CTA -->
<section class="section cta">
<div class="container cta__inner">
<h2>Start an evaluation.</h2>
<p class="muted">One business-day reply. Conversations land with an engineer, not a sales rep.</p>
<RouterLink to="/contact" class="btn btn--primary">Talk to us</RouterLink>
</div>
</section>
</template>
<style scoped>
/* ---------- Hero ---------- */
.hero {
padding: var(--space-9) 0 var(--space-8);
position: relative;
overflow: hidden;
}
.hero__inner {
display: grid;
grid-template-columns: 1.15fr 1fr;
gap: var(--space-7);
align-items: center;
}
@media (max-width: 980px) {
.hero__inner { grid-template-columns: 1fr; }
.hero__visual { order: -1; max-width: 380px; margin: 0 auto; }
}
.hero__sub {
font-size: 1.2rem;
color: var(--color-text-muted);
max-width: 580px;
margin: var(--space-4) 0 var(--space-6);
}
.hero__sub strong { color: var(--color-text); font-weight: 600; }
.hero__cta {
display: flex;
gap: var(--space-3);
flex-wrap: wrap;
margin-bottom: var(--space-6);
}
.hero__proof {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
.hero__visual svg {
width: 100%;
height: auto;
max-width: 480px;
filter: drop-shadow(0 10px 40px rgba(0, 229, 255, 0.15));
}
/* ---------- Values ---------- */
.values-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-5);
}
@media (max-width: 760px) { .values-grid { grid-template-columns: 1fr; } }
.value {
border-left: 2px solid var(--color-accent);
padding: var(--space-2) var(--space-5);
}
.value h3 { font-size: 1.4rem; margin-bottom: var(--space-2); }
.value p { margin: 0; color: var(--color-text-muted); }
/* ---------- Section heads ---------- */
.section-head { margin-bottom: var(--space-6); max-width: 720px; }
.section-head h2 { margin-bottom: var(--space-3); }
.section-head__lead { font-size: 1.05rem; }
/* ---------- Proof ---------- */
.proof {
background: linear-gradient(180deg, transparent, var(--color-bg-soft), transparent);
border-top: 1px solid var(--color-border-soft);
border-bottom: 1px solid var(--color-border-soft);
}
.proof__inner {
display: grid;
grid-template-columns: 1.2fr 1fr;
gap: var(--space-7);
align-items: center;
}
@media (max-width: 860px) { .proof__inner { grid-template-columns: 1fr; } }
.proof__stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-4);
}
.proof__stats > div {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: var(--space-5) var(--space-4);
text-align: center;
}
.proof__stats strong {
display: block;
font-size: 2rem;
color: var(--color-accent);
font-family: var(--font-mono);
margin-bottom: var(--space-1);
}
.proof__stats span { color: var(--color-text-dim); font-size: 0.85rem; }
/* ---------- Blog tease ---------- */
.post-card {
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);
display: flex;
flex-direction: column;
gap: var(--space-3);
color: var(--color-text);
transition: border-color var(--transition), transform var(--transition);
}
.post-card:hover {
border-color: rgba(0, 229, 255, 0.4);
transform: translateY(-3px);
color: var(--color-text);
}
.post-card h3 { margin: 0; font-size: 1.2rem; }
.post-card p { margin: 0; flex: 1; }
/* ---------- Final CTA ---------- */
.cta {
text-align: center;
background:
radial-gradient(600px 240px at 50% 50%, rgba(0, 229, 255, 0.12), transparent 70%);
}
.cta__inner { max-width: 640px; margin: 0 auto; }
.cta h2 { margin-bottom: var(--space-3); }
.cta p { margin-bottom: var(--space-5); }
</style>

View File

@@ -0,0 +1,25 @@
<script setup>
import { RouterLink } from 'vue-router'
</script>
<template>
<section class="section nf">
<div class="container nf__inner">
<span class="mono dim">ERR · 404</span>
<h1>Lost in orbit.</h1>
<p class="muted">The page you're looking for has drifted. Let's get you back.</p>
<div class="nf__cta">
<RouterLink to="/" class="btn btn--primary">Home</RouterLink>
<RouterLink to="/products" class="btn btn--ghost">Products</RouterLink>
<RouterLink to="/contact" class="btn btn--ghost">Talk to us</RouterLink>
</div>
</div>
</section>
</template>
<style scoped>
.nf { min-height: 60vh; display: flex; align-items: center; }
.nf__inner { text-align: center; max-width: 560px; margin: 0 auto; }
.nf h1 { margin-top: var(--space-3); font-size: clamp(2.5rem, 7vw, 4.5rem); }
.nf__cta { display: flex; gap: var(--space-3); justify-content: center; flex-wrap: wrap; margin-top: var(--space-5); }
</style>

View 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>

View File

@@ -0,0 +1,53 @@
<script setup>
import { onMounted, ref } from 'vue'
import { RouterLink } from 'vue-router'
import { api } from '@/api/client'
import SectionHero from '@/components/SectionHero.vue'
import ProductCard from '@/components/ProductCard.vue'
const products = ref([])
const loading = ref(true)
onMounted(async () => {
try { products.value = await api.listProducts() }
catch (e) {}
finally { loading.value = false }
})
</script>
<template>
<SectionHero
eyebrow="Products"
title="IP cores for AI, space, and robotics."
subtitle="Three production-bound blocks. Quote-based licensing. Datasheets and evaluation kits on request."
/>
<section class="section">
<div class="container">
<div v-if="loading" class="dim mono">Loading</div>
<div v-else class="grid grid--3">
<ProductCard v-for="p in products" :key="p.slug" :product="p" />
</div>
<div class="cta-inline">
<p class="muted">Looking for a custom block or full ASIC service?</p>
<RouterLink to="/contact" class="btn btn--ghost">Start a conversation </RouterLink>
</div>
</div>
</section>
</template>
<style scoped>
.cta-inline {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--space-4);
margin-top: var(--space-7);
padding: var(--space-5);
border: 1px dashed var(--color-border);
border-radius: var(--radius-lg);
}
@media (max-width: 640px) { .cta-inline { flex-direction: column; align-items: flex-start; } }
.cta-inline p { margin: 0; }
</style>

View File

@@ -0,0 +1,156 @@
<script setup>
import { RouterLink } from 'vue-router'
import SectionHero from '@/components/SectionHero.vue'
const pillars = [
{
title: 'Parallel by design',
body: 'Datapaths are parallel from the first line of RTL. The architecture is not a serial CPU that we widened — it is parallel-native.',
},
{
title: 'Verification-first',
body: 'Every block ships with UVM testbenches, a coverage plan, and traceable test vectors. Heritage you can audit, not heritage you have to take on faith.',
},
{
title: 'FPGA → ASIC portability',
body: 'The same RTL synthesises on Xilinx UltraScale+, Intel Agilex, and standard 28nm / 40nm ASIC libraries. Prototype fast, ship slow, but with the same code.',
},
{
title: 'Security as architecture',
body: 'Constant-time datapaths, side-channel-aware routing, and a security white paper per release — not a checklist bolted on at integration time.',
},
]
</script>
<template>
<SectionHero
eyebrow="Technology"
title="An architecture built for AI, space, and robotics — in that order."
subtitle="A short tour of the design principles that show up in every block we ship. If you want the spec-sheet version, request a datasheet."
/>
<!-- PILLARS -->
<section class="section">
<div class="container">
<div class="grid grid--2 pillars">
<article v-for="(p, i) in pillars" :key="p.title" class="pillar">
<span class="pillar__num mono">{{ String(i + 1).padStart(2, '0') }}</span>
<h3>{{ p.title }}</h3>
<p class="muted">{{ p.body }}</p>
</article>
</div>
</div>
</section>
<!-- DIAGRAM -->
<section class="section diagram-section">
<div class="container">
<div class="section-head">
<span class="eyebrow">System view</span>
<h2>The stack, at a glance.</h2>
<p class="muted">Three product blocks. One shared verification toolchain. Same RTL from FPGA to ASIC.</p>
</div>
<div class="stack-diagram">
<div class="layer layer--top">
<span class="layer__title">Customer SoC / FPGA</span>
<span class="layer__sub mono">Your design</span>
</div>
<div class="layer__pipe"></div>
<div class="layer-row">
<div class="layer layer--block">
<span class="layer__title">AI Inference IP</span>
<span class="layer__sub mono">INT8 / INT4</span>
</div>
<div class="layer layer--block">
<span class="layer__title">Cybersecurity IP</span>
<span class="layer__sub mono">AES · SHA · ECC</span>
</div>
<div class="layer layer--block">
<span class="layer__title">Communication IP</span>
<span class="layer__sub mono">SpaceWire · CAN · UART</span>
</div>
</div>
<div class="layer__pipe"></div>
<div class="layer layer--base">
<span class="layer__title">Verification &amp; Integration Toolchain</span>
<span class="layer__sub mono">UVM · Reference designs · Integration guides</span>
</div>
</div>
</div>
</section>
<!-- CTA -->
<section class="section text-center">
<div class="container">
<h2>Want the long version?</h2>
<p class="muted">Request a technical brief or schedule a 30-minute architect call.</p>
<RouterLink to="/contact" class="btn btn--primary">Request the brief</RouterLink>
</div>
</section>
</template>
<style scoped>
.pillars { gap: var(--space-5); }
.pillar {
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-6);
position: relative;
}
.pillar__num {
color: var(--color-accent);
font-size: 0.8rem;
letter-spacing: 0.1em;
display: block;
margin-bottom: var(--space-3);
}
.diagram-section {
background: linear-gradient(180deg, transparent, var(--color-bg-soft), transparent);
border-block: 1px solid var(--color-border-soft);
}
.section-head { margin-bottom: var(--space-6); max-width: 720px; }
.stack-diagram {
display: flex;
flex-direction: column;
align-items: stretch;
gap: var(--space-2);
max-width: 880px;
margin: 0 auto;
}
.layer {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: var(--space-4) var(--space-5);
text-align: center;
}
.layer--top {
background: linear-gradient(180deg, rgba(0, 229, 255, 0.08), var(--color-surface));
border-color: rgba(0, 229, 255, 0.3);
}
.layer--base {
background: linear-gradient(0deg, rgba(0, 229, 255, 0.06), var(--color-surface));
border-color: rgba(0, 229, 255, 0.25);
}
.layer__title { display: block; font-weight: 600; font-size: 1.05rem; color: var(--color-text); }
.layer__sub { display: block; color: var(--color-text-dim); font-size: 0.8rem; margin-top: var(--space-1); letter-spacing: 0.05em; }
.layer__pipe {
width: 2px;
height: 20px;
background: var(--color-accent);
align-self: center;
opacity: 0.7;
}
.layer-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-3);
}
@media (max-width: 720px) {
.layer-row { grid-template-columns: 1fr; }
}
</style>

29
frontend/vite.config.js Normal file
View File

@@ -0,0 +1,29 @@
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
port: 5173,
proxy: {
'/api': {
target: env.VITE_API_PROXY || 'http://127.0.0.1:8000',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
sourcemap: false,
target: 'es2020',
},
}
})