Laborator de cofetărie · Tulcea
Prăjiturile de acasă, așa cum ți le amintești.
Torturi, prăjituri, cheesecake și patiserie făcute în laboratorul nostru din Tulcea — cu răbdare, cu pricepere și cu gustul copilăriei.
poftești la ele!
- Torturi
- Prăjituri
- Cheesecake
- Patiserie
- Plăcinte
- Tarte
mișcă mouse-ul ca să rotești tortulderulează ca să rotești tortul
How this hero was made
A single product photo from laprajituri.ro became a cake you can turn with the mouse (desktop) or by scrolling (phone). Everything below is reproducible; the sources are in the Drive folder laprajituri-prototip/_video/. Nothing here uses GSAP — the page is plain canvas + requestAnimationFrame so it drops into any Bricks site.
1. A clean cake, not a cutout
Background removal (Higgsfield and local rembg) left a ragged base and lost the cake board. What worked was regenerating the cake: GPT Image 2.5 with background: transparent, quality high, resolution 2k, aspect 4:5, the original photo as image_references, and a prompt asking to reproduce the cake identically and rebuild the cut-off base and the gold board. 2.75 credits per variant. Alpha below 10 was zeroed to kill the faint halo.
2. The partial-arc video
The transparent cake was composited on flat purple #540074 at 1080×1080 (cake 64% of the height, centred slightly low) and used as start_image for Kling 3.0 std, 10 s, 1:1, sound off, 15 credits. Prompt gist: slow, perfectly smooth quarter-turn from about 45° left to 45° right, unchanging flat purple background, cake always in frame, soft contact shadow, no zoom, no cuts. Output: 960×960, 24 fps, 241 frames, background #53026f. Seedance 2.5 and Kling pro/4k are gated behind the Plus plan — get_cost does not tell you, only the start call fails (nothing is charged).
3. Frames that rotate evenly
The generated motion was slow for the first ~100 frames and steady after, so picking every n-th frame would have felt uneven. Instead: extract all frames (ffmpeg → png); background colour = median of the corner pixels; union bounding box of non-background pixels over all frames → crop a square at 1.16× that box (the cake then fills ~86% of the disc); compute per-frame motion with Farneback optical flow on the cake box (90th percentile of |dx|), cumulate it and pick frames at equal cumulative motion. Two sets: 160 frames at 820 px WebP q74 (4.7 MB, desktop) and 80 frames at 560 px q72 (1.4 MB, phones).
4. Drawing without stutter
Every trick here was measured, not guessed (headless Chromium, DPR 2, full sweep of the pointer):
• Frames are decoded once into ImageBitmaps — drawing from <img> re-decodes the WebP on every frame and stutters.
• Each bitmap is pre-scaled once to the exact canvas size (createImageBitmap(bm, {resizeWidth, resizeHeight})) so drawImage is a 1:1 blit. Scaling lazily during the sweep gave 50 ms spikes.
• Sub-frame cross-fade: frame ⌊p⌋ at alpha 1, frame ⌊p⌋+1 at alpha = fraction. Kills the tick between frames on slow moves; no visible ghosting at 0.44° per frame.
• A lerp (0.09 per tick) between the target frame and the drawn frame gives inertia.
• Progressive loading in strides 16/8/4/2/1 with a nearest-loaded fallback; interaction is gated until one frame in four is in, because the fallback jumps in 16-frame steps before that.
• Context {alpha:false}, canvas on its own layer (will-change: transform; transform: translateZ(0)), and the disc gets isolation: isolate + -webkit-mask-image — Safari composites overflow: hidden + border-radius badly around a repainting child.
Result at DPR 2: 16.6 ms average, 20.7 ms worst frame, no frame over 25 ms (before: 20.3 / 49.7 / 18 slow frames).
5. Two input modes
Desktop (> 991 px): pointer X across the viewport maps to the whole arc; leaving the window eases the cake back to the front view; arrow keys step 4 frames. Tablet and phone (≤ 991 px): scroll drives it — progress = scrollY / (stage top + stage height), clamped to the page’s maximum scroll on short pages — so the cake turns as the hero leaves the screen, with no gesture to discover. Phones (≤ 767 px) get the light frame set. prefers-reduced-motion shows the first frame only.
6. Layout
The disc is the video’s window: it is painted in the video’s own background colour (#53026f), so frames and disc have no seam; the pink ring and the halo sit on top as CSS. The first frame is an <img> poster under the canvas, hidden once the canvas has drawn. Two columns on desktop, stage first on small screens. Palette: purple #540074, coral #e04552, pink #f8a5a7, cream #fbf6f1; type: Fraunces (headings, slogan italic) + system sans for UI text.
7. Reusing it on another page
One Bricks code element (signed, noRoot) holds the stage markup, its CSS and the JS. To move it: upload the two frame sets to wp-content/uploads/tort-360/, change BASE, and keep N/FW/PREFIX in sync with the sets you uploaded. On the La Prăjituri staging site the same stage lives inside a native two-column hero (the page’s own heading, text and button stay native and editable); only the disc is code. If the section loses its side gutters, the element-level padding overrode the theme’s — include var(--lp-gutter) left and right.
8. Traps
• Kling keeps the aspect of the start image regardless of aspect_ratio — compose the start image in the aspect you want.
• Bricks: update_post_meta unslashes — always wp_slash(), then read back and compare md5. Code elements need signature = wp_hash(code).
• The host’s WAF returns 403 for a PUT body that looks like PHP/JS — base64 it, decode and md5-check server-side.
• Neither pinul.com server has ffmpeg; extract frames elsewhere.
• Higgsfield’s Virality Predictor on the raw clip: overall 55, hook 38, sustain 100 — a product turntable, not a social clip.