Fix learn 404, coin animation, full regions, and AI key errors.

Use numeric /learn/{num} routes, register Tailwind coin animations with方孔铜钱 UI, expand to 34 provinces, and surface missing OPENAI_API_KEY clearly.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-10 21:32:20 +08:00
parent 96b659fbe5
commit a487b17165
15 changed files with 931 additions and 159 deletions
+23 -18
View File
@@ -2,7 +2,13 @@ import React, { useEffect, useState } from "react";
import clsx from "clsx";
const rotationDuration = 3800;
const bezier = "cubic-bezier(0.645,0.045,0.355,1)";
const COIN_ANIM: Record<string, string> = {
"front-front": "animate-coin-front-front",
"front-back": "animate-coin-front-back",
"back-front": "animate-coin-back-front",
"back-back": "animate-coin-back-back",
};
function Coin(props: {
frontList: boolean[];
@@ -15,7 +21,6 @@ function Coin(props: {
if (!props.rotation) {
return;
}
const id = setTimeout(() => {
setLastFront(props.frontList);
props.onTransitionEnd();
@@ -37,23 +42,30 @@ function Coin(props: {
);
}
/** 方孔铜钱造型 */
function CoinFace({ side }: { side: "front" | "back" }) {
const isFront = side === "front";
return (
<div
className={clsx(
"absolute inset-0 flex items-center justify-center rounded-full border-[3px] shadow-inner",
"absolute inset-0 rounded-full border-[3px] shadow-md",
isFront
? "border-amber-500 bg-gradient-to-br from-amber-200 to-amber-400 text-amber-950"
: "border-stone-500 bg-gradient-to-br from-stone-300 to-stone-500 text-stone-800",
? "border-amber-600 bg-gradient-to-br from-amber-300 via-amber-400 to-amber-600"
: "border-stone-600 bg-gradient-to-br from-stone-400 via-stone-500 to-stone-700",
)}
style={{
backfaceVisibility: "hidden",
transform: isFront ? undefined : "rotateY(180deg)",
}}
>
<span className="select-none text-lg font-bold sm:text-xl">
{isFront ? "正" : "反"}
<div className="absolute inset-[18%] rounded-sm border-2 border-amber-800/40 bg-amber-900/10" />
<span
className={clsx(
"absolute bottom-1 left-0 right-0 text-center text-[10px] font-bold sm:text-xs",
isFront ? "text-amber-950/80" : "text-stone-200/90",
)}
>
{isFront ? "乾" : "坤"}
</span>
</div>
);
@@ -64,24 +76,17 @@ function CoinItem(props: {
lastFront: boolean;
rotation: boolean;
}) {
let animate = "";
if (props.rotation) {
animate = `animate-[coin-${getFront(props.lastFront)}-${getFront(
props.front,
)}_${rotationDuration / 1000}s_${bezier}]`;
}
const animKey = `${getFront(props.lastFront)}-${getFront(props.front)}`;
const animClass = props.rotation ? COIN_ANIM[animKey] : "";
return (
<div
className="h-16 w-16 sm:h-20 sm:w-20"
style={{ perspective: "800px" }}
>
<div className="h-[4.5rem] w-[4.5rem] sm:h-20 sm:w-20" style={{ perspective: "900px" }}>
<div
style={{
transform: `rotateY(${props.front ? 0 : 180}deg)`,
transformStyle: "preserve-3d",
}}
className={clsx("relative h-full w-full", animate)}
className={clsx("relative h-full w-full", animClass)}
>
<CoinFace side="front" />
<CoinFace side="back" />