Details

How the Just Style Me analyzer works

The landmark model runs in the browser. After consent, the downscaled photo and the numbers are stored, and a fixed palette produces the guide.

Published 2026-11-04 · Just Style Me editors

The analyzer is a measuring tool with a writing step attached. It does not receive a mood or a favorite color. It receives a downscaled photograph, after you consent, and a small set of numbers taken from that picture in the browser. It checks that a person asked for the reading, stores the photo with the record, and turns the numbers into an undertone, a depth, a contrast, a clarity, a face shape, and a season. A second season, the nearest neighbor on the same scoring map, comes along because borders are common. Prose may be drafted by a language model from the numbers, not from the picture. The colors in the guide are not invented by the model. They come from a fixed library. If you remember only that split, the rest of the machinery is easier to trust, and easier to doubt in the right places.

This page describes the implementation as it actually runs, including the limits. It is the right page to read before you argue with a result. The undertone guide explains how to look at warmth with cloth. The face-shape guide explains what the six names do and do not mean for a neckline. Here, the subject is the path from a camera frame to a link.

The photo is measured in the tab, then stored if you agree

You either upload a file or take a picture with the camera. For an upload, the browser builds a bitmap with createImageBitmap and asks that call to honor the file’s EXIF orientation, so a phone photo that was stored sideways is turned upright before anyone measures it. The camera path uses the video frame directly. In both cases the picture is drawn to a canvas in the page and scaled before landmarks run. That local step is still where the colors are sampled. The upload happens only when you check the consent box and ask for the guide. The file sent is the downscaled canvas, as a JPEG, plus the measurement JSON. We keep both.

A local preview can appear before you ask for a written guide. The same math that the server will run can run in the page, so you can see a season label before the upload. That preview is a convenience. The guide you keep is the server’s recomputation. The share link stores the text. The photograph is the separate object in storage, tied to the same submission.

Scaled so the long edge is at most 840

Before landmarks are found, the canvas is scaled so the longer side is at most 840 pixels. A smaller image is left at its own size. A larger one is reduced in proportion, and the new width and height are rounded to whole pixels. The point of the cap is stability. A huge file would make the landmark pass and the color samples slower without making the median of a cheek more meaningful. A tiny file is the opposite problem: there may not be enough even skin to sample, and the page will say so rather than invent a color.

The 840-pixel edge is also why a crop matters. If your face is a small oval in a large room, the scaled image still contains mostly room. The sampler cannot recover detail the scale threw away. Fill a good part of the frame with the face, in front of a plain background if you can. The tool will refuse a frame with no detectable face. It will not refuse a frame that is technically valid and still a bad exposure. That second case is what the confidence notes are for.

A face model that runs on your machine

Landmarks come from MediaPipe’s Face Landmarker, running in the browser. The model file is self-hosted with the site, and the vision runtime is loaded from this site as well. The measurement step does not send the photo to a third-party vision API. The landmarker is asked for a still image, for up to three faces, and not for blendshapes or a transformation matrix. It tries a GPU delegate first. If that fails, it tries the CPU delegate. Either way, the output used here is a list of points on the face, normalized and then multiplied by the canvas size.

When several faces are present, the largest one is used. Largest means the face whose landmark box covers the most area, not the face closest to the center and not the face you intended if someone else is bigger in the frame. The page adds a line when that happens. If you meant the smaller person, crop them in and try again. Points are required for the forehead, the chin, the cheeks, the jaw, and the eyes. An incomplete mesh stops the reading instead of guessing a missing point.

Skin: cheek and forehead, median, highlights dropped

Skin color is not a single pixel and not the average of the whole face, which would mix in eyebrows, nostrils, and lips. The sampler visits a set of cheek and forehead landmarks and, around each one, reads a small patch. Pixels that are clipped highlights are discarded. In practice that means a pixel whose brightest channel is over 250, or whose darkest channel is under a low floor, does not enter the median. A strong red spike, much redder than it is green, is also left out, because a blemish or a bit of lip color should not paint the cheek. What remains is reduced to a median red, a median green, and a median blue, each channel on its own, so one bright speck cannot pull the result the way a mean would.

