shadimage
Remocn
componentfomo-limit-orderssource: ok · hit

Order Flow

An independent 18-second trading-interface demo with fictional market data, graphite and amber styling, an original stepped mark, and editable camera choreography.

npx shadcn add "https://www.remocn.dev/r/fomo-limit-orders.json"
free

Where it goes

data-table

Use cases

dashboard

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
remotion@fontsource/manrope

Files10 · source cached 10/9/2026, 6:06:59 AM

registry/remocn-templates/fomo-limit-orders/content.ts
export interface FomoContent {
  intro: string;
  feature: string;
  audience: string;
  ticker: string;
  assetName: string;
  leverage: string;
  position: string;
  positionValue: string;
  positionProfit: string;
  positionSize: string;
  positionQuantity: string;
  positionReturn: string;
  entryPrice: string;
  liquidationPrice: string;
  marketPrice: string;
  change: string;
  openInterest: string;
  limitPrice: string;
  margin: string;
  orderSize: string;
  action: string;
  closing: string;
}

export const fomoContent: FomoContent = {
  intro: "New in Order Flow",
  feature: "Plan your next exit",
  audience: "Before the market moves",
  ticker: "DEMO",
  assetName: "Demo Market",
  leverage: "5x",
  position: "2x Long",
  positionValue: "$12,640.00",
  positionProfit: "+$640.00",
  positionSize: "$12.64K",
  positionQuantity: "160 DEMO",
  positionReturn: "10.67%",
  entryPrice: "$75.00",
  liquidationPrice: "$37.50",
  marketPrice: "$79.00",
  change: "1.28%",
  openInterest: "$24M OI",
  limitPrice: "$82.00",
  margin: "$6,000.00",
  orderSize: "$12.64K",
  action: "Set an exit price",
  closing: "Your price. Your next move.",
};

export interface FomoTheme {
  ink: string;
  panel: string;
  paper: string;
  accent: string;
  positive: string;
}
export const fomoTheme: FomoTheme = {
  ink: "#121310",
  panel: "#20221c",
  paper: "#f6f1e7",
  accent: "#e8b45a",
  positive: "#80bf97",
};
export interface FomoLimitOrdersProps {
  brandName?: string;
  accentColor?: string;
  content?: Partial<FomoContent>;
  theme?: Partial<FomoTheme>;
  /** Optional, user-owned soundtrack. The source reference audio is not redistributed. */
  audioSrc?: string;
  volume?: number;
}
export interface SceneProps {
  t: number;
  content: FomoContent;
  theme: FomoTheme;
  brandName: string;
}
registry/remocn-templates/fomo-limit-orders/index.tsx
"use client";

import "@fontsource/manrope/latin-400.css";
import "@fontsource/manrope/latin-500.css";
import "@fontsource/manrope/latin-600.css";
import "@fontsource/manrope/latin-800.css";
import { AbsoluteFill, Audio, useCurrentFrame, useVideoConfig } from "remotion";
import {
  type FomoLimitOrdersProps,
  fomoContent,
  fomoTheme,
  type SceneProps,
} from "./content";
import {
  FOMO_FPS,
  FOMO_FRAMES,
  FOMO_HEIGHT,
  FOMO_WIDTH,
  tween,
} from "./motion";
import { Closing } from "./scenes/closing";
import { Confirmation } from "./scenes/confirmation";
import { Opening } from "./scenes/opening";
import { Phone } from "./scenes/phone";
import { Price } from "./scenes/price";
import { Slider } from "./scenes/slider";

export type { FomoContent, FomoLimitOrdersProps, FomoTheme } from "./content";
export { fomoContent, fomoTheme } from "./content";
export {
  FOMO_FPS,
  FOMO_FRAMES,
  FOMO_HEIGHT,
  FOMO_WIDTH,
  fomoTimeline,
} from "./motion";

export function FomoLimitOrders(props: FomoLimitOrdersProps) {
  const frame = useCurrentFrame();
  const { width, height, fps } = useVideoConfig();
  const t = frame / fps;
  const scale = Math.min(width / 480, height / 270);
  const theme = {
    ...fomoTheme,
    ...props.theme,
    ...(props.accentColor ? { accent: props.accentColor } : {}),
  };
  const scene: SceneProps = {
    t,
    theme,
    content: { ...fomoContent, ...props.content },
    brandName: props.brandName ?? "Order Flow",
  };
  const volume = Number.isFinite(props.volume)
    ? Math.min(1, Math.max(0, props.volume ?? 1))
    : 1;
  return (
    <AbsoluteFill
      style={{
        background: theme.ink,
        color: "#fff",
        overflow: "hidden",
        fontFamily: "Manrope, sans-serif",
        fontWeight: 400,
        WebkitFontSmoothing: "antialiased",
      }}
    >
      <div
        style={{
          position: "absolute",
          left: (width - 480 * scale) / 2,
          top: (height - 270 * scale) / 2,
          width: 480,
          height: 270,
          transform: `scale(${scale})`,
          transformOrigin: "0 0",
        }}
      >
        {t < 2.7 ? (
          <Opening {...scene} />
        ) : t < 8 ? (
          <Phone {...scene} />
        ) : t < 10.9 ? (
          <Slider {...scene} />
        ) : t < 12.6 ? (
          <div
            style={{
              position: "absolute",
              inset: 0,
              opacity: 1 - tween(t, 12.3, 12.6),
            }}
          >
            <Price {...scene} />
          </div>
        ) : t < 14.15 ? (
          <div
            style={{
              position: "absolute",
              inset: 0,
              opacity: tween(t, 12.6, 12.78),
            }}
          >
            <Confirmation {...scene} t={t + 1.85} />
          </div>
        ) : (
          <Closing {...scene} t={t + 1.85} />
        )}
      </div>
      {props.audioSrc?.trim() ? (
        <Audio src={props.audioSrc} volume={volume} />
      ) : null}
    </AbsoluteFill>
  );
}

