defmodule PhiaUi.Components.Background do @moduledoc """ Background pattern and decoration components for PhiaUI. 15 components covering CSS-only patterns, SVG patterns, and canvas-based animated backgrounds. All are tier `:animation`. Canvas-based components require JS hooks and respect `prefers-reduced-motion`. ## Components | Component | Technique | Hook | |------------------------|----------------------------------|-----------------| | `gradient_mesh` | JS-animated blob divs | `PhiaMeshBg` | | `noise_bg` | SVG feTurbulence filter | none | | `animated_gradient_bg` | CSS animated gradient | none | | `retro_grid` | Perspective-transformed grid | none | | `wave_bg` | SVG wave paths with animation | none | | `hex_pattern` | SVG hexagon tile | none | | `stripe_pattern` | CSS repeating-linear-gradient | none | | `checker_pattern` | CSS repeating-conic-gradient | none | | `dot_grid` | CSS radial-gradient tiling | none | | `bokeh_bg` | CSS-animated blurred blobs | none | | `conic_gradient_bg` | Animated conic-gradient | none | | `flicker_grid` | Canvas flicker dots | `PhiaFlickerGrid`| | `beam_bg` | CSS beam sweep animation | none | | `warp_grid` | Perspective dot grid | none | | `flowing_lines` | SVG stroke-dashoffset lines | none | """ use Phoenix.Component import PhiaUi.ClassMerger, only: [cn: 1] # --------------------------------------------------------------------------- # gradient_mesh/1 # --------------------------------------------------------------------------- attr(:id, :string, required: true, doc: "Unique ID (required for phx-hook)") attr(:colors, :list, default: ["#ff6b6b", "#4ecdc4", "#45b7d1", "#96c93d"], doc: "List of hex colors for the mesh blobs" ) attr(:blob_count, :integer, default: 4, doc: "Number of animated blobs") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders an animated mesh gradient background using JS-driven blobs. Requires the `PhiaMeshBg` JS hook. Each blob is an absolutely-positioned div that shifts position via `requestAnimationFrame`. Respects `prefers-reduced-motion` (disables animation in the hook). ## Example
<.gradient_mesh id="hero-mesh" colors={["#f093fb", "#f5576c", "#4facfe"]} />
Content over gradient
""" def gradient_mesh(assigns) do colors_json = Jason.encode!(assigns.colors) assigns = assign(assigns, :colors_json, colors_json) ~H""" """ end # --------------------------------------------------------------------------- # noise_bg/1 # --------------------------------------------------------------------------- attr(:opacity, :any, default: 0.3, doc: "Noise overlay opacity (0–1)") attr(:base_frequency, :any, default: 0.65, doc: "SVG feTurbulence baseFrequency") attr(:num_octaves, :integer, default: 3, doc: "feTurbulence numOctaves") attr(:color, :string, default: "currentColor", doc: "Filter color applied via feColorMatrix") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a grainy noise texture background using SVG `feTurbulence`. The noise is applied as an absolutely-positioned overlay. Place inside a `relative` container. Use `opacity` to control the texture intensity. ## Example
<.noise_bg opacity={0.15} />
Content
""" def noise_bg(assigns) do filter_id = "phia-noise-#{:erlang.unique_integer([:positive])}" assigns = assign(assigns, :filter_id, filter_id) ~H""" """ end # --------------------------------------------------------------------------- # animated_gradient_bg/1 # --------------------------------------------------------------------------- attr(:from, :string, default: "#667eea", doc: "Gradient start color") attr(:via, :string, default: "#764ba2", doc: "Gradient middle color") attr(:to, :string, default: "#f64f59", doc: "Gradient end color") attr(:duration, :integer, default: 8, doc: "Animation duration in seconds") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a smoothly animated gradient background. Uses CSS `background-position` animation (same mechanism as aurora). Colors are passed as CSS variables for smooth interpolation. ## Example
<.animated_gradient_bg from="#667eea" via="#764ba2" to="#f64f59" />
Content
""" def animated_gradient_bg(assigns) do ~H"""
"background-size: 400% 400%; " <> "animation: aurora #{@duration}s ease infinite;" } aria-hidden="true" {@rest} > {render_slot(@inner_block)}
""" end # --------------------------------------------------------------------------- # retro_grid/1 # --------------------------------------------------------------------------- attr(:color, :string, default: "rgba(100,100,100,0.3)", doc: "Grid line color") attr(:cell_size, :integer, default: 40, doc: "Grid cell size in pixels") attr(:perspective, :integer, default: 500, doc: "CSS perspective value in pixels") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a retro perspective-transformed grid background. Creates a 3D floor-plane illusion via CSS `perspective` and `rotateX`. The grid is rendered using CSS `background-image: linear-gradient`. ## Example
<.retro_grid color="rgba(0,255,0,0.2)" cell_size={32} />
Retro terminal
""" def retro_grid(assigns) do ~H""" """ end # --------------------------------------------------------------------------- # wave_bg/1 # --------------------------------------------------------------------------- attr(:color, :string, default: "rgba(99,102,241,0.15)", doc: "Wave fill color (rgba)") attr(:color2, :string, default: "rgba(139,92,246,0.1)", doc: "Second wave fill color") attr(:duration, :integer, default: 8, doc: "Wave animation duration in seconds") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a horizontally scrolling SVG wave background. Two overlapping SVG waves animate at slightly different speeds for depth. Uses `phia-wave-scroll` keyframe. ## Example
<.wave_bg color="rgba(99,102,241,0.2)" />
Wave content
""" def wave_bg(assigns) do ~H""" """ end # --------------------------------------------------------------------------- # hex_pattern/1 # --------------------------------------------------------------------------- attr(:color, :string, default: "rgba(100,100,100,0.15)", doc: "Hexagon stroke color") attr(:size, :integer, default: 30, doc: "Hexagon radius in pixels") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders an SVG hexagon tile pattern background. Uses SVG `` with a `` hexagon shape. ## Example
<.hex_pattern color="rgba(99,102,241,0.2)" size={24} />
Hex grid content
""" def hex_pattern(assigns) do id = "phia-hex-#{:erlang.unique_integer([:positive])}" # Compute hexagon geometry s = assigns.size w = round(s * 2) h = round(:math.sqrt(3) * s) # Hexagon points (flat-top) pts = [0, 1, 2, 3, 4, 5] |> Enum.map(fn i -> angle = :math.pi() / 180 * (60 * i) x = s + round(s * :math.cos(angle)) y = div(h, 2) + round(s * :math.sin(angle)) "#{x},#{y}" end) |> Enum.join(" ") assigns = assign(assigns, id: id, hex_w: w, hex_h: h, pts: pts) ~H""" """ end # --------------------------------------------------------------------------- # stripe_pattern/1 # --------------------------------------------------------------------------- attr(:color, :string, default: "rgba(100,100,100,0.1)", doc: "Stripe color") attr(:width, :integer, default: 20, doc: "Stripe width in pixels") attr(:direction, :atom, default: :diagonal, values: [:diagonal, :horizontal, :vertical], doc: "Stripe direction" ) attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a CSS stripe pattern background. Uses `repeating-linear-gradient`. Directions: `:diagonal` (45°), `:horizontal`, `:vertical`. ## Example
<.stripe_pattern color="rgba(99,102,241,0.1)" direction={:diagonal} width={16} />
Striped content
""" def stripe_pattern(assigns) do ~H""" """ end defp stripe_style(:diagonal, color, w) do "background-image: repeating-linear-gradient(45deg, #{color}, #{color} #{w}px, transparent #{w}px, transparent #{w * 2}px);" end defp stripe_style(:horizontal, color, w) do "background-image: repeating-linear-gradient(0deg, #{color}, #{color} #{div(w, 2)}px, transparent #{div(w, 2)}px, transparent #{w}px);" end defp stripe_style(:vertical, color, w) do "background-image: repeating-linear-gradient(90deg, #{color}, #{color} #{div(w, 2)}px, transparent #{div(w, 2)}px, transparent #{w}px);" end # --------------------------------------------------------------------------- # checker_pattern/1 # --------------------------------------------------------------------------- attr(:color, :string, default: "rgba(100,100,100,0.1)", doc: "Checker square color") attr(:size, :integer, default: 24, doc: "Checker square size in pixels") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a CSS checkerboard pattern background. Uses `repeating-conic-gradient` for efficient CSS-only rendering. ## Example
<.checker_pattern color="rgba(0,0,0,0.05)" size={20} />
""" def checker_pattern(assigns) do ~H""" """ end # --------------------------------------------------------------------------- # dot_grid/1 # --------------------------------------------------------------------------- attr(:color, :string, default: "rgba(100,100,100,0.3)", doc: "Dot color") attr(:dot_size, :integer, default: 2, doc: "Dot radius in pixels") attr(:spacing, :integer, default: 24, doc: "Grid spacing in pixels") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a CSS dot grid background using `radial-gradient` tiling. Distinct from the existing `dot_pattern` (which uses an SVG mask). This uses pure CSS for maximum performance. ## Example
<.dot_grid dot_size={3} spacing={20} />
Content on dots
""" def dot_grid(assigns) do ~H"""
"background-size: #{@spacing}px #{@spacing}px;" } aria-hidden="true" {@rest} > {render_slot(@inner_block)}
""" end # --------------------------------------------------------------------------- # bokeh_bg/1 # --------------------------------------------------------------------------- attr(:colors, :list, default: ["#ff6b6b", "#4ecdc4", "#45b7d1", "#96c93d", "#f7dc6f"], doc: "Blob colors" ) attr(:count, :integer, default: 5, doc: "Number of bokeh blobs") attr(:blur, :string, default: "blur-3xl", doc: "Tailwind blur class for blobs") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a set of soft blurred bokeh-style blobs as a background. Blobs are positioned using deterministic offsets from `:erlang.unique_integer`. Each blob animates with `phia-bokeh-drift` at slightly different speeds. ## Example
<.bokeh_bg colors={["#f59e0b", "#10b981", "#6366f1"]} count={4} />
Content
""" def bokeh_bg(assigns) do n = assigns.count colors = assigns.colors blobs = Enum.map(0..(n - 1), fn i -> seed = :erlang.unique_integer([:positive]) color = Enum.at(colors, rem(i, length(colors))) left = rem(seed, 80) top = rem(seed * 3 + 17, 80) size = 150 + rem(seed * 7, 200) dur = 6 + rem(i, 4) bx = rem(seed * 5, 40) - 20 by = rem(seed * 3, 30) - 15 bx2 = rem(seed * 7, 40) - 20 by2 = rem(seed * 11, 30) - 15 %{color: color, left: left, top: top, size: size, dur: dur, bx: bx, by: by, bx2: bx2, by2: by2} end) assigns = assign(assigns, :blobs, blobs) ~H""" """ end # --------------------------------------------------------------------------- # conic_gradient_bg/1 # --------------------------------------------------------------------------- attr(:from_color, :string, default: "#f43f5e", doc: "Start color of conic gradient") attr(:to_color, :string, default: "#6366f1", doc: "End color of conic gradient") attr(:duration, :integer, default: 6, doc: "Spin animation duration in seconds") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a rotating conic gradient background. Uses `@property --phia-conic-angle` for smooth CSS angle animation. Add the required `@property` declaration from `theme.css` for full support. ## Example
<.conic_gradient_bg from_color="#f43f5e" to_color="#6366f1" />
Conic gradient content
""" def conic_gradient_bg(assigns) do ~H"""
"animation: phia-conic-spin #{@duration}s linear infinite;" } aria-hidden="true" {@rest} > {render_slot(@inner_block)}
""" end # --------------------------------------------------------------------------- # flicker_grid/1 # --------------------------------------------------------------------------- attr(:id, :string, required: true, doc: "Unique ID (required for phx-hook)") attr(:color, :string, default: "rgba(99,102,241,0.5)", doc: "Dot color") attr(:dot_size, :integer, default: 3, doc: "Dot radius in pixels") attr(:gap, :integer, default: 20, doc: "Grid spacing in pixels") attr(:class, :string, default: nil) attr(:rest, :global) @doc """ Renders a canvas-based flickering grid background. Each dot in a grid independently flickers at random opacity. Requires the `PhiaFlickerGrid` JS hook. Respects `prefers-reduced-motion`. ## Example
<.flicker_grid id="flicker-hero" color="rgba(99,102,241,0.6)" />
Content
""" def flicker_grid(assigns) do ~H""" """ end # --------------------------------------------------------------------------- # beam_bg/1 # --------------------------------------------------------------------------- attr(:color, :string, default: "rgba(99,102,241,0.5)", doc: "Beam color (rgba)") attr(:width, :string, default: "w-1/3", doc: "Tailwind width class for the beam") attr(:duration, :integer, default: 4, doc: "Sweep animation duration in seconds") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a sweeping light beam animation background. A gradient beam sweeps from left to right on a loop using `phia-beam-sweep`. Place inside a `relative overflow-hidden` container. ## Example
<.beam_bg color="rgba(139,92,246,0.4)" width="w-1/4" />
Beam content
""" def beam_bg(assigns) do ~H""" """ end # --------------------------------------------------------------------------- # warp_grid/1 # --------------------------------------------------------------------------- attr(:color, :string, default: "rgba(100,100,100,0.2)", doc: "Grid line color") attr(:cell_size, :integer, default: 32, doc: "Grid cell size in pixels") attr(:perspective, :integer, default: 400, doc: "CSS perspective in pixels") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders a perspective-warped dot grid background. Combines a dot grid (`radial-gradient`) with a perspective transform to create a floor-plane depth effect. ## Example
<.warp_grid color="rgba(99,102,241,0.25)" />
Warp grid
""" def warp_grid(assigns) do ~H""" """ end # --------------------------------------------------------------------------- # flowing_lines/1 # --------------------------------------------------------------------------- attr(:color, :string, default: "rgba(99,102,241,0.4)", doc: "Line stroke color") attr(:count, :integer, default: 5, doc: "Number of flowing lines") attr(:stroke_width, :any, default: 1.5, doc: "SVG stroke-width") attr(:duration, :integer, default: 3, doc: "Base animation duration in seconds") attr(:class, :string, default: nil) attr(:rest, :global) slot(:inner_block) @doc """ Renders animated SVG flowing lines using `stroke-dashoffset` animation. Reuses the `phia-line-draw` keyframe. Each line has a staggered delay. ## Example
<.flowing_lines color="rgba(99,102,241,0.3)" count={6} />
Flowing lines
""" def flowing_lines(assigns) do n = assigns.count lines = Enum.map(0..(n - 1), fn i -> seed = :erlang.unique_integer([:positive]) y1 = rem(seed, 80) + 10 y2 = rem(seed * 3 + 13, 80) + 10 delay = i * 0.4 len = 1000 %{i: i, y1: y1, y2: y2, delay: delay, len: len} end) assigns = assign(assigns, :lines, lines) ~H""" """ end end