Add product

Six short steps. Everything goes to Shopify as a draft — nothing is live until you press Publish.

    Start from a similar product saves the most time

    Copies its attributes, features, notes and sizes. You still add your own colours and photos.

    Basics

    SKUs are made for you: LAM-LTHR01-8X4-OLVGRN-MAT

    Look

    Finish

    Material

    Features tap to add · up to 4

    Where to use

    More fields — series, surface, slip rating, sample price, stock…

    Type ₹ / sq ft or the price per pack — the other fills itself. The site shows ₹/sq ft; the cart charges per pack.

    Size *₹ / sq ftPrice ₹ per pack *MRP ₹ per packPieces / packCoverage / pack (sq ft) auto from size

    Each colour's photos show only when that colour is picked on the site. Its first photo is the main image. Leave the name blank for a single-colour product.

    Colour family ticked for you from the colour names

    Images made from each colour's main photo

    Made as soon as you add photos — the main photo should be a flat, straight-on swatch.

    Description

    SEO audit

    Every product scored against the rubric. Click a row to load its copy in SEO copy.

    SEO copy

    Meta title, description and keywords are a template over stored metafields — no model call, free and repeatable. The model only writes long-form body copy, and only if a key is set.

    Images

    Gemini Vision reads your supplier photos, then generates one render per scene and attaches them to the product. Scenes are per category.

    Read in the browser and sent as base64. Two or three angles beats one.

    Flat templates

    Every image that can be built from one flat swatch — no model call, no cost, a few hundred milliseconds. Fixed geometry over your own photo: the size shot, the thickness shot, the laid-floor grid, the pattern repeat.

    Parsed by the same parseSizeLabel the product form uses — mm, cm, inch or ft.

    Laminates & panels get feet on the size drawing; the rest mm.

    Glazed tiles get a pale body edge; full-body carries the face colour through.

    Capped by the photo: a 640px swatch cannot fill 2048.

    Wants a flat, square-on shot of the surface — the kind of photo a supplier sends. An angled studio render will look skewed when pasted into fixed geometry, which is itself worth seeing once.

    Preview only — nothing is attached to a product and nothing is written to Shopify. Right-click any result to save it.

    Studio shot — 3D render

    The same swatch, but built as an actual scene: a slab with real thickness on a floor, two rectangular lights, a camera with a focal length. Roughness is a property of the material here, so a matte and a polished tile genuinely differ — the one thing the 2D composite cannot express. Seconds rather than milliseconds, still no model call.

    Needs a Chrome on this machine (three.js renders inside a headless one, because Node has no WebGL). Set CHROME_PATH if it is somewhere unusual.

    The only thing that varies per product — it decides whether the surface scatters light or mirrors it. Everything else (size, thickness, body and ground colour) is read from the fields above.

    Rectify a photo

    Every template assumes a flat, square-on swatch. Hand one an angled photo and it produces confident nonsense — the beige backdrop of a studio shot gets pasted in and called a tile. This finds the tile's four corners and undoes the camera's perspective, so a phone photo becomes a usable swatch.

    OpenCV does this, not sharp: sharp has affine transforms only, and straightening a photo needs a four-point homography. Needs the Python venv — python3 -m venv .venv-cv && ./.venv-cv/bin/pip install opencv-python-headless numpy.

    A tile on a plainish background. The corners are found by colour, so texture is fine.

    Sets the output aspect, so a plank straightens to 2:1 rather than to whatever the camera saw.

    PDF images

    Pulls every picture embedded in a catalogue PDF at its original resolution — not a screenshot. Works whatever the layout, because a PDF stores each image once as its own object. Transparent cutouts come out as PNG with their transparency; repeats are kept once. Flat-colour swatches drawn as vector shapes are rendered at 300 dpi with their colour code. Backgrounds reused across pages count as decoration: hidden, and left out of the zip.

    Room renders · the Cycles PDP pack

    A real path-traced render, not a composite. The tile is built as actual geometry at its real size — a 1200 x 600 slab is 1.2 x 0.6 m with a 3 mm joint and a 1.5 mm chamfer — and Cycles works out the light. Three catalog-ready shots per SKU.

    This is minutes, not milliseconds, and it needs Blender on the box. Nothing here calls a model, so the cost is electricity.

    1 · the room

    Every .blend in lib/productImages/templates/. Drop one in and press Refresh — nothing to restart. The grey box is a calibration room for checking tile scale and grout, not a product shot.

    Multiplies the scene's own lamps and emissive surfaces. A bought interior is lit for mood, and the floor is the product — 2 to 3 opens it up. Unlike exposure this adds real light, so the tile's pattern keeps its contrast.

    The artist framed their kitchen; that is usually right. But a low camera views the floor at a grazing angle and flattens a patterned tile to nothing — pick auto for those.

    Leave blank. Tone mapping only, so it lifts the black point too and flattens texture — measured, +1.6 EV cost 18% of the tile's pattern contrast. Use Lighting first.

    2 · the tile

    Parsed for real millimetres. This is what sets the tile's size in the room.

    Sets roughness and the clear coat. It is what the grazing shot exists to prove.

    3 · how it is laid

    Herringbone is not in this build: it interlocks only at 2:1 and needs its own lattice.

    2048 is a PDP zoom. Drop to 768 while you are tuning — it is roughly 7x faster.

    Signals

    GA4 funnel data → a relevance score and the Popular / Trending / Bestseller tags. Preview computes everything and writes nothing.

    Needs GCP_PROJECT_ID and GA4_DATASET, and a GA4→BigQuery export behind them. Without both it refuses rather than querying a guessed dataset.

    Marketing

    Journeys are data, not code. A channel with no key reports itself off rather than pretending.

    Ads events

    Builds the Meta Conversions API payload so you can check the hashing without firing anything. Email and phone are SHA-256 hashed before they leave this process.

    Preview only — nothing is sent. Meta is unconfigured, so the send path reports itself off.

    Settings

    Some routes need the x-cron-secret header. It's kept in this browser's localStorage only — never sent anywhere but this service.

    Find it with grep CRON_SECRET material-admin/.env

    There is no staff login on this service yet. Every read endpoint is open to anyone who can reach the port. Fine on localhost; it must close before this is deployed.

    Routes

    Everything this service exposes.