Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/website.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Website CI

on:
push:
branches: [main]
paths: ["website/**"]
pull_request:
paths: ["website/**"]

jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: website
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
elixir-version: "1.18"
otp-version: "27"
- uses: actions/cache@v4
with:
path: |
website/deps
website/_build
key: website-mix-${{ hashFiles('website/mix.exs') }}
- run: mix deps.get
- run: mix assets.build
- run: mix compile --warnings-as-errors
- run: mix test
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

ExTauri wraps [Tauri](https://tauri.app) to enable Phoenix LiveView applications to run as native desktop apps on macOS, Windows, and Linux.

**Website:** feature showcase, installation guide, and API tour — source in [website/](website/), built with [Francis](https://francis.build).

![example.gif](example.gif)

## Features
Expand Down
7 changes: 7 additions & 0 deletions website/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
_build/
deps/
doc/
test/
README.md
.gitignore
.formatter.exs
3 changes: 3 additions & 0 deletions website/.formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
12 changes: 12 additions & 0 deletions website/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/_build/
/cover/
/deps/
/doc/
erl_crash.dump
*.ez
ex_tauri_website-*.tar

# tailwind + digest build output
/priv/static/tw.css
/priv/static/cache_manifest.json
/priv/static/**/*.gz
41 changes: 41 additions & 0 deletions website/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
ARG ELIXIR_VERSION=1.18.4
ARG OTP_VERSION=27.3.4
ARG DEBIAN_VERSION=bookworm-20250520-slim
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"

# Build Stage
FROM ${BUILDER_IMAGE} AS build
ENV MIX_ENV=prod

RUN apt-get update -y && apt-get install -y build-essential git \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*

WORKDIR /app

COPY . .

RUN mix local.hex --force && mix local.rebar --force && mix deps.get && mix compile
RUN mix assets.build
RUN mix release

# Run Stage
FROM ${RUNNER_IMAGE} AS run
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8

RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \
&& apt-get clean && rm -f /var/lib/apt/lists/*_* \
&& sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen

WORKDIR /app
ENV MIX_ENV=prod
ENV HOME=/app

COPY --from=build /app/_build/${MIX_ENV}/rel/ex_tauri_website ./
COPY --from=build /app/priv ./priv

EXPOSE 4000

CMD ["bin/ex_tauri_website", "start"]
40 changes: 40 additions & 0 deletions website/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# ExTauri Website

The showcase website for [ExTauri](https://github.com/filipecabaco/ex_tauri), built with
[Francis](https://francis.build) and [Tailwind CSS](https://tailwindcss.com), following the
structure of [francis_site](https://github.com/francis-build/francis_site).

## Structure

- `lib/ex_tauri_website.ex` — the whole app: a single `use Francis` module with `/`, `/health`, static file serving, secure headers, and CSP
- `priv/templates/index.html.eex` — the page template, styled with Tailwind utilities
- `assets/css/app.css` — Tailwind v4 entrypoint (theme tokens + component classes)
- `priv/static/` — JS, images, and the compiled `tw.css` (build output, not committed)

## Development

```bash
mix deps.get
mix assets.build # compile Tailwind + digest static assets
mix francis.server # http://localhost:4000
iex -S mix francis.server # with an IEx console
```

Set `PORT` to serve on a different port. While iterating on styles, rebuild CSS
with `mix tailwind default` (or run `mix tailwind default -- --watch` in a
second terminal).

## Tests

```bash
mix test
```

## Production

```bash
docker build -t ex_tauri_website .
docker run -p 4000:4000 ex_tauri_website
```

Or build a release directly with `MIX_ENV=prod mix do assets.build, release`.
101 changes: 101 additions & 0 deletions website/assets/css/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
@import "tailwindcss";
@source "../../priv/templates/index.html.eex";
@source "../../priv/static/site.js";

@theme {
--color-ink: #0b0a10;
--color-ink-soft: #121017;
--color-panel: #17141f;
--color-panel-2: #1d1928;
--color-bdr: #2a2438;
--color-bdr-soft: #221d30;
--color-txt: #ece9f4;
--color-mut: #a29bb8;
--color-faint: #6f6885;
--color-grape: #a78bfa;
--color-grape-deep: #8b5cf6;
--color-amber: #ffc131;
--color-code: #100e17;

--font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}

@layer base {
html {
scroll-behavior: smooth;
scroll-padding-top: 84px;
}
body {
@apply bg-ink font-sans text-txt antialiased;
}
::selection {
background: rgb(139 92 246 / 0.35);
}
}

@layer components {
.btn-primary {
@apply inline-flex items-center gap-2 rounded-xl px-5 py-3 text-[15px] font-semibold no-underline transition hover:-translate-y-px bg-gradient-to-br from-grape-deep to-[#7c3aed] text-white shadow-[0_6px_24px_rgba(139,92,246,.35)] hover:shadow-[0_8px_30px_rgba(139,92,246,.5)];
}
.btn-ghost {
@apply inline-flex items-center gap-2 rounded-xl px-5 py-3 text-[15px] font-semibold no-underline transition hover:-translate-y-px border border-bdr bg-panel text-txt hover:border-grape-deep;
}

.eyebrow {
@apply mb-2.5 text-[13px] font-bold uppercase tracking-widest text-amber;
}

.card {
@apply rounded-2xl border border-bdr-soft bg-panel p-6 transition hover:-translate-y-0.5 hover:border-grape-deep;
}

.inline-code {
@apply rounded-md bg-grape-deep/10 px-1.5 py-px font-mono text-[13px] text-grape;
}

.code-shell {
@apply overflow-hidden rounded-xl border border-bdr bg-code text-left;
}
.code-label {
@apply border-b border-bdr-soft bg-white/[0.015] px-4 py-2 font-mono text-xs text-faint;
}
.code-shell pre {
@apply overflow-x-auto p-5 font-mono text-[13.5px] leading-relaxed text-[#d6d2e4];
}

.tab {
@apply inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-bdr bg-panel px-4 py-2 font-sans text-sm font-semibold text-mut transition hover:border-grape-deep hover:text-txt;
}
.tab.active {
@apply border-grape-deep bg-grape-deep/15 text-grape;
}
.tab-panel {
@apply hidden;
}
.tab-panel.active {
@apply block;
}

.copy-btn {
@apply inline-flex cursor-pointer border-0 bg-transparent p-0.5 text-faint transition hover:text-grape;
}

.footer-link {
@apply text-mut hover:text-txt;
}
}

/* Syntax highlighter token colors (spans injected by site.js) */
.tok-kw { color: #c4b5fd; }
.tok-mod { color: #7dd3fc; }
.tok-atom { color: var(--color-amber); }
.tok-str { color: #86efac; }
.tok-com { color: #5f5876; font-style: italic; }
.tok-num { color: #fda4af; }
.tok-fn { color: #f0abfc; }
.tok-var { color: #93c5fd; }
.tok-cmd { color: #7dd3fc; }

.code-shell pre::-webkit-scrollbar { height: 8px; }
.code-shell pre::-webkit-scrollbar-thumb { background: var(--color-bdr); border-radius: 4px; }
13 changes: 13 additions & 0 deletions website/config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Config

config :tailwind,
version: "4.1.12",
default: [
args: ~w(
--input=assets/css/app.css
--output=priv/static/tw.css
),
cd: Path.expand("..", __DIR__)
]

import_config "#{config_env()}.exs"
4 changes: 4 additions & 0 deletions website/config/dev.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Config

config :francis, dev: true
config :logger, level: :debug
4 changes: 4 additions & 0 deletions website/config/prod.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Config

config :francis, dev: false
config :logger, level: :info
4 changes: 4 additions & 0 deletions website/config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Config

config :francis, dev: false
config :logger, level: :warning
41 changes: 41 additions & 0 deletions website/lib/ex_tauri_website.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule ExTauriWebsite do
@moduledoc """
The ExTauri showcase website — a Francis app serving a Tailwind-styled page.
"""
use Francis,
static: [at: "/", from: {:ex_tauri_website, "priv/static"}],
bandit_opts: [port: String.to_integer(System.get_env("PORT") || "4000")]

plug(Francis.Plug.SecureHeaders)

plug(Francis.Plug.CSP,
directives: %{
"default-src" => "'self'",
"img-src" => "'self' data:",
"style-src" => "'self' 'unsafe-inline'",
"script-src" => "'self'"
}
)

get("/health", fn _ -> %{status: "ok"} end)

get("/", fn conn ->
html =
:code.priv_dir(:ex_tauri_website)
|> Path.join("templates/index.html.eex")
|> EEx.eval_file(
tw_css: Francis.Static.static_path("tw.css"),
site_js: Francis.Static.static_path("site.js"),
example_gif: Francis.Static.static_path("images/example.gif"),
year: Date.utc_today().year
)

html(conn, html)
end)

unmatched(fn conn ->
conn
|> Plug.Conn.put_resp_content_type("text/html")
|> Plug.Conn.send_resp(404, Francis.ErrorPage.render(404))
end)
end
32 changes: 32 additions & 0 deletions website/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule ExTauriWebsite.MixProject do
use Mix.Project

def project do
[
app: :ex_tauri_website,
version: "0.1.0",
elixir: "~> 1.14",
elixirc_paths: ["lib"],
start_permanent: Mix.env() == :prod,
deps: deps(),
aliases: aliases()
]
end

def application do
[mod: {ExTauriWebsite, []}, extra_applications: [:logger]]
end

defp deps do
[
{:francis, "~> 0.3.3"},
{:tailwind, "~> 0.4", runtime: Mix.env() == :dev}
]
end

defp aliases do
[
"assets.build": ["tailwind default", "francis.digest priv/static --clean"]
]
end
end
Binary file added website/priv/static/images/example.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading