When the shadow refuses to fade — the quest for blue

Previously on the CogitActive Saga:
The “shadow” wasn’t a color at all — it was a filter. And filters don’t accept colors. Which meant that, for now, there was no elegant way to recolor it.

Introduction

In the previous chapter, I had finally managed to bend a rating plugin into something it was never meant to be — a doorbell disguised as a widget, a quiet way for readers to request that comments be reopened. The technical foundations were in place, the lyrical voice had taken shape, and the button itself had begun to look like it belonged at the threshold of the comment form.

Re-open comments button (hover)
All right reserved

But just as the transformation felt complete, a small, stubborn detail refused to cooperate. A detail so simple it almost escaped notice — and yet so visible it couldn’t be ignored. The button, despite its carefully chosen colors and polished SVG design, remained veiled under a grey shadow the plugin insisted on applying. Not a color, not a style, but a CSS filter — a grayscale layer that dulled everything it touched.

.can-rate .point, .can-rate.is-scale .points:hover .point:hover ~ .point, .can-rate.is-scale.has-focus .points .point.has-focus ~ .point, .can-rate.is-scale.has-focus .points .point:focus ~ .point, .is-scale .point.is-checked ~ .point:not(.is-checked), .has-checked.is-scale .point.is-checked ~ .point:not(.is-checked), .has-rated.is-count .is-unchecked {
    filter: grayscale(1);
}

I had matched the button to my comment form’s button, i.e., crafted the SVG in its “hover” state, and aligned every visual cue with the comment form it was meant to echo. And still, the shadow lingered. A filter that refused to fade. A problem that refused to stay small.

Re-open comments button (grey)
All right reserved

That shadow is where this post begins. This chapter follows that pursuit — the search for a way to override a filter that doesn’t want to be overridden, the experiments that almost worked, and the unexpected turns along the path toward a solution.

When time runs short and the AIs step in

I had already spent too long hunting for the right CSS selector, only to discover that the culprit wasn’t a color at all but a filtera property I barely knew anything about. With my usual lack of time making a proper deep dive unrealistic, I took the shortcut I always promise myself I won’t take: I asked Copilot for help.

When Copilot Smart is anything but

I started with Copilot (Smart) in the web client, armed with what I thought was a clear prompt: I explained that my plugin was applying filter: grayscale(1) to an external SVG button, that the icon was originally #c1cdef with white text, and that I wanted the non‑hover state to be #253f8c instead of grey. In my mind, this was a small, contained problem — the kind of thing an AI should be able to untangle in a single reply.

At first, Copilot treated my problem as if it were trivial: if the button shouldn’t be grey, just remove the filter and set the color:

.can-rate .point {
    filter: none !important;
    fill: #253f8c;
}

Simple. Except that when I tested Copilot’s suggestion directly in my browser’s DevTools — live, in place, on the very element it was supposed to affect — nothing changed. The button stayed grey, untouched, unmoved, as if the CSS weren’t even there. So I asked again. And again. And again. Each time Copilot responded with the same calm confidence, offering yet another variation on the same theme: maybe color instead of fill. The code grew longer, the selectors more specific, the tone more authoritative — but the outcome never changed. After several rounds of increasingly elaborate non‑solutions, the real misunderstanding surfaced: Copilot was answering as if my SVG was inline. Didn’t I specify in my original prompt “an external SVG button”?

Every time I clarified that nothing changed in DevTools, Copilot doubled down with yet another confident variation — opacity: 1, even a background-color as if an opaque external SVG would politely let the background shine through. When that didn’t work, it proposed more specific selectors (accusing on the way my “very aggressive selector chain”), then even more specific ones, as though the problem were simply a matter of CSS stubbornness rather than the nature of the icon itself. The replies grew longer, the code blocks more ornate, the tone more authoritative — but the logic never shifted. No matter how many times I restated the situation, Copilot kept treating my SVG as something it wasn’t.

Frustrated, I escalated to Think Deeper, but the code only grew more complicated. Extra selectors, more specific rules, even a bit of JavaScript sprinkled in for authority — but under the surface, it was still the same idea: just set fill: #253f8c and call it a day. The filter, the fact that the SVG was an external file, the way the plugin handled the icon — all of that remained invisible to Copilot. After several rounds of increasingly baroque CSS that did absolutely nothing to the actual button, I realized I wasn’t fighting the grey anymore. I was fighting pure dumbness.

