Iurii RoguliaIurii Rogulia
AboutServicesPricingProjectsStackReviewsPhrasesBlog
Contact
Iurii ships.

Iurii Rogulia, senior full-stack software engineer. Professionally building software since 2001.

Think of a number
PricingQuality checklistPrivacy PolicyCookie Policy

Business

TMI Iurii Rogulia
VAT ID: FI29845875
DUNS: 368664211
Lappeenranta, Finland 🇫🇮

[email protected]
Back to projects

Iurii Visualizes: pi-pi.ee – Live Custom-Colour Preview

July 28, 2026

Recolour a product photo to any colour in the browser, instantly – an SVG duotone filter tints transparent master images on the fly, so a shop can offer made-to-order colours without pre-rendering a single variant.

Live Demo

Stack

Next.jsReactTypeScriptTailwind CSS

Topics

E-commerceSVGCSSPerformanceUX

Key Results

  • Shoppers see the product in their chosen colour instantly – recolouring happens in the browser with zero network requests
  • Made-to-order colours with no asset cost – one transparent master per photo covers every colour, instead of a separate image file per colour
  • The photo keeps its own shadows and highlights – the tint rides on the original lighting, so recoloured shots still look like photographs, not flat fills
  • The picked colour name flows straight into the order request – no re-typing, no mismatch between what was seen and what was ordered
pi-pi.ee – Live Custom-Colour Preview

The Business Problem

Pi-pi.ee stocks its products in three colours – black, grey, white – and photographs each one. But customers kept asking for colours that aren’t on the shelf: a green to blend into a garden, an anthracite to match a façade, a terracotta for a courtyard. Every one of those is a made-to-order paint job, perfectly doable, but invisible on the website. The buyer had to imagine it.

The obvious fix – photograph the product in every offered colour – doesn’t scale. Ten colours across several product shots is dozens of new image files to shoot, edit, name, store, and re-do the moment the catalogue changes. And it still only covers a fixed list. The real requirement was different: let the visitor see any offered colour on the actual product, right on the page, without the shop producing a single extra photo.

The Solution

The recolouring happens entirely in the browser, on the photo that’s already loaded. Each product photo ships with a transparent-background master. A single SVG duotone filter desaturates that master to pure luminance and re-maps it onto a colour ramp built from the chosen colour – shadows to a darkened version, midtones to the colour itself, highlights to a lightened version. The product’s own lighting is preserved, so a recoloured shot still reads as a photograph rather than a flat silhouette.

The visitor picks from a set of named swatches – no raw hex or RGB input, just ‘olive’, ‘terracotta’, ‘anthracite’ – and the preview updates on the same frame. Because a custom colour is a special order rather than a stock SKU, the chosen colour name is reported back up to the product form and pre-filled into the order request, so what the buyer saw is exactly what the shop receives. Swatch labels run through the same next-intl dictionary as the rest of the catalogue, so the colour names are localised alongside everything else.

Try It

This is the exact recolour that runs on the product page – the same transform, live in your browser: the product’s white master is painted with a diffuse tint plus a white specular highlight. Pick a colour and pull the sliders: saturation, shadow depth and highlight are the transform’s real knobs.

Colour

Results

AspectOutcome
Recolour latencyInstant – CSS filter, zero network requests
Image assetsOne transparent master per photo covers every colour
Colour rangeAny target colour; the UI exposes a curated, localised set
Shading fidelityOriginal shadows and highlights preserved via a luminance ramp
Order accuracyChosen colour name pre-fills the special-order request

Under the Hood

For the technically curious, here’s how the live preview is built.

One Filter, Any Colour

The core is an SVG filter that turns a product photo into pure brightness, then paints that brightness with the target colour. feColorMatrix with saturate 0 collapses the image to luminance; feComponentTransfer then rewrites each channel through a three-stop table – shadow, base, highlight – derived from the target colour. The shadow stop keeps folds and contours dark; the highlight stop keeps sheen; the base stop is the colour itself.

const SHADOW_MUL = 0.28;
const HIGHLIGHT_MIX = 0.68;
 
function hexToRgb(hex: string): [number, number, number] {
  const v = hex.replace("#", "");
  return [
    parseInt(v.slice(0, 2), 16) / 255,
    parseInt(v.slice(2, 4), 16) / 255,
    parseInt(v.slice(4, 6), 16) / 255,
  ];
}
 