The page keeps two diagnostics. One is how many skin pixels survived. A thin sample produces a note that the undertone is rough. The other is the share of considered pixels that were near blown-out white. A high share means a flash or a hard window is clipping the skin, which pushes lightness up and washes the undertone. If almost no even skin survives, the reading stops and asks for a window, no flash, and lighter makeup if you can manage it. Foundation that is still on the skin is not removed by this filter. The median reports the surface it can see.

Hair: a band above the forehead

Hair is harder, because it is not inside the face mesh. The sampler builds a band above the forehead landmark, the point numbered 10 in this mesh, using the face height as a ruler. The band sits just above that point and spans roughly the width between the outer forehead and cheek points. It does not search the whole background, and it does not know what a hat is. Inside the band, a pixel is kept only if it is unlike the skin and unlike the background corners of the photo. Likeness here is a color distance in Lab, computed in the browser so the sampler can compare pixels. Near-white pixels are dropped. If enough pixels remain, their median is the hair color, and a confidence score rises with the count. If the band fails, confidence drops to a low floor, the hair color falls back toward the skin, and later contrast math leans on the eyes.

That fallback is the case the note describes: the hair sample looked close to the background or to the forehead, so contrast leans more on the eyes. Covered hair, a scarf, a hood, hair pulled tightly away from the forehead, or a wall the same color as the hair all take this path. The reading can still finish. You should not treat the hair hex as a measurement of hair you kept out of the band. Retake with the hair visible against a contrasting wall if the contrast label matters to you. It usually does. Hair is the largest value mass in the frame, which is why the hair color guide treats dye level as a contrast decision.

Eyes and lips

When the mesh includes iris landmarks, the eye sample uses the iris centers, the points numbered 468 and 473. If those points are not present, the sampler falls back to the eye-area points it does have. Around each center it reads a small patch and keeps the more colorful pixels, dropping pale, low-chroma pixels that are usually sclera or a catchlight. The survivors are reduced to a median. If almost none survive, the eye color falls back to a darkened version of the skin, which is a honest failure rather than a invented iris. Heavy liner and bright catchlights make this sample noisier. A clear, front-facing eye is the input the median was built for.

Lips are the interior of the lip landmarks, not the skin around the mouth. The sampler takes the box of those points, insets it so the border and the surrounding skin stay out, and again takes a median, skipping blown highlights. Lipstick is part of that median. The tool does not detect makeup and then subtract it. If too little lip surface is visible, the lip color falls back to the skin color, and anything downstream that expected a lip chroma is working with a weaker clue. Close the mouth in a relaxed way, skip a very dark liner that eats the lip area, and the sample has something to read.

The server converts those RGBs to Lab again

The browser used Lab conversions while sampling, because it had to decide which pixels were skin-like or background-like, and it may show you a local preview. The numbers that travel are still ordinary red, green, and blue, rounded to integers, plus the ratios and the diagnostic counts. On the server those RGBs are converted to CIE Lab* again. L* is lightness. a* runs from green toward pink. b* runs from blue toward yellow. The server does not accept a season, an undertone, or a face-shape label in the payload. It derives them. A browser cannot send a flattering conclusion ahead of the arithmetic.

Undertone comes from that Lab reading of the skin. The yellow-blue axis, b*, is weighed against the pink-green axis, a*. More yellow relative to pink pulls warm. More pink relative to yellow pulls cool. A green-bias check, comparing the green channel with a mix of red and blue, can mark the skin as olive when the yellow is present without a strong pink. Neutral is the reading in which those pulls are weak. The labels are warm, cool, neutral, and olive. They are summaries of a measured cast, not a lineage and not a vein test done in a bathroom.

Value comes from skin L* alone. At or above 68 the value is light. From 49 up to that line it is medium. Below 49 it is deep. These cuts are deliberate and a little coarse, which is appropriate for a photograph. A different exposure can move L* across a boundary. That is one reason a surprising depth should be repeated in open shade before you rebuild a wardrobe around it.

Contrast and chroma

Contrast is a lightness gap, not a mood. The server takes the absolute difference between hair L* and skin L*, and between eye L* and skin L*. When hair confidence is healthy, the hair gap counts for most of the score and the eye gap for the rest. When confidence is low, the weights flip toward the eyes, which is the “leans on the eyes” rule in arithmetic form. The score is then cut into high, medium, and low. A wide gap is high. A narrow one is low. The middle band is medium. You can read what to wear with those labels on the contrast pages. The number is only as good as the hair sample and the exposure.