Not only dumb, but also color‑blind

After giving up on Think Deeper’s baroque detours, I went back to Copilot Smart — not because it was smarter, but because at least it didn’t bury its confusion under layers of over‑engineered CSS. With Smart, the conversation felt simpler, more direct, and I could patiently nudge it toward what I actually needed: stick with the filter. If the plugin insisted on greyscaling my external SVG, then maybe — just maybe — we could tweak the filter’s attributes to tint the icon instead of flattening it. After all, I’d seen WordPress themes recolor images with filters before, like slipping a pair of colored sunglasses over a photo. So I tried, very gently, to steer Copilot in that direction, hoping it would finally help me, and it did…

.can-rate .point {
    filter: brightness(0) saturate(100%) invert(18%) sepia(33%) saturate(2400%) hue-rotate(210deg) brightness(90%) contrast(90%);
}

…try, but failed again. The button was blue, yes, but the whole button was blue — the icon, the background, and even the text “The time has come. Let the comments open once more.” were swallowed into a single monochrome block.

The very first step — brightness(0) saturate(100%) — forces the entire element to pure black before any tinting happens. That trick works fine for recoloring a simple icon or a flat image, but for a button that contains actual text, it’s catastrophic. Once everything is reduced to black, the filter has no way to distinguish between the SVG shape and the words sitting on top of it. The moment the blue tint is applied, it paints everything uniformly, as if the button were nothing more than a single‑layer silhouette.

When I asked whether we could skip that destructive first step — keep the text intact, recolor only the icon — Copilot declared it impossible. No workaround, no nuance, no alternative path. Just a flat “can’t be done.” And that’s when I finally turned to Gemini, using the very same prompt I had fed the two previous “geniuses,” hoping a fresh perspective might see what Copilot could not:

To change your non-hover button color to #253f8c instead of grey, you should replace filter: grayscale(1); with a more complex filter string that shifts your base color (#c1cdef) to your target navy blue (#253f8c).

Recommended Solution

Since CSS filters work by transforming existing pixels, the specific values needed depend on your starting color. For a base of #c1cdef to reach #253f8c, use this replacement:

/* Replace 'filter: grayscale(1);' with this: */
filter: brightness(0.65) saturate(2.5) hue-rotate(10deg);

Use code with caution.