export const OrderFlow = FomoLimitOrders;
export type OrderFlowProps = FomoLimitOrdersProps;

export const fomoLimitOrdersConfig = {
  componentName: "OrderFlow",
  importPath: "@/components/remocn/templates/fomo-limit-orders",
  controls: {
    brandName: {
      type: "text-content" as const,
      default: "Order Flow",
      description: "Closing wordmark",
    },
    accentColor: {
      type: "color" as const,
      default: "#e8b45a",
      description: "Controls and progress color",
    },
  },
  durationInFrames: FOMO_FRAMES,
  fps: FOMO_FPS,
  compositionWidth: FOMO_WIDTH,
  compositionHeight: FOMO_HEIGHT,
};
registry/remocn-templates/fomo-limit-orders/motion.ts
import { Easing, interpolate } from "remotion";

export const FOMO_FPS = 60;
export const FOMO_FRAMES = 1108;
export const FOMO_WIDTH = 1920;
export const FOMO_HEIGHT = 1080;
export const fomoTimeline = [
  { id: "opening", from: 0, to: 162 },
  { id: "phone", from: 162, to: 480 },
  { id: "slider", from: 480, to: 654 },
  { id: "price", from: 654, to: 756 },
  { id: "confirmation", from: 756, to: 849 },
  { id: "closing", from: 849, to: FOMO_FRAMES },
] as const;

export const ease = Easing.bezier(0.22, 1, 0.36, 1);
export const travel = Easing.bezier(0.65, 0, 0.3, 1);
/** Fit to 25 source-frame measurements, 10.483–10.883 seconds. */
export const contraction = Easing.bezier(0.652, 0.343, 0.075, 0.909);
export const clamp = (n: number) => Math.min(1, Math.max(0, n));
export function ramp(t: number, start: number, end: number) {
  return clamp((t - start) / (end - start));
}
export function tween(
  t: number,
  start: number,
  end: number,
  a = 0,
  b = 1,
  easing = ease,
) {
  return a + (b - a) * easing(ramp(t, start, end));
}
export function key(t: number, times: number[], values: number[]) {
  return interpolate(t, times, values, {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
}
export function money(n: number) {
  return `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}

/** Centerline of the phone border, in the same local coordinates as its camera. */
export function phoneBorderPoint(progress: number) {
  const inset = 0.85;
  const r = 59 - inset;
  const straight = 230 - 59;
  const arc = (Math.PI * r) / 2;
  const distance = clamp(progress) * (straight + arc + 66);
  if (distance <= straight) return { x: 230 - distance, y: 680 - inset };
  if (distance <= straight + arc) {
    const angle = Math.PI / 2 + (distance - straight) / r;
    return { x: 59 + r * Math.cos(angle), y: 621 + r * Math.sin(angle) };
  }
  return { x: inset, y: 621 - (distance - straight - arc) };
}
registry/remocn-templates/fomo-limit-orders/scenes/closing.tsx
import type { SceneProps } from "../content";
import { ramp, tween } from "../motion";
import { AssetIcon, Layer } from "../ui";

export function Closing({ t, content, brandName, theme }: SceneProps) {
  const brand = tween(t, 18.15, 18.38);
  return (
    <>
      {t < 18.3 && (
        <Layer
          x={15}
          y={111}
          w={450}
          h={42}
          style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            gap: 5,
            fontSize: 22.5,
            letterSpacing: -1,
            whiteSpace: "nowrap",
            opacity: 1 - ramp(t, 18.13, 18.24),
          }}
        >
          {content.closing.split(" ").map((word, i) => {
            const p = tween(t, 16 + i * 0.3, 16.18 + i * 0.3);
            return (
              <span
                key={i}
                style={{
                  opacity: p,
                  filter: `blur(${(1 - p) * 4}px)`,
                  transform: `translateY(${(1 - p) * 7}px)`,
                }}
              >
                {word}
              </span>
            );
          })}
        </Layer>
      )}
      {t >= 18.15 && (
        <Layer
          x={15}
          y={98}
          w={450}
          h={64}
          style={{
            display: "grid",
            placeItems: "center",
            fontSize: brandName.length > 9 ? 31 : 42,
            fontWeight: 600,
            letterSpacing: -1.2,
            opacity: brand,
            transform: `scale(${0.95 + 0.05 * brand})`,
            filter: `blur(${(1 - brand) * 5}px)`,
          }}
        >
          <span style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <AssetIcon size={42} color={theme.accent} />
            {brandName}
          </span>
        </Layer>
      )}
    </>
  );
}
registry/remocn-templates/fomo-limit-orders/scenes/confirmation.tsx
import type { SceneProps } from "../content";
import { key, tween } from "../motion";
import { AssetIcon, Badge, Layer, muted, row } from "../ui";

export function Confirmation({ t, content: c, theme }: SceneProps) {
  const scale = key(
    t,
    [14.45, 14.65, 15.25, 15.6, 15.8, 16],
    [0.67, 0.73, 1, 1.16, 1.25, 1.3],
  );
  const exit = tween(t, 15.77, 16, 0, 210, (v) => v * v);
  const fill = Math.round(
    key(t, [14.45, 14.7, 15, 15.25, 15.45], [20, 50, 83, 97, 100]),
  );
  return (
    <Layer
      x={240}
      y={135 + exit}
      w={300}
      h={110}
      style={{
        transform: `translate(-50%, -50%) scale(${scale}) rotate(${key(t, [14.45, 15.4, 16], [-1.2, 0, 2])}deg)`,
        background: theme.panel,
        borderRadius: 12,
        boxShadow: "0 0 10px #18131e65",
        fontWeight: 500,
        letterSpacing: -0.4,
      }}
    >
      <Layer x={10} y={10}>
        <Badge theme={theme}>{c.position}</Badge>
      </Layer>
      <Layer
        x={235}
        y={12}
        w={55}
        style={{ fontSize: 14, textAlign: "right", color: "#d0ced9" }}
      >
        Cancel
      </Layer>
      <Layer x={10} y={41} style={{ ...row, gap: 5, fontSize: 16 }}>
        <AssetIcon size={18} color={theme.accent} />
        <span>{c.ticker}</span>
        <span style={{ color: muted, fontSize: 12 }}>⊙</span>
        <span>{c.limitPrice}</span>
      </Layer>
      <Layer
        x={214}
        y={45}
        style={{ ...row, gap: 8, fontSize: 12, color: muted }}
      >
        <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden>
          <circle
            cx="7"
            cy="7"
            r="5"
            fill="none"
            stroke="#302946"
            strokeWidth="1.8"
          />
          <circle
            cx="7"
            cy="7"
            r="5"
            fill="none"
            stroke={theme.accent}
            strokeWidth="1.8"
            strokeDasharray={`${(31.416 * fill) / 100} 31.416`}
            transform="rotate(-90 7 7)"
          />
        </svg>
        <span>{fill}% filled</span>
      </Layer>
      <Layer
        x={10}
        y={86}
        w={280}
        style={{
          ...row,
          justifyContent: "space-between",
          fontSize: 12,
          color: "#bbb8c6",
        }}
      >
        <span>
          Margin{" "}
          <span style={{ color: "white", fontSize: 15, marginLeft: 4 }}>
            {c.margin}
          </span>
        </span>
        <span>
          Lev. size{" "}
          <span style={{ color: "white", fontSize: 15, marginLeft: 4 }}>
            {c.orderSize}
          </span>
        </span>
      </Layer>
    </Layer>
  );
}
registry/remocn-templates/fomo-limit-orders/scenes/opening.tsx
import type { SceneProps } from "../content";
import { ramp, tween } from "../motion";
import { Layer } from "../ui";

export function Opening({ t, content }: SceneProps) {
  const text =
    t < 0.7 ? content.intro : t < 1.9 ? content.feature : content.audience;
  const start = t < 0.7 ? 0 : t < 1.9 ? 0.7 : 1.9;
  const words = text.split(" ");
  return (
    <>
      {Array.from({ length: 10 }, (_, i) => {
        const upper = i < 5;
        const column = i % 5;
        const height = [18, 48, 85, 61, 30][column];
        const drift = tween(t, 0.8 + column * 0.025, 1.25 + column * 0.025);
        return (
          <Layer
            key={i}
            x={upper ? 277 + column * 47 : -35 + column * 47}
            y={upper ? -14 : 270 - height + 12}
            w={47}
            h={height}
            style={{
              opacity: ramp(t, 0.8, 1.05) * (1 - ramp(t, 2.45, 2.7)),
              transform: `translateY(${(1 - drift) * (upper ? -75 : 75) + Math.sin(t * 2 + column) * 5}px)`,
              borderRadius: 2,
              background: `linear-gradient(${upper ? 150 : 30}deg, #c29b5452, #8770444a 40%, #4a412d30 76%, #12131000)`,
              boxShadow: "inset 0.5px 0 1px #efd19626",
              filter: "blur(0.7px)",
            }}
          />
        );
      })}
      <Layer
        x={19}
        y={110}
        w={450}
        h={42}
        style={{
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
          gap: 6,
          fontSize: 24.5,
          letterSpacing: -1.1,
          whiteSpace: "nowrap",
          opacity: 1 - ramp(t, 2.55, 2.7),
        }}
      >
        {words.map((word, i) => {
          const enter = tween(t, start + i * 0.1, start + i * 0.1 + 0.2);
          return (
            <span
              key={`${start}-${i}`}
              style={{
                opacity: enter,
                filter: `blur(${(1 - enter) * 5}px)`,
                transform: `translateY(${(1 - enter) * 9}px)`,
              }}
            >
              {word}
            </span>
          );
        })}
      </Layer>
    </>
  );
}
registry/remocn-templates/fomo-limit-orders/scenes/phone.tsx
import type { SceneProps } from "../content";
import { key, phoneBorderPoint, ramp, travel, tween } from "../motion";
import { AssetIcon, Badge, Layer, muted, row } from "../ui";

