Florist website
A storefront for an independent flower shop that makes each bouquet feel special, then sends people to order through a delivery app or WhatsApp.
- Role
- Design and front-end development
- Status
- Live
- Category
- Flower shop storefront
- Place and year
- Morocco, 2026
- Stack
- HTML, CSS, JavaScript, Vite, Python
- Links
- The shop’s name, logo and address are left out to protect the client’s privacy, so the live site isn’t linked. The source code isn’t public.








PageSpeed Insights, desktop
The story
The challenge
The shop takes orders through a delivery app in its own city and on WhatsApp across Morocco. It needed a website that presents the bouquets with the same care that goes into them, then hands every visitor to the right place to order.
The approach
A soft, editorial storefront built around the shop’s own photography, in semantic HTML, CSS and a little JavaScript. A Python pipeline cuts out each bouquet for layered compositions, and the parallax runs on CSS scroll-driven animations instead of a JavaScript loop.
The result
The site is live. PageSpeed Insights scores it 100 for performance, accessibility, best practices and SEO on desktop, and 97 for performance on mobile.
What I built
- A creations gallery filtered by occasion, with a lightbox and prices that mirror the shop’s delivery-app menu.
- Ordering that routes to the delivery app locally and to WhatsApp or phone elsewhere in Morocco.
- Scroll-driven parallax in CSS, with a still composition where browsers don’t support it.
- Headlines revealed line by line, shown at once when reduced motion is on.
- A Python pipeline that turns the original photography into WebP and cuts out bouquets.
- Canonical URLs, Open Graph, JSON-LD, robots.txt, sitemap.xml and llms.txt, generated at build.
Under the hood
real excerpts from the codebaseParallax without a JavaScript loop
Every drifting element shares one keyframe and reads the scroll position through animation-timeline. Browsers without support, and visitors who prefer reduced motion, get the still layout.
src/style.css
@media (prefers-reduced-motion: no-preference) {
@supports (animation-timeline: view()) {
[data-parallax] img,
.section-index,
.reviews__ring,
.why__glow,
.why::before,
.botanical--order {
animation: drift linear both;
animation-timeline: view();
}Cutting out the bouquets
rembg removes the background, a repair pass restores ribbons it loses against dark clothing, and each cut-out is cropped and saved at several widths.
scripts/cut_bouquets.py
def main() -> None:
session = new_session("isnet-general-use")
for source, name, widths, erase in CUTS:
photo = Image.open(SRC / source).convert("RGB")
cut = remove(photo, session=session)
if name in RESTORE:
cut = restore(photo, cut, RESTORE[name])
cut = clean(cut, erase)
cut = cut.crop(cut.getchannel("A").getbbox())Decisions
No framework
A one-page storefront doesn’t need one. Semantic HTML, CSS and a little JavaScript, with self-hosted Cormorant Garamond and Manrope.
One source for prices
Prices come from the shop’s delivery-app menu and live in the markup as data attributes, so keeping them in sync is a single edit.
Brand marks left intact
The delivery app and WhatsApp logos follow each company’s rules, and the word WhatsApp stays written next to its glyph.
Results
- Performance, desktop
- 100
- Performance, mobile
- 97
- Accessibility
- 100
- Best practices
- 100
- SEO
- 100
- LCP, desktop
- 0.6s
- CLS
- 0
PageSpeed Insights on the live site, 1 November 2026. Also checked in Chromium, Firefox and WebKit.






Status
The shop’s name, logo, city, phone number and social accounts are left out of this case study. The screenshots replace them with a neutral wordmark and masked details.