Why this works

  1. grayscale(1): This was removing all color, leaving only lightness levels
  2. brightness(0.65): Your base color (#c1cdef) is very light. To reach the dark blue of #253f8c, you first need to darken the element significantly
  3. saturate(2.5): This increases the intensity of the blue already present in your base color to match the deeper tone of your target
  4. hue-rotate(10deg): This slightly shifts the blue toward the specific navy hue of #253f8c

After wasting so much of my already scarce time on Copilot’s cluelessness, Gemini felt refreshingly sane: no fantasy about inline SVGs, no impossible properties, no flatten‑to‑black pipeline that erased my text. At least this time the button remained a button. But the color… meh. It wasn’t blue — not my blue. So I asked Gemini to try again. Worse. Much worse. Not even close. At least, it suggested a tool: a CSS Color Filter Generator (external link). According to Gemini, if I needed an exact match — “down to the 1% loss level” — this was the way to go.

filter: brightness(0) saturate(100%) invert(16%) sepia(93%) saturate(1521%) hue-rotate(212deg) brightness(99%) contrast(91%);

I tried it. And yes, the color was perfect. Absolutely perfect. Except for one tiny detail: like most generators, it assumed a starting color of pure black (#000000). Which meant the very first part of the filter was the dreaded brightness(0) saturate(100%) — the same destructive step that turns everything into a silhouette. Great for recoloring icons. Terrible for buttons with text. So I went back to Gemini and asked whether it could fine‑tune its earlier attempt to produce an actual blue this time. I’ll spare you the descent back into cluelessness — or more precisely, into color‑blindness. Instead, I’ll let the results speak for themselves. Below is a comparison of my blue versus the various AI blues 1 (and yes, I even went back to Copilot; I never learn).

Targeted colorGemini color 1Gemini color 2Gemini color 3Copilot color
#253f8c#8182bc#797799#71763e#a2b87d

One is never better served than by oneself

So here I was, back to square one. The shortcut had turned into a detour, the detour into a time sink, and the time sink into a reminder that I should have done things properly from the start. If I wanted this button to behave, I would have to understand the filter property myself — not rely on AIs that couldn’t even agree on what color blue was.

The CSS filter property

The filter CSS property applies graphical effects like blur or color shift to an element. Filters are commonly used to adjust the rendering of images, backgrounds, and borders.MDN Web Docs

The CSS filter property applies graphical effects to an element. These effects are modeled after image‑processing operations: blur, brightness, contrast, hue shifts, and more. A filter works by transforming the rendered pixels of an element. It simply takes whatever pixels the browser has already rendered and applies mathematical transformations to them. In other words, a filter behaves like placing a sheet of tinted glass over a finished image. Filters can be used for subtle adjustments (e.g., slightly increasing contrast) or dramatic transformations (e.g., turning an image neon green).

Once you understand that filters act like a post‑processing layer—transforming pixels after the browser has already rendered them—you can start to appreciate how flexible and expressive this feature really is. Modern browsers treat filters almost like built‑in Photoshop tools: fast, composable, and surprisingly powerful. What makes the filter property especially interesting is that it isn’t limited to images. Anything that produces pixels—text, backgrounds, borders, SVGs, even entire components—can be filtered. This means you can desaturate a logo, blur a card’s background, boost the contrast of a hero image, or invert the colors of an icon, all with a single line of CSS.

CSS Filters are a powerful tool that authors can use to achieve varying visual effects (sort of like Photoshop filters for the browser).Chris Coyier

CSS defines 11 filter functions 3, each modeling a familiar image‑processing operation. Here’s a quick overview of what each one does:

blur() — Softens the element by applying a Gaussian blur.

brightness() — Makes the element lighter or darker.

contrast() — Increases or decreases the difference between light and dark areas.

grayscale() — Removes color information, producing a black‑and‑white effect.

hue-rotate() — Rotates all hues around the color wheel.

invert() — Inverts colors, creating a photographic negative.

opacity() — Adjusts transparency (similar to the opacity property, but GPU‑accelerated).

saturate() — Boosts or reduces color intensity.

sepia() — Applies a warm, vintage brown tone.

drop-shadow() — Adds a shadow that follows the element’s alpha mask (unlike box-shadow).

url() — Applies an SVG filter defined in an external file.

These functions all behave consistently: each takes a value (length, percentage, number, or angle), and if the value is invalid, the browser simply ignores that function.

One of the most powerful aspects of the filter property is that you can chain multiple functions together. You may indeed combine any number of functions to manipulate the rendering. The browser applies them in order, passing the output of one into the next. Order matters. They are applied left to right, each one transforming the output of the previous one. For example, applying hue-rotate() before drop-shadow() will rotate the shadow’s color; applying it after will not. Beware, if any filter in a chain is invalid, the entire filter declaration is ignored.

Filters are good at adjusting the mood of an image (warm, cool, vintage, high‑contrast), creating dramatic visual effects, or creating hover effects (here we are). But what about recoloring an external SVG?

Un travail de fourmi (“ant’s work”)

Unfortunately, I didn’t find a single tutorial explaining how to transform #c1cdef into #253f8c. Most likely because recoloring an external SVG with filters isn’t their usual use case. But is it even possible? My experiments with the color‑blind “geniuses” had at least proved that recoloring something was possible. Reaching the perfect blue might be tricky — perhaps even impossible — but I was up for the challenge.

At least my understanding was clearer now. I knew that if the first step destroys contrast (for example, brightness(0)), every subsequent step works on a flat silhouette — so avoid that. If the first step removes color (grayscale(1)), later hue or saturation adjustments have nothing meaningful to work with. And the real trick is the order: each function transforms the output of the previous one. This is why filter‑based recoloring is delicate — you’re always transforming the existing pixels, not repainting them.

Not being an expert in color theory, I decided to start with the first code Gemini had produced — at least that one wasn’t dark olive. And so, diligently, experimentally, and quite honestly, without really knowing what I was doing, I began tuning the filter one function at a time, one decimal at a time.

filter: brightness(0.65) saturate(2.5) hue-rotate(10deg);

If a tweak brought me closer to blue, I kept going. If it drifted away, I nudged the value in the opposite direction. If a function refused to help, I moved on to the next one in the chain. And then back to the first. And then forward again. It was a slow, circular dance of microscopic adjustments — and I was going nowhere.

Instead of wandering blindly through decimals, I finally switched to a more grounded approach. HEX — that cryptic mix of letters and numbers I barely knew stood for rr gg bb — wasn’t helping me see what was really happening. So I moved to RGB, where each channel is explicit and measurable. Then I used the Eyedropper in the DevTools to capture the actual color of the transformed button after each tweak. Slowly, patiently, I tried to steer those values toward my target: rgb(37, 63, 140). It wasn’t elegant. It wasn’t fast. But at least now I could see whether I was moving in the right direction — one microscopic adjustment at a time.

Box 1: Not that dumb after all

While drafting this post — after I had already spent hours tweaking decimals, sampling pixels, and inching my way toward the right shade of blue — I asked Copilot in Edge to help me clean up my terminology. I expected minor corrections. Instead, it casually produced this:

“Filters operate on channels, not hex values, which means the only sensible way forward was to think in terms of HSL rather than #rrggbb. If I could understand how far my starting color was from my target in hue, saturation, and lightness, I could at least tune the filter functions in the right direction instead of blindly poking decimals.

Reading that, I had one of those moments where you stop, blink, and wonder how your brain managed to miss the most straightforward connection. I had spent hours nudging decimals like a safecracker with a blindfold, while the answer — or at least a far more rational approach — had been sitting one polite prompt away.

To make the connection explicit for my readers (explanation provided by Copilot in Edge):

#c1cdef is a very light, low‑saturation blue
#253f8c is a much darker, much more saturated navy

Color‑science translation:

Hue difference: small → only a modest hue-rotate() is ever needed
Saturation difference: large → saturate() must do the heavy lifting
Lightness difference: huge → brightness() must darken significantly

Which gives a real strategy:

use brightness() to match lightness
use saturate() to match intensity
use hue-rotate() to align the hue

In theory, this is the correct approach. So yes, after all my sarcasm toward its counterpart, I’ll say it plainly: Copilot in Edge had outsmarted me.

After hours — days, really — of microscopic adjustments, I finally got close enough. Not perfect, as the table below demonstrates, but undeniably in the right neighborhood:

filter: brightness(0.31) saturate(7.6) hue-rotate(-1deg);
rgb(37, 63, 140)rgb(42, 63, 140)
#253f8c#2a3f8c

Close. Very close. But — because there is always a but — the moment I applied this filter, something else broke.

Epilogue

My “almost perfect” solution had quietly sabotaged the one thing that mattered most: readability. After all the calculations, the filters, the decimals, the RGB detective work, I had finally reached a blue that was close enough to call a victory. But the moment I applied it, the button’s text became unreadable.

Re-open comments button (blue)
All right reserved

My original button text was #333333 — a perfectly serviceable dark grey on the original pale blue (#c1cdef). The filter transformed it into #101010 or RGB (16, 16, 16). Both are technically “dark greys,” so what’s the matter, you may wonder? The matter is contrast. Dark grey on pale blue is fine; dark grey on dark blue is barely visible. On my site, whenever text sits on this navy, I use white, not black — for a reason. In other words, my clever filter had turned a readable button into a stealth button.

And that’s the twist. Not the color. Not the math. Not the filter chain. The twist is that the “almost perfect” solution quietly undermined the very thing the button exists for: to be read, and then clicked.

So, the shadow stays — not because I can’t remove it, but because removing it breaks more than it fixes. In the end, I kept the default shadow. Not out of defeat, but out of respect for the balance between aesthetics and usability. A button is meant to be clicked, not admired under a microscope.

To be continued…


1 There were actually far more AI‑generated “blues” than the ones I’m showing here — each one somehow duller, greyer, or more washed‑out than the last. Listing them all wouldn’t add anything; the pattern was painfully clear long before the end. ^
2 The expression “Impossible n’est pas français” is famously attributed to Napoleon. Several historical sources report him using a version of it in letters to his generals, essentially meaning: “Impossible is not a word I recognize.” Over time, the phrase evolved into a patriotic proverb celebrating determination and the refusal to accept defeat. Today, it’s often used with a wink — a reminder that giving up is not part of the French vocabulary. Definitely not part of mine! ^
3 Victor Ikechukwu (2022) Everything You Need to Know about All 11 CSS Filters (external link). CoderPad. ^