|
56 | 56 | SUBTITLE_HEX = "#C8DDD9" # near-white with a slight teal tint — readable on dark scrim |
57 | 57 |
|
58 | 58 |
|
59 | | -# ── Freeform Background Compositions ────────────────────────────────────── |
| 59 | +# ── Multi-Stop Gradient Background ──────────────────────────────────────── |
60 | 60 | # |
61 | | -# Each composition is a list of blob descriptors: |
62 | | -# (cx_frac, cy_frac, rx_frac, ry_frac, color, opacity) |
63 | | -# Fractions are relative to canvas width/height (0.0–1.0). |
64 | | -# All blobs share a single heavy Gaussian blur filter. |
| 61 | +# Technique extracted from Layer5 reference SVGs (Artboard 1.svg): |
| 62 | +# - Overlapping full-canvas rectangles with multi-stop linear gradients |
| 63 | +# - 10-16 gradient stops per layer for rich, deep color transitions |
| 64 | +# - stop-opacity controls where each layer is visible vs transparent, |
| 65 | +# allowing underlying layers to show through |
| 66 | +# - Radial gradient for the white subject clearing (from chs-2-intro.svg) |
| 67 | +# - No blur filter needed — the many intermediate color stops create |
| 68 | +# smooth transitions naturally |
65 | 69 | # |
66 | | -# Three canonical patterns (from the style guide): |
| 70 | +# The signature Layer5 gradient ramp (from Artboard 1.svg) has 16 stops |
| 71 | +# transitioning from Dark Jungle Green through Charcoal, six intermediate |
| 72 | +# blue-greens, Keppel, to Caribbean Green — then back to Charcoal. |
67 | 73 | # |
68 | | -# CORNER_WARMTH — Gold upper-left, teal right edge/bottom-right, dark |
69 | | -# bottom-left, white center-to-upper-center. Creates a |
70 | | -# sunrise or atmospheric glow effect. |
71 | | -# |
72 | | -# DEEP_SPACE — Dark blue-black majority, teal and gold accents pushed |
73 | | -# to corners, bright white-to-light zone at mid-canvas. |
74 | | -# Like looking through a clearing in a nebula. |
75 | | -# |
76 | | -# The white subject halo is always the LAST blob added (on top), positioned |
77 | | -# where Five will be composed (right 40% of the image, centered vertically). |
78 | | -# It is deliberately off-center and non-circular to feel organic, not like |
79 | | -# a spotlight. |
| 74 | +# Each composition layer dict: |
| 75 | +# type: "linear" or "radial" |
| 76 | +# For linear: x1, y1, x2, y2 (fractions of canvas W,H) |
| 77 | +# For radial: cx, cy (fractions), r (fraction of max(W,H)) |
| 78 | +# stops: [(offset, color, stop_opacity), ...] — stop_opacity optional (default 1.0) |
80 | 79 |
|
81 | 80 | CORNER_WARMTH = [ |
82 | | - # cx, cy, rx, ry, color, opacity |
83 | | - # |
84 | | - # Daytime: Saffron sun packed into upper-left corner, Steel Teal sweeping the |
85 | | - # upper-right sky (prominent blue sky field — matching "Twitter post 2.ai" / Kubernetes |
86 | | - # signpost reference), Keppel + Caribbean Green saturating the entire bottom edge, |
87 | | - # large luminous white clearing center-right where Five stands. |
88 | | - # Reference: Layer5 "Twitter post 2.ai" (Kubernetes signpost), "legos.ai", "worship.ai" |
89 | | - (0.05, 0.06, 0.36, 0.30, SAFFRON, 1.00), # deep saffron — upper-left sun, full |
90 | | - (0.22, 0.14, 0.30, 0.26, BANANA, 0.90), # banana mania — warm halo off the sun |
91 | | - (0.82, 0.06, 0.46, 0.38, STEEL_TEAL, 0.92), # steel teal — upper-right sky, prominent |
92 | | - (0.08, 0.86, 0.32, 0.28, TEAL, 0.96), # keppel — bottom-left, vivid |
93 | | - (0.45, 0.94, 0.50, 0.24, TEAL, 0.98), # keppel — bottom center, full-width band |
94 | | - (0.88, 0.88, 0.42, 0.28, TEAL_LIGHT, 0.96), # caribbean green — bottom-right, vivid |
95 | | - (0.44, 0.36, 0.36, 0.30, STEEL_TEAL, 0.68), # steel teal — mid-sky transition |
96 | | - # Large luminous white clearing — center-right, where Five stands |
97 | | - (0.66, 0.44, 0.54, 0.68, WHITE, 0.97), |
| 81 | + # Daytime: Saffron upper-left (sun), Keppel/Caribbean Green at right + bottom, |
| 82 | + # Dark Jungle Green base at lower-left, MASSIVE white clearing center-right. |
| 83 | + # Reference: "4000 members", "Recognition Program", "layer5-hero.webp" |
| 84 | + |
| 85 | + # Layer 1: Teal from the RIGHT — Keppel/Caribbean Green builds from right edge |
| 86 | + {"type": "linear", |
| 87 | + "x1": 0.0, "y1": 0.5, "x2": 1.0, "y2": 0.5, |
| 88 | + "stops": [ |
| 89 | + (0.00, "#1E2117", 0.0), |
| 90 | + (0.20, "#1E2117", 0.0), |
| 91 | + (0.35, "#262C27", 0.15), |
| 92 | + (0.45, "#323B3B", 0.35), |
| 93 | + (0.52, "#3C494E", 0.50), |
| 94 | + (0.58, "#375154", 0.60), |
| 95 | + (0.63, "#305D5D", 0.70), |
| 96 | + (0.68, "#266E6A", 0.80), |
| 97 | + (0.73, "#1A847B", 0.88), |
| 98 | + (0.78, "#0B9E8F", 0.92), |
| 99 | + (0.82, "#00B39F", 0.96), |
| 100 | + (0.86, "#00B59F", 0.98), |
| 101 | + (0.89, "#00BDA2", 1.00), |
| 102 | + (0.92, "#00CAA6", 1.00), |
| 103 | + (0.95, "#00D3A9", 1.00), |
| 104 | + (1.00, "#3C494E", 0.90), |
| 105 | + ]}, |
| 106 | + |
| 107 | + # Layer 2: Teal from the BOTTOM — ground plane |
| 108 | + {"type": "linear", |
| 109 | + "x1": 0.5, "y1": 0.0, "x2": 0.5, "y2": 1.0, |
| 110 | + "stops": [ |
| 111 | + (0.00, "#1E2117", 0.0), |
| 112 | + (0.30, "#1E2117", 0.0), |
| 113 | + (0.45, "#262C27", 0.10), |
| 114 | + (0.55, "#323B3B", 0.25), |
| 115 | + (0.62, "#3C494E", 0.40), |
| 116 | + (0.68, "#375154", 0.55), |
| 117 | + (0.73, "#305D5D", 0.65), |
| 118 | + (0.78, "#266E6A", 0.75), |
| 119 | + (0.82, "#1A847B", 0.85), |
| 120 | + (0.86, "#0B9E8F", 0.90), |
| 121 | + (0.90, "#00B39F", 0.95), |
| 122 | + (0.93, "#00BDA2", 1.00), |
| 123 | + (0.96, "#00D3A9", 1.00), |
| 124 | + (1.00, "#3C494E", 0.85), |
| 125 | + ]}, |
| 126 | + |
| 127 | + # Layer 3: Saffron from UPPER-LEFT — sun warmth |
| 128 | + {"type": "linear", |
| 129 | + "x1": -0.05, "y1": -0.05, "x2": 0.75, "y2": 0.75, |
| 130 | + "stops": [ |
| 131 | + (0.00, "#FFF3C5", 0.92), |
| 132 | + (0.05, "#FAE6A0", 0.95), |
| 133 | + (0.10, "#F5D875", 0.95), |
| 134 | + (0.16, "#F0CB45", 0.95), |
| 135 | + (0.22, "#EBC017", 1.00), |
| 136 | + (0.28, "#D4AD15", 0.95), |
| 137 | + (0.34, "#BFA012", 0.88), |
| 138 | + (0.40, "#A5870E", 0.78), |
| 139 | + (0.46, "#886F0C", 0.65), |
| 140 | + (0.52, "#6D5B0D", 0.50), |
| 141 | + (0.58, "#56490E", 0.35), |
| 142 | + (0.64, "#45390F", 0.22), |
| 143 | + (0.70, "#352E11", 0.12), |
| 144 | + (0.78, "#2A2613", 0.05), |
| 145 | + (0.85, "#1E2117", 0.0), |
| 146 | + ]}, |
| 147 | + |
| 148 | + # Layer 4: White clearing — radial, center-right where Five stands |
| 149 | + # Exact ramp from chs-2-intro.svg radialGradient (white→keppel halo) |
| 150 | + {"type": "radial", "cx": 0.58, "cy": 0.46, "r": 0.52, |
| 151 | + "stops": [ |
| 152 | + (0.22, "#FFFFFF", 0.97), |
| 153 | + (0.32, "#F7FCFC", 0.92), |
| 154 | + (0.40, "#E2F6F4", 0.82), |
| 155 | + (0.48, "#BFEBE7", 0.68), |
| 156 | + (0.56, "#8FDDD4", 0.50), |
| 157 | + (0.64, "#52CBBE", 0.32), |
| 158 | + (0.74, "#12B8A6", 0.14), |
| 159 | + (0.85, "#00B39F", 0.05), |
| 160 | + (1.00, "#00B39F", 0.0), |
| 161 | + ]}, |
98 | 162 | ] |
99 | 163 |
|
100 | 164 | DEEP_SPACE = [ |
101 | | - # Night sky: Charcoal anchors three corners for deep space darkness, |
102 | | - # Steel Teal dominates the upper sky sweep, Saffron in upper-RIGHT as a |
103 | | - # LARGE vivid color field (not just a corner dot) — matching "Bi-Weekly Meshery |
104 | | - # Build & Release" where saffron occupies 40%+ of the canvas right side, and |
105 | | - # "Newcomers Meeting" where saffron/gold is a dominant warm presence. |
106 | | - # Reference: "Bi-Weekly Meshery Build & Release.ai", "Newcomers Meeting.ai", |
107 | | - # "Meet Five our intergalactic Cloud Native Hero" illustration |
108 | | - (0.04, 0.06, 0.28, 0.30, CHARCOAL, 0.98), # very dark — upper-left corner |
109 | | - (0.04, 0.94, 0.26, 0.24, CHARCOAL, 0.95), # very dark — lower-left corner |
110 | | - (0.96, 0.94, 0.22, 0.20, CHARCOAL, 0.90), # very dark — lower-right corner |
111 | | - (0.88, 0.10, 0.46, 0.44, SAFFRON, 0.90), # gold — upper-RIGHT, large vivid field |
112 | | - (0.76, 0.08, 0.32, 0.28, BANANA, 0.68), # banana — warm halo around the gold |
113 | | - (0.44, 0.18, 0.70, 0.52, STEEL_TEAL, 0.90), # steel teal — large upper-sky sweep |
114 | | - (0.16, 0.52, 0.34, 0.46, STEEL_TEAL, 0.80), # steel teal — mid-left depth layer |
115 | | - # Large luminous clearing — generous, center-weighted where Five stands |
116 | | - (0.60, 0.46, 0.54, 0.66, WHITE, 0.95), |
117 | | - (0.65, 0.42, 0.24, 0.30, OFF_WHITE, 0.90), # bright core |
| 165 | + # Night sky: darker overall. Steel Teal concentrated at upper edge, |
| 166 | + # fades quickly. Charcoal dominates lower 60%. Saffron accent upper-RIGHT. |
| 167 | + # White clearing is tighter — more dark space visible around it. |
| 168 | + # Reference: "Meet Five", "Adventures of Five Vol 2" cover |
| 169 | + |
| 170 | + # Layer 1: Steel Teal from the TOP — pulled back, fades by mid-canvas |
| 171 | + {"type": "linear", |
| 172 | + "x1": 0.5, "y1": -0.1, "x2": 0.5, "y2": 0.8, |
| 173 | + "stops": [ |
| 174 | + (0.00, "#477E96", 1.00), |
| 175 | + (0.05, "#477E96", 1.00), |
| 176 | + (0.10, "#457A8E", 0.95), |
| 177 | + (0.16, "#436F82", 0.88), |
| 178 | + (0.22, "#406D7F", 0.78), |
| 179 | + (0.28, "#3C5F6D", 0.65), |
| 180 | + (0.35, "#375360", 0.50), |
| 181 | + (0.42, "#324854", 0.36), |
| 182 | + (0.50, "#2D3E49", 0.24), |
| 183 | + (0.58, "#29353E", 0.14), |
| 184 | + (0.68, "#252D33", 0.06), |
| 185 | + (0.80, "#1E2117", 0.0), |
| 186 | + ]}, |
| 187 | + |
| 188 | + # Layer 2: Steel Teal from LEFT edge — subtle, fades quickly |
| 189 | + {"type": "linear", |
| 190 | + "x1": -0.1, "y1": 0.4, "x2": 0.8, "y2": 0.4, |
| 191 | + "stops": [ |
| 192 | + (0.00, "#477E96", 0.75), |
| 193 | + (0.06, "#457A8E", 0.65), |
| 194 | + (0.14, "#406D7F", 0.50), |
| 195 | + (0.22, "#3C5F6D", 0.36), |
| 196 | + (0.30, "#375360", 0.24), |
| 197 | + (0.38, "#324854", 0.14), |
| 198 | + (0.48, "#2D3E49", 0.06), |
| 199 | + (0.60, "#1E2117", 0.0), |
| 200 | + ]}, |
| 201 | + |
| 202 | + # Layer 3: Charcoal reinforcement — darker overall, starts earlier |
| 203 | + {"type": "linear", |
| 204 | + "x1": 0.5, "y1": 0.0, "x2": 0.5, "y2": 1.0, |
| 205 | + "stops": [ |
| 206 | + (0.00, "#3C494F", 0.0), |
| 207 | + (0.25, "#3C494F", 0.10), |
| 208 | + (0.40, "#3C494F", 0.30), |
| 209 | + (0.50, "#3C494F", 0.55), |
| 210 | + (0.60, "#3C494F", 0.75), |
| 211 | + (0.72, "#3C494F", 0.90), |
| 212 | + (0.85, "#3C494F", 0.96), |
| 213 | + (1.00, "#3C494F", 1.00), |
| 214 | + ]}, |
| 215 | + |
| 216 | + # Layer 4: Extra darkness from lower-left corner diagonal |
| 217 | + {"type": "linear", |
| 218 | + "x1": 0.9, "y1": -0.1, "x2": -0.1, "y2": 1.1, |
| 219 | + "stops": [ |
| 220 | + (0.00, "#1E2117", 0.0), |
| 221 | + (0.35, "#1E2117", 0.0), |
| 222 | + (0.50, "#1E2117", 0.25), |
| 223 | + (0.65, "#1E2117", 0.55), |
| 224 | + (0.80, "#1E2117", 0.80), |
| 225 | + (1.00, "#1E2117", 0.95), |
| 226 | + ]}, |
| 227 | + |
| 228 | + # Layer 5: Saffron star UPPER-RIGHT — warm accent |
| 229 | + {"type": "linear", |
| 230 | + "x1": 0.15, "y1": 0.80, "x2": 1.05, "y2": -0.10, |
| 231 | + "stops": [ |
| 232 | + (0.00, "#1E2117", 0.0), |
| 233 | + (0.40, "#1E2117", 0.0), |
| 234 | + (0.50, "#45390F", 0.10), |
| 235 | + (0.56, "#6D5B0D", 0.25), |
| 236 | + (0.62, "#886F0C", 0.40), |
| 237 | + (0.68, "#A5870E", 0.58), |
| 238 | + (0.74, "#BFA012", 0.72), |
| 239 | + (0.80, "#D4AD15", 0.84), |
| 240 | + (0.85, "#EBC017", 0.94), |
| 241 | + (0.90, "#F0CB45", 0.96), |
| 242 | + (0.94, "#F5D875", 0.92), |
| 243 | + (0.97, "#FFF3C5", 0.85), |
| 244 | + (1.00, "#3C494E", 0.60), |
| 245 | + ]}, |
| 246 | + |
| 247 | + # Layer 6: White clearing — tighter radius, less reach |
| 248 | + {"type": "radial", "cx": 0.56, "cy": 0.46, "r": 0.46, |
| 249 | + "stops": [ |
| 250 | + (0.18, "#FFFFFF", 0.96), |
| 251 | + (0.28, "#F7FCFC", 0.88), |
| 252 | + (0.36, "#E2F6F4", 0.74), |
| 253 | + (0.44, "#BFEBE7", 0.56), |
| 254 | + (0.52, "#8FDDD4", 0.38), |
| 255 | + (0.62, "#52CBBE", 0.20), |
| 256 | + (0.72, "#12B8A6", 0.08), |
| 257 | + (0.84, "#00B39F", 0.02), |
| 258 | + (1.00, "#00B39F", 0.0), |
| 259 | + ]}, |
118 | 260 | ] |
119 | 261 |
|
120 | 262 | # Map category → composition. Corner Warmth is the warmer, more energetic look; |
|
147 | 289 |
|
148 | 290 | def bg_blobs_svg(category, W, H): |
149 | 291 | """ |
150 | | - Return (filter_def, background_svg) for the full-canvas freeform gradient. |
| 292 | + Return (defs_block, background_svg) for the multi-stop gradient background. |
151 | 293 |
|
152 | | - filter_def — goes inside the top-level <defs> block |
153 | | - background_svg — the base rect + blurred color blobs |
| 294 | + Uses the same technique as Layer5's official illustrations (Artboard 1.svg): |
| 295 | + overlapping layers with multi-stop gradients (10-16 stops per layer) and |
| 296 | + stop-opacity for compositing. No blur filter needed — the many intermediate |
| 297 | + color stops create smooth, rich transitions naturally. |
154 | 298 |
|
155 | | - Blur stdDeviation scales with canvas width: ~10% of width for a |
156 | | - 1200px canvas gives stdDeviation=120, matching the style guide reference. |
| 299 | + defs_block — gradient definitions, goes inside the top-level <defs> |
| 300 | + background_svg — the base rect + gradient layers |
157 | 301 | """ |
158 | 302 | composition = CATEGORY_COMPOSITION.get(category, CORNER_WARMTH) |
159 | | - blur_std = round(W * 0.10) # 120px at 1200px canvas — wide, smooth color transitions |
160 | 303 |
|
161 | | - filter_def = ( |
162 | | - f'<filter id="bgBlur" x="-100%" y="-100%" width="300%" height="300%">\n' |
163 | | - f' <feGaussianBlur stdDeviation="{blur_std}"/>\n' |
164 | | - f' </filter>' |
165 | | - ) |
| 304 | + gradient_defs = [] |
| 305 | + layer_rects = [] |
| 306 | + |
| 307 | + for i, layer in enumerate(composition): |
| 308 | + grad_id = f"bgGrad{i}" |
| 309 | + |
| 310 | + # Build stop elements |
| 311 | + stop_lines = [] |
| 312 | + for s in layer["stops"]: |
| 313 | + offset, color = s[0], s[1] |
| 314 | + opacity_attr = "" |
| 315 | + if len(s) > 2 and s[2] < 1.0: |
| 316 | + opacity_attr = f' stop-opacity="{s[2]}"' |
| 317 | + stop_lines.append( |
| 318 | + f' <stop offset="{offset}" stop-color="{color}"{opacity_attr}/>' |
| 319 | + ) |
| 320 | + stops_xml = "\n".join(stop_lines) |
| 321 | + |
| 322 | + if layer["type"] == "linear": |
| 323 | + x1 = layer["x1"] * W |
| 324 | + y1 = layer["y1"] * H |
| 325 | + x2 = layer["x2"] * W |
| 326 | + y2 = layer["y2"] * H |
| 327 | + gradient_defs.append( |
| 328 | + f' <linearGradient id="{grad_id}" ' |
| 329 | + f'gradientUnits="userSpaceOnUse" ' |
| 330 | + f'x1="{x1:.0f}" y1="{y1:.0f}" x2="{x2:.0f}" y2="{y2:.0f}">\n' |
| 331 | + f'{stops_xml}\n' |
| 332 | + f' </linearGradient>' |
| 333 | + ) |
| 334 | + elif layer["type"] == "radial": |
| 335 | + cx = layer["cx"] * W |
| 336 | + cy = layer["cy"] * H |
| 337 | + r = layer["r"] * max(W, H) |
| 338 | + gradient_defs.append( |
| 339 | + f' <radialGradient id="{grad_id}" ' |
| 340 | + f'gradientUnits="userSpaceOnUse" ' |
| 341 | + f'cx="{cx:.0f}" cy="{cy:.0f}" r="{r:.0f}">\n' |
| 342 | + f'{stops_xml}\n' |
| 343 | + f' </radialGradient>' |
| 344 | + ) |
166 | 345 |
|
167 | | - ellipses = [] |
168 | | - for cx_f, cy_f, rx_f, ry_f, color, opacity in composition: |
169 | | - cx = cx_f * W |
170 | | - cy = cy_f * H |
171 | | - rx = rx_f * W |
172 | | - ry = ry_f * H |
173 | | - ellipses.append( |
174 | | - f' <ellipse cx="{cx:.0f}" cy="{cy:.0f}" ' |
175 | | - f'rx="{rx:.0f}" ry="{ry:.0f}" ' |
176 | | - f'fill="{color}" opacity="{opacity}"/>' |
| 346 | + layer_rects.append( |
| 347 | + f' <rect width="{W}" height="{H}" fill="url(#{grad_id})"/>' |
177 | 348 | ) |
178 | 349 |
|
| 350 | + defs_block = "\n".join(gradient_defs) |
| 351 | + |
179 | 352 | background_svg = ( |
180 | 353 | f'<!-- Base background -->\n' |
181 | 354 | f' <rect width="{W}" height="{H}" fill="{EERIE_BLACK}"/>\n' |
182 | | - f' <!-- Freeform gradient blobs (all share heavy Gaussian blur) -->\n' |
183 | | - f' <g filter="url(#bgBlur)" clip-path="url(#canvas)">\n' |
184 | | - + '\n'.join(ellipses) |
| 355 | + f' <!-- Multi-stop gradient layers (Layer5 illustration technique) -->\n' |
| 356 | + f' <g clip-path="url(#canvas)">\n' |
| 357 | + + '\n'.join(layer_rects) |
185 | 358 | + '\n </g>' |
186 | 359 | ) |
187 | 360 |
|
188 | | - return filter_def, background_svg |
| 361 | + return defs_block, background_svg |
189 | 362 |
|
190 | 363 |
|
191 | 364 | # ── Close-range Five glow ───────────────────────────────────────────────── |
@@ -464,6 +637,7 @@ def generate_hero_svg(title, subtitle, category, output_path, repo_root, |
464 | 637 | </linearGradient> |
465 | 638 | {bg_filter_def} |
466 | 639 | {glow_filter_def} |
| 640 | + <!-- Note: bg_filter_def now contains gradient definitions, not a blur filter --> |
467 | 641 | </defs> |
468 | 642 |
|
469 | 643 | {bg_svg} |
|
0 commit comments