function PhoneUI({ t, content: c, theme }: SceneProps) {
  return (
    <>
      <Layer
        x={62}
        y={32}
        style={{ opacity: ramp(t, 3.85, 4.3), fontSize: 14, fontWeight: 600 }}
      >
        9:41
      </Layer>
      <Layer
        x={280}
        y={37}
        style={{ opacity: ramp(t, 3.85, 4.3), fontSize: 9, letterSpacing: 3 }}
      >
        ▮▮▮ ◔ ▰
      </Layer>
      <Layer x={54} y={73} style={{ ...row, gap: 14 }}>
        <span style={{ position: "relative", display: "flex" }}>
          <AssetIcon size={44} color={theme.accent} />
          <span
            style={{
              position: "absolute",
              inset: 0,
              borderRadius: "50%",
              background: "black",
              opacity: 1 - ramp(t, 3.5, 4.1),
              mixBlendMode: "darken",
            }}
          />
          <svg
            width="44"
            height="44"
            viewBox="0 0 40 40"
            style={{ position: "absolute", opacity: 1 - ramp(t, 3.5, 4.1) }}
          >
            <circle
              cx="20"
              cy="20"
              r="19.8"
              fill="none"
              stroke="#ffffff10"
              strokeWidth="0.4"
            />
            <path d="M9 25h5v6H9Zm8-8h5v14h-5Zm8-8h6v22h-6Z" fill="white" />
          </svg>
        </span>
        <div>
          <div style={{ ...row, gap: 8, fontSize: 16.5, letterSpacing: -0.5 }}>
            {c.ticker}
            <span
              style={{
                fontSize: 12,
                color: theme.accent,
                borderRadius: 3,
                padding: "1px 5px",
                background: `${theme.accent}25`,
                opacity: ramp(t, 3.35, 3.85),
              }}
            >
              {c.leverage}
            </span>
          </div>
          <div style={{ marginTop: 3, fontSize: 12, color: muted }}>
            {c.assetName.slice(
              0,
              Math.floor(tween(t, 3.28, 4.1, 0, c.assetName.length)),
            )}
          </div>
        </div>
      </Layer>
      <div style={{ opacity: ramp(t, 3.85, 4.3) }}>
        <Layer x={23} y={135} style={{ fontSize: 24, letterSpacing: -0.9 }}>
          {c.marketPrice}
        </Layer>
        <Layer x={24} y={169} style={{ fontSize: 12, color: theme.positive }}>
          ↑ $1.00 ({c.change})
        </Layer>
        <Layer x={22} y={215} w={355} h={110}>
          <svg width="355" height="110" viewBox="0 0 355 110" aria-hidden>
            {[20, 55, 90].map((y) => (
              <path
                key={y}
                d={`M0 ${y}H355`}
                stroke="#ffffff0a"
                strokeWidth="0.5"
              />
            ))}
            {Array.from({ length: 57 }, (_, i) => {
              const y =
                85 - i * 1.07 + Math.sin(i * 0.6) * 13 + Math.sin(i * 1.4) * 4;
              const positive = i % 5 !== 1 && i % 7 !== 2;
              return (
                <g
                  key={i}
                  stroke={positive ? theme.positive : "#cf495e"}
                  fill={positive ? theme.positive : "#cf495e"}
                >
                  <path d={`M${i * 6 + 2} ${y - 6}v20`} strokeWidth="0.6" />
                  <rect
                    x={i * 6}
                    y={y}
                    width="3.5"
                    height={5 + (i % 6)}
                    rx="0.3"
                  />
                </g>
              );
            })}
          </svg>
        </Layer>
        <Layer
          x={9}
          y={334}
          w={365}
          style={{
            ...row,
            justifyContent: "space-between",
            color: "#b5b2be",
            fontSize: 12,
            fontWeight: 600,
          }}
        >
          <span>
            <span style={{ color: theme.positive }}>╂</span> 15m ⌃
          </span>
          {["1D", "1W", "3M", "6M", "1Y", "All"].map((s, i) => (
            <span
              key={s}
              style={{
                padding: 4,
                background: i === 0 ? "#2b2c26" : undefined,
                borderRadius: 4,
                color: i === 0 ? "white" : undefined,
              }}
            >
              {s}
            </span>
          ))}
          <span style={{ color: theme.positive, fontSize: 21 }}>ϟ</span>
        </Layer>
        <Layer
          x={17}
          y={379}
          w={366}
          h={138}
          style={{
            background: `linear-gradient(125deg, ${theme.ink}, ${theme.panel})`,
            borderRadius: 11,
            boxShadow: "inset 0 1px 8px #ffffff04",
          }}
        >
          <Layer x={11} y={9}>
            <Badge theme={theme}>{c.position}</Badge>
          </Layer>
          <Layer
            x={11}
            y={39}
            w={344}
            style={{
              ...row,
              justifyContent: "space-between",
              fontSize: 21,
              fontWeight: 500,
              letterSpacing: -1.1,
            }}
          >
            <span>{c.positionValue}</span>
            <span style={{ color: theme.positive }}>{c.positionProfit}</span>
          </Layer>
          <Layer x={11} y={70} style={{ fontSize: 13, color: muted }}>
            Lev. size <span style={{ color: "#e7e6ed" }}>{c.positionSize}</span>{" "}
            ({c.positionQuantity})
          </Layer>
          <Layer x={265} y={70} style={{ fontSize: 13, color: "#bab7c4" }}>
            <span style={{ color: theme.positive }}>▴</span> {c.positionReturn}
          </Layer>
          <Layer
            x={16}
            y={97}
            w={334}
            h={0.6}
            style={{ background: "#ffffff0b" }}
          />
          <Layer
            x={11}
            y={108}
            w={344}
            style={{
              ...row,
              justifyContent: "space-between",
              fontSize: 13,
              color: muted,
            }}
          >
            <span>
              Avg. entry{" "}
              <span style={{ color: "white", marginLeft: 6 }}>
                {c.entryPrice}
              </span>
            </span>
            <span>
              Liq. price{" "}
              <span style={{ color: "white", marginLeft: 6 }}>
                {c.liquidationPrice}
              </span>
            </span>
          </Layer>
        </Layer>
        <Layer
          x={28}
          y={541}
          w={345}
          style={{
            ...row,
            justifyContent: "space-between",
            color: theme.accent,
            fontSize: 13,
            fontWeight: 600,
          }}
        >
          <span>
            <span style={{ color: muted, marginRight: 10 }}>+</span>Add a note
          </span>
          <span>Add SL/TP</span>
        </Layer>
        <Layer x={51} y={575} style={{ fontSize: 11, color: muted }}>
          Overview  Activity  Details
        </Layer>
        <Layer
          x={24}
          y={593}
          w={350}
          h={44}
          style={{ ...row, gap: 15, fontSize: 13, fontWeight: 600 }}
        >
          <div
            style={{
              width: 43,
              height: 42,
              display: "grid",
              placeItems: "center",
              borderRadius: 11,
              background: "#282c22",
              border: "1px solid #7e806c",
            }}
          >
            <svg width="19" height="19" viewBox="0 0 24 24">
              <path d="m4 16 11-11 5 5-11 11-6 1Z" fill="white" />
              <path d="m13 7 5 5" stroke="#282c22" />
            </svg>
          </div>
          <div
            style={{
              background: theme.accent,
              color: theme.ink,
              borderRadius: 9,
              padding: "11px 14px",
              whiteSpace: "nowrap",
              boxShadow: "inset 0 1px 1px #ffffff99",
            }}
          >
            Close position
          </div>
          <div
            style={{
              background: theme.panel,
              color: theme.positive,
              borderRadius: 9,
              padding: "11px 32px",
            }}
          >
            Long
          </div>
        </Layer>
      </div>
    </>
  );
}

