brand/spotlight.ex
Spotlight
The EmpoweredHouse app icon lit by the warm glow above a fading row of plain app tiles.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.brand_spotlight animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
name |
string | "EmpoweredHouse" |
The line under the icon. |
tagline |
any | "Software shaped to your company" |
The mono line under the name. `nil` hides it. |
tiles |
integer | 7 |
Placeholder tiles per row, three to seven. |
rows |
integer | 1 |
One or two rows of placeholder tiles. |
Frame props, shared by every composition
| Prop | Type | Default | What it does |
|---|---|---|---|
animated |
boolean | false |
Turns motion on. Without it the composition renders in its final, still state. |
trigger |
"mount" | "in-view" | "in-view-repeat" | "in-view" |
When the entrance plays: on page load, once when scrolled into view, or every time it comes back into view. |
hover |
boolean | false |
Enables the hover micro-interactions the composition supports. |
glow |
boolean | true |
The warm incandescent halo behind the composition. |
fade_out |
boolean | false |
Fades the bottom edge into the surface underneath. |
isometric |
boolean | false |
Tilts the composition into the isometric plane of the platform stack. |
theme |
"dark" | "light" | "dark" |
`dark` for the site canvas, `light` for the cream reversed surface. |
class |
string | nil |
Extra classes for the frame. |
rest |
global | — |
Any other HTML attribute, set on the frame. |
Source
Two files. Copy them into your app if you would rather own the code than depend on the library.
lib/eh_visuals/brand/spotlight.ex
defmodule EHVisuals.Brand.Spotlight do
@moduledoc """
The EmpoweredHouse app icon in the warm light, above a row of plain app
tiles that fades towards its ends.
"""
use EHVisuals.Composition,
name: "brand/spotlight",
title: "Spotlight",
description:
"The EmpoweredHouse app icon lit by the warm glow above a fading row of plain app tiles."
attr(:name, :string, default: "EmpoweredHouse", doc: "The line under the icon.")
attr(:tagline, :any,
default: "Software shaped to your company",
doc: "The mono line under the name. `nil` hides it."
)
attr(:tiles, :integer, default: 7, doc: "Placeholder tiles per row, three to seven.")
attr(:rows, :integer, default: 1, doc: "One or two rows of placeholder tiles.")
attr(:label, :string,
default: "The EmpoweredHouse app icon lit from above, over a row of plain app tiles"
)
frame_attrs()
@spec brand_spotlight(map()) :: Phoenix.LiveView.Rendered.t()
def brand_spotlight(assigns) do
count = assigns.tiles |> max(3) |> min(7)
rows = assigns.rows |> max(1) |> min(2)
left = 200 - (count * 52 - 12) / 2
top = if assigns.tagline, do: 214, else: 198
tiles =
for row <- 0..(rows - 1), index <- 0..(count - 1) do
distance = round(abs(index - (count - 1) / 2)) + row
%{
x: Float.round(left + index * 52, 1),
y: top + row * 52,
distance: min(distance, 4),
stagger: min(distance + 5, 24)
}
end
assigns =
assigns
|> assign(:items, tiles)
|> assign(:height, top + rows * 52 + 8)
~H"""
<.frame name="brand/spotlight" label={@label} {frame_opts(assigns)}>
<svg viewBox={"0 0 400 #{@height}"}>
<rect
class="ehv-line-faint ehv-a-fade ehv-i-3"
x="128.5"
y="10.5"
width="143"
height="143"
rx="40"
/>
<rect
class="ehv-line-soft ehv-a-fade ehv-i-2"
x="140.5"
y="22.5"
width="119"
height="119"
rx="32"
/>
<g class="ehv-a-pop ehv-i-0">
<g class="ehv-h-lift">
<rect class="ehv-brand-spotlight__icon" x="152.5" y="34.5" width="95" height="95" rx="24" />
<.mark x="156" y="38" size="88" />
</g>
</g>
<g class="ehv-a-fade ehv-i-4">
<text class="ehv-t-strong" x="200" y="180" font-size="14" text-anchor="middle">
{@name}
</text>
<text :if={@tagline} class="ehv-mono" x="200" y="197" font-size="8.5" text-anchor="middle">
{String.upcase(@tagline)}
</text>
</g>
<g :for={tile <- @items} class={"ehv-a-pop ehv-i-#{tile.stagger}"}>
<rect
class={"ehv-brand-spotlight__tile ehv-brand-spotlight__tile--#{tile.distance}"}
x={tile.x + 0.5}
y={tile.y + 0.5}
width="39"
height="39"
rx="11"
/>
</g>
</svg>
</.frame>
"""
end
end
assets/css/brand/spotlight.css
/* brand/spotlight */
.ehv-brand-spotlight { --ehv-max-w: 440px; }
/* The glow sits over the icon, not in the middle of the frame. */
.ehv-brand-spotlight .ehv__glow {
background: radial-gradient(40% 46% at 50% 34%, var(--ehv-glow), transparent 72%);
opacity: 0.8;
}
.ehv-brand-spotlight__icon { fill: var(--ehv-surface-3); stroke: var(--ehv-border-strong); stroke-width: 1; }
/* Plain tiles, fainter the further they are from the icon. */
.ehv-brand-spotlight__tile { fill: var(--ehv-surface-3); stroke: var(--ehv-ink-4); stroke-width: 1; }
.ehv-brand-spotlight__tile--0 { opacity: 1; }
.ehv-brand-spotlight__tile--1 { opacity: 0.78; }
.ehv-brand-spotlight__tile--2 { opacity: 0.52; }
.ehv-brand-spotlight__tile--3 { opacity: 0.3; }
.ehv-brand-spotlight__tile--4 { opacity: 0.15; }
/* The outline of the mark draws itself, then the lime core lands. */
.ehv--animated.ehv-brand-spotlight .ehv-mark__frame {
stroke-dasharray: 1 1;
animation: ehv-draw 1.1s var(--ehv-ease) 300ms backwards;
}
.ehv-brand-spotlight .ehv-mark__core { transform-box: fill-box; transform-origin: center; }
.ehv--animated.ehv-brand-spotlight .ehv-mark__core { animation: ehv-pop 500ms var(--ehv-ease) 1000ms backwards; }
.ehv--hover .ehv-brand-spotlight__tile { transition: opacity 200ms var(--ehv-ease), stroke 200ms var(--ehv-ease); }
.ehv--hover .ehv-brand-spotlight__tile:hover { opacity: 1; stroke: var(--ehv-ink-2); }