hrmkmdtv's picture
Update index.html
a8f4d29 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Arc Project Starter</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
content: ["*"],
theme: {
extend: {}
}
}
</script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState } = React;
// Lucide React icons as simple SVG components
const Copy = ({ className }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
);
const BookOpen = ({ className }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
</svg>
);
const FileText = ({ className }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14,2 14,8 20,8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10,9 9,9 8,9"></polyline>
</svg>
);
const Grid = ({ className }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
</svg>
);
const Calculator = ({ className }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<rect x="4" y="2" width="16" height="20" rx="2"></rect>
<line x1="8" y1="6" x2="16" y2="6"></line>
<line x1="8" y1="10" x2="16" y2="10"></line>
<line x1="8" y1="14" x2="16" y2="14"></line>
<line x1="8" y1="18" x2="16" y2="18"></line>
</svg>
);
const RefreshCw = ({ className }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<polyline points="23 4 23 10 17 10"></polyline>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
</svg>
);
const Download = ({ className }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
);
// Simple Card components
const Card = ({ children, className = "" }) => (
<div className={`rounded-lg border bg-card text-card-foreground shadow-sm ${className}`}>
{children}
</div>
);
const CardContent = ({ children, className = "" }) => (
<div className={`p-6 pt-0 ${className}`}>
{children}
</div>
);
const CardHeader = ({ children, className = "" }) => (
<div className={`flex flex-col space-y-1.5 p-6 ${className}`}>
{children}
</div>
);
const CardTitle = ({ children, className = "" }) => (
<h3 className={`text-2xl font-semibold leading-none tracking-tight ${className}`}>
{children}
</h3>
);
// Simple Button component
const Button = ({ children, onClick, variant = "default", className = "" }) => {
const baseClasses = "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 px-4 py-2";
const variantClasses = variant === "outline"
? "border border-input bg-background hover:bg-accent hover:text-accent-foreground"
: "bg-primary text-primary-foreground hover:bg-primary/90";
return (
<button
className={`${baseClasses} ${variantClasses} ${className}`}
onClick={onClick}
>
{children}
</button>
);
};
function ArcProjectStarter() {
const [books, setBooks] = useState(1);
const [chapters, setChapters] = useState(10);
const [pagesRange, setPagesRange] = useState([15, 25]);
const [panelsRange, setPanelsRange] = useState([4, 6]);
const [excludePagesMin, setExcludePagesMin] = useState(false);
const [excludePanelsMin, setExcludePanelsMin] = useState(false);
const [context, setContext] = useState("A fantasy adventure manga about a young mage discovering ancient magic");
const [outputTitle, setOutputTitle] = useState("My Manga Project");
const [contextTitle, setContextTitle] = useState("Story Context");
const [copySuccess, setCopySuccess] = useState(false);
// Adjusted ranges based on checkboxes
const effectivePagesRange = excludePagesMin ? [pagesRange[1], pagesRange[1]] : pagesRange;
const effectivePanelsRange = excludePanelsMin ? [panelsRange[1], panelsRange[1]] : panelsRange;
// Calculation for page and panel estimates
const totalPagesMin = books * chapters * effectivePagesRange[0];
const totalPagesMax = books * chapters * effectivePagesRange[1];
const totalPanelsMin = totalPagesMin * effectivePanelsRange[0];
const totalPanelsMax = totalPagesMax * effectivePanelsRange[1];
// Explicit total pages of book range string
const totalPagesOfBookRange = `${totalPagesMin}${totalPagesMin !== totalPagesMax ? ` - ${totalPagesMax}` : ''}`;
const outputPrompt = `${outputTitle ? `# ${outputTitle}` : ''}
${contextTitle ? `## ${contextTitle}` : ''}
"${context}"
## Project Specifications
- **Books:** ${books}
- **Chapters per Book:** ${chapters}
- **Pages per Chapter:** ${effectivePagesRange[0]}${effectivePagesRange[0] !== effectivePagesRange[1] ? ` - ${effectivePagesRange[1]}` : ''}
- **Total Pages Range:** ${totalPagesMin}${totalPagesMin !== totalPagesMax ? ` - ${totalPagesMax}` : ''}
- **Panels per Page:** ${effectivePanelsRange[0]}${effectivePanelsRange[0] !== effectivePanelsRange[1] ? ` - ${effectivePanelsRange[1]}` : ''}
- **Total Panels Range:** ${totalPanelsMin}${totalPanelsMin !== totalPanelsMax ? ` - ${totalPanelsMax}` : ''}
- **Total Pages of Complete Work:** ${totalPagesOfBookRange}`;
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(outputPrompt);
setCopySuccess(true);
setTimeout(() => setCopySuccess(false), 2000);
} catch (err) {
console.error('Failed to copy text: ', err);
}
};
const downloadAsFile = () => {
const blob = new Blob([outputPrompt], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${outputTitle.replace(/\s+/g, '_')}_project_specs.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
const resetForm = () => {
setBooks(1);
setChapters(10);
setPagesRange([15, 25]);
setPanelsRange([4, 6]);
setExcludePagesMin(false);
setExcludePanelsMin(false);
setContext("A fantasy adventure manga about a young mage discovering ancient magic");
setOutputTitle("My Manga Project");
setContextTitle("Story Context");
};
// Statistics cards data
const stats = [
{
label: "Total Pages",
value: totalPagesOfBookRange,
icon: FileText,
color: "text-blue-600",
bgColor: "bg-blue-50"
},
{
label: "Total Panels",
value: `${totalPanelsMin}${totalPanelsMin !== totalPanelsMax ? ` - ${totalPanelsMax}` : ''}`,
icon: Grid,
color: "text-purple-600",
bgColor: "bg-purple-50"
},
{
label: "Avg Pages/Chapter",
value: `${effectivePagesRange[0]}${effectivePagesRange[0] !== effectivePagesRange[1] ? ` - ${effectivePagesRange[1]}` : ''}`,
icon: BookOpen,
color: "text-green-600",
bgColor: "bg-green-50"
}
];
return (
<div className="min-h-screen bg-gradient-to-br from-indigo-100 via-purple-50 to-pink-100 p-6">
<div className="max-w-7xl mx-auto">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-800 mb-2">Arc Project Starter</h1>
<p className="text-gray-600">Plan and calculate your manga or comic project specifications</p>
</div>
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
{stats.map((stat, index) => (
<Card key={index} className={`${stat.bgColor} border-0 shadow-lg`}>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">{stat.label}</p>
<p className={`text-2xl font-bold ${stat.color}`}>{stat.value}</p>
</div>
<stat.icon className={`h-8 w-8 ${stat.color}`} />
</div>
</CardContent>
</Card>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Left Panel - Configuration */}
<Card className="bg-white/80 backdrop-blur-sm shadow-xl border-0">
<CardHeader className="bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-t-lg">
<CardTitle className="flex items-center gap-2">
<Calculator className="h-5 w-5" />
Project Configuration
</CardTitle>
</CardHeader>
<CardContent className="p-6 space-y-6">
{/* Basic Structure */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Books</label>
<input
type="number"
min="1"
max="100"
value={books}
onChange={(e) => setBooks(Math.max(1, parseInt(e.target.value) || 1))}
className="w-full border-2 border-gray-200 rounded-lg p-3 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Chapters per Book</label>
<input
type="number"
min="1"
max="1000"
value={chapters}
onChange={(e) => setChapters(Math.max(1, parseInt(e.target.value) || 1))}
className="w-full border-2 border-gray-200 rounded-lg p-3 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all"
/>
</div>
</div>
{/* Pages Range */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Pages per Chapter Range</label>
<div className="flex gap-3 items-center">
<input
type="number"
min="1"
max="1000"
value={pagesRange[0]}
onChange={(e) => setPagesRange([Math.max(1, parseInt(e.target.value) || 1), Math.max(pagesRange[0], pagesRange[1])])}
className="flex-1 border-2 border-gray-200 rounded-lg p-3 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all"
placeholder="Min"
/>
<span className="text-gray-500 font-medium">to</span>
<input
type="number"
min={pagesRange[0]}
max="1000"
value={pagesRange[1]}
onChange={(e) => setPagesRange([pagesRange[0], Math.max(pagesRange[0], parseInt(e.target.value) || pagesRange[0])])}
className="flex-1 border-2 border-gray-200 rounded-lg p-3 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all"
placeholder="Max"
/>
</div>
<label className="flex items-center mt-2 text-sm">
<input
type="checkbox"
checked={excludePagesMin}
onChange={(e) => setExcludePagesMin(e.target.checked)}
className="mr-2 w-4 h-4 text-blue-600"
/>
<span className="text-gray-600">Use only maximum value</span>
</label>
</div>
{/* Panels Range */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Panels per Page Range</label>
<div className="flex gap-3 items-center">
<input
type="number"
min="1"
max="50"
value={panelsRange[0]}
onChange={(e) => setPanelsRange([Math.max(1, parseInt(e.target.value) || 1), Math.max(panelsRange[0], panelsRange[1])])}
className="flex-1 border-2 border-gray-200 rounded-lg p-3 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all"
placeholder="Min"
/>
<span className="text-gray-500 font-medium">to</span>
<input
type="number"
min={panelsRange[0]}
max="50"
value={panelsRange[1]}
onChange={(e) => setPanelsRange([panelsRange[0], Math.max(panelsRange[0], parseInt(e.target.value) || panelsRange[0])])}
className="flex-1 border-2 border-gray-200 rounded-lg p-3 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all"
placeholder="Max"
/>
</div>
<label className="flex items-center mt-2 text-sm">
<input
type="checkbox"
checked={excludePanelsMin}
onChange={(e) => setExcludePanelsMin(e.target.checked)}
className="mr-2 w-4 h-4 text-blue-600"
/>
<span className="text-gray-600">Use only maximum value</span>
</label>
</div>
{/* Reset Button */}
<Button
onClick={resetForm}
variant="outline"
className="w-full border-2 border-gray-300 hover:bg-gray-50"
>
<RefreshCw className="h-4 w-4 mr-2" />
Reset to Defaults
</Button>
</CardContent>
</Card>
{/* Right Panel - Output */}
<Card className="bg-white/80 backdrop-blur-sm shadow-xl border-0">
<CardHeader className="bg-gradient-to-r from-green-500 to-teal-600 text-white rounded-t-lg">
<CardTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" />
Project Output
</CardTitle>
</CardHeader>
<CardContent className="p-6 space-y-6">
{/* Title Inputs */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Project Title</label>
<input
type="text"
value={outputTitle}
onChange={(e) => setOutputTitle(e.target.value)}
className="w-full border-2 border-gray-200 rounded-lg p-3 focus:border-green-500 focus:ring-2 focus:ring-green-200 transition-all"
placeholder="Enter project title..."
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Context Section Title</label>
<input
type="text"
value={contextTitle}
onChange={(e) => setContextTitle(e.target.value)}
className="w-full border-2 border-gray-200 rounded-lg p-3 focus:border-green-500 focus:ring-2 focus:ring-green-200 transition-all"
placeholder="Enter context section title..."
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Project Context</label>
<textarea
value={context}
onChange={(e) => setContext(e.target.value)}
className="w-full border-2 border-gray-200 rounded-lg p-3 focus:border-green-500 focus:ring-2 focus:ring-green-200 transition-all resize-none"
rows={4}
placeholder="Describe your project context, story, theme..."
/>
</div>
{/* Generated Output */}
<div className="bg-gradient-to-r from-gray-50 to-gray-100 rounded-xl p-4 border-2 border-gray-200">
<h3 className="text-lg font-semibold mb-3 text-gray-800">Generated Project Specification</h3>
<pre className="whitespace-pre-wrap text-sm bg-white p-4 rounded-lg border shadow-inner font-mono text-gray-700 max-h-64 overflow-y-auto">
{outputPrompt}
</pre>
{/* Action Buttons */}
<div className="flex gap-3 mt-4">
<Button
onClick={copyToClipboard}
className={`flex-1 transition-all ${copySuccess ? 'bg-green-600 hover:bg-green-700' : 'bg-blue-600 hover:bg-blue-700'} text-white`}
>
<Copy className="h-4 w-4 mr-2" />
{copySuccess ? 'Copied!' : 'Copy to Clipboard'}
</Button>
<Button
onClick={downloadAsFile}
variant="outline"
className="border-2 border-gray-300 hover:bg-gray-50"
>
<Download className="h-4 w-4 mr-2" />
Download
</Button>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
);
}
ReactDOM.render(<ArcProjectStarter />, document.getElementById('root'));
</script>
</body>
</html>