Skip to content

Commit eb759b0

Browse files
kylemclarenclaude
andauthored
feat(sprites): add sprites.dev + docs.sprites.dev OG templates (#2)
Add two OG image templates built on the noir "detective" artwork: - `sprites` — the primary sprites.dev card: text zone on the left over a left-to-right darkening of the artwork, full Sprites lockup, headline with the fly.io/docs brush-stroke underline (recoloured violet→teal), italic subtitle, and the install command. Static content. - `sprites_docs` — docs.sprites.dev per-page card mirroring the fly.io/docs layout: Sprites logo above a dynamic page title (`text` param), with an underlined docs.sprites.dev in the footer. Extract shared building blocks (`sprites_mark`, `sprites_underline`, `sprites_install`) as function components. Rename the previous halftone `sprites` template to `sprites_halftone` so `template=sprites` now serves the new primary card. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a5d473c commit eb759b0

4 files changed

Lines changed: 163 additions & 3 deletions

File tree

lib/og_image_web/components/layouts.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ defmodule OgImageWeb.Layouts do
9393
background-color: #17141f;
9494
}
9595
96+
.bg-sprites-footer {
97+
background-image: url(<%= image_to_base64_url("sprites-footer.webp") %>);
98+
background-size: cover;
99+
background-position: 72% center;
100+
}
101+
96102
.bg-sprites-pattern {
97103
background-image: url(<%= image_to_base64_url("bmxy.png") %>);
98104
background-size: cover;

lib/og_image_web/controllers/image_controller.ex

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,26 @@ defmodule OgImageWeb.ImageController do
2828
|> render_image(:light)
2929
end
3030

31-
def show(conn, %{"template" => "sprites", "text" => text}) do
31+
def show(conn, %{"template" => "sprites_halftone", "text" => text}) do
3232
conn
3333
|> assign(:text, prepare_html(text))
34-
|> render_image(:sprites)
34+
|> render_image(:sprites_halftone)
35+
end
36+
37+
# `sprites` is the primary (noir split) template — ignores the `text` param.
38+
def show(conn, %{"template" => "sprites"}), do: render_image(conn, :sprites)
39+
40+
# Docs template — honours `text` as the page title (defaults when omitted).
41+
def show(conn, %{"template" => "sprites_docs", "text" => text}) do
42+
conn
43+
|> assign(:text, prepare_html(text))
44+
|> render_image(:sprites_docs)
45+
end
46+
47+
def show(conn, %{"template" => "sprites_docs"}) do
48+
conn
49+
|> assign(:text, "Documentation")
50+
|> render_image(:sprites_docs)
3551
end
3652

3753
def show(conn, %{"template" => "machines_api", "text" => text}) do

lib/og_image_web/controllers/image_html.ex

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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 &amp; 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)
84 KB
Loading

0 commit comments

Comments
 (0)