Iurii RoguliaIurii Rogulia
AboutServicesPricingProjectsStackReviewsPhrasesBlog
Contact
Iurii ships.

Iurii Rogulia, IT partner for business & fractional CTO. 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? — PDF Verification Workflow Animation
HTPBE? — PDF Verification Workflow Animation
March 4, 2026
HTPBE? — 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

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
Building a React Product Configurator with Zustand and Motion
April 21, 2026· 12 min
Building a React Product Configurator with Zustand and Motion

How to build a React product configurator with Zustand state, DaisyUI components, Motion animations, and live SVG preview — architecture, pricing logic, and

Stack

Next.jsReactTypeScript

Libraries

DaisyUIFramer MotionZustandnanoid

Topics

Product ConfiguratorUI/UXE-commerceInteractive