export function Phone(props: SceneProps) {
  const { t } = props;
  const push = tween(t, 4.63, 5.18, 0, 1, travel);
  const pull = tween(t, 6.04, 6.92, 0, 1, travel);
  const orbit = tween(t, 6.92, 7.7, 0, 1, travel);
  const openingScale = tween(t, 3.4, 4.3, 1.45, 1);
  const s = openingScale + 0.18 * push - 0.28 * pull + 0.55 * orbit;
  const x = tween(t, 3.4, 4.3, 62, 75) - 67 * push + 134 * pull + 95 * orbit;
  const y = tween(t, 3.4, 4.3, 9, 45) - 435 * push - 13 * pull - 309 * orbit;
  const dotTimes = [
    6.55, 6.75, 7, 7.25, 7.5, 7.65, 7.75, 7.8, 7.85, 7.9, 7.95, 8,
  ];
  const point = phoneBorderPoint(tween(t, 6.55, 7.75, 0, 1, travel));
  const dotX = tween(t, 7.85, 8, x + point.x * s, 240);
  const dotY = tween(t, 7.85, 8, y + point.y * s, 135);
  const radius = key(
    t,
    dotTimes,
    [4, 6, 6, 6.5, 7.5, 9.5, 12, 16, 24, 57, 160, 390],
  );
  return (
    <>
      <Layer
        w={480}
        h={270}
        style={{ background: "black", opacity: ramp(t, 2.7, 2.9) }}
      />
      <Layer
        x={x}
        y={y}
        w={400}
        h={680}
        style={{
          transform: `scale(${s})`,
          transformOrigin: "0 0",
          opacity: ramp(t, 2.7, 3),
          filter: `blur(${Math.sin(push * Math.PI) * 1.1}px)`,
        }}
      >
        <Layer
          w={400}
          h={680}
          style={{
            border: "1.7px solid #55584a",
            boxSizing: "border-box",
            borderRadius: 59,
            opacity: ramp(t, 3.65, 4.35) * (1 - 0.8 * push * (1 - pull)),
            boxShadow: "0 0 2px #ad926155, inset 0 0 4px #ad926122",
            background: "#000",
          }}
        />
        <Layer
          w={400}
          h={680}
          style={{
            borderLeft: "1.2px solid #e8b45a",
            boxSizing: "border-box",
            borderBottom: "1.2px solid #e8b45a",
            borderRadius: 59,
            opacity: ramp(t, 6.55, 7.2),
            maskImage: "linear-gradient(0deg, black, transparent 34%)",
            boxShadow: "-1px 1px 3px #d59b4044",
          }}
        />
        <div style={{ opacity: 1 - ramp(t, 7.78, 7.96), fontWeight: 500 }}>
          <PhoneUI {...props} />
        </div>
      </Layer>
      <Layer
        w={480}
        h={270}
        style={{
          opacity: Math.sin(push * Math.PI) * 0.24,
          background:
            "linear-gradient(120deg, transparent 10%, #ebc47b 53%, transparent 75%)",
          mixBlendMode: "screen",
        }}
      />
      <Layer
        w={480}
        h={270}
        style={{
          opacity: ramp(t, 6.1, 7),
          background:
            "linear-gradient(100deg, #000 0%, transparent 43%, #0004 66%, #000 96%)",
        }}
      />
      {t >= 6.55 && (
        <>
          <Layer
            x={dotX - radius * 2}
            y={dotY - radius * 2}
            w={radius * 4}
            h={radius * 4}
            style={{
              background: "#eed7a6",
              borderRadius: "50%",
              filter: `blur(${Math.min(10, radius)}px)`,
              opacity: t < 7.88 ? 0.2 : 0,
            }}
          />
          <Layer
            x={dotX - radius}
            y={dotY - radius}
            w={radius * 2}
            h={radius * 2}
            style={{
              background: "#fff",
              borderRadius: "50%",
              boxShadow: "0 0 4px #f6e3bf",
            }}
          />
        </>
      )}
    </>
  );
}
registry/remocn-templates/fomo-limit-orders/scenes/price.tsx
import type { SceneProps } from "../content";
import { ramp, travel, tween } from "../motion";
import { AssetIcon, Layer, lightMuted as muted } from "../ui";