Chroma, the clear-versus-soft axis, uses the colorfulness of the features plus that contrast score. Lips contribute more than eyes. Colorfulness here is the distance of the Lab point from a neutral gray of the same lightness, not a guess about personality. A high combined clarity is labeled clear. A low one is labeled soft. The middle is medium. A soft result with a harsh black collar often looks pasted on. A clear result in only dusty colors often looks slightly dimmed. The label is a description of the features in this photo, including makeup if you wore it. Bare skin and a bare mouth, in daylight, are the cleaner input.

Face shape is the nearest of six prototypes

Face shape uses six ratios from the landmarks: length against width, jaw against cheek, forehead against jaw, cheek against forehead, chin against cheek, and the angle at the chin. Length runs from the forehead point to the chin point. It does not run from the hairline. A high hairline or a low one is not in the measurement, which is why a person can disagree with the label and be disagreeing with a geometric convention rather than with a mirror. The six prototypes are oval, round, square, heart, oblong, and diamond. Each has a reference set of those ratios. The nearest prototype, by a weighted distance, wins.

If two prototypes are very close, the report says so. The named shape is the nearest, not a sharp category. Glasses, necklines, and haircuts on the face-shape pages are suggestions for that geometry. They are not a claim that a jaw changed species. A beard, a hat, or a hand under the chin can move a landmark. Take the picture with the jaw visible and the face toward the camera. Profile photos fail the mesh more often than they reveal a hidden shape.

Twelve seasons, scored, with a neighbor

The season is a scored map onto twelve names: the light, true, and bright springs and summers, the soft, true, and dark autumns, and the bright, true, and dark winters. Each season has a profile of undertones, values, chromas, and contrasts it can accept, and some combinations earn an extra point when they are characteristic, such as a cool, high-contrast, non-light reading toward true winter, or a light, low-contrast, warm reading toward light spring. The highest score is the season you see. The second highest is the neighbor, and it is returned on purpose.

A neighbor is not a consolation prize. It is the admission that a person can sit on a border, especially between soft summer and soft autumn, or between a light season and its true sibling. Use the main season as the first wardrobe test and the neighbor as the second drape, particularly if confidence is only medium. The map is a styling tool. It is not a biological type, and the report’s own research note is written to keep that limit visible. Two photos can cross a border if one was lit with a warm lamp. Believe the daylight version.

What is posted, and what is refused

The POST body is the measurements plus a Cloudflare Turnstile token. Skin, hair, eyes, and lips travel as red, green, and blue. With them go hair confidence, the count of usable skin pixels, the highlight-clipping ratio, and the six face ratios. The token is a bot check for the analyze action, verified with Cloudflare before any guide is written. It is not an account, and it is not a photograph. Requests from another origin are refused. A body that is too large is refused with a plain instruction to send measurements, not a photo. Field names that look like an image, a data URL, a pixel dump, or a file payload are refused even if the rest of the JSON is well formed.

The route is rate limited. The binding is configured for 8 requests in a 60-second period, keyed by IP address. Cloudflare applies that kind of limiter per location, so the practical effect is about eight readings a minute per IP per Cloudflare location. It is approximate. It is not a single global hard cap for the whole network, and a burst that lands on more than one location is not counted as one bucket. If the limiter itself errors, the page asks you to wait. If you are over the limit, it asks you to wait a minute. The limit exists so one network cannot hammer the writing model. It is not a judgment about how often you may think about clothes.

The model drafts sentences. The library keeps the colors

After the reading is computed, the worker may ask Cloudflare Workers AI to draft the guide as JSON. The configured model is @cf/meta/llama-3.3-70b-instruct-fp8-fast. The prompt gives the model the measurements and the palette that already belongs to the season. It tells the model not to invent hex codes. When a draft comes back, explanatory sentences can replace the library’s sentences if they are a sensible length and if they do not contain the kind of language this site refuses. The hex on each swatch is kept from the fixed palette library. Names of the swatches stay. Outfit colors stay. A model that returns the wrong number of notes does not get to half-update the row. The library line remains.

