diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..1778f10
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+public-hoist-pattern[]=*@nextui-org/*
diff --git a/eslint.config.mjs b/eslint.config.mjs
index c85fb67..29c995f 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -10,7 +10,12 @@ const compat = new FlatCompat({
});
const eslintConfig = [
- ...compat.extends("next/core-web-vitals", "next/typescript"),
+ ...compat.config({
+ extends: ["next/core-web-vitals", "next/typescript"],
+ rules: {
+ "@typescript-eslint/no-unused-vars": "off",
+ },
+ }),
];
export default eslintConfig;
diff --git a/messages/en.json b/messages/en.json
index 1fb6b93..2b4ecaf 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -1,7 +1,7 @@
{
"GetStart": {
"title": "Distribute your App with MirrorChyan",
- "description": "MirrorChyan is an open-source App distribution platform that you can use to distribute your App.",
+ "description": "MirrorChyan is an App distribution platform that you can use to distribute your App.",
"getStart": "Get Started",
"apiDoc": "API Documentation"
},
diff --git a/messages/zh.json b/messages/zh.json
index 2cd3c1f..cd8acdf 100644
--- a/messages/zh.json
+++ b/messages/zh.json
@@ -1,15 +1,15 @@
{
"GetStart": {
"title": "使用MirrorChyan分发你的App",
- "description": "MirrorChyan是一个开源的App分发平台,你可以使用它来分发你的App。",
+ "description": "MirrorChyan是一个App分发平台,你可以使用它来分发你的App。",
"getStart": "开始使用",
- "apiDoc": "API文档"
+ "apiDoc": "API文档",
+ "becomeSponsor": "到爱发电赞助"
},
"GetKey": {
"title": "欢迎使用MirrorChyan",
"orderId": "订单号",
- "getKey": "获取API Key",
- "becomeSponsor": "到爱发电赞助"
+ "getKey": "获取API Key"
},
"ShowKey": {
"thanksForSponsor": "感谢您的赞助",
diff --git a/package.json b/package.json
index 56da0ff..5248556 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev --turbopack",
+ "dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
diff --git a/src/app/[locale]/get-key/page.tsx b/src/app/[locale]/get-key/page.tsx
index 690ac9e..e9c7cec 100644
--- a/src/app/[locale]/get-key/page.tsx
+++ b/src/app/[locale]/get-key/page.tsx
@@ -7,9 +7,10 @@ import { Link } from "@/i18n/routing"
export default function GetKey() {
const t = useTranslations('GetKey')
const [orderId, setOrderId] = useState('')
+
return (
<>
-
+
>
diff --git a/src/app/[locale]/get-start/page.tsx b/src/app/[locale]/get-start/page.tsx
index 9cd6dfd..692c14b 100644
--- a/src/app/[locale]/get-start/page.tsx
+++ b/src/app/[locale]/get-start/page.tsx
@@ -1,13 +1,52 @@
-import { BackgroundLines } from "@/components/BackgroundLines"
+import { BackgroundBeamsWithCollision } from "@/components/BackgroundBeamsWithCollision"
+import { FlipWords } from "@/components/FlipWord"
import { Link } from "@/i18n/routing"
+import { cn } from "@/lib/utils/css"
import { useTranslations } from "next-intl"
+// import { CheckIcon } from '@heroicons/react/20/solid'
export default function GetStart() {
const t = useTranslations('GetStart')
+
+ const plans = [
+ {
+ name: 'Mirror酱日卡',
+ price: '¥2.37',
+ itemId: '83f9d3b8cac611ef8fc352540025c377',
+ description: '感谢投喂',
+ tmoji: '( •̀ ω •́ )✧',
+ mostPopular: false,
+ },
+ {
+ name: 'Mirror酱月卡',
+ price: '¥2.97',
+ itemId: '3134f94ac9aa11ef9d725254001e7c00',
+ description: 'Mirror酱的零食罐头',
+ tmoji: 'o((>ω< ))o',
+ mostPopular: false,
+ },
+ {
+ name: 'Mirror酱季卡',
+ price: '¥3.87',
+ itemId: '9e6c7b28c9aa11efb47452540025c377',
+ description: 'Mirror酱的午餐盒',
+ tmoji: 'o(≧▽≦)o',
+ mostPopular: false,
+ },
+ {
+ name: 'Mirror酱年卡',
+ price: '¥5.97',
+ itemId: '69c45576c9aa11ef9ace52540025c377',
+ description: '老板大气',
+ tmoji: 'ヾ(≧▽≦*)o',
+ mostPopular: true,
+ },
+ ]
+
return (
-
+
-
+
{t('title')}
@@ -28,7 +67,57 @@ export default function GetStart() {
+
+ {plans.map((plan) => (
+
+
+ {plan.name}
+
+
+ {plan.description}
+
+
+
+ {plan.price}
+
+
+
+ {t('becomeSponsor')}
+
+ {/*
+ {plan.features.map((feature) => (
+ -
+
+ {feature}
+
+ ))}
+
*/}
+
+ ))}
+
-
+
)
}
diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx
index 6eb7fe2..a5d53ba 100644
--- a/src/app/[locale]/layout.tsx
+++ b/src/app/[locale]/layout.tsx
@@ -3,6 +3,8 @@ import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
+import { Providers } from './provider';
+
export default async function LocaleLayout({
children,
params,
@@ -21,11 +23,13 @@ export default async function LocaleLayout({
const messages = await getMessages();
return (
-
-
-
- {children}
-
+
+
+
+
+ {children}
+
+
);
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 1d4dfa5..273071d 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -11,11 +11,5 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
- return (
-
-
- {children}
-
-
- );
+ return children;
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
new file mode 100644
index 0000000..d82c1da
--- /dev/null
+++ b/src/app/page.tsx
@@ -0,0 +1,6 @@
+import { redirect } from 'next/navigation';
+
+// This page only renders when the app is built statically (output: 'export')
+export default function RootPage() {
+ redirect('/en');
+}
diff --git a/src/components/BackgroundBeamsWithCollision.tsx b/src/components/BackgroundBeamsWithCollision.tsx
new file mode 100644
index 0000000..011a83d
--- /dev/null
+++ b/src/components/BackgroundBeamsWithCollision.tsx
@@ -0,0 +1,259 @@
+"use client";
+import { cn } from "@/lib/utils/css";
+import { motion, AnimatePresence } from "framer-motion";
+import React, { useRef, useState, useEffect } from "react";
+
+export const BackgroundBeamsWithCollision = ({
+ children,
+ className,
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) => {
+ const containerRef = useRef
(null);
+ const parentRef = useRef(null);
+
+ const beams = [
+ {
+ initialX: 10,
+ translateX: 10,
+ duration: 7,
+ repeatDelay: 3,
+ delay: 2,
+ },
+ {
+ initialX: 600,
+ translateX: 600,
+ duration: 3,
+ repeatDelay: 3,
+ delay: 4,
+ },
+ {
+ initialX: 100,
+ translateX: 100,
+ duration: 7,
+ repeatDelay: 7,
+ className: "h-6",
+ },
+ {
+ initialX: 400,
+ translateX: 400,
+ duration: 5,
+ repeatDelay: 14,
+ delay: 4,
+ },
+ {
+ initialX: 800,
+ translateX: 800,
+ duration: 11,
+ repeatDelay: 2,
+ className: "h-20",
+ },
+ {
+ initialX: 1000,
+ translateX: 1000,
+ duration: 4,
+ repeatDelay: 2,
+ className: "h-12",
+ },
+ {
+ initialX: 1200,
+ translateX: 1200,
+ duration: 6,
+ repeatDelay: 4,
+ delay: 2,
+ className: "h-6",
+ },
+ ];
+
+ return (
+
+ {beams.map((beam) => (
+
+ ))}
+
+ {children}
+
+
+ );
+};
+
+const CollisionMechanism = React.forwardRef<
+ HTMLDivElement,
+ {
+ containerRef: React.RefObject;
+ parentRef: React.RefObject;
+ beamOptions?: {
+ initialX?: number;
+ translateX?: number;
+ initialY?: number;
+ translateY?: number;
+ rotate?: number;
+ className?: string;
+ duration?: number;
+ delay?: number;
+ repeatDelay?: number;
+ };
+ }
+>(({ parentRef, containerRef, beamOptions = {} }, ref) => {
+ const beamRef = useRef(null);
+ const [collision, setCollision] = useState<{
+ detected: boolean;
+ coordinates: { x: number; y: number } | null;
+ }>({
+ detected: false,
+ coordinates: null,
+ });
+ const [beamKey, setBeamKey] = useState(0);
+ const [cycleCollisionDetected, setCycleCollisionDetected] = useState(false);
+
+ useEffect(() => {
+ const checkCollision = () => {
+ if (
+ beamRef.current &&
+ containerRef.current &&
+ parentRef.current &&
+ !cycleCollisionDetected
+ ) {
+ const beamRect = beamRef.current.getBoundingClientRect();
+ const containerRect = containerRef.current.getBoundingClientRect();
+ const parentRect = parentRef.current.getBoundingClientRect();
+
+ if (beamRect.bottom >= containerRect.top) {
+ const relativeX =
+ beamRect.left - parentRect.left + beamRect.width / 2;
+ const relativeY = beamRect.bottom - parentRect.top;
+
+ setCollision({
+ detected: true,
+ coordinates: {
+ x: relativeX,
+ y: relativeY,
+ },
+ });
+ setCycleCollisionDetected(true);
+ }
+ }
+ };
+
+ const animationInterval = setInterval(checkCollision, 50);
+
+ return () => clearInterval(animationInterval);
+ }, [cycleCollisionDetected, containerRef]);
+
+ useEffect(() => {
+ if (collision.detected && collision.coordinates) {
+ setTimeout(() => {
+ setCollision({ detected: false, coordinates: null });
+ setCycleCollisionDetected(false);
+ }, 2000);
+
+ setTimeout(() => {
+ setBeamKey((prevKey) => prevKey + 1);
+ }, 2000);
+ }
+ }, [collision]);
+
+ return (
+ <>
+
+
+ {collision.detected && collision.coordinates && (
+
+ )}
+
+ >
+ );
+});
+
+CollisionMechanism.displayName = "CollisionMechanism";
+
+const Explosion = ({ ...props }: React.HTMLProps) => {
+ const spans = Array.from({ length: 20 }, (_, index) => ({
+ id: index,
+ initialX: 0,
+ initialY: 0,
+ directionX: Math.floor(Math.random() * 80 - 40),
+ directionY: Math.floor(Math.random() * -50 - 10),
+ }));
+
+ return (
+
+
+ {spans.map((span) => (
+
+ ))}
+
+ );
+};
diff --git a/src/components/FlipWord.tsx b/src/components/FlipWord.tsx
new file mode 100644
index 0000000..9c7d3f5
--- /dev/null
+++ b/src/components/FlipWord.tsx
@@ -0,0 +1,98 @@
+"use client";
+import React, { useCallback, useEffect, useState } from "react";
+import { AnimatePresence, motion } from "framer-motion";
+import { cn } from "@/lib/utils/css";
+
+export const FlipWords = ({
+ words,
+ duration = 3000,
+ className,
+}: {
+ words: string[];
+ duration?: number;
+ className?: string;
+}) => {
+ const [currentWord, setCurrentWord] = useState(words[0]);
+ const [isAnimating, setIsAnimating] = useState(false);
+
+ // thanks for the fix Julian - https://github.com/Julian-AT
+ const startAnimation = useCallback(() => {
+ const word = words[words.indexOf(currentWord) + 1] || words[0];
+ setCurrentWord(word);
+ setIsAnimating(true);
+ }, [currentWord, words]);
+
+ useEffect(() => {
+ if (!isAnimating)
+ setTimeout(() => {
+ startAnimation();
+ }, duration);
+ }, [isAnimating, duration, startAnimation]);
+
+ return (
+ {
+ setIsAnimating(false);
+ }}
+ >
+
+ {/* edit suggested by Sajal: https://x.com/DewanganSajal */}
+ {currentWord.split(" ").map((word, wordIndex) => (
+
+ {word.split("").map((letter, letterIndex) => (
+
+ {letter}
+
+ ))}
+
+
+ ))}
+
+
+ );
+};
diff --git a/tailwind.config.js b/tailwind.config.js
index 3efb45f..dd2eed5 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,5 +1,6 @@
import { default as flattenColorPalette } from "tailwindcss/lib/util/flattenColorPalette";
import daisyui from "daisyui";
+import { nextui } from "@nextui-org/react";
/** @type {import('tailwindcss').Config} */
export const content = [
@@ -9,12 +10,13 @@ export const content = [
// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx,mdx}",
+ "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}"
];
export const darkMode = "class";
export const theme = {
extend: {},
};
-export const plugins = [addVariablesForColors, daisyui];
+export const plugins = [addVariablesForColors, daisyui, nextui()];
// This plugin adds each Tailwind color as a global CSS variable, e.g. var(--gray-200).
function addVariablesForColors({ addBase, theme }) {