|
|
@@ -0,0 +1,536 @@
|
|
|
+import React, { useState, useEffect } from 'react';
|
|
|
+import Head from 'next/head';
|
|
|
+import { useRouter } from 'next/router';
|
|
|
+import MagicalGirlCardACG from '../components/MagicalGirlCardACG';
|
|
|
+import { useCooldown } from '../lib/cooldown';
|
|
|
+import { quickCheck } from '@/lib/sensitive-word-filter';
|
|
|
+
|
|
|
+interface Questionnaire {
|
|
|
+ questions: string[];
|
|
|
+}
|
|
|
+
|
|
|
+interface MagicalGirlACG {
|
|
|
+ codename: string;
|
|
|
+ appearance: {
|
|
|
+ outfit: string;
|
|
|
+ accessories: string;
|
|
|
+ colorScheme: string;
|
|
|
+ overallLook: string;
|
|
|
+ };
|
|
|
+ magicWeapon: {
|
|
|
+ name: string;
|
|
|
+ form: string;
|
|
|
+ basicAbilities: string[];
|
|
|
+ description: string;
|
|
|
+ };
|
|
|
+ specialMove: {
|
|
|
+ name: string;
|
|
|
+ chant: string;
|
|
|
+ description: string;
|
|
|
+ };
|
|
|
+ awakening: {
|
|
|
+ name: string;
|
|
|
+ evolvedAbilities: string[];
|
|
|
+ evolvedForm: string;
|
|
|
+ evolvedOutfit: string;
|
|
|
+ };
|
|
|
+ analysis: {
|
|
|
+ personalityAnalysis: string;
|
|
|
+ abilityReasoning: string;
|
|
|
+ coreTraits: string[];
|
|
|
+ predictionBasis: string;
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+const SaveJsonButton: React.FC<{ magicalGirlDetails: MagicalGirlACG; answers: string[] }> = ({ magicalGirlDetails, answers }) => {
|
|
|
+ const [isMobile, setIsMobile] = useState(false);
|
|
|
+ const [showJsonText, setShowJsonText] = useState(false);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ const userAgent = navigator.userAgent.toLowerCase();
|
|
|
+ const isMobileDevice = /mobile|android|iphone|ipad|ipod|blackberry|iemobile|opera mini/.test(userAgent);
|
|
|
+ setIsMobile(isMobileDevice);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const downloadJson = () => {
|
|
|
+ const dataToSave = {
|
|
|
+ ...magicalGirlDetails,
|
|
|
+ userAnswers: answers
|
|
|
+ };
|
|
|
+ const jsonData = JSON.stringify(dataToSave, null, 2);
|
|
|
+ const blob = new Blob([jsonData], { type: 'application/json' });
|
|
|
+ const url = URL.createObjectURL(blob);
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = url;
|
|
|
+ link.download = `魔法少女_${magicalGirlDetails.codename || 'data'}.json`;
|
|
|
+ document.body.appendChild(link);
|
|
|
+ link.click();
|
|
|
+ document.body.removeChild(link);
|
|
|
+ URL.revokeObjectURL(url);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSave = () => {
|
|
|
+ if (isMobile) {
|
|
|
+ setShowJsonText(true);
|
|
|
+ } else {
|
|
|
+ downloadJson();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ if (showJsonText) {
|
|
|
+ return (
|
|
|
+ <div className="text-left">
|
|
|
+ <div className="mb-4 text-center">
|
|
|
+ <p className="text-sm text-gray-600 mb-2">请复制以下数据并保存</p>
|
|
|
+ <button
|
|
|
+ onClick={() => setShowJsonText(false)}
|
|
|
+ className="text-blue-600 text-sm"
|
|
|
+ >
|
|
|
+ 返回
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <textarea
|
|
|
+ value={JSON.stringify({ ...magicalGirlDetails, userAnswers: answers }, null, 2)}
|
|
|
+ readOnly
|
|
|
+ className="w-full h-64 p-3 border rounded-lg text-xs font-mono bg-gray-50 text-gray-900"
|
|
|
+ onClick={(e) => (e.target as HTMLTextAreaElement).select()}
|
|
|
+ />
|
|
|
+ <p className="text-xs text-gray-500 mt-2 text-center">点击文本框可全选内容</p>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <button
|
|
|
+ onClick={handleSave}
|
|
|
+ className="generate-button"
|
|
|
+ >
|
|
|
+ {isMobile ? '查看原始数据' : '下载设定文件'}
|
|
|
+ </button>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const ACGPage: React.FC = () => {
|
|
|
+ const router = useRouter();
|
|
|
+ const [questions, setQuestions] = useState<string[]>([]);
|
|
|
+ const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
|
|
+ const [answers, setAnswers] = useState<string[]>([]);
|
|
|
+ const [currentAnswer, setCurrentAnswer] = useState('');
|
|
|
+ const [loading, setLoading] = useState(true);
|
|
|
+ const [submitting, setSubmitting] = useState(false);
|
|
|
+ const [isTransitioning, setIsTransitioning] = useState(false);
|
|
|
+ const [magicalGirlDetails, setMagicalGirlDetails] = useState<MagicalGirlACG | null>(null);
|
|
|
+ const [showImageModal, setShowImageModal] = useState(false);
|
|
|
+ const [savedImageUrl, setSavedImageUrl] = useState<string | null>(null);
|
|
|
+ const [showIntroduction, setShowIntroduction] = useState(true);
|
|
|
+ const [error, setError] = useState<string | null>(null);
|
|
|
+ const [showDetails, setShowDetails] = useState(false);
|
|
|
+ const { isCooldown, startCooldown, remainingTime } = useCooldown('generateDetailsCooldown', 60000);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ fetch('/acg_questionnaire.json')
|
|
|
+ .then(response => {
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error('加载问卷文件失败');
|
|
|
+ }
|
|
|
+ return response.json();
|
|
|
+ })
|
|
|
+ .then((data: Questionnaire) => {
|
|
|
+ setQuestions(data.questions);
|
|
|
+ setAnswers(new Array(data.questions.length).fill(''));
|
|
|
+ setLoading(false);
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('加载问卷失败:', error);
|
|
|
+ setError('📋 加载问卷失败,请刷新页面重试');
|
|
|
+ setLoading(false);
|
|
|
+ });
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const handleNext = () => {
|
|
|
+ if (currentAnswer.trim().length === 0) {
|
|
|
+ setError('⚠️ 请输入答案后再继续');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (currentAnswer.length > 30) {
|
|
|
+ setError('⚠️ 答案不能超过30字');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setError(null);
|
|
|
+
|
|
|
+ proceedToNextQuestion(currentAnswer.trim());
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleQuickOption = (option: string) => {
|
|
|
+ proceedToNextQuestion(option);
|
|
|
+ };
|
|
|
+
|
|
|
+ const proceedToNextQuestion = (answer: string) => {
|
|
|
+ const newAnswers = [...answers];
|
|
|
+ newAnswers[currentQuestionIndex] = answer;
|
|
|
+ setAnswers(newAnswers);
|
|
|
+
|
|
|
+ if (currentQuestionIndex < questions.length - 1) {
|
|
|
+ setIsTransitioning(true);
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ setCurrentQuestionIndex(currentQuestionIndex + 1);
|
|
|
+ setCurrentAnswer(newAnswers[currentQuestionIndex + 1] || '');
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ setIsTransitioning(false);
|
|
|
+ }, 50);
|
|
|
+ }, 250);
|
|
|
+ } else {
|
|
|
+ handleSubmit(newAnswers);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSubmit = async (finalAnswers: string[]) => {
|
|
|
+ if (isCooldown) {
|
|
|
+ setError(`请等待 ${remainingTime} 秒后再生成`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ setSubmitting(true);
|
|
|
+ setError(null);
|
|
|
+ const result = await quickCheck(finalAnswers.join(''));
|
|
|
+ if (result.hasSensitiveWords) {
|
|
|
+ router.push('/arrested');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/generate-magical-girl-acg', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify({ answers: finalAnswers })
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ const errorData = await response.json();
|
|
|
+
|
|
|
+ if (response.status === 429) {
|
|
|
+ const retryAfter = errorData.retryAfter || 60;
|
|
|
+ throw new Error(`请求过于频繁!请等待 ${retryAfter} 秒后再试。`);
|
|
|
+ } else if (response.status >= 500) {
|
|
|
+ throw new Error('服务器内部错误,请稍后重试');
|
|
|
+ } else {
|
|
|
+ throw new Error(errorData.message || errorData.error || '生成失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const result: MagicalGirlACG = await response.json();
|
|
|
+ setMagicalGirlDetails(result);
|
|
|
+ setError(null);
|
|
|
+ } catch (error) {
|
|
|
+ if (error instanceof Error) {
|
|
|
+ const errorMessage = error.message;
|
|
|
+
|
|
|
+ if (errorMessage.includes('请求过于频繁')) {
|
|
|
+ setError('🚫 请求太频繁了!每2分钟只能生成一次哦~请稍后再试吧!');
|
|
|
+ } else if (errorMessage.includes('网络') || error instanceof TypeError) {
|
|
|
+ setError('🌐 网络连接有问题!请检查网络后重试~');
|
|
|
+ } else {
|
|
|
+ setError(`✨ 魔法失效了!${errorMessage}`);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ setError('✨ 魔法失效了!生成详情时发生未知错误,请重试');
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ setSubmitting(false);
|
|
|
+ startCooldown();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSaveImage = (imageUrl: string) => {
|
|
|
+ setSavedImageUrl(imageUrl);
|
|
|
+ setShowImageModal(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleStartQuestionnaire = () => {
|
|
|
+ setShowIntroduction(false);
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ if (loading) {
|
|
|
+ return (
|
|
|
+ <div className="magic-background">
|
|
|
+ <div className="container">
|
|
|
+ <div className="card">
|
|
|
+ <div className="text-center text-lg">加载中...</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (questions.length === 0) {
|
|
|
+ return (
|
|
|
+ <div className="magic-background">
|
|
|
+ <div className="container">
|
|
|
+ <div className="card">
|
|
|
+ <div className="error-message">加载问卷失败</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ const isLastQuestion = currentQuestionIndex === questions.length - 1;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <Head>
|
|
|
+ <title>魔法少女调查问卷 ~ 二次元篇</title>
|
|
|
+ <meta name="description" content="回答问卷,生成您的专属二次元魔法少女" />
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
|
+ <link rel="icon" href="/favicon.ico" />
|
|
|
+ </Head>
|
|
|
+
|
|
|
+ <div className="magic-background">
|
|
|
+ <div className="container">
|
|
|
+ <div className="card">
|
|
|
+ <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', marginBottom: '1rem' }}>
|
|
|
+ <img src="/questionnaire-logo.svg" width={250} height={160} alt="Questionnaire Logo" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {showIntroduction ? (
|
|
|
+ <div className="text-center">
|
|
|
+ <div className="mb-6 leading-relaxed text-gray-800"
|
|
|
+ style={{ lineHeight: '1.5', marginTop: '3rem', marginBottom: '4rem' }}
|
|
|
+ >
|
|
|
+ 回答这些问题,唤醒你心中沉睡的魔法少女之魂!<br />
|
|
|
+ <p style={{ fontSize: '0.8rem', marginTop: '1rem', color: '#999', fontStyle: 'italic' }}>本测试将为你量身打造专属的二次元魔法少女设定</p>
|
|
|
+ </div>
|
|
|
+ <button
|
|
|
+ onClick={handleStartQuestionnaire}
|
|
|
+ className="generate-button text-lg"
|
|
|
+ style={{ marginBottom: '0rem' }}
|
|
|
+ >
|
|
|
+ 开始问卷
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <div className="text-center" style={{ marginTop: '2rem' }}>
|
|
|
+ <button
|
|
|
+ onClick={() => router.push('/')}
|
|
|
+ className="footer-link"
|
|
|
+ >
|
|
|
+ 返回首页
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <div style={{ marginBottom: '1.5rem' }}>
|
|
|
+ <div className="flex justify-between items-center" style={{ marginBottom: '0.5rem' }}>
|
|
|
+ <span className="text-sm text-gray-600">
|
|
|
+ 问题 {currentQuestionIndex + 1} / {questions.length}
|
|
|
+ </span>
|
|
|
+ <span className="text-sm text-gray-600">
|
|
|
+ {Math.round(((currentQuestionIndex + 1) / questions.length) * 100)}%
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div className="w-full bg-gray-200 rounded-full h-2">
|
|
|
+ <div
|
|
|
+ className="h-2 rounded-full transition-all duration-300"
|
|
|
+ style={{
|
|
|
+ width: `${((currentQuestionIndex + 1) / questions.length) * 100}%`,
|
|
|
+ background: 'linear-gradient(to right, #ff9a9e, #fecfef)'
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div style={{ marginBottom: '1rem', minHeight: '80px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
|
+ <h2
|
|
|
+ className="text-xl font-medium leading-relaxed text-center text-pink-900"
|
|
|
+ style={{
|
|
|
+ opacity: isTransitioning ? 0 : 1,
|
|
|
+ transition: 'opacity 0.3s ease-in-out, transform 0.3s ease-in-out',
|
|
|
+ transform: isTransitioning ? 'translateX(-100px)' : 'translateX(0)',
|
|
|
+ animation: !isTransitioning && currentQuestionIndex > 0 ? 'slideInFromRight 0.3s ease-out' : 'none'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {questions[currentQuestionIndex]}
|
|
|
+ </h2>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="input-group">
|
|
|
+ <textarea
|
|
|
+ value={currentAnswer}
|
|
|
+ onChange={(e) => setCurrentAnswer(e.target.value)}
|
|
|
+ placeholder="请输入您的答案(不超过30字)"
|
|
|
+ className="input-field resize-none h-24"
|
|
|
+ maxLength={30}
|
|
|
+ />
|
|
|
+ <div className="text-right text-sm text-gray-500" style={{ marginTop: '-2rem', marginRight: '0.5rem' }}>
|
|
|
+ {currentAnswer.length}/30
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="flex gap-2 justify-center" style={{ marginBottom: '1rem', marginTop: '2rem' }}>
|
|
|
+ <button
|
|
|
+ onClick={() => handleQuickOption('还没想好')}
|
|
|
+ disabled={isTransitioning}
|
|
|
+ className="generate-button h-10"
|
|
|
+ style={{ marginBottom: 0, padding: 0 }}
|
|
|
+ >
|
|
|
+ 还没想好
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ onClick={() => handleQuickOption('不想回答')}
|
|
|
+ disabled={isTransitioning}
|
|
|
+ className="generate-button h-10"
|
|
|
+ style={{ marginBottom: 0, padding: 0 }}
|
|
|
+ >
|
|
|
+ 不想回答
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button
|
|
|
+ onClick={handleNext}
|
|
|
+ disabled={submitting || currentAnswer.trim().length === 0 || isTransitioning || isCooldown}
|
|
|
+ className="generate-button"
|
|
|
+ >
|
|
|
+ {isCooldown
|
|
|
+ ? `请等待 ${remainingTime} 秒`
|
|
|
+ : submitting
|
|
|
+ ? (
|
|
|
+ <span className="flex items-center justify-center">
|
|
|
+ <svg className="animate-spin h-4 w-4 text-white" style={{ marginLeft: '-0.25rem', marginRight: '0.5rem' }} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
|
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
|
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
|
+ </svg>
|
|
|
+ 提交中...
|
|
|
+ </span>
|
|
|
+ )
|
|
|
+ : isLastQuestion
|
|
|
+ ? '提交'
|
|
|
+ : '下一题'}
|
|
|
+ </button>
|
|
|
+
|
|
|
+ {error && (
|
|
|
+ <div className="error-message">
|
|
|
+ {error}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <div className="text-center" style={{ marginTop: '1rem' }}>
|
|
|
+ <button
|
|
|
+ onClick={() => router.push('/')}
|
|
|
+ className="footer-link"
|
|
|
+ >
|
|
|
+ 返回首页
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {magicalGirlDetails && (
|
|
|
+ <>
|
|
|
+ <MagicalGirlCardACG
|
|
|
+ magicalGirl={magicalGirlDetails}
|
|
|
+ gradientStyle="linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)"
|
|
|
+ onSaveImage={handleSaveImage}
|
|
|
+ />
|
|
|
+ <div className="card" style={{ marginTop: '1rem' }}>
|
|
|
+ <div className="text-center">
|
|
|
+ <button
|
|
|
+ onClick={() => setShowDetails(!showDetails)}
|
|
|
+ className="text-lg font-medium text-pink-900 hover:text-pink-700 transition-colors duration-200"
|
|
|
+ style={{ background: 'none', border: 'none', cursor: 'pointer' }}
|
|
|
+ >
|
|
|
+ {showDetails ? '点击收起设定说明' : '点击展开设定说明'} {showDetails ? '▼' : '▶'}
|
|
|
+ </button>
|
|
|
+ {showDetails && (
|
|
|
+ <div className="text-left" style={{ marginTop: '1rem' }}>
|
|
|
+ <div className="mb-4">
|
|
|
+ <h4 className="font-medium text-pink-800 mb-2">1. 魔法武器</h4>
|
|
|
+ <p className="text-sm text-gray-700 leading-relaxed">
|
|
|
+ 每个魔法少女都拥有独特的魔法武器,这是她们力量的源泉和象征。武器的形态和能力多种多样,完全反映了持有者的内心和特质。
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <div className="mb-4">
|
|
|
+ <h4 className="font-medium text-pink-800 mb-2">2. 必杀技</h4>
|
|
|
+ <p className="text-sm text-gray-700 leading-relaxed">
|
|
|
+ 必杀技是魔法少女最强大的招式,通常需要咏唱咒语并伴随着华丽的动画演出。这是她们在关键时刻扭转战局、守护重要之物的最终王牌。
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <div className="mb-4">
|
|
|
+ <h4 className="font-medium text-pink-800 mb-2">3. 觉醒/超进化</h4>
|
|
|
+ <p className="text-sm text-gray-700 leading-relaxed">
|
|
|
+ 当魔法少女的意志和情感达到顶峰时,她们可能会迎来力量的觉醒或超进化。这不仅会带来更华丽的服装和更强大的武器,更象征着角色的成长和蜕变。
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="card" style={{ marginTop: '1rem' }}>
|
|
|
+ <div className="text-center">
|
|
|
+ <h3 className="text-lg font-medium text-pink-900" style={{ marginBottom: '1rem' }}>保存人物设定</h3>
|
|
|
+ <SaveJsonButton magicalGirlDetails={magicalGirlDetails} answers={answers} />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <footer className="footer text-white">
|
|
|
+ <p className="text-white">
|
|
|
+ 问卷与系统设计 <a className="footer-link">@末伏之夜</a>
|
|
|
+ </p>
|
|
|
+ <p className="text-white">
|
|
|
+ <a href="https://github.com/colasama" target="_blank" rel="noopener noreferrer" className="footer-link">@Colanns</a> 急速出品
|
|
|
+ </p>
|
|
|
+ <p className="text-white">
|
|
|
+ 本项目 AI 能力由
|
|
|
+ <a href="https://github.com/KouriChat/KouriChat" target="_blank" rel="noopener noreferrer" className="footer-link">KouriChat</a> &
|
|
|
+ <a href="https://api.kourichat.com/" target="_blank" rel="noopener noreferrer" className="footer-link">Kouri API</a>
|
|
|
+ 强力支持
|
|
|
+ </p>
|
|
|
+ <p className="text-white">
|
|
|
+ <a href="https://github.com/colasama/MahoShojo-Generator" target="_blank" rel="noopener noreferrer" className="footer-link">colasama/MahoShojo-Generator</a>
|
|
|
+ </p>
|
|
|
+ </footer>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {showImageModal && savedImageUrl && (
|
|
|
+ <div className="fixed inset-0 bg-black flex items-center justify-center z-50"
|
|
|
+ style={{ backgroundColor: 'rgba(0, 0, 0, 0.7)', paddingLeft: '2rem', paddingRight: '2rem' }}
|
|
|
+ >
|
|
|
+ <div className="bg-white rounded-lg max-w-lg w-full max-h-[80vh] overflow-auto relative">
|
|
|
+ <div className="flex justify-between items-center m-0">
|
|
|
+ <div></div>
|
|
|
+ <button
|
|
|
+ onClick={() => setShowImageModal(false)}
|
|
|
+ className="text-gray-500 hover:text-gray-700 text-3xl leading-none"
|
|
|
+ style={{ marginRight: '0.5rem' }}
|
|
|
+ >
|
|
|
+ ×
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <p className="text-center text-sm text-gray-600" style={{ marginTop: '0.5rem' }}>
|
|
|
+ 💫 长按图片保存到相册
|
|
|
+ </p>
|
|
|
+ <div className="items-center flex flex-col" style={{ padding: '0.5rem' }}>
|
|
|
+ <img
|
|
|
+ src={savedImageUrl}
|
|
|
+ alt="魔法少女详细档案"
|
|
|
+ className="w-1/2 h-auto rounded-lg mx-auto"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default ACGPage;
|