feat: web improvements

pull/715/head
Travis Fischer 2025-06-23 22:17:46 -05:00
rodzic 72750d1361
commit e388bcee46
11 zmienionych plików z 1259 dodań i 408 usunięć

Wyświetl plik

@ -124,6 +124,7 @@ export function registerV1StripeWebhook(app: HonoApp) {
assert(deployment, 404, `deployment "${deploymentId}" not found`)
}
const { project } = consumer
assert(project, 404, `project "${projectId}" not found`)
// TODO: Treat this as a transaction...
await Promise.all([
@ -158,13 +159,12 @@ export function registerV1StripeWebhook(app: HonoApp) {
const { consumerId, userId, projectId, deploymentId, plan } =
subscription.metadata
// TODO: This should be coming from Stripe Checkout, and a very flow
// follow-up webhook event should record the subscription and
// initialize the consumer, but it feels wrong to me to just be
// logging and ignore this event. In the future, if we support both
// Stripe Checkout and non-Stripe Checkout-based subscription flows,
// then this codepath should act very similarly to
// `customer.subscription.updated`.
// TODO: This should be coming from Stripe Checkout, and a subsequent
// webhook event should record the subscription and initialize the
// consumer, but it feels wrong to me to just be logging and ignore
// this event. In the future, if we support both Stripe Checkout and
// non-Stripe Checkout-based subscription flows, then this codepath
// should act very similarly to `customer.subscription.updated`.
if (
!consumerId ||
!userId ||

Wyświetl plik

@ -115,8 +115,17 @@ export async function createStripeCheckoutSession(
}
}
assert(
pricingPlan,
404,
`Unable to update stripe subscription for invalid pricing plan "${plan}"`
)
const updateParams: Stripe.SubscriptionUpdateParams = {
collection_method: 'charge_automatically',
description:
pricingPlan.description ??
`Subscription to ${project.name} ${pricingPlan.name}`,
metadata: {
plan: plan ?? null,
consumerId: consumer.id,
@ -126,12 +135,6 @@ export async function createStripeCheckoutSession(
}
}
assert(
pricingPlan,
404,
`Unable to update stripe subscription for invalid pricing plan "${plan}"`
)
const items: Stripe.SubscriptionUpdateParams.Item[] = await Promise.all(
pricingPlan.lineItems.map(async (lineItem) => {
const priceId = await getStripePriceIdForPricingPlanLineItem({

Wyświetl plik

@ -26,10 +26,16 @@
"@agentic/platform-types": "workspace:*",
"@agentic/platform-validators": "workspace:*",
"@number-flow/react": "^0.5.10",
"@pmndrs/assets": "^1.7.0",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tooltip": "^1.2.7",
"@react-three/cannon": "^6.6.0",
"@react-three/drei": "^10.2.0",
"@react-three/fiber": "^9.1.2",
"@react-three/postprocessing": "^3.0.4",
"@react-three/rapier": "^2.1.0",
"@tanstack/react-form": "^1.12.3",
"@tanstack/react-query": "^5.80.10",
"@tanstack/react-query-devtools": "^5.80.10",
@ -50,7 +56,9 @@
"react-use": "^17.6.0",
"sonner": "^2.0.5",
"stripe": "catalog:",
"suspend-react": "^0.1.3",
"tailwind-merge": "^3.3.1",
"three": "^0.177.0",
"type-fest": "catalog:"
},
"devDependencies": {
@ -58,6 +66,7 @@
"@tailwindcss/typography": "^0.5.16",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/three": "^0.177.0",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.10",

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.2 KiB

Wyświetl plik

@ -0,0 +1,264 @@
import Link from 'next/link'
import { GitHubStarCounter } from '@/components/github-star-counter'
import { HeroSimulation2 } from '@/components/hero-simulation-2'
import { SupplySideCTA } from '@/components/supply-side-cta'
import {
calendarBookingUrl,
docsUrl,
githubUrl,
twitterUrl
} from '@/lib/config'
export default function TheSecondBestDamnLandingPageEver() {
return (
<>
{/* Hero section */}
<section className='mb-16 relative'>
<div className='flex gap-8'>
{/* <div className='absolute top-0 bottom-0 left-0 right-0 bg-[url(/dots.svg)] bg-repeat bg-center bg-size-[32px_auto] opacity-30 dark:opacity-100' />
<div className='absolute top-0 bottom-0 left-0 right-0 bg-[radial-gradient(39%_50%_at_50%_50%,rgba(255,255,255,.3)_0%,rgb(255,255,255)_100%)] dark:bg-[radial-gradient(39%_50%_at_50%_50%,rgba(10,10,10,0)_0%,rgb(10,10,10)_100%)]' /> */}
<div className='flex flex-col gap-8 relative z-10'>
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
Your API Paid MCP, Instantly
</h1>
<h5 className='text-center text-lg max-w-2xl'>
Run one command to turn any MCP server or OpenAPI service into a
paid MCP product,{' '}
<em>with built-in distribution to over 20k AI engineers</em>.
</h5>
<SupplySideCTA />
</div>
<div className='w-[40%] h-full min-h-full' />
<HeroSimulation2 className='absolute! top-[-50%]! left-[30%] w-full h-[200%]!' />
</div>
</section>
<section></section>
{/* How it works section */}
<section className='flex flex-col gap-8 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
How It Works
</h2>
<div>TODO</div>
</section>
{/* Features section */}
<section className='flex flex-col gap-8 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
Production-Ready and Extremely Flexible
</h2>
<div className='grid gap-6 max-w-2xl'>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Auth
</h4>
<p className='text-sm'>
Ship to production fast with Agentic's free, built-in
authentication. Email & password, OAuth, GitHub, Google, Twitter,
etc if your origin API requires OAuth credentials, Agentic
likely already supports it, and if not,{' '}
<Link
href={calendarBookingUrl}
target='_blank'
rel='noopener'
className='link'
>
let me know
</Link>
.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Stripe Billing
</h4>
<p className='text-sm'>
Charge for your MCP products with a flexible, declarative pricing
model built on top of Stripe. Agentic supports almost any
combination of fixed and{' '}
<span className='font-semibold'>usage-based billing</span> models,
both at the MCP level, at the tool-call level, and at the custom
metric level (e.g., tokens, image transformations, etc).
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Support both MCP and HTTP
</h4>
<p className='text-sm'>
All agentic products support being used both as a standard MCP
server <em>and</em> as an extremely simple HTTP API. MCP is
important for interop, discoverability, and future-proofing,
whereas being able to call your agentic tools via simple{' '}
<em>HTTP POST</em> requests makes tool use easy to debug and makes
integration with existing LLM tool calling patterns a breeze. With
Agentic, you get the best of both worlds, including future support
for unreleased MCP features and related specs.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
API keys
</h4>
<p className='text-sm'>
When a customer subscribes to your product, they're given a unique
API key. MCP URLs are appended with this API key to correlate
usage with their subscription. Customer HTTP tool calls use the
same API key as a standard HTTP <em>Authorization</em> header.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Rate-limiting
</h4>
<p className='text-sm'>
All agentic products are protected by durable rate-limiting built
on top of Cloudflare's global infrastructure. Customize the
default rate-limits, change them based on a customer's pricing
plan, or create custom tool-specific overrides. REST assured that
your origin API will be safe behind Agentic's robust API gateway.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Caching
</h4>
<p className='text-sm'>
Opt-in to caching with familiar <em>cache-control</em> and{' '}
<em>stale-while-revalidate</em> options. MCP tool calls include
caching information in their <em>_meta</em> fields, providing
parity with standard HTTP headers. All caching takes place in
Cloudflare's global edge cache, and caching will only be enabled
if you choose to enable it for your product or individual tools.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Analytics
</h4>
<p className='text-sm'>
Agentic tracks all tool calls for usage-based billing and
analytics at a fine-grained level, so you can drill in and deeply
understand how your customers are using your product.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Versioning
</h4>
<p className='text-sm'>
Just like Vercel, Agentic uses immutable deployments, so every
time you make a change to your product's config, pricing, or docs,
a unique preview deployment is created for that change. This
enables instant rollbacks if there are problems with a deployment.
Publishing deployments publicly uses semantic versioning (
<em>semver</em>), so your customers can choose how to handle
breaking changes.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
That's just the start
</h4>
<p className='text-sm'>
<Link href={docsUrl} className='link'>
Check out our docs
</Link>{' '}
for more details on Agentic's MCP API gateway.
</p>
</div>
</div>
</section>
{/* Marketplace section */}
{/* <section className='flex flex-col gap-8 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
MCP Marketplace
</h2>
<p>
<i>Coming soon...</i>
</p>
</section> */}
{/* Open source section */}
<section className='flex flex-col items-center gap-8 max-w-2xl text-center mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
Agentic is 100% Open Source
</h2>
<p className=''>
Open source is very dear to my heart, and I couldn't be happier that
Agentic is fully OSS. It's written in{' '}
<span className='font-semibold'>TypeScript</span> and has a small but
vibrant developer community.{' '}
<Link
href={githubUrl}
target='_blank'
rel='noopener'
className='link'
>
Check out the source on GitHub
</Link>{' '}
or{' '}
<Link
href={twitterUrl}
target='_blank'
rel='noopener'
className='link'
>
ping me on Twitter with questions / feedback
</Link>
.
</p>
<GitHubStarCounter />
</section>
{/* Social proof section */}
<section className='gap-8 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
TODO: social proof
</h2>
<p className='text-center text-lg max-w-2xl'>TODO</p>
</section>
{/* CTA section */}
<section className='flex flex-col gap-12 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
Deploy Your MCP Today
</h2>
<SupplySideCTA variant='github-2' />
</section>
</>
)
}

Wyświetl plik

@ -1,13 +1,8 @@
import Link from 'next/link'
import { DemandSideCTA } from '@/components/demand-side-cta'
import { GitHubStarCounter } from '@/components/github-star-counter'
import { SupplySideCTA } from '@/components/supply-side-cta'
import {
calendarBookingUrl,
docsUrl,
githubUrl,
twitterUrl
} from '@/lib/config'
import { githubUrl, twitterUrl } from '@/lib/config'
export default function TheBestDamnLandingPageEver() {
return (
@ -20,19 +15,20 @@ export default function TheBestDamnLandingPageEver() {
<div className='flex flex-col gap-8 relative z-10'>
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
Your API Paid MCP, Instantly
The App Store for LLM Tools
</h1>
<h5 className='text-center text-lg max-w-2xl'>
Run one command to turn any MCP server or OpenAPI service into a
paid MCP product,{' '}
<em>with built-in distribution to over 20k AI engineers</em>.
Agentic is a curated marketplace of production-grade LLM tools. All
tools are exposed as both MCP servers as well as simple HTTP APIs.
</h5>
<SupplySideCTA />
<DemandSideCTA />
</div>
</section>
<section></section>
{/* How it works section */}
<section className='flex flex-col gap-8 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
@ -42,162 +38,16 @@ export default function TheBestDamnLandingPageEver() {
<div>TODO</div>
</section>
{/* Features section */}
{/* Marketplace section */}
<section className='flex flex-col gap-8 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
Production-Ready and Extremely Flexible
</h2>
<div className='grid gap-6 max-w-2xl'>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Auth
</h4>
<p className='text-sm'>
Ship to production fast with Agentic's free, built-in
authentication. Email & password, OAuth, GitHub, Google, Twitter,
etc if your origin API requires OAuth credentials, Agentic
likely already supports it, and if not,{' '}
<Link
href={calendarBookingUrl}
target='_blank'
rel='noopener'
className='link'
>
let me know
</Link>
.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Stripe Billing
</h4>
<p className='text-sm'>
Charge for your MCP products with a flexible, declarative pricing
model built on top of Stripe. Agentic supports almost any
combination of fixed and{' '}
<span className='font-semibold'>usage-based billing</span> models,
both at the MCP level, at the tool-call level, and at the custom
metric level (e.g., tokens, image transformations, etc).
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Support both MCP and HTTP
</h4>
<p className='text-sm'>
All agentic products support being used both as a standard MCP
server <em>and</em> as an extremely simple HTTP API. MCP is
important for interop, discoverability, and future-proofing,
whereas being able to call your agentic tools via simple{' '}
<em>HTTP POST</em> requests makes tool use easy to debug and makes
integration with existing LLM tool calling patterns a breeze. With
Agentic, you get the best of both worlds, including future support
for unreleased MCP features and related specs.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
API keys
</h4>
<p className='text-sm'>
When a customer subscribes to your product, they're given a unique
API key. MCP URLs are appended with this API key to correlate
usage with their subscription. Customer HTTP tool calls use the
same API key as a standard HTTP <em>Authorization</em> header.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Rate-limiting
</h4>
<p className='text-sm'>
All agentic products are protected by durable rate-limiting built
on top of Cloudflare's global infrastructure. Customize the
default rate-limits, change them based on a customer's pricing
plan, or create custom tool-specific overrides. REST assured that
your origin API will be safe behind Agentic's robust API gateway.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Caching
</h4>
<p className='text-sm'>
Opt-in to caching with familiar <em>cache-control</em> and{' '}
<em>stale-while-revalidate</em> options. MCP tool calls include
caching information in their <em>_meta</em> fields, providing
parity with standard HTTP headers. All caching takes place in
Cloudflare's global edge cache, and caching will only be enabled
if you choose to enable it for your product or individual tools.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Analytics
</h4>
<p className='text-sm'>
Agentic tracks all tool calls for usage-based billing and
analytics at a fine-grained level, so you can drill in and deeply
understand how your customers are using your product.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Versioning
</h4>
<p className='text-sm'>
Just like Vercel, Agentic uses immutable deployments, so every
time you make a change to your product's config, pricing, or docs,
a unique preview deployment is created for that change. This
enables instant rollbacks if there are problems with a deployment.
Publishing deployments publicly uses semantic versioning (
<em>semver</em>), so your customers can choose how to handle
breaking changes.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
That's just the start
</h4>
<p className='text-sm'>
<Link href={docsUrl} className='link'>
Check out our docs
</Link>{' '}
for more details on Agentic's MCP API gateway.
</p>
</div>
</div>
</section>
{/* Marketplace section */}
{/* <section className='flex flex-col gap-8 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
MCP Marketplace
MCP Tools that just work
</h2>
<p>
<i>Coming soon...</i>
</p>
</section> */}
</section>
{/* Open source section */}
<section className='flex flex-col items-center gap-8 max-w-2xl text-center mb-16'>
@ -245,10 +95,10 @@ export default function TheBestDamnLandingPageEver() {
{/* CTA section */}
<section className='flex flex-col gap-12 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
Deploy Your MCP Today
Level up your AI Agents with the best tools
</h2>
<SupplySideCTA variant='github-2' />
<DemandSideCTA />
</section>
</>
)

Wyświetl plik

@ -8,11 +8,15 @@ export function DemandSideCTA() {
return (
<div className='flex justify-center items-center gap-8'>
<HeroButton asChild className='h-full'>
<Link href='/marketplace'>MCP Marketplace</Link>
<Link href='/marketplace' className='font-mono'>
gotoTools();
</Link>
</HeroButton>
<Button variant='outline' asChild className='h-full'>
<Link href={docsMarketplaceUrl}>Check out the docs</Link>
<Link href={docsMarketplaceUrl} className='font-mono'>
readDocs();
</Link>
</Button>
</div>
)

Wyświetl plik

@ -0,0 +1,140 @@
'use client'
import { Physics, useSphere } from '@react-three/cannon'
import { Environment, useTexture } from '@react-three/drei'
import { Canvas, useFrame, useThree } from '@react-three/fiber'
import {
Bloom,
EffectComposer,
// N8AO,
SMAA,
SSAO
} from '@react-three/postprocessing'
import * as THREE from 'three'
const rfs = THREE.MathUtils.randFloatSpread
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32)
const baubleMaterial = new THREE.MeshStandardMaterial({
color: 'white',
roughness: 0,
envMapIntensity: 1
})
export function HeroSimulation2({ className }: { className?: string }) {
return (
<Canvas
shadows
gl={{ antialias: false }}
dpr={[1, 1.5]}
camera={{ position: [0, 0, 20], fov: 35, near: 1, far: 40 }}
className={className}
>
<ambientLight intensity={0.5} />
<color attach='background' args={['#dfdfdf']} />
<spotLight
intensity={1}
angle={0.2}
penumbra={1}
position={[30, 30, 30]}
castShadow
shadow-mapSize={[512, 512]}
/>
<Physics gravity={[0, 2, 0]} iterations={10}>
<Pointer />
<Clump />
</Physics>
<Environment files='/adamsbridge.hdr' />
<EffectComposer enableNormalPass={true} multisampling={0}>
<SSAO />
{/* <N8AO
halfRes
color='black'
aoRadius={2}
intensity={1}
aoSamples={6}
denoiseSamples={4}
/> */}
<Bloom mipmapBlur levels={7} intensity={0.3} />
<SMAA />
</EffectComposer>
</Canvas>
)
}
function Clump({
mat = new THREE.Matrix4(),
vec = new THREE.Vector3(),
numBalls = 64
}) {
const texture = useTexture('/mcp.png')
const [ref, api] = useSphere(() => ({
args: [1],
mass: 1,
angularDamping: 0.1,
linearDamping: 0.65,
position: [rfs(20), rfs(20), rfs(20)]
}))
useFrame((_state, _delta) => {
for (let i = 0; i < numBalls; i++) {
// Get current whereabouts of the instanced sphere
;(ref.current as any).getMatrixAt(i, mat)
// ref.current.children[i]!.getM(i, mat)
// Normalize the position and multiply by a negative force.
// This is enough to drive it towards the center-point.
api
.at(i)
?.applyForce(
vec
.setFromMatrixPosition(mat)
.normalize()
.multiplyScalar(-40)
.toArray(),
[0, 0, 0]
)
}
})
return (
<instancedMesh
ref={ref}
castShadow
receiveShadow
args={[sphereGeometry, baubleMaterial, numBalls]}
material-map={texture}
/>
)
}
function Pointer() {
const viewport = useThree((state) => state.viewport)
const [ref, api] = useSphere(() => ({
type: 'Kinematic',
args: [3],
position: [0, 0, 0]
}))
useFrame((state) =>
api.position.set(
(state.pointer.x * viewport.width) / 2,
(state.pointer.y * viewport.height) / 2,
0
)
)
return (
<mesh ref={ref} scale={0.2}>
<sphereGeometry />
<meshBasicMaterial color={[4, 4, 4]} toneMapped={false} />
<pointLight intensity={8} distance={10} />
</mesh>
)
}

Wyświetl plik

@ -0,0 +1,146 @@
'use client'
import {
MarchingCube,
MarchingCubes,
MeshTransmissionMaterial,
RenderTexture,
Text
} from '@react-three/drei'
import { Canvas, useFrame } from '@react-three/fiber'
import { BallCollider, Physics, RigidBody } from '@react-three/rapier'
import { useRef } from 'react'
import { suspend } from 'suspend-react'
import * as THREE from 'three'
const inter = import('@pmndrs/assets/fonts/inter_regular.woff')
// https://codesandbox.io/p/sandbox/metaballs-forked-g7xjjq?file=%2Fsrc%2FApp.js
function MetaBall({
float = false,
strength = 0.5,
color,
vec = new THREE.Vector3(),
...props
}: {
float?: boolean
strength?: number
color?: string
vec?: THREE.Vector3
} & Parameters<typeof RigidBody>[0]) {
const api = useRef<any>(null)
useFrame((_state, delta) => {
if (float) {
delta = Math.min(delta, 0.1)
api.current?.applyImpulse(
vec
//.set(-state.pointer.x, -state.pointer.y, 0)
.copy(api.current.translation())
.normalize()
.multiplyScalar(delta * -0.2)
)
}
})
return (
<RigidBody
ref={api}
colliders={false}
restitution={0.6}
linearDamping={4}
angularDamping={4}
{...props}
>
<MarchingCube
strength={strength}
subtract={6}
color={new THREE.Color(color)}
/>
<BallCollider args={[0.1]} />
</RigidBody>
)
}
function Pointer({ vec = new THREE.Vector3() }) {
const ref = useRef<any>(null)
useFrame(({ pointer, viewport }) => {
const { width, height } = viewport.getCurrentViewport()
vec.set((pointer.x * width) / 2, (pointer.y * height) / 2, 0)
ref.current?.setNextKinematicTranslation(vec)
})
return (
<RigidBody type='kinematicPosition' colliders={false} ref={ref}>
<BallCollider args={[0.3]} />
</RigidBody>
)
}
export function HeroSimulation({ className }: { className?: string }) {
return (
<Canvas
dpr={[1, 1.5]}
orthographic
camera={{ position: [0, 0, 5], zoom: 300 }}
className={className}
>
<color attach='background' args={['blue']} />
<ambientLight intensity={1} />
<Physics gravity={[0, -5, 0]}>
<MarchingCubes
resolution={40}
maxPolyCount={10_000}
enableUvs={false}
enableColors
>
<MeshTransmissionMaterial
thickness={0.4}
anisotropicBlur={0.1}
chromaticAberration={0.1}
vertexColors
roughness={0}
>
<RenderTexture attach='buffer'>
<color attach='background' args={[new THREE.Color(2, 2, 2)]} />
<Text
scale={0.25}
fontSize={1}
position={[0, 0, -5]}
letterSpacing={-0.025}
outlineWidth={0.03}
font={(suspend(inter) as any).default}
color='black'
>
MCP
</Text>
</RenderTexture>
</MeshTransmissionMaterial>
{Array.from({ length: 10 }, (_, index) => (
<MetaBall
float
strength={1}
key={'1' + index}
color='white'
position={[Math.random() * 0.5, Math.random() * 0.5, 0]}
/>
))}
<Pointer />
</MarchingCubes>
</Physics>
{/* <Environment
files="https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/industrial_workshop_foundry_1k.hdr"
environmentIntensity={0.5}
/> */}
</Canvas>
)
}

Plik diff jest za duży Load Diff