If the model is mocked in the environment, the library writes the guide and the model is not called. If the call fails, or if it does not return within its timeout, the same fallback runs. The result page says which path you got. A workers-AI line means the prose was drafted by the model and checked against the library, and that the hex values are the library’s. A library line means this copy was used because the model was mocked or unavailable. Either way you are looking at the season the numbers scored, not at a season the model preferred. Read the sentences as a stylist would. If one of them is vague, the swatches are still the part that was not up for invention.

The report lives in the hash

The guide, the reading, and a short record of which writer ran are gzip-compressed in the browser and placed in the URL fragment, the part after the #. The browser then opens the result page at that address. Browsers do not send the fragment to the server with the request. The result page reads the fragment locally and unpacks it. Nothing about the report is written to a database. There is no account to attach it to. If you lose the link, we cannot look it up, because we did not keep it. If you share the link, the other person has the report. Treat it the way you would treat a written description of your coloring.

The server does log a short operational event after a successful reading: the season, the undertone, the face shape, the confidence, and whether the prose came from the model or the library. It does not log the photograph, and it does not log the raw color numbers or the Turnstile token. The privacy policy is the place those details live, including cookies, Turnstile, and the fact that this site does not publish a street address. You can drop a result link from your browser history yourself. That is the retention story. It is short on purpose.

Limits you should actually use

White balance still shifts undertone. A camera that “improves” skin toward a standard pink, or a yellow lamp in a bedroom, will move b* and sometimes a*. The useful check is daylight, with the light on your face and not behind you. The confidence notes say this even on a clean reading, because it is always true. Clipped highlights from a window or a flash push lightness up. A very dark room hides the gap between hair, eyes, and skin. Makeup moves the samples directly: foundation replaces cheek color, lipstick replaces the lip median, and liner complicates the iris patch. If you want a reading of your coloring rather than of your routine, wear less of it for the picture.

Covered hair weakens contrast, as described above. Multiple faces are resolved by size, not by intent. A face shape that lands near two prototypes should be worn as a hint. None of these notes make the tool useless. They tell you which part of the label to hold lightly. Repeat a surprising result once, in better light, before you spend money. Then hold cloth up, because simultaneous contrast at the jaw is still the test a palette has to pass. Use the analyzer as the measuring step. Use a window as the editor. The season name is a convenient handle for a handful of numbers, and the numbers are only as calm as the light you gave them.

Questions people actually ask

Is my photo uploaded?
Yes, if you check the consent box. The landmark model still measures the face in the browser, on a canvas whose long edge is at most 840 pixels. Submitting uploads that downscaled image plus the color numbers, face ratios, and a Turnstile token. We store the photo, the reading, the guide, the IP address, approximate location, user agent, and referrer. The share link after the # does not contain the picture. The privacy policy explains retention and how to ask for deletion.
Why did my hair reading look uncertain?
Hair is sampled from a band above the forehead point, and only from pixels that differ from the skin and from the background. A hat, a scarf, a hairline pulled fully out of that band, or a wall the same color as the hair leaves too few pixels. Confidence then drops, and the contrast score leans on the gap between the eyes and the skin. Retake the photo with hair visible against a different wall. Until then, treat the contrast label as an eye-led estimate, not as a measurement of the hair you kept out of the frame.
Can the language model invent my colors?
It can draft sentences. It does not get to replace the hex values. Those stay on the swatches from the fixed palette for the season the numbers scored. If the draft is the wrong shape, too short, or full of language the editor refuses, the library sentence remains. If the model is mocked, times out, or errors, the library writes the guide and the result page says that the library copy was used. You should judge the prose as an explanation of a palette, not as a new palette. The colors were chosen before the model spoke.
What if the season seems wrong?
One photo is one exposure. White balance, a yellow lamp, a flash, heavy makeup, and a covered hairline all move the numbers. The report includes a neighbor season because the score is a ranking, not a blood type. Repeat the reading in open shade, with daylight white balance if the camera allows it, and with the face filling a good part of the frame. Then compare both labels with a cloth test at a window. The analyzer is a measured start. It is not a claim that a single frame has settled your coloring for good. Borders between seasons are ordinary.

Keep going