empowered.house ↗

sections/newsletter.ex

Newsletter signup

A newsletter signup with a heading, a line of copy, an email input with a lime button and a small note.

Use it
# in the module that renders the template
use EHVisuals

<div class="h-96 rounded-xl border border-border">
  <.sections_newsletter animated />
</div>
default
isometric
empty input · custom copy
hover · no glow · no note
light surface
isometric · fade out · still

Props

Prop Type Default What it does
eyebrow string "Newsletter"
title string "Operations notes, once a month" Up to 44 characters.
email string "marta@northwind.com" What is typed into the input, up to 30 characters. Empty shows the placeholder.
placeholder string "you@company.com"
action string "Subscribe" The button. Up to 12 characters.
note any "One email a month. Unsubscribe any time." The line under the input, up to 60 characters. `nil` hides it.
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/sections/newsletter.ex
newsletter.ex
defmodule EHVisuals.Sections.Newsletter do
  @moduledoc """
  A newsletter signup section: a heading, a line of copy, an email input with
  a lime button inside it, and a small note underneath.
  """

  use EHVisuals.Composition,
    name: "sections/newsletter",
    title: "Newsletter signup",
    description:
      "A newsletter signup with a heading, a line of copy, an email input with a lime button and a small note."

  attr(:eyebrow, :string, default: "Newsletter")
  attr(:title, :string, default: "Operations notes, once a month", doc: "Up to 44 characters.")

  attr(:email, :string,
    default: "marta@northwind.com",
    doc: "What is typed into the input, up to 30 characters. Empty shows the placeholder."
  )

  attr(:placeholder, :string, default: "you@company.com")
  attr(:action, :string, default: "Subscribe", doc: "The button. Up to 12 characters.")

  attr(:note, :any,
    default: "One email a month. Unsubscribe any time.",
    doc: "The line under the input, up to 60 characters. `nil` hides it."
  )

  attr(:label, :string, default: "A newsletter signup with an email input and a subscribe button")
  frame_attrs()

  @spec sections_newsletter(map()) :: Phoenix.LiveView.Rendered.t()
  def sections_newsletter(assigns) do
    typed = assigns.email != ""
    action_w = Float.round(24 + String.length(assigns.action) * 6.0, 1)

    assigns =
      assigns
      |> assign(:typed, typed)
      |> assign(:action_w, action_w)
      |> assign(:action_x, 406 - action_w)
      |> assign(:caret_x, Float.round(108 + String.length(assigns.email) * 6.55, 1))
      |> assign(:height, if(assigns.note, do: 204, else: 184))

    ~H"""
    <.frame name="sections/newsletter" label={@label} {frame_opts(assigns)}>
      <svg viewBox={"0 0 480 #{@height}"}>
        <g class="ehv-h-lift">
          <rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="479" height={@height - 1} rx="12" />
          <text
            class="ehv-mono ehv-a-fade ehv-i-1"
            x="240"
            y="38"
            font-size="8.5"
            text-anchor="middle"
          >
            {String.upcase(@eyebrow)}
          </text>
          <text
            class="ehv-t-strong ehv-a-fade ehv-i-2"
            x="240"
            y="62"
            font-size="16"
            text-anchor="middle"
          >
            {@title}
          </text>
          <g class="ehv-a-fade ehv-i-3">
            <rect class="ehv-fill-soft" x="112" y="78" width="256" height="5" rx="2.5" />
            <rect class="ehv-fill-soft" x="144" y="90" width="192" height="5" rx="2.5" />
          </g>
          <g class="ehv-a-rise ehv-i-4">
            <rect
              class={[
                "ehv-well ehv-sections-newsletter__input",
                @typed && "ehv-sections-newsletter__input--focus"
              ]}
              x="70.5"
              y="116.5"
              width="339"
              height="33"
              rx="9"
            />
            <path
              class="ehv-line-ink ehv-round"
              d="M83.5 127.5 h13 v11 h-13 z M83.5 128.5 l6.5 5 l6.5 -5"
            />
            <text :if={@typed} x="105" y="137.5" font-size="12">{@email}</text>
            <text :if={!@typed} class="ehv-t-3" x="105" y="137.5" font-size="12">{@placeholder}</text>
            <line
              :if={@typed}
              class="ehv-line-lime ehv-sections-newsletter__caret"
              x1={@caret_x}
              x2={@caret_x}
              y1="126"
              y2="140"
            />
          </g>
          <g class="ehv-a-pop ehv-i-6">
            <rect class="ehv-fill-lime" x={@action_x} y="120" width={@action_w} height="26" rx="7" />
            <text
              class="ehv-t-strong ehv-t-on-lime"
              x={@action_x + @action_w / 2}
              y="137"
              font-size="11"
              text-anchor="middle"
            >
              {@action}
            </text>
          </g>
          <text
            :if={@note}
            class="ehv-t-3 ehv-a-fade ehv-i-7"
            x="240"
            y="174"
            font-size="10.5"
            text-anchor="middle"
          >
            {@note}
          </text>
        </g>
      </svg>
    </.frame>
    """
  end
end
assets/css/sections/newsletter.css
newsletter.css
/* sections/newsletter */
.ehv-sections-newsletter { --ehv-max-w: 520px; --ehv-iso-scale: 0.82; }

.ehv .ehv-sections-newsletter__input--focus { stroke: var(--ehv-lime); }

@keyframes ehv-sections-newsletter-blink { 50% { opacity: 0; } }
.ehv--animated .ehv-sections-newsletter__caret {
  animation: ehv-sections-newsletter-blink 1.1s steps(1) infinite;
}

.ehv--hover .ehv-sections-newsletter__input { transition: stroke 200ms var(--ehv-ease); }
.ehv--hover:hover .ehv-sections-newsletter__input { stroke: var(--ehv-lime); }