@@ -295,7 +295,7 @@ defmodule OgImageWeb.ImageHTML do
295295 @ doc """
296296 A logo and text for Sprites on a dark background with halftone pattern.
297297 """
298- def sprites ( assigns ) do
298+ def sprites_halftone ( assigns ) do
299299 ~H"""
300300 < body class = "bg-sprites flex h-screen relative overflow-hidden " >
301301 <!-- Violet halftone pattern layer - diagonal mask from top-right -->
@@ -357,6 +357,144 @@ defmodule OgImageWeb.ImageHTML do
357357 """
358358 end
359359
360+ # -- Shared Sprites building blocks ----------------------------------------
361+
362+ @ doc "Pixel-invader Sprites mark. Pass fills to recolour for light/dark backgrounds."
363+ attr :class , :string , default: "w-16 h-16"
364+ attr :fill , :string , default: "#c084fc"
365+ attr :fill_accent , :string , default: "#c084fc"
366+ attr :fill_inner , :string , default: "#e0c9ff"
367+
368+ def sprites_mark ( assigns ) do
369+ ~H"""
370+ < svg class = { @ class } viewBox = "0 0 724 582 " fill-rule = "evenodd " aria-hidden = "true " >
371+ < path d = "M205.594 197.898h322.661v108.263H205.594z " fill = { @ fill } />
372+ < path
373+ d = "M578.582 508.799h-72.323v72.686h72.323v-72.686zm-361.614 0h-72.323v72.686h72.323v-72.686zm361.614-290.742h72.323v145.371h-72.323v72.686h-74.229 1.906v72.685H216.968v-72.685h-72.323v-72.686H72.323V218.057h72.322v-73.163h72.323V72.686h72.323v72.685h144.645V72.686h72.323v72.685h72.323v72.686zM72.323 508.799V363.428H0v145.371h72.323zm650.904 0V363.428h-72.322v145.371h72.322zM289.423 290.742h-.132.132zm-72.455-72.685h72.323v72.685h-72.323v-72.685zm216.968 0h72.323v72.685h-72.323v-72.685zM578.582 0h-72.323v72.686h72.323V0zM216.968 0h-72.323v72.686h72.323V0z "
374+ fill = { @ fill_accent }
375+ />
376+ < path
377+ d = "M144.645 363.428V218.057h72.323v-72.686h289.291v72.686h72.323v72.685l-.001.001v72.685h-72.322v72.686h-72.323V508.8H289.291v-72.686h-72.323v-72.686h-72.323zm144.646-72.686h-72.323v-72.685h72.323v72.685zm216.968 0h-72.323v-72.685h72.323v72.685z "
378+ fill = { @ fill_inner }
379+ />
380+ </ svg >
381+ """
382+ end
383+
384+ @ doc "The fly.io-docs brush-stroke underline, recoloured with the Sprites violet→teal gradient."
385+ attr :class , :string , default: "absolute -bottom-2 left-0 w-full h-5"
386+ attr :from , :string , default: "#8b5cf6"
387+ attr :to , :string , default: "#2ee6a1"
388+
389+ def sprites_underline ( assigns ) do
390+ ~H"""
391+ < svg viewBox = "0 0 1213 73 " aria-hidden = "true " preserveAspectRatio = "none " height = "22 " class = { @ class } >
392+ < path
393+ fill = "url(#sprites-underline-gradient) "
394+ d = "M1213.19 35.377c2.37-13.011-22.95-10.753-31.04-14.087C1086.89 5.705 911.742 2.887 815.218 2.809c-78.003.231-155.966-1.833-233.961.481-57.545.429-114.885 6.164-172.419 7.383-121.164 5.39-242.94 10.751-362.507 32.199-12.356 3.286-25.614 4.255-37.332 9.401-29.507 22.983 27.103 20.15 39.468 17.234 357.956-47.703 362.767-46.261 636.452-50.97 121.033-2.508 241.892 6.658 428.341 19.243 4.74.404 8.98-4.032 8-8.788a942.105 942.105 0 0154.69 6.378c9.44 1.843 18.92 3.583 28.29 5.729 4.01.839 8.02-1.718 8.95-5.712v-.01z "
395+ />
396+ < defs >
397+ < linearGradient id = "sprites-underline-gradient " gradientTransform = "rotate(110) " >
398+ < stop offset = "5% " stop-color = { @ from } />
399+ < stop offset = "95% " stop-color = { @ to } />
400+ </ linearGradient >
401+ </ defs >
402+ </ svg >
403+ """
404+ end
405+
406+ @ doc "Dark install-command pill: `$ curl https://sprites.dev/install.sh | bash`."
407+ def sprites_install ( assigns ) do
408+ ~H"""
409+ < div
410+ class = "inline-flex items-center gap-3 font-mono text-[1.125rem] tracking-[0.2px] whitespace-nowrap "
411+ style = "background: rgba(11,6,22,0.80); border: 1px solid rgba(150,120,220,0.32); border-radius: 13px; padding: 0.9rem 1.5rem; box-shadow: 0 10px 34px rgba(0,0,0,0.42); "
412+ >
413+ < span class = "font-semibold text-[#34e0a1] " > $</ span >
414+ < span class = "text-[#ece4ff] " > curl https://sprites.dev/install.sh | bash</ span >
415+ </ div >
416+ """
417+ end
418+
419+ @ doc """
420+ Primary Sprites template — the noir "split": text zone on the left over a
421+ left-to-right darkening of the detective artwork, full lockup + install.
422+ """
423+ def sprites ( assigns ) do
424+ ~H"""
425+ < body class = "bg-[#0a0713] flex h-screen relative overflow-hidden " >
426+ < div class = "bg-sprites-footer absolute inset-0 " > </ div >
427+ <!-- Left-to-right darkening keeps the text zone legible over the artwork -->
428+ < div class = "absolute inset-0 " style = "
429+ background: linear-gradient(90deg, rgba(8,5,16,0.96) 0%, rgba(8,5,16,0.90) 30%, rgba(8,5,16,0.45) 50%, rgba(8,5,16,0) 66%);
430+ "> </ div >
431+
432+ < div class = "absolute inset-y-0 left-0 flex flex-col justify-center " style = "padding: 0 4.5rem; " >
433+ < div class = "flex items-center gap-4 " style = "margin-bottom: 2.5rem; " >
434+ < . sprites_mark class = "w-14 h-14 " />
435+ < span class = "font-mono font-semibold text-[2.25rem] tracking-[0.5px] text-[#f6f2ff] " > Sprites</ span >
436+ </ div >
437+ < h1 class = "relative inline-block font-bold text-[#f7f4ff] leading-[1.04] tracking-[-1.5px] text-[4rem] whitespace-nowrap " >
438+ Stateful sandboxes
439+ < . sprites_underline />
440+ </ h1 >
441+ < div class = "italic text-[#c6b4ee] leading-[1.1] tracking-[-0.5px] text-[2.1rem] whitespace-nowrap " style = "margin-top: 1.75rem; " >
442+ with checkpoint & restore.
443+ </ div >
444+ < div style = "margin-top: 2.75rem; " >
445+ < . sprites_install />
446+ </ div >
447+ </ div >
448+ </ body >
449+ """
450+ end
451+
452+ @ doc """
453+ docs.sprites.dev template — mirrors the fly.io/docs layout (underlined URL
454+ label + dynamic page-title headline) on the noir split artwork. Honours the
455+ `text` param as the page title.
456+ """
457+ def sprites_docs ( assigns ) do
458+ ~H"""
459+ < body class = "bg-[#0a0713] flex h-screen relative overflow-hidden " >
460+ < div class = "bg-sprites-footer absolute inset-0 " style = "background-position: 58% center; " > </ div >
461+ <!-- Left-to-right darkening keeps the text zone legible over the artwork -->
462+ < div class = "absolute inset-0 " style = "
463+ background: linear-gradient(90deg, rgba(8,5,16,0.97) 0%, rgba(8,5,16,0.92) 34%, rgba(8,5,16,0.52) 52%, rgba(8,5,16,0.06) 70%);
464+ "> </ div >
465+
466+ <!-- Logo above the page title (like the main template) -->
467+ < div class = "absolute inset-y-0 left-0 flex flex-col justify-center " style = "padding: 0 4.5rem; max-width: 760px; " >
468+ < div class = "flex items-center gap-4 " style = "margin-bottom: 2rem; " >
469+ < . sprites_mark class = "w-12 h-12 " />
470+ < span class = "font-mono font-semibold text-[1.75rem] tracking-[0.3px] text-[#f6f2ff] " > Sprites</ span >
471+ </ div >
472+ <%
473+ # Extract string from {:safe, text} tuple if needed
474+ raw_text = case @ text do
475+ { :safe , text } -> text
476+ text when is_binary(text) - > text
477+ _ - > to_string(@text)
478+ end
479+ clipped_text = OgImageWeb.ImageHTML.clip_text(raw_text, 20)
480+ text_size_class = OgImageWeb.ImageHTML.get_sprites_text_size(clipped_text)
481+ % >
482+ < h1 class = { "font-bold text-[#f7f4ff] leading-[1.08] tracking-[-1px] break-words #{ text_size_class } " } >
483+ <%= clipped_text %>
484+ </ h1 >
485+ </ div >
486+
487+ <!-- Footer: underlined docs.sprites.dev -->
488+ < div class = "absolute " style = "left: 4.5rem; bottom: 3rem; " >
489+ < span class = "relative inline-block font-mono font-semibold text-[1.4rem] tracking-[0.3px] text-[#c6b4ee] " >
490+ docs.sprites.dev
491+ < . sprites_underline class = "absolute -bottom-1 left-0 w-full h-2 " />
492+ </ span >
493+ </ div >
494+ </ body >
495+ """
496+ end
497+
360498 # Helper function to determine text size for sprites template
361499 def get_sprites_text_size ( text ) when is_binary ( text ) do
362500 length = String . length ( text )
0 commit comments