sablier-icon
Recolor the Sablier icon SVG to a user-specified color with an analogous gradient, and optionally export to PNG, JPG, or ICO (favicon).
Source
The base icon is at assets/icon.svg (relative to this skill directory). It is a two-path SVG with viewBox="0 0 189.9 236.73"
(aspect ratio ~0.802:1). Two CSS classes (.cls-1, .cls-2) reference two <linearGradient> elements in <defs>:
<linearGradient id="linear-gradient">— contains two<stop>elements:offset="0"→stop-color="#f77423"(top / darker)offset="1"→stop-color="#fbce5b"(bottom / lighter)
<linearGradient id="linear-gradient-2">— inherits stops from the first viaxlink:href="#linear-gradient"
To recolor, replace the two stop-color values inside <linearGradient id="linear-gradient">. Always preserve the original
viewBox and aspect ratio — never add or change width/height attributes on the SVG.
A flat variant is at assets/icon-white.svg — a single-path SVG with fill="white" and viewBox="0 0 386 480" (aspect
ratio ~0.804:1). Used only when --flat is passed.
Color Resolution
Resolve the user's color input using this priority (first match wins):
- Exact alias —
primary,secondary,orange,blue(see aliases in palette below) - Exact palette name — e.g.
primary-start,dark-300,gray-400 - Raw hex — accept
#RRGGBBorRRGGBB(6-digit only, reject 3/8-digit). Normalize to lowercase#rrggbb - CSS color name — standard CSS named colors (e.g.
red,teal,cornflowerblue)
If multiple palette entries match a prefix (e.g. dark matches dark, dark-100, dark-300), prefer the exact match.
If no exact match exists, ask the user to be more specific.
Sablier Brand Palette
Source: sablier-labs/branding
| Name | Hex | Notes |
|---|---|---|
| primary-start | #ff7300 |
Orange gradient start |
| primary-end | #ffb800 |
Orange gradient end |
| primary / orange | #ff9c00 |
Median orange (default primary) |
| secondary-start | #003dff |
Blue gradient start |
| secondary-end | #00b7ff |
Blue gradient end |
| secondary / blue | #0063ff |
Median blue (default secondary) |
| secondary-desaturated | #266cd9 |
Desaturated blue |
| dark | #14161f |
Darkest background |
| dark-100 | #1e212f |
App background |
| dark-300 | #2a2e41 |
Card borders |
| dark-400 | #30354a |
Input borders |
| gray-100 | #e1e4ea |
Body text |
| gray-400 | #8792ab |
Labels |
| red | #e52e52 |
Error / destructive |
| white | #ffffff |
Original icon color |
| black | #000000 |
Pure black (rarely used) |
Gradient Generation
Brand gradient pairs
Use these exact hex pairs — no computation needed:
| Alias | Start (offset=0, top) | End (offset=1, bottom) |
|---|---|---|
| primary / orange | #f77423 |
#fbce5b |
| secondary / blue | #003dff |
#00b7ff |
When the user says "primary" or "orange", the output is identical to icon.svg (no stop-color changes needed).
When the user says "secondary" or "blue", replace the two stop-colors with the blue pair.
Arbitrary colors — piecewise HSL algorithm
For any color not in the brand gradient pairs above, generate an analogous two-stop gradient:
- Convert the resolved hex to HSL
(h, s%, l%) - Branch by saturation:
- Achromatic (
s < 10): keeps = 0for both stops, vary only lightness- Start:
hsl(h, 0%, max(l - 8, 10)%) - End:
hsl(h, 0%, min(l + 12, 90)%)
- Start:
- Chromatic (
s >= 10): keep hue constant, adjust lightness and gently reduce end saturation- Start:
hsl(h, s%, max(l - 8, 15)%) - End:
hsl(h, floor(s * 0.9)%, min(l + 12, 88)%)
- Start:
- Achromatic (
- Ensure
endL > startLafter clamps; if not, setstartL = endL - 10 - Convert both HSL values back to 6-digit lowercase hex
SVG Generation
Gradient mode (default)
- Read
assets/icon.svg - Resolve gradient start/end colors:
- If the color matches a brand gradient pair alias, use the exact hex pair
- Otherwise, apply the piecewise HSL algorithm
- Find the two
<stop>elements inside<linearGradient id="linear-gradient">and replace theirstop-colorvalues:offset="0"→ start hexoffset="1"→ end hex
- Structural checks:
- Exactly two
stop-colorreplacements occurred xlink:href="#linear-gradient"onlinear-gradient-2is still intactviewBox="0 0 189.9 236.73"preserved, nowidth/heightattributes added
- Exactly two
- Write to
sablier-icon-<color-name>.svg
Flat mode (--flat flag)
- Read
assets/icon-white.svg - Resolve the color to a single hex value:
- If the color matches a brand gradient pair alias (
primary,secondary), use the median palette hex (#ff9c00,#0063ff) - Otherwise, use the resolved hex directly
- If the color matches a brand gradient pair alias (
- Replace
fill="white"on the<path>element only — never touchfill="none"on the root<svg>element - Verify exactly one replacement occurred
- Verify
viewBox="0 0 386 480"preserved, nowidth/heightattributes added - Write to
sablier-icon-<color-name>.svg
Filenames
Use the brand alias when matched by name (e.g. primary), otherwise strip the # prefix and lowercase the hex value
(e.g. #E52E52 → e52e52). If the color cannot be resolved, ask the user to provide a valid hex code.
PNG / JPG Export
If the user passes --format png or --format jpg:
- Generate the recolored SVG first
- Verify
rsvg-convertis available:command -v rsvg-convert >/dev/null 2>&1 || { echo "Error: rsvg-convert not found. Install with: brew install librsvg"; exit 1; } - Use
rsvg-convertfor SVG→PNG (it correctly renders CSS gradients, unlike ImageMagick's SVG renderer which produces grayscale) - For JPG, convert the PNG with
magick(verify availability:command -v magick >/dev/null 2>&1)
Gradient mode (from icon.svg, viewBox 189.9×236.73):
# PNG (transparent background, 1024px height, width auto-computed from aspect ratio ≈822px)
rsvg-convert -h 1024 "<input>.svg" -o "<output>.png"
# JPG (dark background since JPG has no transparency) — render PNG first, then convert
rsvg-convert -h 1024 "<input>.svg" -o "<output>.tmp.png"
magick "<output>.tmp.png" -background "#14161f" -flatten "<output>.jpg"
rm "<output>.tmp.png"
Flat mode (from icon-white.svg, viewBox 386×480):
# PNG (transparent background, explicit height, width auto-computed ≈824px)
rsvg-convert -h 1024 "<input>.svg" -o "<output>.png"
# JPG (dark background)
rsvg-convert -h 1024 "<input>.svg" -o "<output>.tmp.png"
magick "<output>.tmp.png" -background "#14161f" -flatten "<output>.jpg"
rm "<output>.tmp.png"
Verify the exported file's dimensions match the expected aspect ratio.
ICO Export (--format ico or --favicon)
--favicon is a shorthand for --format ico. Both produce a multi-resolution .ico file containing 16x16, 32x32, and 48x48 embedded PNGs — the standard sizes for favicon.ico.
- Generate the recolored SVG first
- Verify
rsvg-convertandmagickare available (same checks as PNG/JPG) - Render intermediate PNGs at each favicon size using
rsvg-convert(height-constrained to preserve aspect ratio), then center each on a square transparent canvas withmagick -extent - Combine into a single
.icowithmagick - Clean up intermediate PNGs
# Render PNGs preserving aspect ratio (height-constrained), then center on square canvas
for size in 16 32 48; do
rsvg-convert -h "$size" "<input>.svg" -o "<output>-${size}-raw.tmp.png"
magick "<output>-${size}-raw.tmp.png" -background transparent -gravity center -extent "${size}x${size}" "<output>-${size}.tmp.png"
rm "<output>-${size}-raw.tmp.png"
done
# Combine into multi-resolution ICO
magick "<output>-16.tmp.png" "<output>-32.tmp.png" "<output>-48.tmp.png" "<output>.ico"
# Clean up
rm "<output>-16.tmp.png" "<output>-32.tmp.png" "<output>-48.tmp.png"
The output filename follows the sablier-icon-<color-name>.ico pattern — or favicon.ico if the user explicitly requests that name.
The output filename follows the same sablier-icon-<color-name>.<ext> pattern for all other formats.
Examples
primary→sablier-icon-primary.svgwith brand orange gradient (identical toicon.svg)secondary→sablier-icon-secondary.svgwith blue gradient#003dff/#00b7ff#e52e52→sablier-icon-e52e52.svgwith red gradient (piecewise HSL algorithm)white→sablier-icon-white.svgwith achromatic subtle lightness gradient#00d395 --flat→sablier-icon-00d395.svgwith flatfill="#00d395"red --format jpg→sablier-icon-red.svg+sablier-icon-red.jpgsecondary --format png→sablier-icon-secondary.svg+sablier-icon-secondary.png(blue gradient + raster export)primary --format ico→sablier-icon-primary.svg+sablier-icon-primary.ico(multi-resolution 16/32/48px)primary --favicon→ same as--format ico