Packages
petal_components
4.4.0
4.7.0
4.6.2
4.6.1
4.6.0
4.5.0
4.4.0
4.3.0
4.2.1
4.2.0
4.1.2
4.1.1
4.1.0
4.0.12
4.0.11
4.0.10
4.0.6
4.0.5
4.0.4
4.0.3
4.0.1
3.2.2
3.2.1
3.2.0
3.1.0
3.0.2
3.0.1
3.0.0
2.9.3
2.9.2
retired
2.9.1
retired
2.9.0
retired
2.8.4
2.8.3
2.8.2
2.8.1
2.8.0
2.7.4
2.7.3
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.2
2.5.1
2.5.0
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.1
2.2.0
2.1.2
2.1.1
2.1.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.9.3
1.9.2
1.9.1
1.9.0
1.8.0
1.7.1
1.7.0
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.0
1.2.14
1.2.13
1.2.12
1.2.11
1.2.10
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.19.10
0.19.9
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.0
0.15.0
0.14.1
0.14.0
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.0
Shadcn-style Phoenix LiveView components that AI assistants can actually use. Pair with the MCP server so AI coding tools can inspect the real component API.
Current section
Files
Jump to
Current section
Files
lib/petal_components/meteors.ex
defmodule PetalComponents.Meteors do
@moduledoc """
A shower of meteors streaking across the parent container. Pure CSS —
positions, delays and durations are generated server-side, so the effect
costs zero JavaScript.
Place it inside any `relative` container (a hero section, a card) and it
fills the space behind your content.
"""
use Phoenix.Component
attr :count, :integer, default: 20, doc: "number of meteors"
attr :seed, :integer,
default: 0,
doc:
"vary this when rendering several meteor fields on one page so each gets a different scatter pattern"
attr :angle, :string,
default: "215deg",
doc: "direction the meteors travel, as a CSS angle (default streaks down and to the left)"
attr :color, :string,
default: "#64748b",
doc: "meteor and trail colour. Any CSS colour; the default slate suits dark panels"
attr :reverse, :boolean,
default: false,
doc: "mirror the shower horizontally, so meteors stream the opposite way"
attr :class, :any, default: nil, doc: "extra classes for the container"
attr :rest, :global
@doc """
Renders a meteor shower that fills its nearest `relative` ancestor.
<div class="relative overflow-hidden rounded-xl bg-gray-950 p-12">
<.meteors count={30} />
<.h2 class="relative text-white">Ship faster with Petal</.h2>
</div>
Meteor positions are deterministic for a given `seed`, so LiveView
re-renders never make the meteors jump.
"""
def meteors(assigns) do
assigns =
assigns
|> assign(:meteor_styles, build_styles(assigns.count, assigns.seed))
|> assign(
:field_style,
"--pc-meteor-angle: #{assigns.angle}; --pc-meteor-color: #{assigns.color};"
)
~H"""
<div
class={["pc-meteors", @reverse && "pc-meteors--reverse", @class]}
style={@field_style}
aria-hidden="true"
{@rest}
>
<span :for={style <- @meteor_styles} class="pc-meteor" style={style}></span>
</div>
"""
end
# Deterministic pseudo-random placement: stable across re-renders (no
# meteor-jumping when LiveView patches the page) and across test runs.
defp build_styles(count, seed) when count > 0 do
for i <- 1..count do
top = rand({seed, i, :top}, 60) - 20
left = rand({seed, i, :left}, 100)
delay = rand({seed, i, :delay}, 500) / 100
duration = rand({seed, i, :duration}, 400) / 100 + 4
"top: #{top}%; left: #{left}%; animation-delay: #{delay}s; animation-duration: #{duration}s;"
end
end
defp build_styles(_count, _seed), do: []
defp rand(term, max), do: rem(:erlang.phash2(term), max)
end