export function Price({ t, content: c, theme }: SceneProps) {
  const zoom = tween(t, 11.65, 12.25, 1, 2.7, travel);
  const targetX = tween(t, 11.65, 12.25, 240, 277, travel);
  const typed =
    t < 11.84
      ? "$"
      : c.limitPrice.slice(
          0,
          Math.floor(tween(t, 11.84, 12.12, 1, c.limitPrice.length, (v) => v)),
        );

  const alpha = ramp(t, 10.9, 11.18);

  return (
    <>
      <Layer
        w={480}
        h={270}
        style={{
          background: `linear-gradient(145deg, ${theme.paper}, #ebe1ce 65%, #f7f0e3)`,
        }}
      />
      <Layer
        x={240 - targetX * zoom}
        y={135 - 135 * zoom}
        w={480}
        h={270}
        style={{
          transform: `scale(${zoom})`,
          transformOrigin: "0 0",
          color: "#28281f",
        }}
      >
        <div style={{ opacity: alpha, fontWeight: 500 }}>
          <Layer x={100} y={61}>
            <AssetIcon size={30} color={theme.accent} />
          </Layer>
          <Layer x={140} y={63} style={{ fontSize: 12 }}>
            {c.ticker}
            <div style={{ marginTop: 2, color: muted, fontSize: 9 }}>
              {c.openInterest}
            </div>
          </Layer>
          <Layer
            x={300}
            y={65}
            w={79}
            style={{ textAlign: "right", fontSize: 12 }}
          >
            {c.marketPrice}
            <div style={{ marginTop: 3, color: "#326846", fontSize: 9 }}>
              {c.change}
            </div>
          </Layer>
          <Layer
            x={100}
            y={110}
            w={279}
            h={0.6}
            style={{ background: "#c5bba466" }}
          />
          <Layer
            x={100}
            y={158}
            w={279}
            h={0.6}
            style={{ background: "#c5bba444" }}
          />
          <Layer x={117} y={120} style={{ fontSize: 11 }}>
            Limit price
            <div style={{ marginTop: 4, fontSize: 8, color: muted }}>
              +3.80% above market
            </div>
          </Layer>
          <Layer x={100} y={181} w={279} style={{ display: "flex", gap: 6 }}>
            {["+1%", "+5%", "+10%", "+20%"].map((s) => (
              <div
                key={s}
                style={{
                  width: 66,
                  height: 21,
                  borderRadius: 5,
                  border: "0.5px solid #cbbda282",
                  background: "#ffffff24",
                  display: "grid",
                  placeItems: "center",
                  fontSize: 9,
                }}
              >
                {s}
              </div>
            ))}
          </Layer>
        </div>
        <Layer
          x={225}
          y={122}
          w={96}
          h={28}
          style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "flex-end",
            transform: `translateX(${tween(t, 10.9, 11.18, -39, 0)}px)`,
            gap: 5,
            fontSize: 12,
            fontWeight: 600,
            letterSpacing: -0.35,
          }}
        >
          <span style={{ opacity: alpha }}>{typed}</span>
          <span
            style={{
              width: 1.5,
              height: 19,
              background: theme.accent,
              opacity: 1,
            }}
          />
          <span style={{ color: "#865616", marginLeft: -1, opacity: alpha }}>
            Mid
          </span>
        </Layer>
      </Layer>
    </>
  );
}
registry/remocn-templates/fomo-limit-orders/scenes/slider.tsx
import type { SceneProps } from "../content";
import { contraction, key, money, ramp, tween } from "../motion";
import { Chevron, Layer, lightMuted as muted } from "../ui";

