main
رصد · Rasad — product-studio website (Nuxt 3 + Vue 3 + Tailwind + Three.js + GSAP)
npm install
npm run dev # http://localhost:3000
npm run generate # static, SEO-complete build in .output/public
Concept
The whole site is one engraved astrolabe (اسطرلاب), the Persian precision instrument, operated by scrolling. The plate is projected for Tehran (35.7°N). Its rete (عنکبوت) carries 15 star pointers = 15 products in 4 constellations.
Signature moments
- Engraving – on load the instrument draws itself line by line (hero).
- The rete –
#productspins for 15 steps; each step turns the rete clockwise so the next star (waiting on the left, RTL "next") lands under the lapis rule. Swipe, arrow keys, the progress rule and the star labels all navigate. - Anatomy – the instrument tilts and separates into its four physical layers: shared infra / data model / products / team.
Architecture
| Path | Role |
|---|---|
lib/astrolabe.js |
Framework-agnostic Three.js scene. Damped targets (set({cx, cy, size, tilt, explode, focus, active…})), line draw-on, label projection, low-power mode (half density, DPR cap), reduced-motion (no spin, instant), pauses when tab hidden. |
lib/story.js |
The camera path: scroll position + section metrics → scene target. One continuous interpolation, no section-by-section cuts. Separate desktop/mobile compositions of the same states. |
components/AstrolabeStage.client.vue |
Client-only canvas. GSAP ticker drives frames; ScrollTrigger measures sections and refreshes on resize/fonts. |
components/*Section.vue, ZijTable.vue |
Server-rendered content. Every product is real HTML in the Zij table + JSON-LD ItemList of SoftwareApplication. |
data/products.js |
Single content source (15 products, 4 families, Persian digit helpers). |
RTL notes
<html lang="fa" dir="rtl">fromnuxt.config, never mirrored after the fact.- Only logical utilities (
ms-/me-/ps-/pe-/start-/end-,text-start). The physicallefton star labels is intentional: they are placed in canvas pixel space. - Latin (product names, URLs, code, phone) is isolated with
<bdi dir="ltr">; numbers use Persian digits in Persian copy. - No letter-spacing on Persian (breaks joins). Directional motion is RTL-semantic: next enters from the left, ArrowLeft = next, rightward swipe = next.
Content (company, figures, contact) is placeholder.
Languages
Vue
55.4%
JavaScript
39.8%
TypeScript
3.6%
CSS
1.2%