chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,55 @@
'use client'
import type { ReactNode } from 'react'
import { GooMarkSvg } from '@/app/(landing)/components/mothership/components/goo-marks/goo-mark-svg'
import {
useGooHover,
useGooIds,
} from '@/app/(landing)/components/mothership/components/goo-marks/use-goo-hover'
/**
* Sim circle goo-mark: FAN RINGS.
* 5 same-size circles (radius 30), centers stepped horizontally by 9,
* symmetric about center - fuses into a vesica weave through the middle.
*/
export interface FanRingsProps {
size?: number
className?: string
animate?: boolean
forceHover?: boolean
}
export function FanRings({
size = 110,
className,
animate = true,
forceHover = false,
}: FanRingsProps) {
const { amt, bind } = useGooHover({ animate, forceHover })
const { gradId, gooId } = useGooIds()
const cx = 50
const cy = 50
const r = 30
const n = 5
const sp = 9 * (1 + amt * 0.25)
const start = -((n - 1) / 2) * sp
const circles: ReactNode[] = []
for (let i = 0; i < n; i++) {
circles.push(<circle key={i} cx={(cx + start + i * sp).toFixed(2)} cy={cy} r={r} />)
}
return (
<GooMarkSvg
size={size}
gradId={gradId}
gooId={gooId}
ariaLabel='Fan rings'
bind={bind}
className={className}
>
{circles}
</GooMarkSvg>
)
}
@@ -0,0 +1,63 @@
'use client'
import { GooMarkSvg } from '@/app/(landing)/components/mothership/components/goo-marks/goo-mark-svg'
import {
REACH,
TAU,
useGooHover,
useGooIds,
} from '@/app/(landing)/components/mothership/components/goo-marks/use-goo-hover'
/**
* Sim circle goo-mark: FLOWER OF LIFE.
* 7 circles - 1 center + 6 on a hexagon at radius 20 - each radius 20,
* scaled so the outer circle edges land on REACH.
*/
export interface FlowerOfLifeProps {
size?: number
className?: string
animate?: boolean
forceHover?: boolean
}
export function FlowerOfLife({
size = 110,
className,
animate = true,
forceHover = false,
}: FlowerOfLifeProps) {
const { amt, bind } = useGooHover({ animate, forceHover })
const { gradId, gooId } = useGooIds()
const cx = 50
const cy = 50
const hexR = 20
const cr = 20
const k = REACH / (hexR + cr)
const crS = cr * k
const breathe = 1 + amt * 0.06
const centers: [number, number][] = [[cx, cy]]
for (let i = 0; i < 6; i++) {
const a = i * (TAU / 6)
centers.push([cx + hexR * Math.cos(a), cy + hexR * Math.sin(a)])
}
return (
<GooMarkSvg
size={size}
gradId={gradId}
gooId={gooId}
ariaLabel='Flower of life'
bind={bind}
className={className}
>
{centers.map(([x, y], i) => {
const nx = cx + (x - cx) * k
const ny = cy + (y - cy) * k
return (
<circle key={i} cx={nx.toFixed(2)} cy={ny.toFixed(2)} r={(crS * breathe).toFixed(2)} />
)
})}
</GooMarkSvg>
)
}
@@ -0,0 +1,25 @@
/**
* Shared SVG `<defs>` for the circle goo-mark family: the brand radial gradient
* (`#2C2C2C` center → `#5F5F5F` edge) and the metaball goo filter (blur → alpha
* threshold). Values are locked to the brand recipe.
*/
interface GooDefsProps {
gradId: string
gooId: string
gooFusion?: number
}
export function GooDefs({ gradId, gooId, gooFusion = 1.5 }: GooDefsProps) {
return (
<defs>
<radialGradient id={gradId} gradientUnits='userSpaceOnUse' cx='50' cy='50' r='44'>
<stop stopColor='#2C2C2C' />
<stop offset='1' stopColor='#5F5F5F' />
</radialGradient>
<filter id={gooId} x='-25%' y='-25%' width='150%' height='150%'>
<feGaussianBlur in='SourceGraphic' stdDeviation={gooFusion} result='b' />
<feColorMatrix in='b' type='matrix' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -9' />
</filter>
</defs>
)
}
@@ -0,0 +1,64 @@
import type { ReactNode } from 'react'
import { cn } from '@sim/emcn'
import { GooDefs } from '@/app/(landing)/components/mothership/components/goo-marks/goo-defs'
import {
GOO_FUSION,
STROKE,
} from '@/app/(landing)/components/mothership/components/goo-marks/use-goo-hover'
/**
* Shared `<svg>` shell for the circle goo marks. Children are the stroke
* `<path>`/`<circle>` elements (plus optional plain dots). Applies the gradient
* stroke + goo filter.
*/
interface GooMarkSvgProps {
size: number
gradId: string
gooId: string
/**
* Retained for caller ergonomics, but no longer rendered: these marks are
* decorative glyphs paired with a visible heading, so the `<svg>` is
* `aria-hidden` rather than labeled.
*/
ariaLabel?: string
bind?: Record<string, () => void>
children: ReactNode
dots?: ReactNode
gooFusion?: number
className?: string
}
export function GooMarkSvg({
size,
gradId,
gooId,
bind,
children,
dots,
gooFusion = GOO_FUSION,
className,
}: GooMarkSvgProps) {
return (
<svg
viewBox='0 0 100 100'
width={size}
height={size}
aria-hidden='true'
className={cn('block outline-none', className)}
{...bind}
>
<GooDefs gradId={gradId} gooId={gooId} gooFusion={gooFusion} />
<g
filter={`url(#${gooId})`}
stroke={`url(#${gradId})`}
strokeWidth={STROKE}
strokeLinecap='round'
strokeLinejoin='round'
fill='none'
>
{children}
</g>
{dots ? <g>{dots}</g> : null}
</svg>
)
}
@@ -0,0 +1,76 @@
'use client'
import type { ReactNode } from 'react'
import { GooMarkSvg } from '@/app/(landing)/components/mothership/components/goo-marks/goo-mark-svg'
import {
useGooHover,
useGooIds,
} from '@/app/(landing)/components/mothership/components/goo-marks/use-goo-hover'
/**
* Sim circle goo-mark: GRID-9.
* 3x3 lattice of overlapping circles, centers spaced one radius apart
* (circle radius 16), with intersection dots (r2.2, gradient fill) on the
* 9 lattice nodes.
*/
export interface Grid9Props {
size?: number
className?: string
/** Show the 9 intersection dots (default true). */
dots?: boolean
animate?: boolean
forceHover?: boolean
}
export function Grid9({
size = 110,
className,
dots = true,
animate = true,
forceHover = false,
}: Grid9Props) {
const { amt, bind } = useGooHover({ animate, forceHover })
const { gradId, gooId } = useGooIds()
const cx = 50
const cy = 50
const r = 16
const offs = [-r, 0, r]
const dotR = 2.2 * (1 + amt * 0.5)
const circles: ReactNode[] = []
const nodeDots: ReactNode[] = []
let i = 0
for (const oy of offs) {
for (const ox of offs) {
const x = (cx + ox).toFixed(2)
const y = (cy + oy).toFixed(2)
circles.push(<circle key={`c${i}`} cx={x} cy={y} r={r} />)
nodeDots.push(
<circle
key={`d${i}`}
cx={x}
cy={y}
r={dotR.toFixed(2)}
fill={`url(#${gradId})`}
stroke='none'
/>
)
i++
}
}
return (
<GooMarkSvg
size={size}
gradId={gradId}
gooId={gooId}
ariaLabel='Grid-9'
bind={bind}
className={className}
dots={dots ? nodeDots : undefined}
>
{circles}
</GooMarkSvg>
)
}
@@ -0,0 +1,24 @@
export {
FanRings,
type FanRingsProps,
} from '@/app/(landing)/components/mothership/components/goo-marks/fan-rings'
export {
FlowerOfLife,
type FlowerOfLifeProps,
} from '@/app/(landing)/components/mothership/components/goo-marks/flower-of-life'
export {
Grid9,
type Grid9Props,
} from '@/app/(landing)/components/mothership/components/goo-marks/grid-9'
export {
Lissajous32,
type Lissajous32Props,
} from '@/app/(landing)/components/mothership/components/goo-marks/lissajous-32'
export {
LissajousMorph,
type LissajousMorphProps,
} from '@/app/(landing)/components/mothership/components/goo-marks/lissajous-morph'
export {
WovenTorus,
type WovenTorusProps,
} from '@/app/(landing)/components/mothership/components/goo-marks/woven-torus'
@@ -0,0 +1,49 @@
'use client'
import { GooMarkSvg } from '@/app/(landing)/components/mothership/components/goo-marks/goo-mark-svg'
import {
normalizeReach,
type Pt,
sampleClosed,
toPath,
useGooHover,
useGooIds,
} from '@/app/(landing)/components/mothership/components/goo-marks/use-goo-hover'
/**
* Sim circle goo-mark: LISSAJOUS 3:2.
* x = 50 + 40·sin(3t + π/2), y = 50 + 40·sin(2t)
*/
export interface Lissajous32Props {
size?: number
className?: string
animate?: boolean
forceHover?: boolean
}
export function Lissajous32({
size = 110,
className,
animate = true,
forceHover = false,
}: Lissajous32Props) {
const { amt, bind } = useGooHover({ animate, forceHover })
const { gradId, gooId } = useGooIds()
const phase = Math.PI / 2 + amt * 0.4
const fn = (t: number): Pt => [50 + 40 * Math.sin(3 * t + phase), 50 + 40 * Math.sin(2 * t)]
const d = toPath(normalizeReach(sampleClosed(fn)))
return (
<GooMarkSvg
size={size}
gradId={gradId}
gooId={gooId}
ariaLabel='Lissajous 3:2'
bind={bind}
className={className}
>
<path d={d} />
</GooMarkSvg>
)
}
@@ -0,0 +1,87 @@
'use client'
import { cn } from '@sim/emcn'
import {
normalizeReach,
type Pt,
sampleClosed,
toPath,
useGooHover,
useGooIds,
} from '@/app/(landing)/components/mothership/components/goo-marks/use-goo-hover'
import { gradientForTone } from '@/app/(landing)/components/mothership/components/iso-marks/use-goo-mark'
/**
* Sim circle goo-mark: LISSAJOUS MORPH (Context).
* A Lissajous figure that morphs its frequency ratio on hover - 3:2 at rest
* eases to 5:4, with a phase shift - so the curve reweaves itself. Self-contained
* (its own gradient + goo defs) so it can carry its tuned stroke and constants
* independently of the static Build Lissajous.
*/
const lerp = (a: number, b: number, t: number) => a + (b - a) * t
const REST = { a: 3, b: 2, phase: 1.5708 }
const HOVER = { a: 5, b: 4, phase: 3.16 }
const AMP = 40
const STROKE = 2.5
const GOO_FUSION = 1.5
export interface LissajousMorphProps {
size?: number
className?: string
animate?: boolean
forceHover?: boolean
}
export function LissajousMorph({
size = 110,
className,
animate = true,
forceHover = false,
}: LissajousMorphProps) {
const { amt, bind } = useGooHover({ animate, forceHover })
const { gradId, gooId } = useGooIds()
const a = lerp(REST.a, HOVER.a, amt)
const b = lerp(REST.b, HOVER.b, amt)
const phase = lerp(REST.phase, HOVER.phase, amt)
const fn = (t: number): Pt => [50 + AMP * Math.sin(a * t + phase), 50 + AMP * Math.sin(b * t)]
const d = toPath(normalizeReach(sampleClosed(fn)))
const { from, to } = gradientForTone(amt)
return (
<svg
viewBox='0 0 100 100'
width={size}
height={size}
aria-hidden='true'
className={cn('block outline-none', className)}
{...bind}
>
<defs>
<radialGradient id={gradId} gradientUnits='userSpaceOnUse' cx='50' cy='50' r='44'>
<stop stopColor={from} />
<stop offset='1' stopColor={to} />
</radialGradient>
<filter id={gooId} x='-25%' y='-25%' width='150%' height='150%'>
<feGaussianBlur in='SourceGraphic' stdDeviation={GOO_FUSION} result='b' />
<feColorMatrix
in='b'
type='matrix'
values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -9'
/>
</filter>
</defs>
<g
filter={`url(#${gooId})`}
stroke={`url(#${gradId})`}
strokeWidth={STROKE}
strokeLinecap='round'
strokeLinejoin='round'
fill='none'
>
<path d={d} />
</g>
</svg>
)
}
@@ -0,0 +1,119 @@
import { useCallback, useEffect, useId, useRef, useState } from 'react'
/**
* Sim abstract generative icon system - the circle / harmonograph "goo" family.
* Built from parametric curves and overlapping circles, normalized to a fixed
* REACH, fused by the goo filter.
*
* Locked recipe:
* viewBox 0 0 100 100, center (50,50)
* REACH = 40 (~80x80 footprint)
* stroke-width 3, round caps/joins, fill none
* gradient: radial userSpaceOnUse cx50 cy50 r44, #2C2C2C -> #5F5F5F
* goo filter: feGaussianBlur stdDeviation 1.5 + feColorMatrix alpha "20 -9"
*
* Hover (optional, subtle): a gentle breathe/rotate. These marks read well
* static; hover is a light accent, enabled when `animate` (default).
*/
export const REACH = 40
export const STROKE = 3
export const GOO_FUSION = 1.5
export const TAU = Math.PI * 2
export type Pt = [number, number]
/** Sample a closed parametric curve t in [0, 2π]. */
export function sampleClosed(fn: (t: number) => Pt, steps = 720): Pt[] {
const pts: Pt[] = []
for (let i = 0; i <= steps; i++) pts.push(fn((i / steps) * TAU))
return pts
}
/** Scale points so the farthest point from center reaches REACH. */
export function normalizeReach(pts: Pt[], cx = 50, cy = 50, reach = REACH): Pt[] {
let max = 0
for (const [x, y] of pts) {
const r = Math.hypot(x - cx, y - cy)
if (r > max) max = r
}
if (!max) return pts
const k = reach / max
return pts.map(([x, y]) => [cx + (x - cx) * k, cy + (y - cy) * k] as Pt)
}
export function toPath(pts: Pt[], close = true): string {
let d = `M${pts[0][0].toFixed(2)} ${pts[0][1].toFixed(2)}`
for (let i = 1; i < pts.length; i++) d += ` L${pts[i][0].toFixed(2)} ${pts[i][1].toFixed(2)}`
if (close) d += ' Z'
return d
}
export interface UseGooHoverOptions {
/** Enable the subtle hover breathe/rotate. */
animate?: boolean
ease?: number
forceHover?: boolean
}
export interface GooHoverResult {
/** 0 at rest, eases toward 1 on hover. */
amt: number
bind: {
onMouseEnter: () => void
onMouseLeave: () => void
onFocus: () => void
onBlur: () => void
}
}
export function useGooHover({
animate = true,
ease = 0.1,
forceHover = false,
}: UseGooHoverOptions = {}): GooHoverResult {
const [hovered, setHovered] = useState(false)
const amtRef = useRef(0)
const [, setTick] = useState(0)
const activeRef = useRef(forceHover)
const easeRef = useRef(ease)
activeRef.current = (forceHover || hovered) && animate
easeRef.current = ease
useEffect(() => {
if (!animate) return
const reduced =
typeof window !== 'undefined' &&
window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
let raf = 0
const loop = () => {
const target = activeRef.current ? 1 : 0
const next = reduced ? target : amtRef.current + (target - amtRef.current) * easeRef.current
if (Math.abs(next - amtRef.current) > 0.0005) {
amtRef.current = next
setTick((t) => (t + 1) % 1000000)
} else if (next !== amtRef.current) {
amtRef.current = next
}
raf = requestAnimationFrame(loop)
}
raf = requestAnimationFrame(loop)
return () => cancelAnimationFrame(raf)
}, [animate])
return {
amt: amtRef.current,
bind: {
onMouseEnter: useCallback(() => setHovered(true), []),
onMouseLeave: useCallback(() => setHovered(false), []),
onFocus: useCallback(() => setHovered(true), []),
onBlur: useCallback(() => setHovered(false), []),
},
}
}
/** Stable, SSR-safe gradient + filter ids per mark instance. */
export function useGooIds() {
const id = useId().replace(/:/g, '')
return { gradId: `goo-${id}-grad`, gooId: `goo-${id}-goo` }
}
@@ -0,0 +1,62 @@
'use client'
import type { ReactNode } from 'react'
import { GooMarkSvg } from '@/app/(landing)/components/mothership/components/goo-marks/goo-mark-svg'
import {
REACH,
TAU,
useGooHover,
useGooIds,
} from '@/app/(landing)/components/mothership/components/goo-marks/use-goo-hover'
/**
* Sim circle goo-mark: WOVEN TORUS.
* 3 offset rings (radius 22) whose centers sit on a small circle 120° apart -
* reads as orbit / motion.
*/
export interface WovenTorusProps {
size?: number
className?: string
animate?: boolean
forceHover?: boolean
}
export function WovenTorus({
size = 110,
className,
animate = true,
forceHover = false,
}: WovenTorusProps) {
const { amt, bind } = useGooHover({ animate, forceHover })
const { gradId, gooId } = useGooIds()
const cr = 22
const rot = amt * (TAU / 12)
const circles: ReactNode[] = []
for (let i = 0; i < 3; i++) {
const off = (i * (REACH - cr)) / 2
const ang = i * (TAU / 3) + rot
circles.push(
<circle
key={i}
cx={(50 + off * Math.cos(ang)).toFixed(2)}
cy={(50 + off * Math.sin(ang)).toFixed(2)}
r={cr}
/>
)
}
return (
<GooMarkSvg
size={size}
gradId={gradId}
gooId={gooId}
ariaLabel='Woven torus'
bind={bind}
className={className}
>
{circles}
</GooMarkSvg>
)
}
@@ -0,0 +1,41 @@
/**
* Shared SVG `<defs>` for the iso goo-mark family: the brand radial gradient
* (`#2C2C2C` center → `#5F5F5F` edge) and the metaball goo filter. The iso marks
* pass a lower `gooFusion` (0.8) so the wireframes stay crisp while still fusing
* softly at the joints.
*/
interface GooDefsProps {
gradId: string
gooId: string
gooFusion?: number
/** Gradient stops + radial position - default to the locked brand recipe. */
from?: string
to?: string
cx?: number
cy?: number
r?: number
}
export function GooDefs({
gradId,
gooId,
gooFusion = 1.5,
from = '#2C2C2C',
to = '#5F5F5F',
cx = 50,
cy = 50,
r = 44,
}: GooDefsProps) {
return (
<defs>
<radialGradient id={gradId} gradientUnits='userSpaceOnUse' cx={cx} cy={cy} r={r}>
<stop stopColor={from} />
<stop offset='1' stopColor={to} />
</radialGradient>
<filter id={gooId} x='-25%' y='-25%' width='150%' height='150%'>
<feGaussianBlur in='SourceGraphic' stdDeviation={gooFusion} result='b' />
<feColorMatrix in='b' type='matrix' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -9' />
</filter>
</defs>
)
}
@@ -0,0 +1,40 @@
export {
IsoBuildIllustration,
type IsoBuildIllustrationProps,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-build-illustration'
export {
IsoCubeGrid,
type IsoCubeGridProps,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-cube-grid'
export {
IsoCubeRow,
type IsoCubeRowProps,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-cube-row'
export {
IsoFourBox,
type IsoFourBoxProps,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-four-box'
export {
IsoGridPlane,
type IsoGridPlaneProps,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-grid-plane'
export {
IsoIngestIllustration,
type IsoIngestIllustrationProps,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-ingest-illustration'
export {
IsoIntegrateIllustration,
type IsoIntegrateIllustrationProps,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-integrate-illustration'
export {
IsoMonitorIllustration,
type IsoMonitorIllustrationProps,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-monitor-illustration'
export {
IsoStackedPlanes,
type IsoStackedPlanesProps,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-stacked-planes'
export {
IsoStar,
type IsoStarProps,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-star'
@@ -0,0 +1,221 @@
import { cn } from '@sim/emcn'
import {
createIsoLineProps,
ISO_FILL_HIGH,
ISO_FILL_LOW,
ISO_FILL_MID,
ISO_STROKE,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style'
export interface IsoBuildIllustrationProps {
size?: number
className?: string
}
const STROKE_PAINT = ISO_STROKE
const LINE_PROPS = createIsoLineProps('iso-build-line', STROKE_PAINT)
/** Isometric tile module the floor grid and the columns are built on. */
const TILE_WIDTH = 38
const TILE_HEIGHT = 21.94
const FLOOR_PANEL_PATH = 'M0.00 -131.64 L228.00 0.00 L0.00 131.64 L-228.00 0.00 Z'
/** Floor grid lines, split by the axis they animate along. */
const GRID_LINES_Y = [
'M38.00 -109.70 L-190.00 21.94',
'M76.00 -87.76 L-152.00 43.88',
'M114.00 -65.82 L-114.00 65.82',
'M152.00 -43.88 L-76.00 87.76',
'M190.00 -21.94 L-38.00 109.70',
] as const
const GRID_LINES_X = [
'M-38.00 -109.70 L190.00 21.94',
'M-76.00 -87.76 L152.00 43.88',
'M-114.00 -65.82 L114.00 65.82',
'M-152.00 -43.88 L76.00 87.76',
'M-190.00 -21.94 L38.00 109.70',
] as const
/**
* The 16 columns, in painter's order (back to front), as `{ cx, topY, height }`
* where `topY` is the centre of the column's top rhombus and `height` is its
* downward extrusion. Tuned to the supplied "skyline" arrangement.
*/
const COLUMNS = [
{ cx: 152, topY: 0, height: 21.94 },
{ cx: 0, topY: -153.57, height: 43.88 },
{ cx: 76, topY: -153.57, height: 87.76 },
{ cx: 114, topY: -87.76, height: 43.88 },
{ cx: -38, topY: -109.7, height: 21.94 },
{ cx: 0, topY: -76.79, height: 10.97 },
{ cx: -190, topY: -10.97, height: 10.97 },
{ cx: -152, topY: 10.97, height: 10.97 },
{ cx: -38, topY: -10.97, height: 10.97 },
{ cx: 38, topY: -10.97, height: 10.97 },
{ cx: 0, topY: 10.97, height: 10.97 },
{ cx: 76, topY: -21.94, height: 43.88 },
{ cx: 38, topY: 10.97, height: 32.91 },
{ cx: 0, topY: 43.88, height: 21.94 },
{ cx: 76, topY: 43.88, height: 21.94 },
{ cx: 0, topY: 98.73, height: 10.97 },
] as const
const svgNumber = (value: number) => value.toFixed(2)
interface ColumnFaces {
top: string
left: string
right: string
}
const getColumnFaces = (cx: number, topY: number, height: number): ColumnFaces => {
const baseY = topY + height
return {
top: `M${svgNumber(cx)} ${svgNumber(topY - TILE_HEIGHT)} L${svgNumber(cx + TILE_WIDTH)} ${svgNumber(topY)} L${svgNumber(cx)} ${svgNumber(topY + TILE_HEIGHT)} L${svgNumber(cx - TILE_WIDTH)} ${svgNumber(topY)} Z`,
left: `M${svgNumber(cx - TILE_WIDTH)} ${svgNumber(topY)} L${svgNumber(cx)} ${svgNumber(topY + TILE_HEIGHT)} L${svgNumber(cx)} ${svgNumber(baseY + TILE_HEIGHT)} L${svgNumber(cx - TILE_WIDTH)} ${svgNumber(baseY)} Z`,
right: `M${svgNumber(cx + TILE_WIDTH)} ${svgNumber(topY)} L${svgNumber(cx)} ${svgNumber(topY + TILE_HEIGHT)} L${svgNumber(cx)} ${svgNumber(baseY + TILE_HEIGHT)} L${svgNumber(cx + TILE_WIDTH)} ${svgNumber(baseY)} Z`,
}
}
/**
* Inline supplied illustration for the Build area - a column "skyline" rising
* off an isometric floor grid. The two grid axes drift along their own diagonal
* on a slow loop (the live-construction read); hovering redraws every contour
* from zero. Pure CSS, so this stays a server component.
*/
export function IsoBuildIllustration({ size = 176, className }: IsoBuildIllustrationProps) {
return (
<svg
viewBox='-263.2717227504693 -263.2717227504693 526.5434455009386 526.5434455009386'
fill='none'
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
aria-hidden={true}
focusable='false'
className={cn('iso-build-illustration block max-w-none shrink-0', className)}
>
<style>
{`
.iso-build-line {
stroke-dasharray: 1;
stroke-dashoffset: 0;
}
@media (prefers-reduced-motion: no-preference) {
[data-build-axis='y'] {
animation: iso-build-grid-flow-y 6200ms cubic-bezier(0.37, 0, 0.22, 1) infinite;
}
[data-build-axis='x'] {
animation: iso-build-grid-flow-x 6200ms cubic-bezier(0.37, 0, 0.22, 1) infinite;
}
}
.iso-build-illustration:hover .iso-build-line {
animation: iso-build-line-draw 900ms cubic-bezier(0.23, 1, 0.32, 1) both;
}
.iso-build-illustration:hover [data-build-layer='grid'] .iso-build-line {
animation-delay: 0ms;
}
.iso-build-illustration:hover [data-build-layer='columns'] .iso-build-line {
animation-delay: 105ms;
}
@keyframes iso-build-line-draw {
from {
stroke-dashoffset: 1;
}
to {
stroke-dashoffset: 0;
}
}
@keyframes iso-build-grid-flow-y {
0%,
100% {
transform: translate(0, 0);
}
50% {
transform: translate(-6px, 3.5px);
}
}
@keyframes iso-build-grid-flow-x {
0%,
100% {
transform: translate(0, 0);
}
50% {
transform: translate(6px, 3.5px);
}
}
@media (prefers-reduced-motion: reduce) {
[data-build-axis='y'],
[data-build-axis='x'],
.iso-build-illustration:hover .iso-build-line {
animation: none;
}
}
`}
</style>
<defs>
<clipPath id='iso-build-floor-clip'>
<path d={FLOOR_PANEL_PATH} />
</clipPath>
<filter id='iso-build-line-connection' x='-100%' y='-100%' width='300%' height='300%'>
<feGaussianBlur in='SourceGraphic' stdDeviation='1' result='b' />
<feColorMatrix
in='b'
type='matrix'
values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -7.2'
result='goo'
/>
<feComposite in='SourceGraphic' in2='goo' operator='over' />
</filter>
</defs>
<g filter='url(#iso-build-line-connection)'>
<g data-build-layer='grid' pointerEvents='none'>
<path d={FLOOR_PANEL_PATH} fill={ISO_FILL_HIGH} stroke='none' pointerEvents='none' />
<g clipPath='url(#iso-build-floor-clip)'>
<g data-build-axis='y'>
{GRID_LINES_Y.map((path) => (
<path key={path} d={path} {...LINE_PROPS} strokeLinecap='butt' />
))}
</g>
<g data-build-axis='x'>
{GRID_LINES_X.map((path) => (
<path key={path} d={path} {...LINE_PROPS} strokeLinecap='butt' />
))}
</g>
</g>
<path d={FLOOR_PANEL_PATH} {...LINE_PROPS} strokeLinejoin='miter' />
</g>
<g data-build-layer='columns' pointerEvents='none'>
{COLUMNS.map((column) => {
const faces = getColumnFaces(column.cx, column.topY, column.height)
const key = `${column.cx}:${column.topY}:${column.height}`
return (
<g key={key} pointerEvents='none'>
<path d={faces.left} {...LINE_PROPS} fill={ISO_FILL_LOW} />
<path d={faces.right} {...LINE_PROPS} fill={ISO_FILL_MID} />
<path d={faces.top} {...LINE_PROPS} fill={ISO_FILL_HIGH} />
</g>
)
})}
</g>
</g>
</svg>
)
}
@@ -0,0 +1,153 @@
'use client'
import { cn } from '@sim/emcn'
import { GooDefs } from '@/app/(landing)/components/mothership/components/iso-marks/goo-defs'
import {
type Edge,
gradientForTone,
isoProject,
type MarkState,
type Pt,
TARGET,
useGooMark,
useMarkIds,
} from '@/app/(landing)/components/mothership/components/iso-marks/use-goo-mark'
/**
* Sim iso goo-mark: CUBE GRID.
* Nine small iso cubes tiled in a 3×3 screen grid. Rest: the nine sit spread
* apart. Hover (gather): they pull in toward the center into a snug grid - still
* clearly nine separate cubes, never merged into a blob. No spin; the motion is
* pure convergence.
*/
interface GridState extends MarkState {
gap: number
tilt: number
tone: number
}
const REST: GridState = { gap: 3.3, tilt: 0.5, tone: 1 }
const HOVER: GridState = { gap: 2.05, tilt: 0.5, tone: 1 }
const GRID = 3
const U = 0.5
const STROKE = 2.4
const GOO_FUSION = 0.55
/** A unit iso cube, projected to screen space and centered on the origin. */
function unitCubeEdges(ky: number): Edge[] {
const corner = (sx: number, sy: number, sz: number) => isoProject(sx * U, sy * U, sz * U, ky)
const c = [
corner(-1, -1, -1),
corner(1, -1, -1),
corner(1, 1, -1),
corner(-1, 1, -1),
corner(-1, -1, 1),
corner(1, -1, 1),
corner(1, 1, 1),
corner(-1, 1, 1),
]
const ed: [number, number][] = [
[0, 1],
[1, 2],
[2, 3],
[3, 0],
[4, 5],
[5, 6],
[6, 7],
[7, 4],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
]
return ed.map(([a, b]) => [c[a], c[b]] as Edge)
}
function buildEdges(s: GridState): Edge[] {
const base = unitCubeEdges(s.tilt)
const edges: Edge[] = []
for (let i = 0; i < GRID; i++) {
for (let j = 0; j < GRID; j++) {
const dx = (i - (GRID - 1) / 2) * s.gap
const dy = (j - (GRID - 1) / 2) * s.gap
for (const [A, B] of base) {
edges.push([
[A[0] + dx, A[1] + dy],
[B[0] + dx, B[1] + dy],
])
}
}
}
return edges
}
function normalizeEdges(edges: Edge[]): Edge[] {
const pts = edges.flat()
let minx = Number.POSITIVE_INFINITY
let maxx = Number.NEGATIVE_INFINITY
let miny = Number.POSITIVE_INFINITY
let maxy = Number.NEGATIVE_INFINITY
for (const [x, y] of pts) {
if (x < minx) minx = x
if (x > maxx) maxx = x
if (y < miny) miny = y
if (y > maxy) maxy = y
}
const w = maxx - minx || 1
const h = maxy - miny || 1
const scale = TARGET / Math.max(w, h)
const ox = 50 - ((minx + maxx) / 2) * scale
const oy = 50 - ((miny + maxy) / 2) * scale
const tx = (p: Pt): Pt => [ox + p[0] * scale, oy + p[1] * scale]
return edges.map(([A, B]) => [tx(A), tx(B)] as Edge)
}
function edgesToD(edges: Edge[]): string {
let d = ''
for (const [A, B] of edges) {
d += `M${A[0].toFixed(2)} ${A[1].toFixed(2)} L${B[0].toFixed(2)} ${B[1].toFixed(2)} `
}
return d.trim()
}
export interface IsoCubeGridProps {
size?: number
className?: string
forceHover?: boolean
}
export function IsoCubeGrid({ size = 110, className, forceHover = false }: IsoCubeGridProps) {
const { current, bind } = useGooMark<GridState>({ rest: REST, hover: HOVER, forceHover })
const { gradId, gooId } = useMarkIds()
const edges = normalizeEdges(buildEdges(current))
const { from, to } = gradientForTone(current.tone)
return (
<svg
viewBox='0 0 100 100'
width={size}
height={size}
role='img'
aria-label='Cube grid'
className={cn(
'focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-[var(--brand-agent)]',
className
)}
style={{ display: 'block' }}
{...bind}
>
<GooDefs gradId={gradId} gooId={gooId} gooFusion={GOO_FUSION} from={from} to={to} />
<g
filter={`url(#${gooId})`}
strokeWidth={STROKE}
strokeLinecap='round'
strokeLinejoin='round'
fill='none'
>
<path d={edgesToD(edges)} stroke={`url(#${gradId})`} />
</g>
</svg>
)
}
@@ -0,0 +1,146 @@
'use client'
import { cn } from '@sim/emcn'
import { GooDefs } from '@/app/(landing)/components/mothership/components/iso-marks/goo-defs'
import {
type Edge,
gradientForTone,
isoProject,
type MarkState,
type Pt,
TARGET,
useGooMark,
useMarkIds,
} from '@/app/(landing)/components/mothership/components/iso-marks/use-goo-mark'
/**
* Sim iso goo-mark: CUBE ROW.
* Three cubes set in a level row. Rest: an even row, all one size. Hover (read):
* each cube resizes to a different scale, like a live gauge re-leveling. No
* spin; the motion is pure isometric scale.
*/
interface RowState extends MarkState {
s0: number
s1: number
s2: number
tilt: number
tone: number
}
const REST: RowState = { s0: 0.62, s1: 0.62, s2: 0.62, tilt: 0.5, tone: 1 }
const HOVER: RowState = { s0: 0.95, s1: 0.55, s2: 0.8, tilt: 0.5, tone: 1 }
const SLOTS = 3
const SPREAD = 0.92
const STROKE = 2.4
const GOO_FUSION = 1.0
function cubeAt(cx: number, cy: number, half: number, ky: number): Edge[] {
const corner = (sx: number, sy: number, sz: number) =>
isoProject(cx + sx * half, cy + sy * half, sz * half, ky)
const c = [
corner(-1, -1, -1),
corner(1, -1, -1),
corner(1, 1, -1),
corner(-1, 1, -1),
corner(-1, -1, 1),
corner(1, -1, 1),
corner(1, 1, 1),
corner(-1, 1, 1),
]
const ed: [number, number][] = [
[0, 1],
[1, 2],
[2, 3],
[3, 0],
[4, 5],
[5, 6],
[6, 7],
[7, 4],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
]
return ed.map(([a, b]) => [c[a], c[b]] as Edge)
}
function buildEdges(s: RowState): Edge[] {
const sizes = [s.s0, s.s1, s.s2]
const edges: Edge[] = []
for (let i = 0; i < SLOTS; i++) {
const t = (i - (SLOTS - 1) / 2) * SPREAD
edges.push(...cubeAt(t, -t, sizes[i], s.tilt))
}
return edges
}
function normalizeEdges(edges: Edge[]): Edge[] {
const pts = edges.flat()
let minx = Number.POSITIVE_INFINITY
let maxx = Number.NEGATIVE_INFINITY
let miny = Number.POSITIVE_INFINITY
let maxy = Number.NEGATIVE_INFINITY
for (const [x, y] of pts) {
if (x < minx) minx = x
if (x > maxx) maxx = x
if (y < miny) miny = y
if (y > maxy) maxy = y
}
const w = maxx - minx || 1
const h = maxy - miny || 1
const scale = TARGET / Math.max(w, h)
const ox = 50 - ((minx + maxx) / 2) * scale
const oy = 50 - ((miny + maxy) / 2) * scale
const tx = (p: Pt): Pt => [ox + p[0] * scale, oy + p[1] * scale]
return edges.map(([A, B]) => [tx(A), tx(B)] as Edge)
}
function edgesToD(edges: Edge[]): string {
let d = ''
for (const [A, B] of edges) {
d += `M${A[0].toFixed(2)} ${A[1].toFixed(2)} L${B[0].toFixed(2)} ${B[1].toFixed(2)} `
}
return d.trim()
}
export interface IsoCubeRowProps {
size?: number
className?: string
forceHover?: boolean
}
export function IsoCubeRow({ size = 110, className, forceHover = false }: IsoCubeRowProps) {
const { current, bind } = useGooMark<RowState>({ rest: REST, hover: HOVER, forceHover })
const { gradId, gooId } = useMarkIds()
const edges = normalizeEdges(buildEdges(current))
const { from, to } = gradientForTone(current.tone)
return (
<svg
viewBox='0 0 100 100'
width={size}
height={size}
role='img'
aria-label='Cube row'
className={cn(
'focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-[var(--brand-agent)]',
className
)}
style={{ display: 'block' }}
{...bind}
>
<GooDefs gradId={gradId} gooId={gooId} gooFusion={GOO_FUSION} from={from} to={to} />
<g
filter={`url(#${gooId})`}
strokeWidth={STROKE}
strokeLinecap='round'
strokeLinejoin='round'
fill='none'
>
<path d={edgesToD(edges)} stroke={`url(#${gradId})`} />
</g>
</svg>
)
}
@@ -0,0 +1,162 @@
'use client'
import { cn } from '@sim/emcn'
import { GooDefs } from '@/app/(landing)/components/mothership/components/iso-marks/goo-defs'
import {
type Edge,
gradientForTone,
isoProject,
type MarkState,
type Pt,
rotate2,
TARGET,
useGooMark,
useMarkIds,
} from '@/app/(landing)/components/mothership/components/iso-marks/use-goo-mark'
/**
* Sim iso goo-mark: FOUR-BOX TWIST.
* Four wireframe boxes layered vertically, each rotated at a progressive angular
* offset so the stack twists into a rounded cluster. Rest: open and twisted,
* still. Hover (close + spin): gap collapses, twist unwinds, spins. An optional
* signal-blue accent box is off by default (the landing stays greyscale).
*/
interface FourBoxState extends MarkState {
gap: number
twist: number
spin: number
tilt: number
tone: number
}
const REST: FourBoxState = { gap: 1, twist: 11, spin: -0.38, tilt: 0.4, tone: 1 }
const HOVER: FourBoxState = { gap: 0, twist: 0, spin: -3.14, tilt: 0.4, tone: 1 }
const BOXES = 2
const STROKE = 2.4
const GOO_FUSION = 1.4
const BLUE = '#9FC6E8'
function boxEdges(s: number, ky: number, rot: number, zc: number): Edge[] {
const corner = (sx: number, sy: number, sz: number) => {
const [rx, ry] = rotate2(sx * s, sy * s, rot)
return isoProject(rx, ry, sz * s * 0.4 + zc, ky)
}
const c = [
corner(-1, -1, -1),
corner(1, -1, -1),
corner(1, 1, -1),
corner(-1, 1, -1),
corner(-1, -1, 1),
corner(1, -1, 1),
corner(1, 1, 1),
corner(-1, 1, 1),
]
const ed: [number, number][] = [
[0, 1],
[1, 2],
[2, 3],
[3, 0],
[4, 5],
[5, 6],
[6, 7],
[7, 4],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
]
return ed.map(([a, b]) => [c[a], c[b]] as Edge)
}
function buildBoxes(c: FourBoxState): Edge[][] {
const twRad = (c.twist * Math.PI) / 180
const totalH = (BOXES - 1) * c.gap
const boxes: Edge[][] = []
for (let i = 0; i < BOXES; i++) {
const zc = i * c.gap - totalH / 2
const rot = c.spin * i * 0.5 + i * twRad
boxes.push(boxEdges(1.0, c.tilt, rot, zc))
}
return boxes
}
function normalizeBoxes(boxes: Edge[][]): Edge[][] {
const pts = boxes.flat().flat()
let minx = Number.POSITIVE_INFINITY
let maxx = Number.NEGATIVE_INFINITY
let miny = Number.POSITIVE_INFINITY
let maxy = Number.NEGATIVE_INFINITY
for (const [x, y] of pts) {
if (x < minx) minx = x
if (x > maxx) maxx = x
if (y < miny) miny = y
if (y > maxy) maxy = y
}
const w = maxx - minx || 1
const h = maxy - miny || 1
const scale = TARGET / Math.max(w, h)
const ox = 50 - ((minx + maxx) / 2) * scale
const oy = 50 - ((miny + maxy) / 2) * scale
const tx = (p: Pt): Pt => [ox + p[0] * scale, oy + p[1] * scale]
return boxes.map((bx) => bx.map(([A, B]) => [tx(A), tx(B)] as Edge))
}
function edgesToD(edges: Edge[]): string {
let d = ''
for (const [A, B] of edges) {
d += `M${A[0].toFixed(2)} ${A[1].toFixed(2)} L${B[0].toFixed(2)} ${B[1].toFixed(2)} `
}
return d.trim()
}
export interface IsoFourBoxProps {
size?: number
className?: string
forceHover?: boolean
/** Render one box (the 2nd from bottom) in the signal-blue accent. */
blueAccent?: boolean
}
export function IsoFourBox({
size = 110,
className,
forceHover = false,
blueAccent = false,
}: IsoFourBoxProps) {
const { current, bind } = useGooMark<FourBoxState>({ rest: REST, hover: HOVER, forceHover })
const { gradId, gooId } = useMarkIds()
const boxes = normalizeBoxes(buildBoxes(current))
const { from, to } = gradientForTone(current.tone)
const blueIdx = blueAccent ? 1 : -1
const normal: Edge[] = []
let blue: Edge[] = []
boxes.forEach((bx, i) => {
if (i === blueIdx) blue = bx
else normal.push(...bx)
})
return (
<svg
viewBox='0 0 100 100'
width={size}
height={size}
aria-hidden='true'
className={cn('block outline-none', className)}
{...bind}
>
<GooDefs gradId={gradId} gooId={gooId} gooFusion={GOO_FUSION} from={from} to={to} />
<g
filter={`url(#${gooId})`}
strokeWidth={STROKE}
strokeLinecap='round'
strokeLinejoin='round'
fill='none'
>
<path d={edgesToD(normal)} stroke={`url(#${gradId})`} />
{blue.length > 0 && <path d={edgesToD(blue)} stroke={BLUE} />}
</g>
</svg>
)
}
@@ -0,0 +1,82 @@
'use client'
import { cn } from '@sim/emcn'
import { GooDefs } from '@/app/(landing)/components/mothership/components/iso-marks/goo-defs'
import {
type Edge,
edgesToPaths,
isoProject,
type MarkState,
rotate2,
useGooMark,
useMarkIds,
} from '@/app/(landing)/components/mothership/components/iso-marks/use-goo-mark'
/**
* Sim iso goo-mark: GRID PLANE.
* A single flat lattice rotated 45deg and squashed into isometric.
* Rest: tilted, still. Hover (open + spin): tilt flattens, spins.
*/
interface GridState extends MarkState {
tilt: number
spin: number
}
const REST: GridState = { tilt: 0.5, spin: 0 }
const HOVER: GridState = { tilt: 0.42, spin: 1.2 }
const DIVISIONS = 4
const STROKE = 1.5
const GOO_FUSION = 0.8
function buildEdges(c: GridState): Edge[] {
const half = 40
const proj = (u: number, v: number) => {
const [ru, rv] = rotate2(u, v, c.spin)
return isoProject(ru * half, rv * half, 0, c.tilt)
}
const E: Edge[] = []
for (let i = 0; i <= DIVISIONS; i++) {
const v = -1 + (2 * i) / DIVISIONS
E.push([proj(-1, v), proj(1, v)])
}
for (let i = 0; i <= DIVISIONS; i++) {
const u = -1 + (2 * i) / DIVISIONS
E.push([proj(u, -1), proj(u, 1)])
}
return E
}
export interface IsoGridPlaneProps {
size?: number
className?: string
forceHover?: boolean
}
export function IsoGridPlane({ size = 110, className, forceHover = false }: IsoGridPlaneProps) {
const { current, bind } = useGooMark<GridState>({ rest: REST, hover: HOVER, forceHover })
const { gradId, gooId } = useMarkIds()
return (
<svg
viewBox='0 0 100 100'
width={size}
height={size}
aria-hidden='true'
className={cn('block outline-none', className)}
{...bind}
>
<GooDefs gradId={gradId} gooId={gooId} gooFusion={GOO_FUSION} />
<g
filter={`url(#${gooId})`}
stroke={`url(#${gradId})`}
strokeWidth={STROKE}
strokeLinecap='round'
strokeLinejoin='round'
fill='none'
>
<path d={edgesToPaths(buildEdges(current))} />
</g>
</svg>
)
}
@@ -0,0 +1,31 @@
import type { SVGProps } from 'react'
export const ISO_LINE_STROKE_WIDTH = 3.2
export const ISO_ENDPOINT_STROKE_WIDTH = 3.3
export const ISO_STROKE = 'color-mix(in srgb, var(--text-subtle) 76%, var(--text-muted))'
export const ISO_FILL_LOW = 'var(--surface-6)'
export const ISO_FILL_MID = 'color-mix(in srgb, var(--surface-3) 58%, var(--surface-6))'
export const ISO_FILL_HIGH = 'var(--surface-3)'
export const ISO_FILL_PULSE_LOW = 'color-mix(in srgb, var(--surface-6) 72%, var(--surface-7))'
export const ISO_FILL_PULSE_MID = 'color-mix(in srgb, var(--surface-3) 34%, var(--surface-6))'
export const ISO_FILL_PULSE_HIGH = 'color-mix(in srgb, var(--surface-3) 82%, var(--surface-6))'
export const ISO_FILL_PROPS = {
stroke: 'none',
pathLength: 1,
pointerEvents: 'none',
} satisfies SVGProps<SVGPathElement>
export function createIsoLineProps(className: string, stroke: string): SVGProps<SVGPathElement> {
return {
className,
fill: 'none',
pathLength: 1,
pointerEvents: 'none',
opacity: 1,
stroke,
strokeWidth: ISO_LINE_STROKE_WIDTH,
strokeLinecap: 'round',
strokeLinejoin: 'round',
}
}
@@ -0,0 +1,323 @@
import { cn } from '@sim/emcn'
import {
createIsoLineProps,
ISO_FILL_PROPS as FILL_PROPS,
ISO_FILL_HIGH,
ISO_FILL_LOW,
ISO_FILL_MID,
ISO_FILL_PULSE_HIGH,
ISO_FILL_PULSE_LOW,
ISO_FILL_PULSE_MID,
ISO_STROKE,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style'
export interface IsoIngestIllustrationProps {
size?: number
className?: string
}
const STROKE_PAINT = ISO_STROKE
const LINE_PROPS = createIsoLineProps('iso-ingest-line', STROKE_PAINT)
/**
* Inline supplied illustration for the Context area - a central store
* cube whose three faces cycle fills like data loading in, flanked by a back
* slab and two outline "context source" glyphs. Hovering spreads the sources
* away from the core and redraws every contour from zero.
*/
export function IsoIngestIllustration({ size = 168, className }: IsoIngestIllustrationProps) {
return (
<svg
viewBox='-263.2717227504693 -263.2717227504693 526.5434455009386 526.5434455009386'
fill='none'
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
aria-hidden={true}
focusable='false'
className={cn('iso-ingest-illustration block max-w-none shrink-0', className)}
>
<style>
{`
.iso-ingest-line {
stroke-dasharray: 1;
stroke-dashoffset: 0;
}
.iso-ingest-back-panel,
.iso-ingest-left-context,
.iso-ingest-right-context,
.iso-ingest-center-face {
transform-box: fill-box;
transform-origin: center;
}
.iso-ingest-center-face {
will-change: fill;
}
@media (prefers-reduced-motion: no-preference) {
.iso-ingest-back-panel,
.iso-ingest-left-context,
.iso-ingest-right-context {
transition: transform 760ms cubic-bezier(0.16, 1, 0.3, 1);
will-change: transform;
}
.iso-ingest-illustration:hover .iso-ingest-back-panel {
transform: translate(18px, -11px);
}
.iso-ingest-illustration:hover .iso-ingest-left-context {
transform: translate(-26px, 13px);
}
.iso-ingest-illustration:hover .iso-ingest-right-context {
transform: translate(26px, 13px);
}
.iso-ingest-center-face-top {
animation: iso-ingest-center-top-load 2300ms steps(1, end) infinite;
}
.iso-ingest-center-face-left {
animation: iso-ingest-center-left-load 1900ms steps(1, end) infinite;
}
.iso-ingest-center-face-right {
animation: iso-ingest-center-right-load 2700ms steps(1, end) infinite;
}
}
.iso-ingest-illustration:hover .iso-ingest-line {
animation: iso-ingest-line-draw 900ms cubic-bezier(0.23, 1, 0.32, 1) both;
}
.iso-ingest-illustration:hover [data-ingest-layer='storage-fills'] .iso-ingest-line {
animation-delay: 0ms;
}
.iso-ingest-illustration:hover [data-ingest-layer='storage-edges'] .iso-ingest-line {
animation-delay: 70ms;
}
.iso-ingest-illustration:hover [data-ingest-layer='storage-details'] .iso-ingest-line {
animation-delay: 160ms;
}
@keyframes iso-ingest-line-draw {
from {
stroke-dashoffset: 1;
}
to {
stroke-dashoffset: 0;
}
}
@keyframes iso-ingest-center-top-load {
0%,
18% {
fill: ${ISO_FILL_HIGH};
}
19%,
43% {
fill: ${ISO_FILL_PULSE_MID};
}
44%,
68% {
fill: ${ISO_FILL_PULSE_LOW};
}
69%,
100% {
fill: ${ISO_FILL_HIGH};
}
}
@keyframes iso-ingest-center-left-load {
0%,
27% {
fill: ${ISO_FILL_LOW};
}
28%,
52% {
fill: ${ISO_FILL_PULSE_LOW};
}
53%,
79% {
fill: ${ISO_FILL_PULSE_HIGH};
}
80%,
100% {
fill: ${ISO_FILL_MID};
}
}
@keyframes iso-ingest-center-right-load {
0%,
22% {
fill: ${ISO_FILL_MID};
}
23%,
48% {
fill: ${ISO_FILL_PULSE_HIGH};
}
49%,
72% {
fill: ${ISO_FILL_PULSE_LOW};
}
73%,
100% {
fill: ${ISO_FILL_MID};
}
}
@media (prefers-reduced-motion: reduce) {
.iso-ingest-back-panel,
.iso-ingest-left-context,
.iso-ingest-right-context,
.iso-ingest-center-face,
.iso-ingest-illustration:hover .iso-ingest-line {
animation: none;
transition: none;
}
}
`}
</style>
<defs>
<filter id='iso-ingest-line-connection' x='-100%' y='-100%' width='300%' height='300%'>
<feGaussianBlur in='SourceGraphic' stdDeviation='1' result='b' />
<feColorMatrix
in='b'
type='matrix'
values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -7.2'
result='goo'
/>
<feComposite in='SourceGraphic' in2='goo' operator='over' />
</filter>
</defs>
<g filter='url(#iso-ingest-line-connection)'>
<g data-ingest-layer='storage-fills' pointerEvents='none'>
<path
d='M44.30 -19.23 L183.46 61.12 Q189.88 64.82 189.88 57.41 L189.88 -103.28 Q189.88 -110.69 183.46 -114.40 L44.30 -194.74 Q37.88 -198.45 37.88 -191.04 L37.88 -30.34 Q37.88 -22.93 44.30 -19.23 Z'
{...FILL_PROPS}
className='iso-ingest-back-panel'
fill={ISO_FILL_LOW}
/>
<path
d='M201.99 57.83 L196.30 61.12 Q189.88 64.82 189.88 57.41 L189.88 -103.28 Q189.88 -110.69 196.30 -114.40 L201.99 -117.68 Q208.41 -121.39 208.41 -113.98 L208.41 46.72 Q208.41 54.13 201.99 57.83 Z'
{...FILL_PROPS}
className='iso-ingest-back-panel'
fill={ISO_FILL_MID}
/>
<path
d='M62.82 -205.44 L201.99 -125.09 Q208.41 -121.39 201.99 -117.68 L196.30 -114.40 Q189.88 -110.69 183.46 -114.40 L44.30 -194.74 Q37.88 -198.45 44.30 -202.15 L49.99 -205.44 Q56.41 -209.14 62.82 -205.44 Z'
{...FILL_PROPS}
className='iso-ingest-back-panel'
fill={ISO_FILL_HIGH}
/>
<path
d='M-145.58 91.46 L-6.42 171.81 Q0.00 175.51 0.00 168.10 L0.00 7.41 Q0.00 -0.00 -6.42 -3.71 L-145.58 -84.05 Q-152.00 -87.76 -152.00 -80.35 L-152.00 80.35 Q-152.00 87.76 -145.58 91.46 Z'
{...FILL_PROPS}
className='iso-ingest-center-face iso-ingest-center-face-left'
fill={ISO_FILL_LOW}
/>
<path
d='M145.58 91.46 L6.42 171.81 Q0.00 175.51 0.00 168.10 L0.00 7.41 Q0.00 -0.00 6.42 -3.71 L145.58 -84.05 Q152.00 -87.76 152.00 -80.35 L152.00 80.35 Q152.00 87.76 145.58 91.46 Z'
{...FILL_PROPS}
className='iso-ingest-center-face iso-ingest-center-face-right'
fill={ISO_FILL_MID}
/>
<path
d='M6.42 -171.81 L145.58 -91.46 Q152.00 -87.76 145.58 -84.05 L6.42 -3.71 Q0.00 -0.00 -6.42 -3.71 L-145.58 -84.05 Q-152.00 -87.76 -145.58 -91.46 L-6.42 -171.81 Q0.00 -175.51 6.42 -171.81 Z'
{...FILL_PROPS}
className='iso-ingest-center-face iso-ingest-center-face-top'
fill={ISO_FILL_HIGH}
/>
</g>
<g data-ingest-layer='storage-edges' pointerEvents='none'>
<path
d='M62.82 -29.92 L201.99 50.42 Q208.41 54.13 201.99 57.83 L196.30 61.12 Q189.88 64.82 183.46 61.12 L44.30 -19.23 Q37.88 -22.93 44.30 -26.64 L49.99 -29.92 Q56.41 -33.63 62.82 -29.92 Z'
{...LINE_PROPS}
className='iso-ingest-line iso-ingest-back-panel'
/>
<path
d='M62.82 -205.44 L201.99 -125.09 Q208.41 -121.39 201.99 -117.68 L196.30 -114.40 Q189.88 -110.69 183.46 -114.40 L44.30 -194.74 Q37.88 -198.45 44.30 -202.15 L49.99 -205.44 Q56.41 -209.14 62.82 -205.44 Z'
{...LINE_PROPS}
className='iso-ingest-line iso-ingest-back-panel'
/>
<path
d='M62.82 -29.92 L201.99 50.42 Q208.41 54.13 208.41 46.72 L208.41 -113.98 Q208.41 -121.39 201.99 -125.09 L62.82 -205.44 Q56.41 -209.14 56.41 -201.73 L56.41 -41.04 Q56.41 -33.63 62.82 -29.92 Z'
{...LINE_PROPS}
className='iso-ingest-line iso-ingest-back-panel'
/>
<path
d='M44.30 -19.23 L183.46 61.12 Q189.88 64.82 189.88 57.41 L189.88 -103.28 Q189.88 -110.69 183.46 -114.40 L44.30 -194.74 Q37.88 -198.45 37.88 -191.04 L37.88 -30.34 Q37.88 -22.93 44.30 -19.23 Z'
{...LINE_PROPS}
className='iso-ingest-line iso-ingest-back-panel'
/>
<path
d='M49.99 -29.92 L44.30 -26.64 Q37.88 -22.93 37.88 -30.34 L37.88 -191.04 Q37.88 -198.45 44.30 -202.15 L49.99 -205.44 Q56.41 -209.14 56.41 -201.73 L56.41 -41.04 Q56.41 -33.63 49.99 -29.92 Z'
{...LINE_PROPS}
className='iso-ingest-line iso-ingest-back-panel'
/>
<path
d='M201.99 57.83 L196.30 61.12 Q189.88 64.82 189.88 57.41 L189.88 -103.28 Q189.88 -110.69 196.30 -114.40 L201.99 -117.68 Q208.41 -121.39 208.41 -113.98 L208.41 46.72 Q208.41 54.13 201.99 57.83 Z'
{...LINE_PROPS}
className='iso-ingest-line iso-ingest-back-panel'
/>
<path
d='M6.42 3.70 L145.58 84.05 Q152.00 87.76 145.58 91.46 L6.42 171.81 Q0.00 175.51 -6.42 171.81 L-145.58 91.46 Q-152.00 87.76 -145.58 84.05 L-6.42 3.70 Q0.00 0.00 6.42 3.70 Z'
{...LINE_PROPS}
/>
<path
d='M6.42 -171.81 L145.58 -91.46 Q152.00 -87.76 145.58 -84.05 L6.42 -3.71 Q0.00 -0.00 -6.42 -3.71 L-145.58 -84.05 Q-152.00 -87.76 -145.58 -91.46 L-6.42 -171.81 Q0.00 -175.51 6.42 -171.81 Z'
{...LINE_PROPS}
/>
<path
d='M6.42 3.70 L145.58 84.05 Q152.00 87.76 152.00 80.35 L152.00 -80.35 Q152.00 -87.76 145.58 -91.46 L6.42 -171.81 Q0.00 -175.51 0.00 -168.10 L0.00 -7.41 Q0.00 0.00 6.42 3.70 Z'
{...LINE_PROPS}
/>
<path
d='M-145.58 91.46 L-6.42 171.81 Q0.00 175.51 0.00 168.10 L0.00 7.41 Q0.00 -0.00 -6.42 -3.71 L-145.58 -84.05 Q-152.00 -87.76 -152.00 -80.35 L-152.00 80.35 Q-152.00 87.76 -145.58 91.46 Z'
{...LINE_PROPS}
/>
<path
d='M-6.42 3.70 L-145.58 84.05 Q-152.00 87.76 -152.00 80.35 L-152.00 -80.35 Q-152.00 -87.76 -145.58 -91.46 L-6.42 -171.81 Q0.00 -175.51 0.00 -168.10 L0.00 -7.41 Q0.00 0.00 -6.42 3.70 Z'
{...LINE_PROPS}
/>
<path
d='M145.58 91.46 L6.42 171.81 Q0.00 175.51 0.00 168.10 L0.00 7.41 Q0.00 -0.00 6.42 -3.71 L145.58 -84.05 Q152.00 -87.76 152.00 -80.35 L152.00 80.35 Q152.00 87.76 145.58 91.46 Z'
{...LINE_PROPS}
/>
</g>
<g data-ingest-layer='storage-details' pointerEvents='none'>
<path
d='M75.88 132.29 C75.88 83.82 109.91 24.89 151.88 0.65 C193.86 -23.58 227.88 -3.94 227.88 44.53 C227.88 93.00 193.86 151.93 151.88 176.17 C109.91 200.40 75.88 180.75 75.88 132.29 Z'
{...LINE_PROPS}
className='iso-ingest-line iso-ingest-right-context'
/>
<path
d='M-194.26 149.09 L-110.45 197.48 Q-77.54 216.48 -77.54 178.48 L-77.54 81.71 Q-77.54 43.71 -110.45 24.71 L-194.26 -23.68 Q-227.17 -42.68 -227.17 -4.68 L-227.17 92.09 Q-227.17 130.09 -194.26 149.09 Z'
{...LINE_PROPS}
className='iso-ingest-line iso-ingest-left-context'
/>
<path
d='M-50.58 -122.45 L-44.42 -118.89 Q-38.00 -115.18 -44.42 -111.48 L-98.08 -80.49 Q-104.50 -76.79 -110.92 -80.49 L-117.08 -84.05 Q-123.50 -87.76 -117.08 -91.46 L-63.42 -122.45 Q-57.00 -126.15 -50.58 -122.45 Z'
{...LINE_PROPS}
/>
</g>
</g>
</svg>
)
}
@@ -0,0 +1,188 @@
import { cn } from '@sim/emcn'
import {
createIsoLineProps,
ISO_FILL_HIGH,
ISO_FILL_LOW,
ISO_FILL_MID,
ISO_STROKE,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style'
export interface IsoIntegrateIllustrationProps {
size?: number
className?: string
}
const STROKE_PAINT = ISO_STROKE
const LINE_PROPS = createIsoLineProps('iso-integrate-line', STROKE_PAINT)
/**
* Inline supplied illustration for the Integrate area - a three-tier isometric
* stack (a socket node up top, a connector port on each of the lower tiers).
* The top and bottom tiers breathe toward the middle on a slow loop; hovering
* redraws every contour from zero.
*/
export function IsoIntegrateIllustration({ size = 172, className }: IsoIntegrateIllustrationProps) {
return (
<svg
viewBox='-263.2717227504693 -263.2717227504693 526.5434455009386 526.5434455009386'
fill='none'
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
aria-hidden={true}
focusable='false'
className={cn('iso-integrate-illustration block max-w-none shrink-0', className)}
>
<style>
{`
.iso-integrate-line {
stroke-dasharray: 1;
stroke-dashoffset: 0;
}
[data-integrate-layer='top-plane'],
[data-integrate-layer='top-socket'],
[data-integrate-layer='bottom-plane'],
[data-integrate-layer='bottom-port'] {
transform-box: fill-box;
transform-origin: center;
will-change: transform;
}
@media (prefers-reduced-motion: no-preference) {
[data-integrate-layer='top-plane'],
[data-integrate-layer='top-socket'] {
animation: iso-integrate-top-float 5200ms cubic-bezier(0.37, 0, 0.22, 1) infinite;
}
[data-integrate-layer='bottom-plane'],
[data-integrate-layer='bottom-port'] {
animation: iso-integrate-bottom-float 5200ms cubic-bezier(0.37, 0, 0.22, 1) infinite;
}
}
.iso-integrate-illustration:hover .iso-integrate-line {
animation: iso-integrate-line-draw 900ms cubic-bezier(0.23, 1, 0.32, 1) both;
}
.iso-integrate-illustration:hover [data-integrate-layer='bottom-plane'] .iso-integrate-line,
.iso-integrate-illustration:hover [data-integrate-layer='bottom-port'] .iso-integrate-line {
animation-delay: 0ms;
}
.iso-integrate-illustration:hover [data-integrate-layer='middle-plane'] .iso-integrate-line,
.iso-integrate-illustration:hover [data-integrate-layer='middle-port'] .iso-integrate-line {
animation-delay: 75ms;
}
.iso-integrate-illustration:hover [data-integrate-layer='top-plane'] .iso-integrate-line,
.iso-integrate-illustration:hover [data-integrate-layer='top-socket'] .iso-integrate-line {
animation-delay: 150ms;
}
@keyframes iso-integrate-top-float {
0%,
16%,
100% {
transform: translateY(0);
}
44%,
64% {
transform: translateY(20px);
}
}
@keyframes iso-integrate-bottom-float {
0%,
16%,
100% {
transform: translateY(0);
}
44%,
64% {
transform: translateY(-20px);
}
}
@keyframes iso-integrate-line-draw {
from {
stroke-dashoffset: 1;
}
to {
stroke-dashoffset: 0;
}
}
@media (prefers-reduced-motion: reduce) {
[data-integrate-layer='top-plane'],
[data-integrate-layer='top-socket'],
[data-integrate-layer='bottom-plane'],
[data-integrate-layer='bottom-port'],
.iso-integrate-illustration:hover .iso-integrate-line {
animation: none;
}
}
`}
</style>
<defs>
<filter id='iso-integrate-line-connection' x='-100%' y='-100%' width='300%' height='300%'>
<feGaussianBlur in='SourceGraphic' stdDeviation='1' result='b' />
<feColorMatrix
in='b'
type='matrix'
values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -7.2'
result='goo'
/>
<feComposite in='SourceGraphic' in2='goo' operator='over' />
</filter>
</defs>
<g filter='url(#iso-integrate-line-connection)'>
<g data-integrate-layer='bottom-plane' pointerEvents='none'>
<path
d='M32.91 19.00 L157.09 90.70 Q190.00 109.70 157.09 128.70 L32.91 200.39 Q0.00 219.39 -32.91 200.39 L-157.09 128.70 Q-190.00 109.70 -157.09 90.70 L-32.91 19.00 Q0.00 -0.00 32.91 19.00 Z'
{...LINE_PROPS}
/>
</g>
<g data-integrate-layer='middle-plane' pointerEvents='none'>
<path
d='M32.91 -90.70 L157.09 -19.00 Q190.00 -0.00 157.09 19.00 L32.91 90.70 Q0.00 109.70 -32.91 90.70 L-157.09 19.00 Q-190.00 -0.00 -157.09 -19.00 L-32.91 -90.70 Q0.00 -109.70 32.91 -90.70 Z'
{...LINE_PROPS}
fill={ISO_FILL_LOW}
/>
</g>
<g data-integrate-layer='middle-port' pointerEvents='none'>
<path
d='M111.45 -23.41 L135.55 -9.50 Q152.00 0.00 135.55 9.50 L120.95 17.92 Q104.50 27.42 88.05 17.92 L63.95 4.02 Q47.50 -5.48 63.95 -14.98 L78.55 -23.41 Q95.00 -32.91 111.45 -23.41 Z'
{...LINE_PROPS}
fill={ISO_FILL_MID}
/>
</g>
<g data-integrate-layer='bottom-port' pointerEvents='none'>
<path
d='M-34.01 129.57 L28.08 165.42 Q44.53 174.92 28.08 184.42 L19.42 189.41 Q2.97 198.91 -13.49 189.41 L-75.58 153.56 Q-92.03 144.06 -75.58 134.56 L-66.92 129.57 Q-50.47 120.07 -34.01 129.57 Z'
{...LINE_PROPS}
fill={ISO_FILL_LOW}
/>
</g>
<g data-integrate-layer='top-plane' pointerEvents='none'>
<path
d='M32.91 -200.39 L157.09 -128.70 Q190.00 -109.70 157.09 -90.70 L32.91 -19.00 Q0.00 -0.00 -32.91 -19.00 L-157.09 -90.70 Q-190.00 -109.70 -157.09 -128.70 L-32.91 -200.39 Q0.00 -219.39 32.91 -200.39 Z'
{...LINE_PROPS}
fill={ISO_FILL_MID}
/>
</g>
<g data-integrate-layer='top-socket' pointerEvents='none'>
<path
d='M37.70 -129.63 C16.71 -117.51 -17.31 -117.51 -38.30 -129.63 C-59.29 -141.74 -59.29 -161.39 -38.30 -173.50 C-17.31 -185.62 16.71 -185.62 37.70 -173.50 C58.69 -161.39 58.69 -141.74 37.70 -129.63 Z'
{...LINE_PROPS}
fill={ISO_FILL_HIGH}
/>
</g>
</g>
</svg>
)
}
@@ -0,0 +1,229 @@
import { cn } from '@sim/emcn'
import {
createIsoLineProps,
ISO_FILL_HIGH,
ISO_FILL_LOW,
ISO_FILL_MID,
ISO_STROKE,
} from '@/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style'
export interface IsoMonitorIllustrationProps {
size?: number
className?: string
}
const STROKE_PAINT = ISO_STROKE
const LINE_PROPS = createIsoLineProps('iso-monitor-line', STROKE_PAINT)
/**
* Inline supplied illustration for the Monitor area - an isometric housing whose
* lid and two side panels drift apart on a slow loop to reveal the stacked inner
* plates (the "look inside every run" read). Hovering redraws every contour from
* zero.
*/
export function IsoMonitorIllustration({ size = 176, className }: IsoMonitorIllustrationProps) {
return (
<svg
viewBox='-263.2717227504693 -263.2717227504693 526.5434455009386 526.5434455009386'
fill='none'
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
aria-hidden={true}
focusable='false'
className={cn('iso-monitor-illustration block max-w-none shrink-0', className)}
>
<style>
{`
.iso-monitor-line {
stroke-dasharray: 1;
stroke-dashoffset: 0;
}
.iso-monitor-separating-panel {
transform-box: fill-box;
transform-origin: center;
will-change: transform;
}
@media (prefers-reduced-motion: no-preference) {
.iso-monitor-panel-top {
animation: iso-monitor-panel-top-separate 5600ms cubic-bezier(0.37, 0, 0.22, 1) infinite;
}
.iso-monitor-panel-right {
animation: iso-monitor-panel-right-separate 5600ms cubic-bezier(0.37, 0, 0.22, 1) infinite;
animation-delay: 120ms;
}
.iso-monitor-panel-left {
animation: iso-monitor-panel-left-separate 5600ms cubic-bezier(0.37, 0, 0.22, 1) infinite;
animation-delay: 240ms;
}
}
.iso-monitor-illustration:hover .iso-monitor-line {
animation: iso-monitor-line-draw 900ms cubic-bezier(0.23, 1, 0.32, 1) both;
}
.iso-monitor-illustration:hover [data-monitor-layer='shadow-plane'] .iso-monitor-line,
.iso-monitor-illustration:hover [data-monitor-layer='base-bar'] .iso-monitor-line {
animation-delay: 0ms;
}
.iso-monitor-illustration:hover [data-monitor-layer='inner-low'] .iso-monitor-line,
.iso-monitor-illustration:hover [data-monitor-layer='inner-high'] .iso-monitor-line {
animation-delay: 70ms;
}
.iso-monitor-illustration:hover [data-monitor-layer='left-panel'] .iso-monitor-line,
.iso-monitor-illustration:hover [data-monitor-layer='right-panel'] .iso-monitor-line {
animation-delay: 140ms;
}
.iso-monitor-illustration:hover [data-monitor-layer='top-lid'] .iso-monitor-line {
animation-delay: 210ms;
}
@keyframes iso-monitor-line-draw {
from {
stroke-dashoffset: 1;
}
to {
stroke-dashoffset: 0;
}
}
@keyframes iso-monitor-panel-top-separate {
0%,
16%,
100% {
transform: translate(0, 0);
}
38%,
64% {
transform: translate(-9px, -18px);
}
}
@keyframes iso-monitor-panel-right-separate {
0%,
16%,
100% {
transform: translate(0, 0);
}
38%,
64% {
transform: translate(18px, -8px);
}
}
@keyframes iso-monitor-panel-left-separate {
0%,
16%,
100% {
transform: translate(0, 0);
}
38%,
64% {
transform: translate(-18px, 9px);
}
}
@media (prefers-reduced-motion: reduce) {
.iso-monitor-separating-panel,
.iso-monitor-illustration:hover .iso-monitor-line {
animation: none;
}
}
`}
</style>
<defs>
<filter id='iso-monitor-line-connection' x='-100%' y='-100%' width='300%' height='300%'>
<feGaussianBlur in='SourceGraphic' stdDeviation='1' result='b' />
<feColorMatrix
in='b'
type='matrix'
values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -7.2'
result='goo'
/>
<feComposite in='SourceGraphic' in2='goo' operator='over' />
</filter>
</defs>
<g filter='url(#iso-monitor-line-connection)'>
<g data-monitor-layer='shadow-plane' pointerEvents='none'>
<path
d='M16.45 -34.38 L211.55 78.26 Q228.00 87.76 211.55 97.26 L16.45 209.89 Q0.00 219.39 -16.45 209.89 L-211.55 97.26 Q-228.00 87.76 -211.55 78.26 L-16.45 -34.38 Q0.00 -43.88 16.45 -34.38 Z'
{...LINE_PROPS}
/>
</g>
<g data-monitor-layer='base-bar' pointerEvents='none'>
<path
d='M-186.71 89.66 L-3.29 195.55 Q0.00 197.45 0.00 193.65 L0.00 179.31 Q0.00 175.51 -3.29 173.61 L-186.71 67.72 Q-190.00 65.82 -190.00 69.62 L-190.00 83.96 Q-190.00 87.76 -186.71 89.66 Z'
{...LINE_PROPS}
fill={ISO_FILL_LOW}
/>
<path
d='M186.71 89.66 L3.29 195.55 Q0.00 197.45 0.00 193.65 L0.00 179.31 Q0.00 175.51 3.29 173.61 L186.71 67.72 Q190.00 65.82 190.00 69.62 L190.00 83.96 Q190.00 87.76 186.71 89.66 Z'
{...LINE_PROPS}
fill={ISO_FILL_MID}
/>
<path
d='M3.29 -41.98 L186.71 63.92 Q190.00 65.82 186.71 67.72 L3.29 173.61 Q0.00 175.51 -3.29 173.61 L-186.71 67.72 Q-190.00 65.82 -186.71 63.92 L-3.29 -41.98 Q0.00 -43.88 3.29 -41.98 Z'
{...LINE_PROPS}
fill={ISO_FILL_HIGH}
/>
</g>
<g
data-monitor-layer='right-panel'
className='iso-monitor-separating-panel iso-monitor-panel-right'
pointerEvents='none'
>
<path
d='M32.91 -68.76 L128.59 -13.52 Q161.50 5.48 161.50 -32.52 L161.50 -88.15 Q161.50 -126.15 128.59 -145.15 L32.91 -200.39 Q0.00 -219.39 0.00 -181.39 L0.00 -125.76 Q0.00 -87.76 32.91 -68.76 Z'
{...LINE_PROPS}
/>
</g>
<g data-monitor-layer='inner-low' pointerEvents='none'>
<path
d='M32.91 -2.94 L119.09 46.82 Q152.00 65.82 119.09 84.82 L32.91 134.58 Q0.00 153.58 -32.91 134.58 L-119.09 84.82 Q-152.00 65.82 -119.09 46.82 L-32.91 -2.94 Q0.00 -21.94 32.91 -2.94 Z'
{...LINE_PROPS}
fill={ISO_FILL_LOW}
/>
</g>
<g data-monitor-layer='inner-high' pointerEvents='none'>
<path
d='M32.91 -46.82 L119.09 2.94 Q152.00 21.94 119.09 40.94 L32.91 90.70 Q0.00 109.70 -32.91 90.70 L-119.09 40.94 Q-152.00 21.94 -119.09 2.94 L-32.91 -46.82 Q0.00 -65.82 32.91 -46.82 Z'
{...LINE_PROPS}
fill={ISO_FILL_MID}
/>
</g>
<g
data-monitor-layer='left-panel'
className='iso-monitor-separating-panel iso-monitor-panel-left'
pointerEvents='none'
>
<path
d='M-157.09 62.88 L-32.91 134.58 Q0.00 153.58 0.00 115.58 L0.00 38.00 Q0.00 0.00 -32.91 -19.00 L-157.09 -90.70 Q-190.00 -109.70 -190.00 -71.70 L-190.00 5.88 Q-190.00 43.88 -157.09 62.88 Z'
{...LINE_PROPS}
/>
</g>
<g
data-monitor-layer='top-lid'
className='iso-monitor-separating-panel iso-monitor-panel-top'
pointerEvents='none'
>
<path
d='M32.91 -200.39 L81.09 -172.58 Q114.00 -153.58 81.09 -134.58 L-43.09 -62.88 Q-76.00 -43.88 -108.91 -62.88 L-157.09 -90.70 Q-190.00 -109.70 -157.09 -128.70 L-32.91 -200.39 Q0.00 -219.39 32.91 -200.39 Z'
{...LINE_PROPS}
/>
</g>
</g>
</svg>
)
}
@@ -0,0 +1,127 @@
'use client'
import { cn } from '@sim/emcn'
import { GooDefs } from '@/app/(landing)/components/mothership/components/iso-marks/goo-defs'
import {
type Edge,
edgesToPaths,
gradientForTone,
isoProject,
type MarkState,
rotate2,
useGooMark,
useMarkIds,
} from '@/app/(landing)/components/mothership/components/iso-marks/use-goo-mark'
/**
* Sim iso goo-mark: STACKED PLANES.
* N lattice sheets layered with a vertical gap.
* Rest: open and spread, slightly tilted, still. Hover (close + spin): gap
* collapses tight, tilt steepens, spins.
*/
interface StackState extends MarkState {
gap: number
tilt: number
spin: number
stroke: number
gradCx: number
gradCy: number
gradR: number
tone: number
}
const REST: StackState = {
gap: 34.5,
tilt: 0.34,
spin: -2.82,
stroke: 2,
gradCx: 50,
gradCy: 50,
gradR: 44,
tone: 1,
}
const HOVER: StackState = {
gap: 11.5,
tilt: 0.33,
spin: -3.14,
stroke: 2,
gradCx: 50,
gradCy: 50,
gradR: 44,
tone: 1,
}
const PLANES = 4
const DIVISIONS = 2
const GOO_FUSION = 1.1
function buildEdges(c: StackState): Edge[] {
const half = 40
const totalH = (PLANES - 1) * c.gap
const proj = (u: number, v: number, z: number) => {
const [ru, rv] = rotate2(u, v, c.spin)
const p = isoProject(ru * half, rv * half, 0, c.tilt)
return [p[0], p[1] + (z - totalH / 2)] as [number, number]
}
const E: Edge[] = []
for (let pl = 0; pl < PLANES; pl++) {
const z = pl * c.gap
for (let i = 0; i <= DIVISIONS; i++) {
const v = -1 + (2 * i) / DIVISIONS
E.push([proj(-1, v, z), proj(1, v, z)])
}
for (let i = 0; i <= DIVISIONS; i++) {
const u = -1 + (2 * i) / DIVISIONS
E.push([proj(u, -1, z), proj(u, 1, z)])
}
}
return E
}
export interface IsoStackedPlanesProps {
size?: number
className?: string
forceHover?: boolean
}
export function IsoStackedPlanes({
size = 110,
className,
forceHover = false,
}: IsoStackedPlanesProps) {
const { current, bind } = useGooMark<StackState>({ rest: REST, hover: HOVER, forceHover })
const { gradId, gooId } = useMarkIds()
const { from, to } = gradientForTone(current.tone)
return (
<svg
viewBox='0 0 100 100'
width={size}
height={size}
aria-hidden='true'
className={cn('block outline-none', className)}
{...bind}
>
<GooDefs
gradId={gradId}
gooId={gooId}
gooFusion={GOO_FUSION}
from={from}
to={to}
cx={current.gradCx}
cy={current.gradCy}
r={current.gradR}
/>
<g
filter={`url(#${gooId})`}
stroke={`url(#${gradId})`}
strokeWidth={current.stroke}
strokeLinecap='round'
strokeLinejoin='round'
fill='none'
>
<path d={edgesToPaths(buildEdges(current))} />
</g>
</svg>
)
}
@@ -0,0 +1,122 @@
'use client'
import { cn } from '@sim/emcn'
import { GooDefs } from '@/app/(landing)/components/mothership/components/iso-marks/goo-defs'
import {
type Edge,
edgesToPaths,
isoProject,
type MarkState,
type Pt,
useGooMark,
useMarkIds,
} from '@/app/(landing)/components/mothership/components/iso-marks/use-goo-mark'
/**
* Sim iso goo-mark: ISO STAR.
* Three cuboid bars crossing at 0 / +60 / -60 degrees forming a 6-point
* interlocking star. Rest: thin bars, still. Hover (open + spin): bars thicken,
* spins.
*/
interface StarState extends MarkState {
thickness: number
spin: number
}
const REST: StarState = { thickness: 5, spin: 0 }
const HOVER: StarState = { thickness: 13, spin: 1.2 }
const BAR_LENGTH = 16
const STROKE = 1.5
const GOO_FUSION = 0.8
function rotZ(p: [number, number, number], a: number): [number, number, number] {
const c = Math.cos(a)
const s = Math.sin(a)
return [p[0] * c - p[1] * s, p[0] * s + p[1] * c, p[2]]
}
function barEdges(L: number, T: number): [number, number, number][][] {
const C = (sx: number, sy: number, sz: number): [number, number, number] => [
sx * L,
sy * T,
sz * T,
]
const corners: [number, number, number][] = [
[-1, -1, -1],
[1, -1, -1],
[1, 1, -1],
[-1, 1, -1],
[-1, -1, 1],
[1, -1, 1],
[1, 1, 1],
[-1, 1, 1],
]
const ci = (s: [number, number, number]) => C(s[0], s[1], s[2])
const ed: [number, number][] = [
[0, 1],
[1, 2],
[2, 3],
[3, 0],
[4, 5],
[5, 6],
[6, 7],
[7, 4],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
]
return ed.map(([a, b]) => [ci(corners[a]), ci(corners[b])])
}
function buildEdges(c: StarState): Edge[] {
const L = BAR_LENGTH * 0.5
const T = c.thickness * 0.5
const angs = [c.spin, Math.PI / 3 + c.spin, -Math.PI / 3 + c.spin]
const E: Edge[] = []
for (const a of angs) {
for (const [p, q] of barEdges(L, T)) {
const pr = rotZ(p, a)
const qr = rotZ(q, a)
const A: Pt = isoProject(pr[0], pr[1], 1 + pr[2], 1)
const B: Pt = isoProject(qr[0], qr[1], 1 + qr[2], 1)
E.push([A, B])
}
}
return E
}
export interface IsoStarProps {
size?: number
className?: string
forceHover?: boolean
}
export function IsoStar({ size = 110, className, forceHover = false }: IsoStarProps) {
const { current, bind } = useGooMark<StarState>({ rest: REST, hover: HOVER, forceHover })
const { gradId, gooId } = useMarkIds()
return (
<svg
viewBox='0 0 100 100'
width={size}
height={size}
aria-hidden='true'
className={cn('block outline-none', className)}
{...bind}
>
<GooDefs gradId={gradId} gooId={gooId} gooFusion={GOO_FUSION} />
<g
filter={`url(#${gooId})`}
stroke={`url(#${gradId})`}
strokeWidth={STROKE}
strokeLinecap='round'
strokeLinejoin='round'
fill='none'
>
<path d={edgesToPaths(buildEdges(current))} />
</g>
</svg>
)
}
@@ -0,0 +1,188 @@
import { useCallback, useEffect, useId, useRef, useState } from 'react'
/**
* Shared rest → hover animation engine for the Sim iso goo-mark family.
*
* Every mark defines a `rest` and `hover` config with the same keys. On hover,
* the hook eases each numeric key from rest toward hover (and back on leave) via
* a fixed easing factor, driven by requestAnimationFrame. The component reads
* `current` each frame to render its SVG.
*
* Locked baseline (shared across all marks): ease 0.08, spin 1.2 on hover.
*/
export type MarkState = Record<string, number>
export interface UseGooMarkOptions<T extends MarkState> {
rest: T
hover: T
/** Easing factor per frame (0..1). Locked baseline = 0.08. */
ease?: number
/** Start in the hovered state (useful for previews). */
forceHover?: boolean
}
export interface UseGooMarkResult<T extends MarkState> {
/** The current tweened values - read this in render. */
current: T
/** Bind these to the element that should react to hover. */
bind: {
onMouseEnter: () => void
onMouseLeave: () => void
onFocus: () => void
onBlur: () => void
}
hovered: boolean
}
export function useGooMark<T extends MarkState>({
rest,
hover,
ease = 0.08,
forceHover = false,
}: UseGooMarkOptions<T>): UseGooMarkResult<T> {
const [hovered, setHovered] = useState(false)
const currentRef = useRef<T>({ ...rest })
const [, setTick] = useState(0)
const rafRef = useRef<number | null>(null)
const restRef = useRef(rest)
const hoverRef = useRef(hover)
const easeRef = useRef(ease)
const activeRef = useRef(forceHover)
restRef.current = rest
hoverRef.current = hover
easeRef.current = ease
activeRef.current = forceHover || hovered
useEffect(() => {
const prefersReduced =
typeof window !== 'undefined' &&
window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
const loop = () => {
const target = activeRef.current ? hoverRef.current : restRef.current
const cur = currentRef.current as MarkState
const tgt = target as MarkState
const e = easeRef.current
let changed = false
for (const k in tgt) {
const next = prefersReduced ? tgt[k] : cur[k] + (tgt[k] - cur[k]) * e
if (Math.abs(next - cur[k]) > 0.0001) changed = true
cur[k] = next
}
if (changed) setTick((t) => (t + 1) % 1000000)
rafRef.current = requestAnimationFrame(loop)
}
rafRef.current = requestAnimationFrame(loop)
return () => {
if (rafRef.current != null) cancelAnimationFrame(rafRef.current)
}
}, [])
const bind = {
onMouseEnter: useCallback(() => setHovered(true), []),
onMouseLeave: useCallback(() => setHovered(false), []),
onFocus: useCallback(() => setHovered(true), []),
onBlur: useCallback(() => setHovered(false), []),
}
return { current: currentRef.current as T, bind, hovered }
}
/** Every mark normalizes its bounding box into this footprint, centered (50,50). */
export const TARGET = 78
/** Locked goo + gradient constants for the whole family. */
export const GOO_GRADIENT = {
from: '#2C2C2C',
to: '#5F5F5F',
} as const
/**
* Rest → hover gradient recipe for the mark family. At rest the marks sit in a
* soft light grey; on hover they deepen to the brand dark gradient. The fade is
* driven by a `tone` key (0 → 1) tweened alongside the geometry, so color and
* shape animate as one.
*/
export const GRADIENT_REST = { from: '#A6A6A6', to: '#C4C4C4' } as const
export const GRADIENT_HOVER = GOO_GRADIENT
function hexLerp(a: string, b: string, t: number): string {
const pa = Number.parseInt(a.slice(1), 16)
const pb = Number.parseInt(b.slice(1), 16)
const lerpChannel = (shift: number) => {
const ca = (pa >> shift) & 255
const cb = (pb >> shift) & 255
return Math.round(ca + (cb - ca) * t)
}
const r = lerpChannel(16)
const g = lerpChannel(8)
const bl = lerpChannel(0)
return `#${((1 << 24) | (r << 16) | (g << 8) | bl).toString(16).slice(1)}`
}
/** Gradient stops for a given hover tone (0 = rest/light, 1 = hover/dark). */
export function gradientForTone(tone: number): { from: string; to: string } {
const t = Math.max(0, Math.min(1, tone))
return {
from: hexLerp(GRADIENT_REST.from, GRADIENT_HOVER.from, t),
to: hexLerp(GRADIENT_REST.to, GRADIENT_HOVER.to, t),
}
}
export type Pt = [number, number]
/** Isometric projection: 45deg in-plane rotation + vertical squash by `ky`, z is up. */
export function isoProject(x: number, y: number, z: number, ky: number): Pt {
const ix = (x - y) * Math.SQRT1_2
const iy = (x + y) * Math.SQRT1_2 * ky
return [ix, iy - z]
}
/** In-plane rotation (used for spin). */
export function rotate2(x: number, y: number, rot: number): Pt {
const c = Math.cos(rot)
const s = Math.sin(rot)
return [x * c - y * s, x * s + y * c]
}
export type Edge = [Pt, Pt]
/**
* Normalize a set of edges into the shared TARGET footprint and emit SVG `<path>`
* markup. Returns the path string only; callers wrap it with defs + filter.
*/
export function edgesToPaths(edges: Edge[]): string {
const pts = edges.flat()
let minx = Number.POSITIVE_INFINITY
let maxx = Number.NEGATIVE_INFINITY
let miny = Number.POSITIVE_INFINITY
let maxy = Number.NEGATIVE_INFINITY
for (const [x, y] of pts) {
if (x < minx) minx = x
if (x > maxx) maxx = x
if (y < miny) miny = y
if (y > maxy) maxy = y
}
const w = maxx - minx || 1
const h = maxy - miny || 1
const scale = TARGET / Math.max(w, h)
const ox = 50 - ((minx + maxx) / 2) * scale
const oy = 50 - ((miny + maxy) / 2) * scale
let d = ''
for (const [A, B] of edges) {
const ax = ox + A[0] * scale
const ay = oy + A[1] * scale
const bx = ox + B[0] * scale
const by = oy + B[1] * scale
d += `M${ax.toFixed(2)} ${ay.toFixed(2)} L${bx.toFixed(2)} ${by.toFixed(2)} `
}
return d.trim()
}
/** Stable, SSR-safe gradient + filter ids per mark instance. */
export function useMarkIds() {
const id = useId().replace(/:/g, '')
return { gradId: `gm-${id}-grad`, gooId: `gm-${id}-goo` }
}
@@ -0,0 +1,108 @@
import type { ComponentType, ReactNode } from 'react'
import {
IsoBuildIllustration,
IsoIngestIllustration,
IsoIntegrateIllustration,
IsoMonitorIllustration,
} from '@/app/(landing)/components/mothership/components/iso-marks'
/**
* Landing Sim section - the high-level "how Sim works" overview that sets up the
* {@link Features} deep-dive. A two-line heading frames Sim as one workspace for
* the whole platform; below, four columns call out its main areas (Integrate ·
* Context · Build · Monitor), each paired with an abstract circle "goo"
* mark and a one-line definition. These are the same four areas Features then
* shows in real product UI, framed here in fewer words, from the "what it is"
* angle, so the overview and the deep-dive don't repeat each other.
*
* The marks are the only client islands in this section: interactive brand
* glyphs (subtle hover breathe/rotate); the section itself stays server-rendered.
*
* Inter-section spacing is owned by the `<main>` flex `gap` in `landing.tsx`;
* this section carries no vertical padding. Horizontal padding (`px-20`) matches
* the sections above, and the section is capped at the shared `max-w-[1460px]`.
*/
type GooMark = ComponentType<{ size?: number; className?: string }>
interface Area {
word: string
/** Abstract circle goo-mark paired with the area. */
Mark: GooMark
/**
* Per-mark render size, tuned so the collection reads as one optical weight.
* The marks have different shape ratios (a full cube vs a flat lattice), so a
* uniform size makes some look bigger; these equalize their visual footprint.
*/
size: number
definition: ReactNode
}
const AREAS: Area[] = [
{
word: 'Integrate',
Mark: IsoIntegrateIllustration,
size: 180,
definition: (
<>
One catalog of 1,000+ integrations
<br />
your agents act through.
</>
),
},
{
word: 'Context',
Mark: IsoIngestIllustration,
size: 170,
definition: 'Your data, stored semantically as the memory your agents reason over.',
},
{
word: 'Build',
Mark: IsoBuildIllustration,
size: 176,
definition: 'Compose agent logic in the visual builder, or just describe it to Sim.',
},
{
word: 'Monitor',
Mark: IsoMonitorIllustration,
size: 174,
definition: 'See inside every run with live traces, logs, and real cost.',
},
]
export function Mothership() {
return (
<section
id='mothership'
aria-labelledby='mothership-heading'
className='mx-auto w-full max-w-[1460px] px-20 max-sm:px-5 max-lg:px-8'
>
<h2
id='mothership-heading'
className='max-w-[1200px] text-balance text-[28px] leading-[1.2] max-sm:text-[22px]'
>
<span className='block text-[var(--text-primary)]'>
Everything your agents need, in one workspace.
</span>
<span className='block text-[var(--text-body)]'>Build, run, and watch every agent.</span>
</h2>
<ul className='mt-16 grid grid-cols-4 gap-8 max-sm:mt-8 max-sm:grid-cols-1 max-sm:gap-10 max-lg:mt-12 max-lg:grid-cols-2 max-lg:gap-x-8 max-lg:gap-y-12'>
{AREAS.map(({ word, Mark, size, definition }) => (
<li key={word} className='flex flex-col gap-[22px]'>
<div className='flex size-[148px] items-center justify-center'>
<Mark size={size} />
</div>
<div className='flex flex-col gap-2'>
<h3 className='text-[var(--text-primary)] text-lg'>{word}</h3>
<p className='max-w-[250px] text-pretty text-[var(--text-body)] text-sm leading-[1.5]'>
{definition}
</p>
</div>
</li>
))}
</ul>
</section>
)
}