// 3-stop luminance ramp per channel: shadow -> base -> highlight.
function ramp(channel: number): string {
  const shadow = channel * SHADOW_MUL;
  const highlight = channel + (1 - channel) * HIGHLIGHT_MIX;
  return `${shadow.toFixed(3)} ${channel.toFixed(3)} ${highlight.toFixed(3)}`;
}

The filter itself is a few static nodes; only the tableValues change when the colour changes, and React re-renders just those three strings:

const rgb = hexToRgb(selectedHex);
 
<filter id={filterId} colorInterpolationFilters="sRGB">
  <feColorMatrix type="saturate" values="0" />
  <feComponentTransfer>
    <feFuncR type="table" tableValues={ramp(rgb[0])} />
    <feFuncG type="table" tableValues={ramp(rgb[1])} />
    <feFuncB type="table" tableValues={ramp(rgb[2])} />
  </feComponentTransfer>
</filter>;

Applying it is a single CSS property on the image – the recolour rides the GPU compositor, not a redraw:

<img src={src} style={{ filter: `url(#${filterId})` }} loading="lazy" />

Transparent Masters, Derived From the Catalogue

The catalogue only stores the stock photo paths (12-black.webp). The picker derives the transparent master path from those by convention, and quietly drops any image that has no master (diagrams, mesh close-ups), so the preview only ever shows the shots that can actually be tinted.

function transparentMasters(images: string[]): string[] {
  const out: string[] = [];
  for (const src of images) {
    const m = src.match(/^(.*\/)(\d+)-(?:black|grey|white)\.(?:webp|png)$/);
    if (m !== null) out.push(`${m[1]}${m[2]}-transparent.png`);
  }
  return out.slice(0, 3);
}

The Colour the Buyer Saw Is the Colour They Order

A custom colour isn’t a stock SKU, so it can’t just be added to the cart – it becomes a special-order request. The picker reports the chosen colour name (already localised) up to the product form, which pre-fills it into the enquiry. There’s no second place for the buyer to re-state the colour, and no gap between the preview and the order:

const [customColorName, setCustomColorName] = useState<string | null>(null);
 
<CustomColorPicker images={images} onChange={setCustomColorName} />;
// ...the form's colour field is driven by customColorName
Iurii RoguliaAvailable

Need something similar?

I build custom solutions – from APIs to full products. Let’s talk about your project.

View all projects

Related projects

HTPBE.TECH – PDF Verification Workflow Animation
HTPBE.TECH – PDF Verification Workflow Animation
March 4, 2026
HTPBE.TECH – PDF Verification Workflow Animation

Looping SVG animation illustrating a PDF verification pipeline – document stack, cloud processing, and green/red folder sorting – built with pure CSS

Stack

TypeScriptReactNext.js

Services

Vercel

Topics

AnimationData VisualizationUI/UXPerformanceProduct DesignSVG
pi-pi.ee – B2B E-commerce for Waterless Urinal Systems
pi-pi.ee – B2B E-commerce for Waterless Urinal Systems
January 17, 2026
pi-pi.ee – B2B E-commerce for Waterless Urinal Systems

i18n B2B e-commerce platform for waterless urinal products across 32 European countries with automated VAT handling, PDF invoicing, and CRM integration.

Stack

Next.jsReactTypeScriptTailwind CSS

Libraries

next-intlDaisyUIStripe SDKreact-pdf

Services

StripeVercelResendNotionGoogle Analytics

Topics

E-commerceB2Bi18nPDFSSRArchitectureSEOSchema.org

Related posts

B2B Quote-to-Order Flow in Next.js: A State Machine That Doesn’t Drift
August 7, 2026· 10 min
B2B Quote-to-Order Flow in Next.js: A State Machine That Doesn’t Drift

B2B quote-to-order flow in Next.js: an RFQ → quote → order state machine, per-deal line items, guarded status transitions, and converting an accepted quote

Stack

Next.jsReactTypeScript

Databases

PostgreSQL

Topics

B2BE-commerceArchitectureSSRSales Automation
Gated B2B Pricing in Next.js: Hiding Prices Behind Per-Customer Access
July 24, 2026· 9 min
Gated B2B Pricing in Next.js: Hiding Prices Behind Per-Customer Access

Gated B2B pricing in Next.js: per-account price lists, server-side access control so prices never leak to crawlers, and a deal-record data model where every

Stack

Next.jsReactTypeScript

Databases

PostgreSQL

Topics

B2BE-commerceAuthSSRArchitectureSecurity