Glassmorphism Generator — Zig source
Create frosted-glass CSS - backdrop-blur, translucent background, subtle border, and soft shadow - with a copy-ready snippet and live preview.
This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.
//! =============================================================================
//! glassmorphism.zig — CosmoDev polyglot showcase port of the
//! `glassmorphism-generator` tool
//! -----------------------------------------------------------------------------
//! Language : Zig (0.13, standard library only)
//! Source: ported from src/lib/glassmorphism.ts (the canonical, live
//! TypeScript lib); mirrors
//! src/tool-sources/glassmorphism-generator/{python.py,rust.rs}
//! License : display source — part of CosmoDev's polyglot tool pages
//! (dev.cosmolabs.org). Shown verbatim alongside the JS/TS/Go/Rust/
//! Python ports and the other language ports.
//! -----------------------------------------------------------------------------
//! Builds a frosted-glass CSS block from blur / opacity / tint / radius /
//! border_opacity (+ optional saturation). Hex tints (#rgb / #rrggbb) are
//! validated by hand (no regex — stdlib only); opacity is clamped to [0,1];
//! the alpha channel is rounded with @floor(x*100 + 0.5)/100 — JS Math.round
//! (half-up) semantics — and numbers render like JS template literals
//! (10.0 -> "10", 0.3 -> "0.3") via jsNum() below. The output therefore
//! matches the live TypeScript lib byte-for-byte.
//!
//! All rendered strings are allocated with the allocator passed in; free them
//! individually or hand the allocator an arena (as main() does).
//! =============================================================================
const std = @import("std");
/// Configuration for one glassmorphism declaration block.
pub const GlassConfig = struct {
blur: f64 = 0,
opacity: f64 = 0,
tint: []const u8 = "#ffffff",
radius: f64 = 0,
border_opacity: f64 = 0,
/// null => 180.0 (lib default)
saturation: ?f64 = null,
};
/// The rendered glassmorphism block plus its full CSS text.
pub const GlassCss = struct {
background: []const u8,
backdrop_filter: []const u8,
webkit_backdrop_filter: []const u8,
border: []const u8,
border_radius: []const u8,
box_shadow: []const u8,
full_css: []const u8,
};
/// True when the byte is an ASCII hex digit (0-9, a-f, A-F).
fn isHexDigit(c: u8) bool {
return (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f') or (c >= 'A' and c <= 'F');
}
/// Parse one ASCII hex digit into 0..15; 0 on a non-digit.
fn hexVal(c: u8) u8 {
if (c >= '0' and c <= '9') return c - '0';
if (c >= 'a' and c <= 'f') return c - 'a' + 10;
return c - 'A' + 10;
}
/// True when every byte is an ASCII hex digit.
fn isHexRun(s: []const u8) bool {
if (s.len == 0) return false;
for (s) |c| {
if (!isHexDigit(c)) return false;
}
return true;
}
/// Render a float into `buf` the way JavaScript's template literal does —
/// shortest round-tripping decimal, no trailing ".0" on whole numbers.
/// Integer-valued floats go through an i64 first: `{d}` on a whole float can
/// keep a trailing ".0" depending on std.fmt version (the
/// css-animation-playground port casts for the same reason), while `{d}` on
/// an i64 is always plain digits. The range guard keeps @intFromFloat in
/// range; far beyond any blur/radius/saturation value.
fn jsNum(buf: []u8, x: f64) []const u8 {
if (x == @trunc(x) and x > -9.2e18 and x < 9.2e18) {
const as_int: i64 = @intFromFloat(x);
return std.fmt.bufPrint(buf, "{d}", .{as_int}) catch "0";
}
return std.fmt.bufPrint(buf, "{d}", .{x}) catch "nan";
}
/// Parse #rgb or #rrggbb into {r, g, b}; null on a bad color.
/// A 3-char hex expands each digit (e.g. "abc" -> "aabbcc"), like CSS.
/// Exactly one leading '#' is stripped, matching the lib's /^#/ replace.
pub fn hexToRgb(hex: []const u8) ?[3]u8 {
// Trim ASCII whitespace first — the set JS String.prototype.trim() strips.
var h = std.mem.trim(u8, hex, " \t\n\x0b\x0c\r");
if (h.len > 0 and h[0] == '#') h = h[1..];
if (h.len == 3 and isHexRun(h)) {
// "a" -> 0xaa — each digit doubles into a pair, like CSS.
var out: [3]u8 = undefined;
for (h, 0..) |c, i| out[i] = hexVal(c) * 16 + hexVal(c);
return out;
}
if (h.len == 6 and isHexRun(h)) {
return .{
hexVal(h[0]) * 16 + hexVal(h[1]),
hexVal(h[2]) * 16 + hexVal(h[3]),
hexVal(h[4]) * 16 + hexVal(h[5]),
};
}
return null;
}
/// Format rgba(), clamping + 2-decimal-rounding the alpha with JS Math.round
/// (half-up) semantics: @floor(x*100 + 0.5)/100. Caller owns the string.
pub fn rgba(gpa: std.mem.Allocator, r: u8, g: u8, b: u8, a: f64) ![]u8 {
const c = @min(@max(a, 0), 1);
var alpha_buf: [32]u8 = undefined;
const alpha = jsNum(&alpha_buf, @floor(c * 100.0 + 0.5) / 100.0);
return std.fmt.allocPrint(gpa, "rgba({d}, {d}, {d}, {s})", .{ r, g, b, alpha });
}
/// Build the full glassmorphism CSS for a config.
pub fn buildGlassmorphism(gpa: std.mem.Allocator, cfg: GlassConfig) !GlassCss {
const blur = @max(cfg.blur, 0);
const opacity = @min(@max(cfg.opacity, 0), 1);
const radius = @max(cfg.radius, 0);
const border_opacity = @min(@max(cfg.border_opacity, 0), 1);
const saturation = cfg.saturation orelse 180;
const rgb = hexToRgb(cfg.tint) orelse [3]u8{ 255, 255, 255 };
var blur_buf: [32]u8 = undefined;
var sat_buf: [32]u8 = undefined;
var radius_buf: [32]u8 = undefined;
const blur_s = jsNum(&blur_buf, blur);
const sat_s = jsNum(&sat_buf, saturation);
const radius_s = jsNum(&radius_buf, radius);
const background = try rgba(gpa, rgb[0], rgb[1], rgb[2], opacity);
const backdrop_filter = try std.fmt.allocPrint(
gpa,
"blur({s}px) saturate({s}%)",
.{ blur_s, sat_s },
);
const border = try std.fmt.allocPrint(
gpa,
"1px solid {s}",
.{try rgba(gpa, 255, 255, 255, border_opacity)},
);
const border_radius = try std.fmt.allocPrint(gpa, "{s}px", .{radius_s});
const box_shadow = "0 8px 32px 0 rgba(31, 38, 135, 0.37)";
// Both prefixed and unprefixed backdrop-filter carry the same value.
const full_css = try std.fmt.allocPrint(gpa,
\\background: {s};
\\-webkit-backdrop-filter: {s};
\\backdrop-filter: {s};
\\border-radius: {s};
\\border: {s};
\\box-shadow: {s};
, .{ background, backdrop_filter, backdrop_filter, border_radius, border, box_shadow });
return .{
.background = background,
.backdrop_filter = backdrop_filter,
.webkit_backdrop_filter = backdrop_filter,
.border = border,
.border_radius = border_radius,
.box_shadow = box_shadow,
.full_css = full_css,
};
}
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const css = try buildGlassmorphism(arena.allocator(), .{
.blur = 10,
.opacity = 0.3,
.tint = "#ffffff",
.radius = 12,
.border_opacity = 0.2,
});
const stdout = std.io.getStdOut().writer();
try stdout.print("{s}\n", .{css.full_css});
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →