# `PhxDemo`
[🔗](https://github.com/thenatespack/phoenix_demo/blob/main/lib/phx_demo.ex#L1)

Automated, reproducible demo videos for Phoenix / LiveView apps.

A demo is an ExUnit test built on `PhoenixTest.Playwright.Case`, so it gets
a real browser, the Ecto sandbox, and seed data from the test process.
`PhxDemo.Case` adds video recording, an overlay cursor, and a timeline
of what happened when. The steps in `PhxDemo.Steps` add pacing.

    defmodule MyAppWeb.Demos.PostsDemo do
      use PhxDemo.Case
      use MyAppWeb, :verified_routes

      demo "create a post", %{conn: conn} do
        conn
        |> scene("Creating a post")
        |> visit(~p"/posts")
        |> say("Here are the posts we already have.")
        |> glide_click_link("New Post")
        |> type_into({:label, "Title"}, "Hello world")
        |> glide_click_button("Save Post")
        |> assert_has("#flash-info", text: "Post created")
      end
    end

Each demo writes into `<output_dir>/<module>/<demo>/`:

  * `*.webm`: the raw recording. Playwright finalizes it when the browser
    context closes, after the test's own `on_exit` callbacks.
  * `timeline.json`: scenes, narration lines, and cursor targets with
    millisecond offsets from roughly the start of the recording.

# `card`

Title card content for `:intro` / `:outro` settings, or nil.

    iex> PhxDemo.card(:intro, true, "create a post", "my_app")
    %{title: "Create a post", subtitle: "My App"}

    iex> PhxDemo.card(:outro, true, "create a post", "my_app")
    %{title: "My App", subtitle: "Thanks for watching"}

    iex> PhxDemo.card(:intro, %{title: "Hi"}, "x", nil)
    %{title: "Hi"}

    iex> PhxDemo.card(:intro, false, "x", nil)
    nil

# `output_dir`

Directory that demo output goes under.

Priority: `:output_dir` option, then `config :phx_demo, :output_dir`,
then `tmp/demos`.

# `settings`

Presentation settings for a demo, resolved at runtime. Priority: `use
PhxDemo.Case` options, then `config :phx_demo, <key>`, then defaults.

  * `:intro`, `:outro`: `true` (generated title card), `false`, or
    `%{title: ..., subtitle: ...}`
  * `:captions`: `:both` (drawn into the video and a soft subtitle track),
    `:burned`, `:soft`, or `false`
  * `:zoom`: `true`, `false`, or a max zoom factor (default 1.6)
  * `:trim`: cut dead time before the first step and after the last one
  * `:intro_ms`, `:outro_ms`: card durations
  * `:music`: path to a background track (any format ffmpeg reads, or a
    `.mid`/`.midi` file rendered with fluidsynth), a prompt to generate a
    track with ElevenLabs (`%{elevenlabs: "calm lo-fi piano"}`, see
    `PhxDemo.MusicGen`), or `false` (default).
    Plays whenever a path is set.
  * `:music_volume`: music level in dB (default -18)
  * `:soundfont`: `.sf2` file used to render a MIDI `:music` track.
    Defaults to the one from `mix phx_demo.soundfont.setup`, then common
    system locations. See `PhxDemo.Midi`.

# `slug`

Turns a module or demo name into a filesystem-safe slug.

    iex> PhxDemo.slug("Create a Post!")
    "create-a-post"

    iex> PhxDemo.slug(MyAppWeb.Demos.PostsDemo)
    "my_app_web-demos-posts_demo"

# `viewport`

Viewport and video size. Defaults to 1280x720.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
