Rebuild footer, finalize structure, and refresh social metadata
- footer: dark #050e11 band with an Instrument Serif display CTA, an inverted Download button, link columns (tracked download hrefs preserved) and copyright - structure: move icon-mark to brand/, theme provider+toggle to theme/, site-constants to lib/; sections/layout/ui/brand/theme now fully separated - button: add an inverted variant for dark surfaces - metadata: point OpenGraph/Twitter at a 1200x630 og.jpg and the JSON-LD screenshot at dashboard.webp; drop the old /screenshots references - remove orphans: framer-motion (replaced by CSS + IntersectionObserver), ascii-background, and the seven superseded product screenshots - add @playwright/test for the e2e suite
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Instrument_Sans, Instrument_Serif, Newsreader, JetBrains_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { ThemeProvider } from "../components/theme-provider";
|
||||
import { ThemeProvider } from "../components/theme/theme-provider";
|
||||
|
||||
const instrumentSans = Instrument_Sans({
|
||||
subsets: ["latin"],
|
||||
@@ -74,10 +74,10 @@ export const metadata: Metadata = {
|
||||
locale: "en_US",
|
||||
images: [
|
||||
{
|
||||
url: "/screenshots/Dashboard.jpeg",
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
alt: "AgentCompany dashboard showing autonomous AI agents managing a company",
|
||||
url: "/images/og.jpg",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "AgentCompany desktop dashboard — quarter goal, budget, agent workload, and recent runs",
|
||||
},
|
||||
],
|
||||
type: "website",
|
||||
@@ -86,7 +86,7 @@ export const metadata: Metadata = {
|
||||
card: "summary_large_image",
|
||||
title: "AgentCompany - Launch an Autonomous AI Company from Your Desktop",
|
||||
description,
|
||||
images: ["/screenshots/Dashboard.jpeg"],
|
||||
images: ["/images/og.jpg"],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
@@ -110,7 +110,7 @@ const softwareJsonLd = {
|
||||
operatingSystem: "macOS, Windows, Linux",
|
||||
url: "https://agent-company.dev",
|
||||
downloadUrl: "https://agent-company.dev/#download",
|
||||
screenshot: "https://agent-company.dev/screenshots/Dashboard.jpeg",
|
||||
screenshot: "https://agent-company.dev/images/dashboard.webp",
|
||||
softwareVersion: landingStructuredDataNarrative.softwareVersion,
|
||||
description,
|
||||
featureList: landingStructuredDataNarrative.featureList,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DownloadSection } from "../components/sections/download-section";
|
||||
import { FAQ } from "../components/sections/faq";
|
||||
import { Features } from "../components/sections/features";
|
||||
import { Footer } from "../components/footer";
|
||||
import { Footer } from "../components/layout/footer";
|
||||
import { Hero } from "../components/sections/hero";
|
||||
import { HowItWorks } from "../components/sections/how-it-works";
|
||||
import { Navbar } from "../components/layout/navbar";
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
const glyphs = " .'`^,:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$";
|
||||
|
||||
function parseRgbHex(hex: string): [number, number, number] {
|
||||
const value = hex.replace("#", "").trim();
|
||||
if (value.length !== 6) return [128, 128, 128];
|
||||
return [
|
||||
Number.parseInt(value.slice(0, 2), 16),
|
||||
Number.parseInt(value.slice(2, 4), 16),
|
||||
Number.parseInt(value.slice(4, 6), 16),
|
||||
];
|
||||
}
|
||||
|
||||
export function AsciiBackground({
|
||||
cssVar = "--accent",
|
||||
baseOpacity = 0.08,
|
||||
speed = 1,
|
||||
}: {
|
||||
cssVar?: string;
|
||||
baseOpacity?: number;
|
||||
speed?: number;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
||||
|
||||
const canvas = canvasRef.current;
|
||||
const container = containerRef.current;
|
||||
if (!canvas || !container) return;
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) return;
|
||||
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let rgb: [number, number, number] = [128, 128, 128];
|
||||
let phase = 0;
|
||||
let frame = 0;
|
||||
let isVisible = true;
|
||||
const pointer = { x: -9999, y: -9999 };
|
||||
|
||||
const updateColor = () => {
|
||||
const value = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim();
|
||||
if (value.startsWith("#")) {
|
||||
rgb = parseRgbHex(value);
|
||||
}
|
||||
};
|
||||
|
||||
updateColor();
|
||||
|
||||
const mutationObserver = new MutationObserver(updateColor);
|
||||
mutationObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
isVisible = entry?.isIntersecting ?? true;
|
||||
},
|
||||
{ threshold: 0 },
|
||||
);
|
||||
intersectionObserver.observe(container);
|
||||
|
||||
const resize = () => {
|
||||
width = container.clientWidth;
|
||||
height = container.clientHeight;
|
||||
const devicePixelRatio = Math.min(window.devicePixelRatio || 1, 2);
|
||||
canvas.width = width * devicePixelRatio;
|
||||
canvas.height = height * devicePixelRatio;
|
||||
context.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
|
||||
};
|
||||
|
||||
const handlePointer = (event: MouseEvent) => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
pointer.x = event.clientX - rect.left;
|
||||
pointer.y = event.clientY - rect.top;
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
frame = requestAnimationFrame(render);
|
||||
if (!isVisible) return;
|
||||
|
||||
context.clearRect(0, 0, width, height);
|
||||
context.font = "14px monospace";
|
||||
context.textAlign = "center";
|
||||
context.textBaseline = "middle";
|
||||
|
||||
const columns = Math.ceil(width / 14);
|
||||
const rows = Math.ceil(height / 14);
|
||||
const [red, green, blue] = rgb;
|
||||
const pointerX = pointer.x;
|
||||
const pointerY = pointer.y;
|
||||
|
||||
for (let row = 0; row < rows; row += 1) {
|
||||
for (let column = 0; column < columns; column += 1) {
|
||||
const centerX = column * 14 + 7;
|
||||
const centerY = row * 14 + 7;
|
||||
const value =
|
||||
Math.sin(0.05 * column + phase) * Math.cos(0.05 * row + phase) +
|
||||
Math.sin(0.012 * column - 0.7 * phase) * Math.cos(0.1 * row) * 0.5;
|
||||
|
||||
if (Math.abs(value) < 0.3) continue;
|
||||
|
||||
const index = Math.floor(Math.abs(value) * glyphs.length);
|
||||
let glyph = glyphs[index % glyphs.length];
|
||||
if (!glyph.trim()) continue;
|
||||
|
||||
let alpha = Math.abs(value) * baseOpacity;
|
||||
const deltaX = centerX - pointerX;
|
||||
const deltaY = centerY - pointerY;
|
||||
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
||||
if (distance < 110) {
|
||||
const intensity = 1 - distance / 110;
|
||||
glyph = Math.random() > 0.5 ? "0" : "1";
|
||||
alpha = 0.35 * intensity;
|
||||
const drift = distance > 1 ? 7 * intensity : 0;
|
||||
const offsetX = (deltaX / distance) * drift;
|
||||
const offsetY = (deltaY / distance) * drift;
|
||||
context.fillStyle = `rgba(${red},${green},${blue},${alpha})`;
|
||||
context.fillText(glyph, centerX - offsetX, centerY - offsetY);
|
||||
} else {
|
||||
context.fillStyle = `rgba(${red},${green},${blue},${alpha})`;
|
||||
context.fillText(glyph, centerX, centerY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
phase += 0.005 * speed;
|
||||
};
|
||||
|
||||
resize();
|
||||
window.addEventListener("resize", resize);
|
||||
window.addEventListener("mousemove", handlePointer);
|
||||
render();
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frame);
|
||||
window.removeEventListener("resize", resize);
|
||||
window.removeEventListener("mousemove", handlePointer);
|
||||
mutationObserver.disconnect();
|
||||
intersectionObserver.disconnect();
|
||||
};
|
||||
}, [baseOpacity, cssVar, speed]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
||||
<canvas ref={canvasRef} className="block w-full h-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import { AsciiBackground } from "./ascii-background";
|
||||
import { IconMark } from "./icon-mark";
|
||||
import { CONTACT_EMAIL, WEBSITE_URL } from "./site-constants";
|
||||
import { ThemeToggle } from "./theme-toggle";
|
||||
import { getTrackedDownloadHref } from "../lib/downloads";
|
||||
|
||||
const footerGroups = [
|
||||
{
|
||||
title: "Product",
|
||||
links: [
|
||||
{ label: "Features", href: "#features" },
|
||||
{ label: "How It Works", href: "#how-it-works" },
|
||||
{ label: "FAQ", href: "#faq" },
|
||||
{ label: "Download", href: "#download" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Downloads",
|
||||
links: [
|
||||
{ label: "Apple Silicon", href: getTrackedDownloadHref("macos-arm64-dmg"), external: true },
|
||||
{ label: "Intel Mac", href: getTrackedDownloadHref("macos-x64-dmg"), external: true },
|
||||
{ label: "Windows Installer", href: getTrackedDownloadHref("windows-universal-installer"), external: true },
|
||||
{ label: "Linux x64 AppImage", href: getTrackedDownloadHref("linux-x64-appimage"), external: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Company",
|
||||
links: [
|
||||
{ label: "Website", href: WEBSITE_URL, external: true },
|
||||
{ label: "Contact", href: `mailto:${CONTACT_EMAIL}`, external: true },
|
||||
{ label: "Download Center", href: "#download" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function Footer() {
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="relative border-t border-[var(--border)] overflow-hidden">
|
||||
<AsciiBackground cssVar="--accent" baseOpacity={0.04} speed={0.5} />
|
||||
|
||||
<div className="relative z-10 mx-auto max-w-6xl px-6 lg:px-8 pt-16 lg:pt-20 pb-10 lg:pb-12">
|
||||
<div className="flex flex-col lg:flex-row gap-y-12 gap-x-8">
|
||||
<div className="shrink-0 lg:w-64 mb-4 lg:mb-0">
|
||||
<a href="#" className="inline-flex items-center gap-2.5" aria-label="AgentCompany home">
|
||||
<IconMark size={28} />
|
||||
<span className="text-[var(--text)] font-semibold text-lg tracking-tight">AgentCompany</span>
|
||||
</a>
|
||||
<p className="mt-4 text-sm leading-relaxed text-[var(--muted)] max-w-xs">
|
||||
Local-first desktop control plane for autonomous AI companies.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-wrap gap-y-12 gap-x-8 justify-between">
|
||||
{footerGroups.map((group) => (
|
||||
<div key={group.title} className="min-w-[120px]">
|
||||
<h3 className="text-sm font-semibold text-[var(--text)] tracking-tight mb-4">{group.title}</h3>
|
||||
<ul className="space-y-3">
|
||||
{group.links.map((link) => (
|
||||
<li key={link.label}>
|
||||
<a
|
||||
href={link.href}
|
||||
{...(link.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
||||
className="text-sm text-[var(--muted)] hover:text-[var(--text)] transition-colors duration-200"
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-14 lg:mt-16 pt-6 border-t border-[var(--border)]" />
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div className="flex items-center gap-4 text-xs text-[var(--muted)]">
|
||||
<span>© {year} AgentCompany</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Container } from "../ui/container";
|
||||
import { Reveal } from "../ui/reveal";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconMark } from "../brand/icon-mark";
|
||||
import { getTrackedDownloadHref } from "../../lib/downloads";
|
||||
import { CONTACT_EMAIL, WEBSITE_URL } from "../../lib/site-constants";
|
||||
|
||||
type FooterLink = { label: string; href: string; external?: boolean };
|
||||
|
||||
const footerGroups: { title: string; links: FooterLink[] }[] = [
|
||||
{
|
||||
title: "Product",
|
||||
links: [
|
||||
{ label: "Features", href: "#features" },
|
||||
{ label: "How it works", href: "#how-it-works" },
|
||||
{ label: "FAQ", href: "#faq" },
|
||||
{ label: "Download", href: "#download" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Downloads",
|
||||
links: [
|
||||
{ label: "Apple Silicon", href: getTrackedDownloadHref("macos-arm64-dmg"), external: true },
|
||||
{ label: "Intel Mac", href: getTrackedDownloadHref("macos-x64-dmg"), external: true },
|
||||
{ label: "Windows Installer", href: getTrackedDownloadHref("windows-universal-installer"), external: true },
|
||||
{ label: "Linux x64 AppImage", href: getTrackedDownloadHref("linux-x64-appimage"), external: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Company",
|
||||
links: [
|
||||
{ label: "Website", href: WEBSITE_URL, external: true },
|
||||
{ label: "Contact", href: `mailto:${CONTACT_EMAIL}`, external: true },
|
||||
{ label: "Download center", href: "#download" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function Footer() {
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="bg-[var(--footer-bg)] text-[var(--footer-text)]">
|
||||
<Container>
|
||||
<div className="border-b border-white/10 py-24 text-center md:py-32">
|
||||
<Reveal>
|
||||
<h2 className="font-display text-[clamp(2.75rem,1.8rem+4vw,5.5rem)] leading-[1] tracking-[-0.02em]">
|
||||
Launch your company now.
|
||||
</h2>
|
||||
<div className="mt-8 flex justify-center">
|
||||
<Button href="#download" variant="inverted">
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-10 py-14 md:grid-cols-[1.6fr_1fr_1fr_1fr]">
|
||||
<div>
|
||||
<a href="#" className="inline-flex items-center gap-2.5" aria-label="AgentCompany home">
|
||||
<IconMark size={28} />
|
||||
<span className="text-lg font-medium tracking-[-0.02em]">AgentCompany</span>
|
||||
</a>
|
||||
<p className="mt-4 max-w-[32ch] text-sm leading-relaxed opacity-60">
|
||||
Local-first desktop control plane for autonomous AI companies.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{footerGroups.map((group) => (
|
||||
<div key={group.title}>
|
||||
<h3 className="eyebrow text-xs opacity-50">{group.title}</h3>
|
||||
<ul className="mt-4 space-y-3">
|
||||
{group.links.map((link) => (
|
||||
<li key={link.label}>
|
||||
<a
|
||||
href={link.href}
|
||||
{...(link.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
||||
className="text-sm opacity-75 transition-opacity hover:opacity-100"
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-white/10 py-6">
|
||||
<p className="text-xs opacity-45">© {year} AgentCompany</p>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import { Menu } from "reicon-react/icons/Menu";
|
||||
import { Xmark } from "reicon-react/icons/Xmark";
|
||||
import { Container } from "../ui/container";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconMark } from "../icon-mark";
|
||||
import { ThemeToggle } from "../theme-toggle";
|
||||
import { IconMark } from "../brand/icon-mark";
|
||||
import { ThemeToggle } from "../theme/theme-toggle";
|
||||
|
||||
const navLinks = [
|
||||
{ label: "Features", href: "#features" },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type ButtonVariant = "primary" | "ghost";
|
||||
type ButtonVariant = "primary" | "ghost" | "inverted";
|
||||
type ButtonSize = "md" | "sm";
|
||||
|
||||
const base =
|
||||
@@ -14,6 +14,7 @@ const sizeClasses: Record<ButtonSize, string> = {
|
||||
const variantClasses: Record<ButtonVariant, string> = {
|
||||
primary: "bg-[var(--cta)] text-[var(--text-on-cta)] hover:bg-[var(--cta-hover)]",
|
||||
ghost: "border border-[var(--border-strong)] text-[var(--text-secondary)] hover:border-[var(--muted)]",
|
||||
inverted: "bg-[#fbfdf6] text-[#0a1d08] hover:bg-[#fbfdf6]/90",
|
||||
};
|
||||
|
||||
/** Link-styled call to action. Every landing CTA is an anchor. */
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"name": "agent-company-landing",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"framer-motion": "12.38.0",
|
||||
"next": "16.2.6",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
@@ -16,6 +15,7 @@
|
||||
"reicon-react": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@tailwindcss/postcss": "^4.2.2",
|
||||
"@types/node": "^24.9.1",
|
||||
"@types/react": "^19.2.2",
|
||||
@@ -757,6 +757,22 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
|
||||
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.58.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
@@ -1148,31 +1164,18 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.38.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
|
||||
"integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.38.0",
|
||||
"motion-utils": "^12.36.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/is-prop-valid": "*",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/is-prop-valid": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
@@ -1475,21 +1478,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.38.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
|
||||
"integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.36.0"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-utils": {
|
||||
"version": "12.36.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz",
|
||||
"integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
@@ -1567,6 +1555,38 @@
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.58.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
||||
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"preview": "wrangler pages dev out"
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "12.38.0",
|
||||
"next": "16.2.6",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
@@ -18,6 +17,7 @@
|
||||
"reicon-react": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@tailwindcss/postcss": "^4.2.2",
|
||||
"@types/node": "^24.9.1",
|
||||
"@types/react": "^19.2.2",
|
||||
|
||||
|
After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 752 KiB |
|
Before Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 465 KiB |
|
Before Width: | Height: | Size: 450 KiB |
|
Before Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 606 KiB |