HTML version //App.jsx import { useState } from 'react' import './App.css' const ingredients = [ { text: '12 ounces burrata', defaultChecked: true }, { text: '2 cups cherry tomatoes', defaultChecked: false }, { text: '1 Tbsp olive oil', defaultChecked: false }, { text: '1 Tbsp balsamic vinegar', defaultChecked: false }, { text: '2 tsp honey', defaultChecked: false }, { text: '1 clove garlic', defaultChecked: false }, { text: '½ tsp dried basil', defaultChecked: false }, { text: '1 tsp chopped fresh thyme', defaultChecked: false }, { text: '¼ tsp dried oregano', defaultChecked: false }, { text: 'salt & pepper', defaultChecked: false }, { text: 'chili flakes', defaultChecked: false }, { text: '1 baguette', defaultChecked: false }, ] const directions = [ 'Preheat oven to 375°F.', 'Slice tomatoes and toss in oil, balsamic vinegar, honey, garlic, basil, thyme, and oregano. Season with salt and pepper.', 'Slice baguette and drizzle with olive oil.', 'Toast up bread in a hot skillet until golden brown.', 'Spread on fresh burrata cheese.', 'Top with marinated tomato slices. Then bake for 12-15 minutes.', 'Top with parmesan, warm marinara sauce, oregano, and chili flakes.', ] export default function App() { const [isOpen, setIsOpen] = useState(false) const [checkedIngredients, setCheckedIngredients] = useState( ingredients.map((ing) => ing.defaultChecked) ) const [checkedDirections, setCheckedDirections] = useState( Array(directions.length).fill(false) ) const toggleIngredient = (i) => { setCheckedIngredients((prev) => prev.map((v, idx) => (idx === i ? !v : v))) } const toggleDirection = (i) => { setCheckedDirections((prev) => prev.map((v, idx) => (idx === i ? !v : v))) } return ( <div className="absolute top-0 left-0 w-full z-50 h-screen flex justify-center items-center p-4 md:p-6 lg:p-8"> <div className="max-w-lg bg-gradient-to-b from-white/60 to-white/30 text-[#1c0708] backdrop-blur-[.65em] border-[1px] border-solid border-white border-opacity-10 rounded-2xl shadow-black/50 overflow-hidden shadow-2xl hover:-translate-y-1 hover:shadow-black/75 hover:backdrop-blur-[1em] transition"> <div className="grid place-items-center grid-cols-6 gap-2"> <div className="col-span-3 p-8"> <h2 className="font-bold text-2xl"> Burrata & Balsamic Roasted Tomato Crostini </h2> <div className="tag-lines"> <span className="tag">Dairy</span>{' '} <span className="tag">Vegetarian</span>{' '} <span className="tag">Mediterranean</span> </div> <p className="text-sm text-[#1c0708]/60"> Oven-roasted tomatoes <br /> and herbs paired with smooth burrata cheese, served on baked bread. </p> <div className={`panel-heading${isOpen ? ' active' : ''}`} role="tab"> <h4 className="panel-title"> <a href="#" onClick={(e) => { e.preventDefault(); setIsOpen(!isOpen) }} className={`mt-6 py-3 px-5 inline-flex bg-[#e84118] hover:bg-[#c23616] transition-colors text-gray-200 font-bold rounded-full text-sm${isOpen ? '' : ' collapsed'}`} role="button" > See Recipe </a> </h4> </div> </div> <div className="col-span-3 -mt-5 -mb-10"> <img alt="" className="recipe_img" src="https://assets.codepen.io/4927073/tomato_board2.png" /> </div> </div> <div className={`panel-collapse${isOpen ? ' open' : ''}`}> <div className="panel-body"> <div className="panel-top"> <div className="level-item"> <div> <div className="heading">Total Servings: 2</div> <div className="heading">Prep Time: 5 min</div> </div> </div> </div> <div className="ingredients"> <h3 className="ingredients">Ingredients:</h3> <ul className="ingredients"> {ingredients.map((ing, i) => ( <li key={i}> <label> <input type="checkbox" checked={checkedIngredients[i]} onChange={() => toggleIngredient(i)} />{' '} <span>{ing.text}</span> </label> </li> ))} </ul> </div> <div className="directions"> <h3 className="directions">Directions:</h3> <ol className="directions"> {directions.map((step, i) => ( <li key={i}> <label> <input type="checkbox" checked={checkedDirections[i]} onChange={() => toggleDirection(i)} />{' '} <span>{step}</span> </label> </li> ))} </ol> </div> </div> </div> </div> </div> ) } #root { width: 100%; height: 100%; } body { background: url(https://assets.codepen.io/4927073/AdobeStock_78589840.jpeg) center/cover no-repeat fixed; font: 1em/1.618 Inter, sans-serif; } h2 { font-family: 'Playfair Display', serif; font-size: 36px !important; font-weight: 100 !important; padding: 0 0 5px 0; width: 160%; text-shadow: -0.5px -0.5px 0.5px rgb(0 0 0 / 30%), 1px 1px 2px rgb(212 212 212 / 30%); mix-blend-mode: hard-light; } .tag-lines { position: relative; margin: 5px 0 7px 2px; gap: 5px; width: 120%; } span.tag { align-items: center; background-color: hsl(0deg 0% 96% / 85%); border-radius: 5px; color: #4a4a4a; display: inline-flex; font-size: 11px; height: 20px; justify-content: center; line-height: 1.5; padding-left: 6px; padding-right: 6px; white-space: nowrap; font-family: 'Roboto Condensed', sans-serif; margin-right: 2px; text-shadow: 0.5px 0.5px 0.5px rgb(255 255 255 / 50%); } p.text-sm { filter: drop-shadow(0.25px 0.25px 0.25px rgb(25 25 25 / 65%)) contrast(4.25) saturate(3.5) opacity(0.65); text-shadow: 0.5px 0.5px 0.5px hsl(0deg 0% 95% / 35%), -0.25px -0.25px 0.25px hsl(0deg 0% 50% / 15%) !important; font-weight: 300; color: rgb(28 7 8 / 0.6); font-size: 16px; font-family: Roboto; mix-blend-mode: darken; letter-spacing: 0.125px; width: 125%; } .panel-title { margin-top: 0; margin-bottom: 0; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; font-family: "Roboto", sans-serif; color: white; } .panel-title > a, .panel-title > a:active { z-index: 5; position: relative; display: block; text-decoration: none; color: #fdfdfd; border: 1px solid rgba(0, 0, 0, 0.25); box-shadow: inset 0px 1px 0px 0px rgba(255, 255, 255, 0.35); } .panel-heading a:before { font-family: "Ionicons"; content: "\f123"; float: right; transition: all 0.5s; } .panel-title a:before { -webkit-transform: rotate(180deg); -moz-transform: rotate(180deg); transform: rotate(180deg); } .panel-title a.collapsed:before { -webkit-transform: rotate(0); -moz-transform: rotate(0); transform: rotate(0); } .panel-collapse { overflow: hidden; max-height: 0; transition: max-height 0.4s ease; } .panel-collapse.open { max-height: 900px; } .panel-body { padding: 20px 30px 30px 30px; position: relative; background-color: hsl(0deg 0% 98% / 75%); display: flex; flex-direction: column; align-items: center; justify-content: center; box-shadow: 0px 0.5vh 5px 0px rgba(0, 0, 0, 0.25); } ul.ingredients { display: grid; grid-template-columns: 2fr 2fr 2fr; font-size: 13px; width: 100%; padding: 0 !important; margin: auto; gap: 0px 5px; top: -10px; position: relative; line-height: 1.5; text-wrap: nowrap; text-overflow: ellipsis; grid-template-rows: repeat(5, 2fr); grid-auto-flow: column; } li:nth-child(4), li:nth-child(8) { grid-column: span 1; grid-row: span 2; text-wrap: wrap; } ul.ingredients li { &:hover { color: #3298dc; } } ul.ingredients li input { -webkit-appearance: none; -moz-appearance: none; appearance: none; &::before { color: #666; font-family: "Font Awesome 5 Free"; font-weight: 900; content: "\f0c8"; margin-right: 0.25rem; } &:checked { ~ span { font-style: italic; color: #666; text-decoration: line-through; } &::before { content: "\f14a"; } } } .panel-body h3.ingredients { width: 100%; margin: auto; padding: 0 0 4px 2px; top: -12px; position: relative; font-family: 'Inter', sans-serif; font-weight: 500; letter-spacing: 0.4px; font-size: 17px; } .panel-top { width: 100%; padding: 0; top: -10px; position: relative; margin-bottom: -28px; right: -15px; text-align: justify; } .level-item { right: 0; position: relative; display: flex; justify-content: flex-end; text-align: right; } .heading { display: block; font-size: 10px; letter-spacing: 1px; margin-bottom: 2px; text-transform: uppercase; line-height: 1.35; } .directions { width: 100%; } .panel-body h3.directions { width: 100%; margin: auto; padding: 0 0 0px 2px; position: relative; font-family: 'Inter', sans-serif; font-weight: 500; letter-spacing: 0.4px; font-size: 17px; top: -2px; } ol.directions { list-style: auto; list-style-position: outside; margin-left: 32px; font-size: 13px; line-height: 1.45; } ol li { counter-increment: cupcake; } ol li::marker { content: none; } ol li::before { content: counter(cupcake); width: 13px; height: 13px; display: inline-flex; align-items: center; justify-content: center; line-height: 1; margin-left: -23px; position: relative; left: -5.5px; top: -2px; z-index: 1; font-size: 10px; font-weight: 500; font-family: Roboto; color: #30363d; text-shadow: 0.5px 0.5px 0.5px rgb(255 255 255 / 75%); } ol.directions li input { -webkit-appearance: none; -moz-appearance: none; appearance: none; &::before { content: ""; margin-right: 0.25rem; box-shadow: #0004 0 1px 2px 0, #0003 0 0.5px 2px 1px, 1px 1px 10px 2px #00000026; background-color: #fff; background-image: linear-gradient( 180deg, #ffffff 10%, hsl(216, 4%, 85%) 60%, 80%, hsl(216, 4%, 70%) ); z-index: 0 !important; width: 13px; height: 13px; display: inline-flex; margin-left: -16px; position: relative; left: -3px; top: 1px; border-radius: 50%; } &:checked { ~ span { font-style: italic; color: #666; text-decoration: line-through; } &::before { content: ""; box-shadow: hsl(0, 0%, 65%) 0 1px 1px 1px, 0 -1px 2px 1px #fff; background: linear-gradient( 0deg, hsl(0, 0%, 90%) 5%, hsl(0, 0%, 95%) 15%, hsl(0, 0%, 100%) 40%, hsl(0, 0%, 95%) 55%, hsl(0, 0%, 70%) 90% ); outline: none; } } } img.recipe_img { content: url(https://assets.codepen.io/4927073/tomato_board2.png); object-fit: cover; width: 350px !important; height: auto; position: relative; max-width: fit-content; z-index: -1; transform: rotate(6deg); top: 2px; right: 18px; filter: drop-shadow(4.5px 4.5px 6px var(--dcolor)) saturate(1.3) opacity(0.95) contrast(0.9) brightness(0.95); --dcolor: #332315; } # Recipe Card — Burrata & Balsamic Roasted Tomato Crostini Glassmorphic recipe card with accordion expand and interactive checkboxes. ## Files - `App.jsx` — component - `App.css` — styles ## Setup 1. Copy `App.jsx` and `App.css` into your project 2. Add to `index.html` before `</head>`: ```html <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"> <link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"> <script src="https://cdn.tailwindcss.com"></script> ``` 3. Requires React 18 + Vite 5 ## Notes - Fonts (Playfair Display, Inter, Roboto, Roboto Condensed) are loaded from the system — no separate import needed - Accordion is powered by `useState` + CSS `max-height` transition (no Bootstrap) - Checkboxes are controlled inputs; icons use Font Awesome 6 solid set (`font-weight: 900`) - Page background is set on `body`; requires `#root { width: 100%; height: 100%; }` to fill the viewport Навигация по записям Image Carousel React.js