added on WebSTLs (webstls.com) at 2026-07-01 22:26:42
| 1 | package MODS::WebSTLs::Themes; | |
| 2 | #====================================================================== | |
| 3 | # WebSTLs — Storefront Themes registry. | |
| 4 | # | |
| 5 | # This file is THE place to add or tweak storefront themes. Each theme | |
| 6 | # is just a Perl hash. To add a new theme, copy any block below, change | |
| 7 | # the values, and add it to @THEMES at the bottom. The platform picks | |
| 8 | # it up immediately — no database migration, no template edits. | |
| 9 | # | |
| 10 | # WHAT EACH FIELD DOES (everything below "id" is just colors): | |
| 11 | # id — number. MUST be unique. Used by storefronts.theme_id. | |
| 12 | # slug — short lowercase name. Used in URLs / filenames. | |
| 13 | # name — what creators see in the picker (e.g. "Dungeon Forge"). | |
| 14 | # tagline — one-line vibe description shown under the name. | |
| 15 | # bg — main page background color. | |
| 16 | # bg_2 — slightly lighter bg, used for header/footer/cards. | |
| 17 | # surface — card / panel background (one notch above bg_2). | |
| 18 | # surface_2 — even lighter card for nested panels. | |
| 19 | # border — line color for card edges, dividers. | |
| 20 | # border_2 — subtler/brighter border for hover states. | |
| 21 | # text — main body text color (READ THIS ON BG: must be legible). | |
| 22 | # text_2 — secondary text (subheads, less-important info). | |
| 23 | # text_3 — tertiary text (timestamps, dim metadata). | |
| 24 | # accent — brand color. Buttons, links, highlights, gradient start. | |
| 25 | # accent_2 — second brand color. Gradient end. Stars, pills. | |
| 26 | # accent_deep— pressed/active variant of accent. | |
| 27 | # accent_glow— rgba(...) version of accent for soft glow effects. | |
| 28 | # | |
| 29 | # DESIGN PHILOSOPHY: | |
| 30 | # These themes target sellers of 3D printable designs. Each one is | |
| 31 | # tuned for a particular niche (tabletop minis, FDM functional parts, | |
| 32 | # premium resin art, sci-fi / cyberpunk, classic D&D, cute prints). | |
| 33 | # | |
| 34 | # Pick colors that work TOGETHER. Test contrast: text on bg must be | |
| 35 | # readable. The site is dark by default; if you make a light theme | |
| 36 | # make sure ALL text colors flip too. | |
| 37 | #====================================================================== | |
| 38 | use strict; | |
| 39 | use warnings; | |
| 40 | ||
| 41 | #---------------------------------------------------------------------- | |
| 42 | # Themes — add new ones here. | |
| 43 | #---------------------------------------------------------------------- | |
| 44 | our @THEMES = ( | |
| 45 | { | |
| 46 | id => 1, | |
| 47 | slug => 'forge', | |
| 48 | name => 'Dungeon Forge', | |
| 49 | tagline => 'Bold purple-and-blue magic. Built for tabletop minis & fantasy terrain.', | |
| 50 | bg => '#050813', | |
| 51 | bg_2 => '#080c1a', | |
| 52 | surface => '#0e1426', | |
| 53 | surface_2 => '#141b33', | |
| 54 | border => '#1c2a48', | |
| 55 | border_2 => '#283c66', | |
| 56 | text => '#e6ecf6', | |
| 57 | text_2 => '#a8b6d1', | |
| 58 | text_3 => '#6f7e9c', | |
| 59 | accent => '#7c3aed', | |
| 60 | accent_2 => '#3b82f6', | |
| 61 | accent_deep => '#5b21b6', | |
| 62 | accent_glow => 'rgba(124,58,237,0.35)', | |
| 63 | }, | |
| 64 | { | |
| 65 | id => 2, | |
| 66 | slug => 'workshop', | |
| 67 | name => 'Workshop', | |
| 68 | tagline => 'Industrial orange on graphite. Perfect for FDM tools, jigs & functional parts.', | |
| 69 | bg => '#0d0e10', | |
| 70 | bg_2 => '#15171a', | |
| 71 | surface => '#1c1f23', | |
| 72 | surface_2 => '#24282d', | |
| 73 | border => '#2d323a', | |
| 74 | border_2 => '#404750', | |
| 75 | text => '#f4f1ec', | |
| 76 | text_2 => '#b8b3a8', | |
| 77 | text_3 => '#7a7468', | |
| 78 | accent => '#f97316', | |
| 79 | accent_2 => '#fbbf24', | |
| 80 | accent_deep => '#c2410c', | |
| 81 | accent_glow => 'rgba(249,115,22,0.35)', | |
| 82 | }, | |
| 83 | { | |
| 84 | id => 3, | |
| 85 | slug => 'atelier', | |
| 86 | name => 'Atelier', | |
| 87 | tagline => 'Clean cream-and-rose. The premium look for high-end resin & art prints.', | |
| 88 | bg => '#f8f5f0', | |
| 89 | bg_2 => '#ede8df', | |
| 90 | surface => '#ffffff', | |
| 91 | surface_2 => '#f4efe7', | |
| 92 | border => '#dcd5c8', | |
| 93 | border_2 => '#c9c0ae', | |
| 94 | text => '#1f1a14', | |
| 95 | text_2 => '#5a4f3e', | |
| 96 | text_3 => '#8a7f6c', | |
| 97 | accent => '#c2410c', | |
| 98 | accent_2 => '#d97757', | |
| 99 | accent_deep => '#9a3412', | |
| 100 | accent_glow => 'rgba(194,65,12,0.20)', | |
| 101 | }, | |
| 102 | { | |
| 103 | id => 4, | |
| 104 | slug => 'mecha', | |
| 105 | name => 'Mecha', | |
| 106 | tagline => 'Neon cyan on void-black. Cyberpunk, sci-fi mechs, futurist tech parts.', | |
| 107 | bg => '#02060a', | |
| 108 | bg_2 => '#050b14', | |
| 109 | surface => '#0a131f', | |
| 110 | surface_2 => '#101c2e', | |
| 111 | border => '#1a3046', | |
| 112 | border_2 => '#2a4a6e', | |
| 113 | text => '#e6f8ff', | |
| 114 | text_2 => '#7fb8d6', | |
| 115 | text_3 => '#4a7d96', | |
| 116 | accent => '#22d3ee', | |
| 117 | accent_2 => '#10b981', | |
| 118 | accent_deep => '#0e7490', | |
| 119 | accent_glow => 'rgba(34,211,238,0.40)', | |
| 120 | }, | |
| 121 | { | |
| 122 | id => 5, | |
| 123 | slug => 'tabletop', | |
| 124 | name => 'Tabletop', | |
| 125 | tagline => 'Old-leather red & parchment gold. Classic D&D, wargame & RPG energy.', | |
| 126 | bg => '#1a0e0a', | |
| 127 | bg_2 => '#22130d', | |
| 128 | surface => '#2c1a13', | |
| 129 | surface_2 => '#38221a', | |
| 130 | border => '#5a3324', | |
| 131 | border_2 => '#7a4632', | |
| 132 | text => '#f4e8d0', | |
| 133 | text_2 => '#c8b08a', | |
| 134 | text_3 => '#8c7458', | |
| 135 | accent => '#dc2626', | |
| 136 | accent_2 => '#d97706', | |
| 137 | accent_deep => '#991b1b', | |
| 138 | accent_glow => 'rgba(220,38,38,0.35)', | |
| 139 | }, | |
| 140 | { | |
| 141 | id => 6, | |
| 142 | slug => 'cottage', | |
| 143 | name => 'Cottage', | |
| 144 | tagline => 'Soft sage & cream. Cute, whimsical, handcrafted — kawaii prints & cozy minis.', | |
| 145 | bg => '#1c1f1a', | |
| 146 | bg_2 => '#24281f', | |
| 147 | surface => '#2e3327', | |
| 148 | surface_2 => '#3a4030', | |
| 149 | border => '#4a5240', | |
| 150 | border_2 => '#65705a', | |
| 151 | text => '#f5f0e1', | |
| 152 | text_2 => '#c4c0a8', | |
| 153 | text_3 => '#8a8770', | |
| 154 | accent => '#84cc16', | |
| 155 | accent_2 => '#facc15', | |
| 156 | accent_deep => '#4d7c0f', | |
| 157 | accent_glow => 'rgba(132,204,22,0.35)', | |
| 158 | }, | |
| 159 | { | |
| 160 | id => 7, | |
| 161 | slug => 'midnight', | |
| 162 | name => 'Midnight', | |
| 163 | tagline => 'Deep blue & silver. Calm, premium, late-night focus.', | |
| 164 | bg => '#0a0e1a', | |
| 165 | bg_2 => '#10172a', | |
| 166 | surface => '#171f36', | |
| 167 | surface_2 => '#1f2a44', | |
| 168 | border => '#283c66', | |
| 169 | border_2 => '#3b5394', | |
| 170 | text => '#e2e8f8', | |
| 171 | text_2 => '#94a3c8', | |
| 172 | text_3 => '#64708e', | |
| 173 | accent => '#6366f1', | |
| 174 | accent_2 => '#a78bfa', | |
| 175 | accent_deep => '#4338ca', | |
| 176 | accent_glow => 'rgba(99,102,241,0.30)', | |
| 177 | }, | |
| 178 | { | |
| 179 | id => 8, | |
| 180 | slug => 'ember', | |
| 181 | name => 'Ember', | |
| 182 | tagline => 'Warm amber on charcoal. Cozy bookshop, fireplace energy.', | |
| 183 | bg => '#1a120a', | |
| 184 | bg_2 => '#22180e', | |
| 185 | surface => '#2c2014', | |
| 186 | surface_2 => '#36271a', | |
| 187 | border => '#4d3922', | |
| 188 | border_2 => '#6e5532', | |
| 189 | text => '#f5e8d4', | |
| 190 | text_2 => '#c8a878', | |
| 191 | text_3 => '#8c7448', | |
| 192 | accent => '#ea580c', | |
| 193 | accent_2 => '#f59e0b', | |
| 194 | accent_deep => '#9a3412', | |
| 195 | accent_glow => 'rgba(234,88,12,0.30)', | |
| 196 | }, | |
| 197 | { | |
| 198 | id => 9, | |
| 199 | slug => 'ink', | |
| 200 | name => 'Ink', | |
| 201 | tagline => 'High-contrast black & white with a single red. Editorial, magazine-bold.', | |
| 202 | bg => '#0a0a0a', | |
| 203 | bg_2 => '#141414', | |
| 204 | surface => '#1c1c1c', | |
| 205 | surface_2 => '#262626', | |
| 206 | border => '#333333', | |
| 207 | border_2 => '#525252', | |
| 208 | text => '#fafafa', | |
| 209 | text_2 => '#a3a3a3', | |
| 210 | text_3 => '#737373', | |
| 211 | accent => '#ef4444', | |
| 212 | accent_2 => '#fafafa', | |
| 213 | accent_deep => '#b91c1c', | |
| 214 | accent_glow => 'rgba(239,68,68,0.35)', | |
| 215 | }, | |
| 216 | { | |
| 217 | id => 10, | |
| 218 | slug => 'nebula', | |
| 219 | name => 'Nebula', | |
| 220 | tagline => 'Deep space purples & magenta. Dreamy, cosmic, otherworldly.', | |
| 221 | bg => '#0d0617', | |
| 222 | bg_2 => '#160a23', | |
| 223 | surface => '#1f1130', | |
| 224 | surface_2 => '#2a173f', | |
| 225 | border => '#3d2459', | |
| 226 | border_2 => '#5a3a82', | |
| 227 | text => '#f3e8ff', | |
| 228 | text_2 => '#c4a8e0', | |
| 229 | text_3 => '#8a6da8', | |
| 230 | accent => '#d946ef', | |
| 231 | accent_2 => '#8b5cf6', | |
| 232 | accent_deep => '#86198f', | |
| 233 | accent_glow => 'rgba(217,70,239,0.35)', | |
| 234 | }, | |
| 235 | { | |
| 236 | id => 11, | |
| 237 | slug => 'parchment', | |
| 238 | name => 'Parchment', | |
| 239 | tagline => 'Warm cream paper with sepia ink. Light theme, vintage map vibes.', | |
| 240 | bg => '#f4ead5', | |
| 241 | bg_2 => '#ebdfc4', | |
| 242 | surface => '#fdf6e3', | |
| 243 | surface_2 => '#f0e3c8', | |
| 244 | border => '#cdb992', | |
| 245 | border_2 => '#a89570', | |
| 246 | text => '#2a1f10', | |
| 247 | text_2 => '#5c4a30', | |
| 248 | text_3 => '#8a7754', | |
| 249 | accent => '#92400e', | |
| 250 | accent_2 => '#b45309', | |
| 251 | accent_deep => '#78350f', | |
| 252 | accent_glow => 'rgba(146,64,14,0.20)', | |
| 253 | }, | |
| 254 | { | |
| 255 | id => 12, | |
| 256 | slug => 'reef', | |
| 257 | name => 'Reef', | |
| 258 | tagline => 'Teal & coral. Tropical, energetic, summer vacation feel.', | |
| 259 | bg => '#062029', | |
| 260 | bg_2 => '#0a2c38', | |
| 261 | surface => '#103847', | |
| 262 | surface_2 => '#15485b', | |
| 263 | border => '#1f6075', | |
| 264 | border_2 => '#2e8094', | |
| 265 | text => '#e0f5fa', | |
| 266 | text_2 => '#88c4d4', | |
| 267 | text_3 => '#5290a0', | |
| 268 | accent => '#fb7185', | |
| 269 | accent_2 => '#14b8a6', | |
| 270 | accent_deep => '#9f1239', | |
| 271 | accent_glow => 'rgba(251,113,133,0.35)', | |
| 272 | }, | |
| 273 | ||
| 274 | # --- Subtle / sophisticated palettes --- | |
| 275 | { | |
| 276 | id => 13, | |
| 277 | slug => 'stone', | |
| 278 | name => 'Stone', | |
| 279 | tagline => 'Warm neutrals with a bronze accent. Quiet, refined, gallery-grade.', | |
| 280 | bg => '#1a1815', | |
| 281 | bg_2 => '#221f1a', | |
| 282 | surface => '#2a2620', | |
| 283 | surface_2 => '#34302a', | |
| 284 | border => '#463f35', | |
| 285 | border_2 => '#635a4d', | |
| 286 | text => '#ebe4d6', | |
| 287 | text_2 => '#aea189', | |
| 288 | text_3 => '#7a6f5a', | |
| 289 | accent => '#b08968', | |
| 290 | accent_2 => '#d4a574', | |
| 291 | accent_deep => '#7a5a40', | |
| 292 | accent_glow => 'rgba(176,137,104,0.30)', | |
| 293 | }, | |
| 294 | { | |
| 295 | id => 14, | |
| 296 | slug => 'pine', | |
| 297 | name => 'Pine', | |
| 298 | tagline => 'Deep forest green with sage highlights. Calm, natural, grounded.', | |
| 299 | bg => '#0a1612', | |
| 300 | bg_2 => '#0f1d18', | |
| 301 | surface => '#14261f', | |
| 302 | surface_2 => '#1b3127', | |
| 303 | border => '#2c4a3d', | |
| 304 | border_2 => '#406a55', | |
| 305 | text => '#e8efe6', | |
| 306 | text_2 => '#99b3a3', | |
| 307 | text_3 => '#5a7a6a', | |
| 308 | accent => '#4a8f5e', | |
| 309 | accent_2 => '#6dab6e', | |
| 310 | accent_deep => '#2d5a3a', | |
| 311 | accent_glow => 'rgba(74,143,94,0.30)', | |
| 312 | }, | |
| 313 | { | |
| 314 | id => 15, | |
| 315 | slug => 'obsidian', | |
| 316 | name => 'Obsidian', | |
| 317 | tagline => 'Near-black with antique gold. The black-tie palette.', | |
| 318 | bg => '#0a0a0a', | |
| 319 | bg_2 => '#121212', | |
| 320 | surface => '#1a1a1a', | |
| 321 | surface_2 => '#232323', | |
| 322 | border => '#2e2e2e', | |
| 323 | border_2 => '#444444', | |
| 324 | text => '#e8e1cd', | |
| 325 | text_2 => '#a89b78', | |
| 326 | text_3 => '#6e6450', | |
| 327 | accent => '#c8a951', | |
| 328 | accent_2 => '#d4b962', | |
| 329 | accent_deep => '#8a7530', | |
| 330 | accent_glow => 'rgba(200,169,81,0.30)', | |
| 331 | }, | |
| 332 | { | |
| 333 | id => 16, | |
| 334 | slug => 'linen', | |
| 335 | name => 'Linen', | |
| 336 | tagline => 'Soft cream with dusty blue. A light, airy, daylight palette.', | |
| 337 | bg => '#f4f0e8', | |
| 338 | bg_2 => '#ebe4d6', | |
| 339 | surface => '#ffffff', | |
| 340 | surface_2 => '#f4f0e8', | |
| 341 | border => '#d4cab8', | |
| 342 | border_2 => '#b3a890', | |
| 343 | text => '#1f2030', | |
| 344 | text_2 => '#565d6e', | |
| 345 | text_3 => '#8a8f9c', | |
| 346 | accent => '#4a6488', | |
| 347 | accent_2 => '#6e88ab', | |
| 348 | accent_deep => '#2c4566', | |
| 349 | accent_glow => 'rgba(74,100,136,0.20)', | |
| 350 | }, | |
| 351 | ||
| 352 | # --- Eye-catching palettes --- | |
| 353 | { | |
| 354 | id => 17, | |
| 355 | slug => 'sunset', | |
| 356 | name => 'Sunset', | |
| 357 | tagline => 'Hot pink fading into tangerine. Warm, vivid, golden-hour energy.', | |
| 358 | bg => '#1a0a14', | |
| 359 | bg_2 => '#25101a', | |
| 360 | surface => '#321524', | |
| 361 | surface_2 => '#401d30', | |
| 362 | border => '#5e2945', | |
| 363 | border_2 => '#823a5d', | |
| 364 | text => '#ffe8f0', | |
| 365 | text_2 => '#d090a5', | |
| 366 | text_3 => '#8c5d70', | |
| 367 | accent => '#ff5e8a', | |
| 368 | accent_2 => '#ff9a3d', | |
| 369 | accent_deep => '#c4204e', | |
| 370 | accent_glow => 'rgba(255,94,138,0.40)', | |
| 371 | }, | |
| 372 | { | |
| 373 | id => 18, | |
| 374 | slug => 'bubblegum', | |
| 375 | name => 'Bubblegum', | |
| 376 | tagline => 'Hot pink with cool mint. Playful, sweet, kawaii pop.', | |
| 377 | bg => '#1a0d18', | |
| 378 | bg_2 => '#221423', | |
| 379 | surface => '#2d1a30', | |
| 380 | surface_2 => '#3d2542', | |
| 381 | border => '#543663', | |
| 382 | border_2 => '#74508a', | |
| 383 | text => '#fce5f5', | |
| 384 | text_2 => '#c590c8', | |
| 385 | text_3 => '#8a5d8e', | |
| 386 | accent => '#ff6bb5', | |
| 387 | accent_2 => '#6dffce', | |
| 388 | accent_deep => '#c43d83', | |
| 389 | accent_glow => 'rgba(255,107,181,0.40)', | |
| 390 | }, | |
| 391 | { | |
| 392 | id => 19, | |
| 393 | slug => 'volt', | |
| 394 | name => 'Volt', | |
| 395 | tagline => 'Electric yellow on jet black. High-energy, sportsbar-bright.', | |
| 396 | bg => '#050505', | |
| 397 | bg_2 => '#0d0d0d', | |
| 398 | surface => '#161616', | |
| 399 | surface_2 => '#1f1f1f', | |
| 400 | border => '#2a2a2a', | |
| 401 | border_2 => '#3d3d3d', | |
| 402 | text => '#f0f5e0', | |
| 403 | text_2 => '#a8b088', | |
| 404 | text_3 => '#686e55', | |
| 405 | accent => '#d4ff00', | |
| 406 | accent_2 => '#a3f000', | |
| 407 | accent_deep => '#92b800', | |
| 408 | accent_glow => 'rgba(212,255,0,0.40)', | |
| 409 | }, | |
| 410 | { | |
| 411 | id => 20, | |
| 412 | slug => 'inferno', | |
| 413 | name => 'Inferno', | |
| 414 | tagline => 'Deep red bleeding into orange. Hot, urgent, fire-engine intensity.', | |
| 415 | bg => '#0d0303', | |
| 416 | bg_2 => '#160606', | |
| 417 | surface => '#1f0a0a', | |
| 418 | surface_2 => '#2c1010', | |
| 419 | border => '#4a1818', | |
| 420 | border_2 => '#6e2828', | |
| 421 | text => '#fce8d6', | |
| 422 | text_2 => '#d49980', | |
| 423 | text_3 => '#8a5f48', | |
| 424 | accent => '#ff3300', | |
| 425 | accent_2 => '#ff8800', | |
| 426 | accent_deep => '#b22000', | |
| 427 | accent_glow => 'rgba(255,51,0,0.40)', | |
| 428 | }, | |
| 429 | ||
| 430 | # --- Wow / bold palettes --- | |
| 431 | { | |
| 432 | id => 21, | |
| 433 | slug => 'vapor', | |
| 434 | name => 'Vapor', | |
| 435 | tagline => 'Magenta and cyan on void purple. Synthwave, neon, 80s arcade.', | |
| 436 | bg => '#0a061a', | |
| 437 | bg_2 => '#100a25', | |
| 438 | surface => '#1a1135', | |
| 439 | surface_2 => '#251d4a', | |
| 440 | border => '#382c66', | |
| 441 | border_2 => '#4d3a8c', | |
| 442 | text => '#ecdef9', | |
| 443 | text_2 => '#a890d8', | |
| 444 | text_3 => '#6c5a99', | |
| 445 | accent => '#ff42db', | |
| 446 | accent_2 => '#00d8ff', | |
| 447 | accent_deep => '#b315a8', | |
| 448 | accent_glow => 'rgba(255,66,219,0.45)', | |
| 449 | }, | |
| 450 | { | |
| 451 | id => 22, | |
| 452 | slug => 'royale', | |
| 453 | name => 'Royale', | |
| 454 | tagline => 'Royal purple with gilded gold. Regal, opulent, throne-room luxury.', | |
| 455 | bg => '#0e081a', | |
| 456 | bg_2 => '#160e26', | |
| 457 | surface => '#1f1432', | |
| 458 | surface_2 => '#2a1c45', | |
| 459 | border => '#3e2c5e', | |
| 460 | border_2 => '#5d4385', | |
| 461 | text => '#f0e8fa', | |
| 462 | text_2 => '#b89cd6', | |
| 463 | text_3 => '#7a6190', | |
| 464 | accent => '#d4af37', | |
| 465 | accent_2 => '#b884d8', | |
| 466 | accent_deep => '#8a6f1f', | |
| 467 | accent_glow => 'rgba(212,175,55,0.30)', | |
| 468 | }, | |
| 469 | { | |
| 470 | id => 23, | |
| 471 | slug => 'acid', | |
| 472 | name => 'Acid', | |
| 473 | tagline => 'Lime against magenta. Loud, clashing, deliberately uncomfortable.', | |
| 474 | bg => '#060808', | |
| 475 | bg_2 => '#0c1010', | |
| 476 | surface => '#131818', | |
| 477 | surface_2 => '#1d2424', | |
| 478 | border => '#283030', | |
| 479 | border_2 => '#3a4444', | |
| 480 | text => '#e8fff0', | |
| 481 | text_2 => '#88b09c', | |
| 482 | text_3 => '#4d6a5e', | |
| 483 | accent => '#c0ff00', | |
| 484 | accent_2 => '#ff00aa', | |
| 485 | accent_deep => '#88b800', | |
| 486 | accent_glow => 'rgba(192,255,0,0.40)', | |
| 487 | }, | |
| 488 | { | |
| 489 | id => 24, | |
| 490 | slug => 'aurora', | |
| 491 | name => 'Aurora', | |
| 492 | tagline => 'Emerald green and lavender on midnight sky. True northern-lights.', | |
| 493 | bg => '#050818', | |
| 494 | bg_2 => '#0a1024', | |
| 495 | surface => '#101632', | |
| 496 | surface_2 => '#181f44', | |
| 497 | border => '#26305c', | |
| 498 | border_2 => '#3a4880', | |
| 499 | text => '#e8f0fa', | |
| 500 | text_2 => '#9fb3d6', | |
| 501 | text_3 => '#647590', | |
| 502 | accent => '#00e88c', | |
| 503 | accent_2 => '#c084fc', | |
| 504 | accent_deep => '#00a85f', | |
| 505 | accent_glow => 'rgba(0,232,140,0.40)', | |
| 506 | }, | |
| 507 | ||
| 508 | # --- Subtle palettes --- | |
| 509 | { | |
| 510 | id => 25, | |
| 511 | slug => 'slate', | |
| 512 | name => 'Slate', | |
| 513 | tagline => 'Cool industrial grey with muted steel blue. Quietly confident.', | |
| 514 | bg => '#0f1318', | |
| 515 | bg_2 => '#161b22', | |
| 516 | surface => '#1d242e', | |
| 517 | surface_2 => '#262e3a', | |
| 518 | border => '#363f4d', | |
| 519 | border_2 => '#4a5566', | |
| 520 | text => '#e6ebf2', | |
| 521 | text_2 => '#9aa5b3', | |
| 522 | text_3 => '#5e6878', | |
| 523 | accent => '#5b8def', | |
| 524 | accent_2 => '#8fa8d4', | |
| 525 | accent_deep => '#3a6cc4', | |
| 526 | accent_glow => 'rgba(91,141,239,0.25)', | |
| 527 | }, | |
| 528 | { | |
| 529 | id => 26, | |
| 530 | slug => 'wheat', | |
| 531 | name => 'Wheat', | |
| 532 | tagline => 'Warm wheat and pale gold. Field-at-harvest, calming neutral light tone.', | |
| 533 | bg => '#f5efe2', | |
| 534 | bg_2 => '#ebe3d0', | |
| 535 | surface => '#fdf9ef', | |
| 536 | surface_2 => '#f0e9d4', | |
| 537 | border => '#d4c79e', | |
| 538 | border_2 => '#a89770', | |
| 539 | text => '#2a2418', | |
| 540 | text_2 => '#5c5238', | |
| 541 | text_3 => '#897d5a', | |
| 542 | accent => '#a8783f', | |
| 543 | accent_2 => '#c8985c', | |
| 544 | accent_deep => '#7d5a2c', | |
| 545 | accent_glow => 'rgba(168,120,63,0.22)', | |
| 546 | }, | |
| 547 | { | |
| 548 | id => 27, | |
| 549 | slug => 'granite', | |
| 550 | name => 'Granite', | |
| 551 | tagline => 'Speckled stone grey with a charcoal accent. Architectural, weighty.', | |
| 552 | bg => '#15171a', | |
| 553 | bg_2 => '#1d1f23', | |
| 554 | surface => '#26282d', | |
| 555 | surface_2 => '#30333a', | |
| 556 | border => '#3e424a', | |
| 557 | border_2 => '#555a64', | |
| 558 | text => '#e4e6ea', | |
| 559 | text_2 => '#9da3ad', | |
| 560 | text_3 => '#646b75', | |
| 561 | accent => '#7a8590', | |
| 562 | accent_2 => '#a8b3c0', | |
| 563 | accent_deep => '#525c66', | |
| 564 | accent_glow => 'rgba(122,133,144,0.30)', | |
| 565 | }, | |
| 566 | { | |
| 567 | id => 28, | |
| 568 | slug => 'mist', | |
| 569 | name => 'Mist', | |
| 570 | tagline => 'Soft blue-grey morning. Calm, foggy, contemplative light theme.', | |
| 571 | bg => '#eef2f6', | |
| 572 | bg_2 => '#dde5ed', | |
| 573 | surface => '#ffffff', | |
| 574 | surface_2 => '#e6ecf4', | |
| 575 | border => '#c2cdd9', | |
| 576 | border_2 => '#9caab8', | |
| 577 | text => '#1d2733', | |
| 578 | text_2 => '#56616e', | |
| 579 | text_3 => '#8593a2', | |
| 580 | accent => '#5882a8', | |
| 581 | accent_2 => '#7ca0c4', | |
| 582 | accent_deep => '#3a5e80', | |
| 583 | accent_glow => 'rgba(88,130,168,0.20)', | |
| 584 | }, | |
| 585 | ||
| 586 | # --- Eye-catching palettes --- | |
| 587 | { | |
| 588 | id => 29, | |
| 589 | slug => 'mango', | |
| 590 | name => 'Mango', | |
| 591 | tagline => 'Ripe mango orange and hot pink. Tropical, fresh-squeezed energy.', | |
| 592 | bg => '#1a0f0a', | |
| 593 | bg_2 => '#23150d', | |
| 594 | surface => '#2e1c12', | |
| 595 | surface_2 => '#3c241a', | |
| 596 | border => '#5a3525', | |
| 597 | border_2 => '#824c34', | |
| 598 | text => '#ffeed8', | |
| 599 | text_2 => '#d4a07a', | |
| 600 | text_3 => '#8c6748', | |
| 601 | accent => '#ff8a3d', | |
| 602 | accent_2 => '#ff4d8a', | |
| 603 | accent_deep => '#c25a18', | |
| 604 | accent_glow => 'rgba(255,138,61,0.40)', | |
| 605 | }, | |
| 606 | { | |
| 607 | id => 30, | |
| 608 | slug => 'coral', | |
| 609 | name => 'Coral', | |
| 610 | tagline => 'Coral pink with peach blush. Warm, friendly, beach-house energy.', | |
| 611 | bg => '#1a1112', | |
| 612 | bg_2 => '#23161a', | |
| 613 | surface => '#301d22', | |
| 614 | surface_2 => '#3e262d', | |
| 615 | border => '#5a3942', | |
| 616 | border_2 => '#7e525d', | |
| 617 | text => '#ffe5e0', | |
| 618 | text_2 => '#d49aa0', | |
| 619 | text_3 => '#8a6068', | |
| 620 | accent => '#ff7e6b', | |
| 621 | accent_2 => '#ffb98a', | |
| 622 | accent_deep => '#c44a3a', | |
| 623 | accent_glow => 'rgba(255,126,107,0.35)', | |
| 624 | }, | |
| 625 | { | |
| 626 | id => 31, | |
| 627 | slug => 'iris', | |
| 628 | name => 'Iris', | |
| 629 | tagline => 'Violet petal and periwinkle. Soft floral, dreamy, garden party.', | |
| 630 | bg => '#10091e', | |
| 631 | bg_2 => '#170f2c', | |
| 632 | surface => '#1e1640', | |
| 633 | surface_2 => '#272055', | |
| 634 | border => '#3a2f78', | |
| 635 | border_2 => '#5040a8', | |
| 636 | text => '#ece4ff', | |
| 637 | text_2 => '#b4a8e0', | |
| 638 | text_3 => '#7e6ea8', | |
| 639 | accent => '#8b5cf6', | |
| 640 | accent_2 => '#a3aaff', | |
| 641 | accent_deep => '#6028d4', | |
| 642 | accent_glow => 'rgba(139,92,246,0.40)', | |
| 643 | }, | |
| 644 | { | |
| 645 | id => 32, | |
| 646 | slug => 'magma', | |
| 647 | name => 'Magma', | |
| 648 | tagline => 'Molten orange cracking through black rock. Volcanic, primal heat.', | |
| 649 | bg => '#0a0606', | |
| 650 | bg_2 => '#150a08', | |
| 651 | surface => '#1f0f0c', | |
| 652 | surface_2 => '#2c1612', | |
| 653 | border => '#4a2218', | |
| 654 | border_2 => '#7a3d2a', | |
| 655 | text => '#fce0d0', | |
| 656 | text_2 => '#d49680', | |
| 657 | text_3 => '#8a5d48', | |
| 658 | accent => '#ff5722', | |
| 659 | accent_2 => '#ffaa00', | |
| 660 | accent_deep => '#a82912', | |
| 661 | accent_glow => 'rgba(255,87,34,0.50)', | |
| 662 | }, | |
| 663 | ||
| 664 | # --- Wow / specialty palettes --- | |
| 665 | { | |
| 666 | id => 33, | |
| 667 | slug => 'dragon', | |
| 668 | name => 'Dragon', | |
| 669 | tagline => 'Emerald scales, ruby flame, antique gold. Three-color heroic fantasy.', | |
| 670 | bg => '#0a0d0c', | |
| 671 | bg_2 => '#101513', | |
| 672 | surface => '#181f1c', | |
| 673 | surface_2 => '#222a25', | |
| 674 | border => '#324036', | |
| 675 | border_2 => '#4d5e44', | |
| 676 | text => '#f0e8d0', | |
| 677 | text_2 => '#a8b09a', | |
| 678 | text_3 => '#6d7565', | |
| 679 | accent => '#10b981', | |
| 680 | accent_2 => '#dc2626', | |
| 681 | accent_deep => '#0d8a60', | |
| 682 | accent_glow => 'rgba(16,185,129,0.40)', | |
| 683 | }, | |
| 684 | { | |
| 685 | id => 34, | |
| 686 | slug => 'frost', | |
| 687 | name => 'Frost', | |
| 688 | tagline => 'Icy white-blue with crystal cyan. Glacial, sharp, winter premium.', | |
| 689 | bg => '#06121e', | |
| 690 | bg_2 => '#0a1c30', | |
| 691 | surface => '#10283f', | |
| 692 | surface_2 => '#163454', | |
| 693 | border => '#244766', | |
| 694 | border_2 => '#3a6890', | |
| 695 | text => '#e8f4ff', | |
| 696 | text_2 => '#a0c4e0', | |
| 697 | text_3 => '#6090b0', | |
| 698 | accent => '#7dd3fc', | |
| 699 | accent_2 => '#bae6fd', | |
| 700 | accent_deep => '#0891b2', | |
| 701 | accent_glow => 'rgba(125,211,252,0.45)', | |
| 702 | }, | |
| 703 | { | |
| 704 | id => 35, | |
| 705 | slug => 'phantom', | |
| 706 | name => 'Phantom', | |
| 707 | tagline => 'Dark purple bleeding into ghostly pink-white. Ethereal, dreamlike, occult.', | |
| 708 | bg => '#0a0612', | |
| 709 | bg_2 => '#120920', | |
| 710 | surface => '#1c1030', | |
| 711 | surface_2 => '#2a1845', | |
| 712 | border => '#3e2a66', | |
| 713 | border_2 => '#5a428a', | |
| 714 | text => '#f8e8ff', | |
| 715 | text_2 => '#c8a8d4', | |
| 716 | text_3 => '#8a6e9e', | |
| 717 | accent => '#f0abfc', | |
| 718 | accent_2 => '#a78bfa', | |
| 719 | accent_deep => '#a855f7', | |
| 720 | accent_glow => 'rgba(240,171,252,0.40)', | |
| 721 | }, | |
| 722 | { | |
| 723 | id => 36, | |
| 724 | slug => 'toxic', | |
| 725 | name => 'Toxic', | |
| 726 | tagline => 'Radioactive green and caution yellow. Hazmat-warning, biohazard intense.', | |
| 727 | bg => '#080a06', | |
| 728 | bg_2 => '#0e1208', | |
| 729 | surface => '#161c0c', | |
| 730 | surface_2 => '#1e2810', | |
| 731 | border => '#2c3a18', | |
| 732 | border_2 => '#445824', | |
| 733 | text => '#ecffd0', | |
| 734 | text_2 => '#a4c078', | |
| 735 | text_3 => '#688048', | |
| 736 | accent => '#84e000', | |
| 737 | accent_2 => '#fde047', | |
| 738 | accent_deep => '#5a9000', | |
| 739 | accent_glow => 'rgba(132,224,0,0.45)', | |
| 740 | }, | |
| 741 | ); | |
| 742 | ||
| 743 | #---------------------------------------------------------------------- | |
| 744 | # OO interface (so .cgi handlers can do MODS::WebSTLs::Themes->new) | |
| 745 | #---------------------------------------------------------------------- | |
| 746 | sub new { return bless({}, shift) } | |
| 747 | ||
| 748 | # Return the full theme list (array of hashrefs). For the picker UI. | |
| 749 | sub all { | |
| 750 | return [ map { { %$_ } } @THEMES ]; # shallow copies | |
| 751 | } | |
| 752 | ||
| 753 | # Look up one theme by id. Falls back to theme #1 if id is unknown | |
| 754 | # so the buyer page never renders an "untheamed" storefront. | |
| 755 | sub by_id { | |
| 756 | my ($self, $id) = @_; | |
| 757 | $id ||= 0; | |
| 758 | $id =~ s/[^0-9]//g; | |
| 759 | foreach my $t (@THEMES) { | |
| 760 | return { %$t } if $t->{id} == $id; | |
| 761 | } | |
| 762 | return { %{ $THEMES[0] } }; | |
| 763 | } | |
| 764 | ||
| 765 | # Load a user's custom theme row by primary key. Returns the same | |
| 766 | # hashref shape as the built-in themes so css_vars()/thumbnail_html() | |
| 767 | # work on either source without branching at every call site. | |
| 768 | # Returns undef if the id doesn't exist. | |
| 769 | # | |
| 770 | # Note: DB columns avoid the SQL reserved words `border` and `text` by | |
| 771 | # prefixing with `c` (cborder, cborder_2, ctext, ctext_*). We unmap | |
| 772 | # them back to the canonical theme-hash key names here. | |
| 773 | sub custom_by_id { | |
| 774 | my ($self, $db, $dbh, $DB, $id) = @_; | |
| 775 | $id ||= 0; $id =~ s/[^0-9]//g; | |
| 776 | return undef unless $id; | |
| 777 | my $row = $db->db_readwrite($dbh, qq~ | |
| 778 | SELECT id, name, bg, bg_2, surface, surface_2, | |
| 779 | cborder AS border, cborder_2 AS border_2, | |
| 780 | ctext AS text, ctext_2 AS text_2, ctext_3 AS text_3, | |
| 781 | accent, accent_2, accent_deep, accent_glow | |
| 782 | FROM `${DB}`.storefront_custom_themes | |
| 783 | WHERE id='$id' | |
| 784 | LIMIT 1 | |
| 785 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 786 | return undef unless $row && $row->{id}; | |
| 787 | $row->{slug} = 'custom'; | |
| 788 | $row->{tagline} = 'Custom theme'; | |
| 789 | $row->{is_custom} = 1; | |
| 790 | return $row; | |
| 791 | } | |
| 792 | ||
| 793 | # One-call lookup: given a storefront row, return the theme that should | |
| 794 | # render. If the storefront has a custom_theme_id set, that wins; | |
| 795 | # otherwise fall back to the built-in theme_id; otherwise the default. | |
| 796 | sub resolve_for_storefront { | |
| 797 | my ($self, $store, $db, $dbh, $DB) = @_; | |
| 798 | $store ||= {}; | |
| 799 | if ($store->{custom_theme_id} && $db && $dbh) { | |
| 800 | my $t = $self->custom_by_id($db, $dbh, $DB, $store->{custom_theme_id}); | |
| 801 | return $t if $t; | |
| 802 | } | |
| 803 | return $self->by_id($store->{theme_id} || 1); | |
| 804 | } | |
| 805 | ||
| 806 | # Build a tiny mock-storefront HTML thumbnail using only the theme's | |
| 807 | # colors. Shown in the editor's theme picker so a creator can see what | |
| 808 | # their store will look like before they apply the theme. Self-contained | |
| 809 | # inline styles — no extra CSS to load. | |
| 810 | sub thumbnail_html { | |
| 811 | my ($self, $theme) = @_; | |
| 812 | $theme ||= $THEMES[0]; | |
| 813 | my $bg = $theme->{bg}; | |
| 814 | my $bg_2 = $theme->{bg_2}; | |
| 815 | my $sf = $theme->{surface}; | |
| 816 | my $bdr = $theme->{border}; | |
| 817 | my $txt = $theme->{text}; | |
| 818 | my $txt_2 = $theme->{text_2}; | |
| 819 | my $a1 = $theme->{accent}; | |
| 820 | my $a2 = $theme->{accent_2}; | |
| 821 | my $grad = "linear-gradient(135deg, $a1 0%, $a2 100%)"; | |
| 822 | return <<"HTML"; | |
| 823 | <div style="background:$bg;padding:10px;height:100%;display:flex;flex-direction:column;gap:6px;font-family:system-ui,sans-serif"> | |
| 824 | <div style="background:$bg_2;border-bottom:1px solid $bdr;padding:6px 8px;display:flex;align-items:center;gap:6px;border-radius:4px"> | |
| 825 | <span style="width:16px;height:16px;border-radius:4px;background:$grad;flex-shrink:0"></span> | |
| 826 | <span style="height:6px;width:38px;background:$txt;opacity:0.85;border-radius:2px"></span> | |
| 827 | <span style="margin-left:auto;display:flex;gap:5px"> | |
| 828 | <span style="height:4px;width:14px;background:$txt_2;opacity:0.6;border-radius:1px"></span> | |
| 829 | <span style="height:4px;width:14px;background:$txt_2;opacity:0.6;border-radius:1px"></span> | |
| 830 | <span style="height:4px;width:14px;background:$txt_2;opacity:0.6;border-radius:1px"></span> | |
| 831 | </span> | |
| 832 | </div> | |
| 833 | <div style="display:flex;flex-direction:column;gap:4px;padding:2px 4px"> | |
| 834 | <span style="height:7px;width:70%;background:$txt;border-radius:2px"></span> | |
| 835 | <span style="height:7px;width:55%;background:$grad;border-radius:2px"></span> | |
| 836 | <span style="height:10px;width:46px;background:$grad;border-radius:3px;margin-top:4px"></span> | |
| 837 | </div> | |
| 838 | <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;margin-top:auto"> | |
| 839 | <div style="background:$sf;border:1px solid $bdr;border-radius:3px;height:30px;position:relative"> | |
| 840 | <span style="position:absolute;bottom:3px;left:3px;height:3px;width:60%;background:$txt;opacity:0.7;border-radius:1px"></span> | |
| 841 | </div> | |
| 842 | <div style="background:$sf;border:1px solid $bdr;border-radius:3px;height:30px;position:relative"> | |
| 843 | <span style="position:absolute;bottom:3px;left:3px;height:3px;width:60%;background:$txt;opacity:0.7;border-radius:1px"></span> | |
| 844 | </div> | |
| 845 | <div style="background:$sf;border:1px solid $bdr;border-radius:3px;height:30px;position:relative"> | |
| 846 | <span style="position:absolute;bottom:3px;left:3px;height:3px;width:60%;background:$txt;opacity:0.7;border-radius:1px"></span> | |
| 847 | </div> | |
| 848 | </div> | |
| 849 | </div> | |
| 850 | HTML | |
| 851 | } | |
| 852 | ||
| 853 | # Build a CSS variable block ready to drop into a <style> tag. | |
| 854 | # Used by store.cgi to override the page's :root vars per storefront. | |
| 855 | sub css_vars { | |
| 856 | my ($self, $theme) = @_; | |
| 857 | $theme ||= $THEMES[0]; | |
| 858 | return join("\n", | |
| 859 | " --col-bg: $theme->{bg};", | |
| 860 | " --col-bg-2: $theme->{bg_2};", | |
| 861 | " --col-surface-1: $theme->{surface};", | |
| 862 | " --col-surface-2: $theme->{surface_2};", | |
| 863 | " --col-surface-3: $theme->{surface_2};", | |
| 864 | " --col-border: $theme->{border};", | |
| 865 | " --col-border-2: $theme->{border_2};", | |
| 866 | " --col-text: $theme->{text};", | |
| 867 | " --col-text-2: $theme->{text_2};", | |
| 868 | " --col-text-3: $theme->{text_3};", | |
| 869 | " --col-text-dim: $theme->{text_3};", | |
| 870 | " --col-accent: $theme->{accent};", | |
| 871 | " --col-accent-deep: $theme->{accent_deep};", | |
| 872 | " --col-accent-bright:$theme->{accent_2};", | |
| 873 | " --col-accent-glow: $theme->{accent_glow};", | |
| 874 | ); | |
| 875 | } | |
| 876 | ||
| 877 | 1; |