export function Slider({ t, theme, content }: SceneProps) {
  const label = t < 8.65;
  const barW =
    t < 10.4833
      ? key(
          t,
          [8.65, 8.8, 9, 9.2, 9.3, 9.55, 9.8, 10.05, 10.3, 10.4833],
          [3500, 2600, 1800, 820, 464, 322, 292, 276, 255, 220],
        )
      : tween(t, 10.4833, 10.8833, 220, 2, contraction);
  const barH = key(
    t,
    [8.65, 8.8, 9, 9.2, 9.3, 9.55, 9.8, 10.3, 10.5],
    [650, 400, 154, 66, 35, 24, 22, 20, 19],
  );
  const centerX = tween(t, 10.45, 10.8833, 240, 250, contraction);
  const price = key(t, [9.3, 9.55, 9.8, 10.05, 10.3], [79, 79.5, 80, 81, 82]);
  const quantity = key(
    t,
    [9.3, 9.55, 9.8, 10.05, 10.3],
    [40, 72, 112, 144, 160],
  );
  const percent = Math.round((quantity / 160) * 100);
  const detailAlpha = ramp(t, 9.15, 9.4) * (1 - ramp(t, 10.3, 10.56));
  const uiScale = key(t, [9.3, 9.55, 9.8, 10.3], [1.6, 1.1, 1, 0.93]);
  const roll = tween(t, 8.18, 8.55, 0, 1, (v) => v);
  const knobSize = barH * 0.77;
  return (
    <>
      <Layer
        w={480}
        h={270}
        style={{
          background: label
            ? "white"
            : `linear-gradient(145deg, ${theme.paper}, #ebe1ce 65%, #f7f0e3)`,
        }}
      />
      {label ? (
        <Layer
          x={130}
          y={116}
          w={220}
          h={38}
          style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            gap: 9,
            color: "#865616",
            fontSize: 23,
            letterSpacing: -0.6,
            opacity: ramp(t, 8.01, 8.1),
          }}
        >
          <span style={{ transform: "rotate(90deg)", display: "flex" }}>
            <Chevron color={theme.accent} size={16} />
          </span>
          <div
            style={{
              width: 78,
              height: 35,
              position: "relative",
              overflow: "visible",
            }}
          >
            <Layer y={-roll * 35} style={{ opacity: 1 - roll }}>
              Market
            </Layer>
            <Layer y={(1 - roll) * 35} style={{ opacity: roll }}>
              Limit
            </Layer>
          </div>
        </Layer>
      ) : (
        <>
          <Layer
            x={centerX - barW / 2}
            y={135 - barH / 2}
            w={barW}
            h={barH}
            style={{
              background: `linear-gradient(100deg, #c78a30, ${theme.accent})`,
              borderRadius: Math.min(9, barH / 3),
              boxShadow:
                t < 10.7
                  ? "inset 0 0.5px 1px #f3d8a3, 0 1px 2px #8d6a301f"
                  : undefined,
            }}
          >
            <Layer
              x={
                barW *
                  key(t, [8.65, 9.2, 9.3, 10.3], [0.5, 0.57, 0.58, 0.955]) -
                knobSize / 2
              }
              y={(barH - knobSize) / 2}
              w={knobSize}
              h={knobSize}
              style={{
                background: "#fff",
                borderRadius: "50%",
                boxShadow: "0 1px 3px #4d3b2126",
                opacity: 1 - ramp(t, 10.4, 10.65),
              }}
            />
          </Layer>
          <Layer
            x={240}
            y={135}
            style={{
              transform: `scale(${uiScale})`,
              opacity: detailAlpha,
              color: "#28281f",
              fontSize: 9,
              whiteSpace: "nowrap",
            }}
          >
            <Layer
              x={-146}
              y={-65}
              w={292}
              style={{
                display: "flex",
                justifyContent: "center",
                gap: 7,
                fontSize: 11,
              }}
            >
              <span>Limit price</span>
              <span>
                {money(price)}{" "}
                <span style={{ color: muted }}>
                  (+{(((price - 79) / 79) * 100).toFixed(2)}%)
                </span>
              </span>
            </Layer>
            <Layer x={-142} y={-31} style={{ fontSize: 13, color: "#5b5548" }}>
              {quantity.toFixed(2)} {content.ticker}
            </Layer>
            <Layer
              x={113}
              y={-31}
              w={30}
              style={{
                textAlign: "right",
                fontSize: 13,
                color: "#865616",
                fontWeight: 600,
              }}
            >
              {percent}%
            </Layer>
            <Layer
              x={-146}
              y={25}
              w={292}
              style={{
                display: "flex",
                justifyContent: "space-between",
                fontSize: 13,
              }}
            >
              <span style={{ color: muted }}>Est. profit</span>
              <span style={{ color: "#326846" }}>
                {money(quantity * (price - 75))}
              </span>
            </Layer>
            <Layer
              x={-128}
              y={53}
              w={274}
              style={{
                display: "flex",
                justifyContent: "space-between",
                fontSize: 12,
              }}
            >
              <span style={{ color: muted }}>Details</span>
              <span>&lt;0.01% fee ﹀</span>
            </Layer>
          </Layer>
        </>
      )}
    </>
  );
}
registry/remocn-templates/fomo-limit-orders/ui.tsx
import type { CSSProperties, ReactNode } from "react";
import type { FomoTheme } from "./content";

export function Layer({
  x = 0,
  y = 0,
  w,
  h,
  style,
  children,
}: {
  x?: number;
  y?: number;
  w?: number | string;
  h?: number | string;
  style?: CSSProperties;
  children?: ReactNode;
}) {
  return (
    <div
      style={{
        position: "absolute",
        left: x,
        top: y,
        width: w,
        height: h,
        ...style,
      }}
    >
      {children}
    </div>
  );
}
export function AssetIcon({
  size = 40,
  color = "#e8b45a",
}: {
  size?: number;
  color?: string;
}) {
  return (
    <svg width={size} height={size} viewBox="0 0 40 40" aria-hidden>
      <circle
        cx="20"
        cy="20"
        r="20"
        fill="#242720"
        stroke="#ffffff0b"
        strokeWidth="0.5"
      />
      <path d="M9 25h5v6H9Zm8-8h5v14h-5Zm8-8h6v22h-6Z" fill={color} />
    </svg>
  );
}
export function Chevron({
  color = "white",
  size = 16,
  up = false,
}: {
  color?: string;
  size?: number;
  up?: boolean;
}) {
  return (
    <svg
      width={size}
      height={size}
      viewBox="0 0 24 24"
      style={{ transform: up ? "rotate(-90deg)" : undefined }}
      aria-hidden
    >
      <path
        d="m5 5 7 7-7 7m8-14 7 7-7 7"
        fill="none"
        stroke={color}
        strokeWidth="2.6"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}
export function Badge({
  children,
  theme,
}: {
  children: ReactNode;
  theme: FomoTheme;
}) {
  return (
    <span
      style={{
        color: theme.positive,
        background: `${theme.positive}20`,
        padding: "2px 5px",
        borderRadius: 3,
        fontSize: 12,
        fontWeight: 600,
      }}
    >
      {children}
    </span>
  );
}
export const row: CSSProperties = { display: "flex", alignItems: "center" };
export const muted = "#aaa79d";
export const lightMuted = "#6a6357";

More from Remocn

Browse all components in this library →