Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/brown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/paper.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/app/generate/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export default function GeneratePage() {

{/* Results Section */}
{recipes.length > 0 && (
<div>
<div className="pb-16">
<h2 className="text-2xl mb-4 minecraft-text">- Your Recipes -</h2>
<div className="grid gap-6">
{recipes.map((recipe) => (
Expand Down
40 changes: 32 additions & 8 deletions src/components/InstructionsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ interface InstructionsSectionProps {
additionalButtons?: ReactNode;
}

// Step number badge component
function StepNumber({ number }: { number: number }) {
return (
<span
className="inline-flex items-center justify-center w-6 h-6 text-xs border border-amber-800/40 flex-shrink-0"
style={{ backgroundColor: "#c9b896" }}
>
{number}
</span>
);
}

export default function InstructionsSection({
recipeName,
ingredients,
Expand Down Expand Up @@ -56,15 +68,17 @@ export default function InstructionsSection({

return (
<div>
<h4 className="text-lg mb-2">- Instructions -</h4>
<ol className="list-decimal list-inside text-sm space-y-1">
<h4 className="text-lg mb-3 tracking-wider">- Instructions -</h4>
<ol className="text-sm space-y-2.5">
{(detailedInstructions || instructions).map((step, index) => (
<li key={index}>{step.replace(/^step\s*\d+[:\.\-]\s*/i, "")}</li>
<li key={index} className="flex items-start gap-3">
<StepNumber number={index + 1} />
<span className="pt-0.5">{step.replace(/^step\s*\d+[:\.\-]\s*/i, "")}</span>
</li>
))}
</ol>

{/* Action Buttons Row */}
<div className="mt-4 flex flex-wrap gap-3 items-center">
<div className="mt-6 flex flex-wrap gap-3 items-center">
{/* Detailed Instructions Button */}
{!detailedInstructions && (
<button
Expand All @@ -73,23 +87,33 @@ export default function InstructionsSection({
handleExpandInstructions();
}}
disabled={isLoadingDetails}
className="minecraft-text bg-black/30 px-4 py-2 border-2 border-black/50 text-sm hover:bg-black/50 transition-colors disabled:opacity-50 hover:cursor-pointer"
className="minecraft-text px-4 sm:px-5 py-2 sm:py-2.5 border-2 border-amber-900/60 text-xs sm:text-sm transition-all duration-150 hover:brightness-110 hover:scale-[1.02] disabled:opacity-50 disabled:hover:scale-100 hover:cursor-pointer"
style={{ backgroundColor: "#5c3d2e", color: "#e8dcc8", textShadow: "none" }}
>
{isLoadingDetails ? "Loading..." : "Detailed Instructions"}
</button>
)}

{/* Show label when detailed instructions are displayed */}
{detailedInstructions && (
<p className="text-xs opacity-60">✓ Showing detailed instructions</p>
<p className="text-xs opacity-60 flex items-center gap-1.5">
<span className="text-amber-700">✓</span> Showing detailed instructions
</p>
)}

{/* Additional buttons passed from parent */}
{additionalButtons}
</div>

{/* Error message */}
{detailsError && <p className="mt-2 text-sm text-red-400">{detailsError}</p>}
{detailsError && (
<p
className="mt-3 text-sm px-3 py-2 border border-red-800/30"
style={{ backgroundColor: "#d4a8a8", color: "#5c1c1c" }}
>
{detailsError}
</p>
)}
</div>
);
}
176 changes: 131 additions & 45 deletions src/components/RecipeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,39 @@ interface RecipeCardProps {
recipe: Recipe;
}

// Reusable badge component with hover effect
function Badge({
children,
variant = "time",
}: {
children: React.ReactNode;
variant?: "time" | "macro";
}) {
const bgColor = variant === "time" ? "#d4c4a8" : "#c9b896";
const borderColor = variant === "time" ? "border-amber-800/50" : "border-amber-900/40";

return (
<span
className={`px-3 py-1 border-2 ${borderColor} transition-all duration-150 hover:brightness-105 hover:scale-[1.02]`}
style={{ backgroundColor: bgColor }}
>
{children}
</span>
);
}

// Section divider component
function SectionDivider() {
return (
<div
className="my-6 h-[2px] opacity-30"
style={{
background: "linear-gradient(to right, transparent, #8b6914, #8b6914, transparent)",
}}
/>
);
}

export default function RecipeCard({ recipe }: RecipeCardProps) {
const [isOpen, setIsOpen] = useState(false);
const [copied, setCopied] = useState(false);
Expand Down Expand Up @@ -66,9 +99,32 @@ ${recipe.instructions.map((step, i) => `${i + 1}. ${step}`).join("\n")}
}
};

// Minecraft book style - parchment with lined paper effect
const bookStyle = isOpen
? {
background: `
repeating-linear-gradient(
transparent,
transparent 31px,
rgba(139, 105, 20, 0.15) 31px,
rgba(139, 105, 20, 0.15) 32px
),
linear-gradient(to right, #8b5a2b 0px, #8b5a2b 8px, transparent 8px),
linear-gradient(to bottom, #e8dcc4, #d4c4a8)
`,
boxShadow: "inset 4px 0 8px rgba(0,0,0,0.2), 0 4px 12px rgba(0,0,0,0.3)",
color: "#3d2b1f",
textShadow: "none",
borderColor: "#5c3d2e",
}
: undefined;

return (
<div
className="feature-card p-6 minecraft-text cursor-pointer"
className={`minecraft-text cursor-pointer transition-all duration-200 ${
isOpen ? "border-2 p-4 sm:p-10" : "feature-card p-4 sm:p-6"
}`}
style={isOpen ? bookStyle : { minHeight: "auto" }}
onClick={() => {
if (isOpen) {
playPageBack();
Expand All @@ -78,66 +134,86 @@ ${recipe.instructions.map((step, i) => `${i + 1}. ${step}`).join("\n")}
setIsOpen(!isOpen);
}}
>
{/* Always visible */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className={isOpen ? "text-2xl" : "text-xl"}>{isOpen ? "▼" : "▶"}</span>
<h3 className="text-xl">{recipe.name}</h3>
</div>
<button
onClick={(e) => {
e.stopPropagation();
playClick();
handleCopy();
}}
className="minecraft-text bg-black/30 px-3 py-1 border-2 border-black/50 text-sm hover:bg-black/50 transition-colors"
{/* Header - Always visible */}
<div className={`flex items-start gap-3 ${isOpen ? "pl-2 sm:pl-4" : ""}`}>
<span
className={`transition-transform duration-200 flex-shrink-0 mt-0.5 ${isOpen ? "text-xl sm:text-2xl" : "text-base sm:text-xl"}`}
>
{copied ? "Copied!" : "Copy"}
</button>
{isOpen ? "▼" : "▶"}
</span>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<h3
className={`font-bold tracking-wide ${isOpen ? "text-lg sm:text-2xl" : "text-base sm:text-xl"}`}
>
{recipe.name}
</h3>
<button
onClick={(e) => {
e.stopPropagation();
playClick();
handleCopy();
}}
className={`minecraft-text px-2 sm:px-4 py-1 sm:py-1.5 border-2 text-xs sm:text-sm transition-all duration-150 flex-shrink-0 ${
isOpen
? "border-amber-900/60 hover:brightness-110 hover:scale-[1.02]"
: "bg-black/30 border-black/50 hover:bg-black/50 hover:scale-[1.02]"
}`}
style={
isOpen
? { backgroundColor: "#5c3d2e", color: "#e8dcc8", textShadow: "none" }
: undefined
}
>
{copied ? "Copied!" : "Copy"}
</button>
</div>
</div>
</div>

{/* Only visible when expanded */}
{/* Expanded content - extra left padding for book binding */}
{isOpen && (
<>
<p className="opacity-80 mb-5 mt-4">{recipe.description}</p>
<div className="pl-2 sm:pl-4">
<p className="opacity-80 mt-4 mb-6 text-base leading-relaxed">{recipe.description}</p>

{/* Time Info */}
<div className="flex flex-wrap gap-4 mb-4 text-sm">
<span className="bg-black/30 px-3 py-1 border-2 border-black/50">
Prep: {recipe.prepTime} min
</span>
<span className="bg-black/30 px-3 py-1 border-2 border-black/50">
Cook: {recipe.cookTime} min
</span>
<span className="bg-black/30 px-3 py-1 border-2 border-black/50">
Total: {recipe.prepTime + recipe.cookTime} min
</span>
<div className="flex flex-wrap gap-2 sm:gap-3 mb-3 text-sm">
<Badge variant="time">Prep: {recipe.prepTime} min</Badge>
<Badge variant="time">Cook: {recipe.cookTime} min</Badge>
<Badge variant="time">Total: {recipe.prepTime + recipe.cookTime} min</Badge>
</div>

{/* Macros */}
<div className="flex flex-wrap gap-4 mb-4 text-sm">
{/* Macros - slightly different tint */}
<div className="flex flex-wrap gap-2 sm:gap-3 text-sm">
{[
{ label: "cal", value: recipe.macros.calories },
{ label: "g protein", value: recipe.macros.protein },
{ label: "g carbs", value: recipe.macros.carbs },
{ label: "g fat", value: recipe.macros.fat },
].map((macro) => (
<span key={macro.label} className="bg-black/30 px-3 py-1 border-2 border-black/50">
<Badge key={macro.label} variant="macro">
{macro.value} {macro.label}
</span>
</Badge>
))}
</div>

<SectionDivider />

{/* Ingredients */}
<div className="mb-4">
<h4 className="text-lg mb-2">- Ingredients -</h4>
<ul className="list-disc list-inside text-sm">
<div className="mb-6">
<h4 className="text-lg mb-3 tracking-wider">- Ingredients -</h4>
<ul className="text-sm space-y-1.5">
{recipe.ingredients.map((ingredient, index) => (
<li key={index}>{ingredient}</li>
<li key={index} className="flex items-start gap-2">
<span className="text-amber-800 mt-0.5">◆</span>
<span>{ingredient}</span>
</li>
))}
</ul>
</div>

<SectionDivider />

{/* Instructions */}
<InstructionsSection
recipeName={recipe.name}
Expand All @@ -152,7 +228,8 @@ ${recipe.instructions.map((step, i) => `${i + 1}. ${step}`).join("\n")}
playClick();
setShowCostBreakdown(!showCostBreakdown);
}}
className="minecraft-text bg-black/30 px-4 py-2 border-2 border-black/50 text-sm hover:bg-black/50 transition-colors hover:cursor-pointer"
className="minecraft-text px-4 py-2 border-2 border-amber-800/50 text-sm transition-all duration-150 hover:brightness-105 hover:scale-[1.02] hover:cursor-pointer"
style={{ backgroundColor: "#d4c4a8" }}
>
{showCostBreakdown ? "Hide Cost Breakdown" : "Cost Breakdown"}
</button>
Expand All @@ -161,19 +238,28 @@ ${recipe.instructions.map((step, i) => `${i + 1}. ${step}`).join("\n")}

{/* Cost Breakdown Content */}
{showCostBreakdown && (
<div className="mt-3 flex flex-wrap gap-4 text-sm">
<span className="bg-black/30 px-3 py-1 border-2 border-black/50">
🛒 Grocery Cost: ~${recipe.estimatedCost.toFixed(2)}
<div className="mt-4 flex flex-wrap gap-3 text-sm">
<span
className="px-3 py-1.5 border-2 border-amber-800/40"
style={{ backgroundColor: "#c9b896" }}
>
🛒 Grocery: ~${recipe.estimatedCost.toFixed(2)}
</span>
<span className="bg-black/30 px-3 py-1 border-2 border-black/50">
🍔 Takeout Equivalent: ~${recipe.takeoutEquivalent.toFixed(2)}
<span
className="px-3 py-1.5 border-2 border-amber-800/40"
style={{ backgroundColor: "#c9b896" }}
>
🍔 Takeout: ~${recipe.takeoutEquivalent.toFixed(2)}
</span>
<span className="bg-green-900/50 px-3 py-1 border-2 border-green-700/50">
<span
className="px-3 py-1.5 border-2 border-green-800/50"
style={{ backgroundColor: "#a8c9a8" }}
>
✅ You Save: ${(recipe.takeoutEquivalent - recipe.estimatedCost).toFixed(2)}
</span>
</div>
)}
</>
</div>
)}
</div>
);
Expand Down