diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d615142 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,38 @@ +# Build artifacts +_build/ +deps/ +doc/ +cover/ + +# Test +test/ +tasks/ +benchmarks/ + +# Dev / editor +.elixir_ls/ +.lexical/ +.git/ +.github/ +.vscode/ +.idea/ + +# Crash dumps and ephemeral files +erl_crash.dump +*.ez +.fetch + +# Website-specific paths +website/_build/ +website/deps/ +website/doc/ +website/cover/ +website/test/ +website/.elixir_ls/ +website/erl_crash.dump +website/priv/static/assets/ +website/priv/static/cache_manifest.json +website/assets/node_modules/ + +# Local agent / plan files +.agents/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c23eb21..abaeeec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,6 +80,37 @@ jobs: - name: Format run: mix format --check-formatted + website: + name: Website tests + runs-on: ubuntu-latest + strategy: + matrix: + include: + - elixir: "1.19.0" + otp: "28.0" + bootstrap_beam: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + - *setup-beam + - *bootstrap-beam + - name: Restore website dependencies cache + uses: actions/cache@v4 + with: + path: website/deps + key: ${{ runner.os }}-elixir${{ matrix.elixir }}-otp${{ matrix.otp }}-website-mix-${{ hashFiles('website/mix.lock') }} + restore-keys: ${{ runner.os }}-elixir${{ matrix.elixir }}-otp${{ matrix.otp }}-website-mix- + - name: Install website dependencies + working-directory: website + run: mix deps.get + - name: Run website tests + working-directory: website + run: mix test + - name: Format + working-directory: website + run: mix format --check-formatted + lua53-suite: name: Lua 5.3 suite runs-on: ubuntu-latest diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7e74e94 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,93 @@ +# Multi-stage Dockerfile for the deflua.com website. +# +# Build context MUST be the lua/ repo root (parent of website/) so the +# `{:lua, path: ".."}` path dependency is reachable from inside the image. +# +# From the repo root: +# docker build -t deflua . +# fly deploy # fly.toml lives next to this Dockerfile + +ARG ELIXIR_VERSION=1.19.4 +ARG OTP_VERSION=28.3.3 +ARG DEBIAN_VERSION=bookworm-20260518-slim + +ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" + +FROM ${BUILDER_IMAGE} AS builder + +RUN apt-get update -y && apt-get install -y build-essential git curl ca-certificates gnupg \ + && mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ + && apt-get update -y && apt-get install -y nodejs \ + && apt-get clean && rm -f /var/lib/apt/lists/*_* + +# Mirror the on-disk layout so `{:lua, path: ".."}` in website/mix.exs +# resolves correctly: website lives at /app/website, lua at /app. +WORKDIR /app + +ENV MIX_ENV="prod" + +RUN mix local.hex --force && mix local.rebar --force + +# Lua package: only the files mix needs to compile the dep. +# README.md is read at compile time by lib/lua.ex (@external_resource). +COPY mix.exs mix.lock README.md ./ +COPY lib ./lib + +# Website mix files first so deps.get is cached when only app code changes. +COPY website/mix.exs website/mix.lock ./website/ +WORKDIR /app/website + +RUN mix deps.get --only $MIX_ENV +RUN mkdir config + +COPY website/config/config.exs website/config/${MIX_ENV}.exs config/ +RUN mix deps.compile + +# Install npm deps separately from app code so they cache. +# package.json references `../deps/phoenix` etc., so deps/ must already exist +# (it does — `mix deps.get` ran above). +COPY website/assets/package.json website/assets/package-lock.json assets/ +RUN cd assets && npm ci --no-audit --no-fund --progress=false + +COPY website/priv priv +COPY website/lib lib +COPY website/assets assets + +# mix compile must run BEFORE assets.deploy because Phoenix's LiveView +# colocated-hooks compiler generates files under _build/ that esbuild +# resolves via NODE_PATH (`phoenix-colocated/website`). +RUN mix compile +RUN mix assets.deploy + +COPY website/config/runtime.exs config/ +COPY website/rel rel +# Belt-and-suspenders: ensure the overlay script is exec'able regardless of +# the source-tree mode it was checked out with. +RUN chmod +x rel/overlays/bin/server && mix release + +# ---- Runtime image ---- +FROM ${RUNNER_IMAGE} + +RUN apt-get update -y && \ + apt-get install -y libstdc++6 openssl libncurses6 locales ca-certificates curl && \ + apt-get clean && rm -f /var/lib/apt/lists/*_* + +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen + +ENV LANG=en_US.UTF-8 \ + LANGUAGE=en_US:en \ + LC_ALL=en_US.UTF-8 + +WORKDIR /app +RUN chown nobody /app + +ENV MIX_ENV="prod" + +COPY --from=builder --chown=nobody:root /app/website/_build/${MIX_ENV}/rel/website ./ + +USER nobody + +CMD ["/app/bin/server"] diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..3597967 --- /dev/null +++ b/fly.toml @@ -0,0 +1,61 @@ +# fly.toml — deflua.com (Lua.ex showcase) +# +# Deploy from the lua/ repo root so the Dockerfile can reach the `:lua` path +# dependency one directory above website/. + +app = "deflua" +primary_region = "ewr" # change after `fly platform regions` if desired +kill_signal = "SIGTERM" +kill_timeout = "10s" + +[build] + dockerfile = "Dockerfile" + +[env] + PHX_HOST = "deflua.com" + PORT = "8080" + +[deploy] + strategy = "rolling" + +# Single HTTP service — Fly's edge handles TLS termination and HTTP/2. +[[services]] + protocol = "tcp" + internal_port = 8080 + auto_stop_machines = "off" # keep machines warm; HN burst can't wait on cold starts + auto_start_machines = true + min_machines_running = 2 # always have spare capacity + + # LiveView holds a websocket open for the whole session. Use connection-based + # concurrency so Fly's proxy spreads sockets across machines and triggers + # autoscale before any one node saturates. + [services.concurrency] + type = "connections" + soft_limit = 1000 # start sending new conns to other machines past this + hard_limit = 2000 # refuse new conns past this on a single machine + + [[services.ports]] + port = 80 + handlers = ["http"] + force_https = true + + [[services.ports]] + port = 443 + handlers = ["tls", "http"] + + # Cheap liveness probe — `/health` is exempt from force_ssl in prod.exs. + [[services.http_checks]] + interval = "15s" + timeout = "5s" + grace_period = "10s" + method = "get" + path = "/health" + protocol = "http" + +# Default VM. shared-cpu-2x / 1GB handles a Phoenix LiveView site easily. +# Bump to performance-2x or shared-cpu-4x if BEAM starts saturating. +[[vm]] + size = "shared-cpu-2x" + memory = "1gb" + cpu_kind = "shared" + cpus = 2 diff --git a/mix.exs b/mix.exs index 443605e..0096174 100644 --- a/mix.exs +++ b/mix.exs @@ -60,6 +60,7 @@ defmodule Lua.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ + {:tidewave, "~> 0.5", only: [:dev]}, {:usage_rules, "~> 0.1", only: [:dev]}, {:ex_doc, "~> 0.38", only: :dev, runtime: false}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index af5f7cb..fdd7c1d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,34 +1,39 @@ %{ "benchee": {:hex, :benchee, "1.5.0", "4d812c31d54b0ec0167e91278e7de3f596324a78a096fd3d0bea68bb0c513b10", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "5b075393aea81b8ae74eadd1c28b1d87e8a63696c649d8293db7c4df3eb67535"}, + "circular_buffer": {:hex, :circular_buffer, "1.0.1", "01f0e3d5fe945080692cf6521c0988e9dbb5dc312831cefe77e5b63a4e658160", [:mix], [], "hexpm", "7d4ece3137d49c1f8dd0b3e0aa7c484f4d83a0be5d4b516c282085c1d5f2d7b9"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"}, + "ex_ast": {:hex, :ex_ast, "0.12.0", "052ad63711da41b7efbfb3490dbf3d757bb67caec17d02f6deb0db4a0363e5f6", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.7", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "66b4797f157d32f0a63c6da227515f78816c0ac8f621f6d7a2b22108e7b4dd85"}, "ex_doc": {:hex, :ex_doc, "0.39.2", "da5549bbce34c5fb0811f829f9f6b7a13d5607b222631d9e989447096f295c57", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "62665526a88c207653dbcee2aac66c2c229d7c18a70ca4ffc7f74f9e01324daa"}, - "finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"}, + "finch": {:hex, :finch, "0.22.0", "5c48fa6f9706a78eb9036cacb67b8b996b4e66d111c543f4c29bb0f879a6806b", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b94e83c47780fc6813f746a1f1a34ee65cda42da4c5ea26a68f0acc4498e23dc"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.7.2", "81c132c0df95963c7a228f74a32d3348773743ed9651f24183bfce0fe6ff16d1", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f4cab73ec31f4fb452de1a17037f8a08826105265aa2d76486fcb848189bef9b"}, - "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "igniter": {:hex, :igniter, "0.8.0", "c7cab589440e5f20ff68e00f60eb094378114dab3105c0784ce8140f8dfdd2c0", [:mix], [{:ex_ast, "~> 0.5", [hex: :ex_ast, repo: "hexpm", optional: false]}, {:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "fcd99096fde4797f7b48bebddcfc58785569acd696346a3eb385bf813f47a7cc"}, + "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, "luaport": {:hex, :luaport, "1.6.3", "df26eb476e4e8a372dc8c812f59ef3280b34743dc04ec339b646491a488b2560", [:rebar3], [], "hexpm", "3df1f51b82b62ed3b128d0d7c6caee4c5143c3bed498737f12302f7bf98b75a1"}, "luerl": {:hex, :luerl, "1.5.1", "f6700420950fc6889137e7a0c11c4a8467dea04a8c23f707a40d83566d14e786", [:rebar3], [], "hexpm", "abf88d849baa0d5dca93b245a8688d4de2ee3d588159bb2faf51e15946509390"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, - "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "mint": {:hex, :mint, "1.8.0", "b964eaf4416f2dee2ba88968d52239fca5621b0402b9c95f55a08eb9d74803e9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "f3c572c11355eccf00f22275e9b42463bc17bd28db13be1e28f8e0bb4adbc849"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, - "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, - "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, - "sourceror": {:hex, :sourceror, "1.10.1", "325753ed460fe9fa34ebb4deda76d57b2e1507dcd78a5eb9e1c41bfb78b7cdfe", [:mix], [], "hexpm", "288f3079d93865cd1e3e20df5b884ef2cb440e0e03e8ae393624ee8a770ba588"}, - "spitfire": {:hex, :spitfire, "0.3.2", "476b7b5151fd053a864dae7b5eaeed01811e8b2ff3f24f3c048af1c9dfee5e3d", [:mix], [], "hexpm", "014f7b8c6dd45d1e3b08103c7e61515a590efc872441cf3e933a20efa4b5c46c"}, + "plug": {:hex, :plug, "1.19.2", "e4950525b22c6789dfb38a3f95d47171ba159da3fc5a33be9643b43d5e8adb98", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6fce20a56af5e60fa5dfecf3f907bb98ec981be43c79a3809a499bc3d133de0"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "req": {:hex, :req, "0.5.18", "48e6431cb4135e8a7815e745177485369a9b4a9924d5fe68ca00eb09ceaed1ef", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21.0 or ~> 0.22.0", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "fa03812c440a9754bf34355e0c5d4f3ed316458db62e3284b7a352ef8dc0b996"}, + "rewrite": {:hex, :rewrite, "1.3.0", "67448ba7975690b35ba7e7f35717efcce317dbd5963cb0577aa7325c1923121a", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "d111ac7ff3a58a802ef4f193bbd1831e00a9c57b33276e5068e8390a212714a5"}, + "sourceror": {:hex, :sourceror, "1.12.0", "da354c5f35aad3cc1132f5d5b0d8437d865e2661c263260480bab51b5eedb437", [:mix], [], "hexpm", "755703683bd014ebcd5de9acc24b68fb874a660a568d1d63f8f98cd8a6ef9cd0"}, + "spitfire": {:hex, :spitfire, "0.3.12", "0f7780e4c6ea3753b65ea0c4924f3dfd5c21a51aaa734ffb9dd0b68d2544f27e", [:mix], [], "hexpm", "a389931287b85330c0e954ab06447e198516ab368a232a0200ed77ca13ca9acf"}, "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, "stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"}, "styler": {:hex, :styler, "1.10.1", "9229050c978bfaaab1d94e8673843576d0127d48fe64824a30babde3d6342475", [:mix], [], "hexpm", "d86cbcc70e8ab424393af313d1d885931ba9dc7c383d7dd30f4ab255a8d39f73"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, + "tidewave": {:hex, :tidewave, "0.5.6", "91f35540b5599640443f1d3a1c6166bf506e202840261a6344e384e8813c1f64", [:mix], [{:circular_buffer, "~> 0.4 or ~> 1.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_reload, ">= 1.6.1", [hex: :phoenix_live_reload, repo: "hexpm", optional: true]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "dc82d52b8b6ffc04680544b17cd340c7d4166bb0d63999eb960850526866b533"}, "usage_rules": {:hex, :usage_rules, "0.1.26", "19d38c8b9b5c35434eae44f7e4554caeb5f08037a1d45a6b059a9782543ac22e", [:mix], [{:igniter, ">= 0.6.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "9f0d203aa288e1b48318929066778ec26fc423fd51f08518c5b47f58ad5caca9"}, } diff --git a/website/.formatter.exs b/website/.formatter.exs new file mode 100644 index 0000000..e945e12 --- /dev/null +++ b/website/.formatter.exs @@ -0,0 +1,5 @@ +[ + import_deps: [:phoenix], + plugins: [Phoenix.LiveView.HTMLFormatter], + inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] +] diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..076e671 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,37 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Temporary files, for example, from tests. +/tmp/ + +# Ignore package tarball (built via "mix hex.build"). +website-*.tar + +# Ignore assets that are produced by build tools. +/priv/static/assets/ + +# Ignore digested assets cache. +/priv/static/cache_manifest.json + +# In case you use Node.js/npm, you want to ignore these. +npm-debug.log +/assets/node_modules/ + diff --git a/website/AGENTS.md b/website/AGENTS.md new file mode 100644 index 0000000..e3daa75 --- /dev/null +++ b/website/AGENTS.md @@ -0,0 +1,437 @@ +This is a web application written using the Phoenix web framework. + +## Project guidelines + +- Use `mix precommit` alias when you are done with all changes and fix any pending issues +- Use the already included and available `:req` (`Req`) library for HTTP requests, **avoid** `:httpoison`, `:tesla`, and `:httpc`. Req is included by default and is the preferred HTTP client for Phoenix apps + +### Phoenix v1.8 guidelines + +- **Always** begin your LiveView templates with `` which wraps all inner content +- The `MyAppWeb.Layouts` module is aliased in the `my_app_web.ex` file, so you can use it without needing to alias it again +- Anytime you run into errors with no `current_scope` assign: + - You failed to follow the Authenticated Routes guidelines, or you failed to pass `current_scope` to `` + - **Always** fix the `current_scope` error by moving your routes to the proper `live_session` and ensure you pass `current_scope` as needed +- Phoenix v1.8 moved the `<.flash_group>` component to the `Layouts` module. You are **forbidden** from calling `<.flash_group>` outside of the `layouts.ex` module +- Out of the box, `core_components.ex` imports an `<.icon name="hero-x-mark" class="w-5 h-5"/>` component for hero icons. **Always** use the `<.icon>` component for icons, **never** use `Heroicons` modules or similar +- **Always** use the imported `<.input>` component for form inputs from `core_components.ex` when available. `<.input>` is imported and using it will save steps and prevent errors +- If you override the default input classes (`<.input class="myclass px-2 py-1 rounded-lg">)`) class with your own values, no default classes are inherited, so your +custom classes must fully style the input + +### JS and CSS guidelines + +- **Use Tailwind CSS classes and custom CSS rules** to create polished, responsive, and visually stunning interfaces. +- Tailwindcss v4 **no longer needs a tailwind.config.js** and uses a new import syntax in `app.css`: + + @import "tailwindcss" source(none); + @source "../css"; + @source "../js"; + @source "../../lib/my_app_web"; + +- **Always use and maintain this import syntax** in the app.css file for projects generated with `phx.new` +- **Never** use `@apply` when writing raw css +- **Always** manually write your own tailwind-based components instead of using daisyUI for a unique, world-class design +- Out of the box **only the app.js and app.css bundles are supported** + - You cannot reference an external vendor'd script `src` or link `href` in the layouts + - You must import the vendor deps into app.js and app.css to use them + - **Never write inline tags within templates** + +### UI/UX & design guidelines + +- **Produce world-class UI designs** with a focus on usability, aesthetics, and modern design principles +- Implement **subtle micro-interactions** (e.g., button hover effects, and smooth transitions) +- Ensure **clean typography, spacing, and layout balance** for a refined, premium look +- Focus on **delightful details** like hover effects, loading states, and smooth page transitions + + + + + +## Elixir guidelines + +- Elixir lists **do not support index based access via the access syntax** + + **Never do this (invalid)**: + + i = 0 + mylist = ["blue", "green"] + mylist[i] + + Instead, **always** use `Enum.at`, pattern matching, or `List` for index based list access, ie: + + i = 0 + mylist = ["blue", "green"] + Enum.at(mylist, i) + +- Elixir variables are immutable, but can be rebound, so for block expressions like `if`, `case`, `cond`, etc + you *must* bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie: + + # INVALID: we are rebinding inside the `if` and the result never gets assigned + if connected?(socket) do + socket = assign(socket, :val, val) + end + + # VALID: we rebind the result of the `if` to a new variable + socket = + if connected?(socket) do + assign(socket, :val, val) + end + +- **Never** nest multiple modules in the same file as it can cause cyclic dependencies and compilation errors +- **Never** use map access syntax (`changeset[:field]`) on structs as they do not implement the Access behaviour by default. For regular structs, you **must** access the fields directly, such as `my_struct.field` or use higher level APIs that are available on the struct if they exist, `Ecto.Changeset.get_field/2` for changesets +- Elixir's standard library has everything necessary for date and time manipulation. Familiarize yourself with the common `Time`, `Date`, `DateTime`, and `Calendar` interfaces by accessing their documentation as necessary. **Never** install additional dependencies unless asked or for date/time parsing (which you can use the `date_time_parser` package) +- Don't use `String.to_atom/1` on user input (memory leak risk) +- Predicate function names should not start with `is_` and should end in a question mark. Names like `is_thing` should be reserved for guards +- Elixir's builtin OTP primitives like `DynamicSupervisor` and `Registry`, require names in the child spec, such as `{DynamicSupervisor, name: MyApp.MyDynamicSup}`, then you can use `DynamicSupervisor.start_child(MyApp.MyDynamicSup, child_spec)` +- Use `Task.async_stream(collection, callback, options)` for concurrent enumeration with back-pressure. The majority of times you will want to pass `timeout: :infinity` as option + +## Mix guidelines + +- Read the docs and options before using tasks (by using `mix help task_name`) +- To debug test failures, run tests in a specific file with `mix test test/my_test.exs` or run all previously failed tests with `mix test --failed` +- `mix deps.clean --all` is **almost never needed**. **Avoid** using it unless you have good reason + +## Test guidelines + +- **Always use `start_supervised!/1`** to start processes in tests as it guarantees cleanup between tests +- **Avoid** `Process.sleep/1` and `Process.alive?/1` in tests + - Instead of sleeping to wait for a process to finish, **always** use `Process.monitor/1` and assert on the DOWN message: + + ref = Process.monitor(pid) + assert_receive {:DOWN, ^ref, :process, ^pid, :normal} + + - Instead of sleeping to synchronize before the next call, **always** use `_ = :sys.get_state/1` to ensure the process has handled prior messages + + + +## Phoenix guidelines + +- Remember Phoenix router `scope` blocks include an optional alias which is prefixed for all routes within the scope. **Always** be mindful of this when creating routes within a scope to avoid duplicate module prefixes. + +- You **never** need to create your own `alias` for route definitions! The `scope` provides the alias, ie: + + scope "/admin", AppWeb.Admin do + pipe_through :browser + + live "/users", UserLive, :index + end + + the UserLive route would point to the `AppWeb.Admin.UserLive` module + +- `Phoenix.View` no longer is needed or included with Phoenix, don't use it + + + +## Phoenix HTML guidelines + +- Phoenix templates **always** use `~H` or .html.heex files (known as HEEx), **never** use `~E` +- **Always** use the imported `Phoenix.Component.form/1` and `Phoenix.Component.inputs_for/1` function to build forms. **Never** use `Phoenix.HTML.form_for` or `Phoenix.HTML.inputs_for` as they are outdated +- When building forms **always** use the already imported `Phoenix.Component.to_form/2` (`assign(socket, form: to_form(...))` and `<.form for={@form} id="msg-form">`), then access those forms in the template via `@form[:field]` +- **Always** add unique DOM IDs to key elements (like forms, buttons, etc) when writing templates, these IDs can later be used in tests (`<.form for={@form} id="product-form">`) +- For "app wide" template imports, you can import/alias into the `my_app_web.ex`'s `html_helpers` block, so they will be available to all LiveViews, LiveComponent's, and all modules that do `use MyAppWeb, :html` (replace "my_app" by the actual app name) + +- Elixir supports `if/else` but **does NOT support `if/else if` or `if/elsif`**. **Never use `else if` or `elseif` in Elixir**, **always** use `cond` or `case` for multiple conditionals. + + **Never do this (invalid)**: + + <%= if condition do %> + ... + <% else if other_condition %> + ... + <% end %> + + Instead **always** do this: + + <%= cond do %> + <% condition -> %> + ... + <% condition2 -> %> + ... + <% true -> %> + ... + <% end %> + +- HEEx require special tag annotation if you want to insert literal curly's like `{` or `}`. If you want to show a textual code snippet on the page in a `
` or `` block you *must* annotate the parent tag with `phx-no-curly-interpolation`:
+
+      
+        let obj = {key: "val"}
+      
+
+  Within `phx-no-curly-interpolation` annotated tags, you can use `{` and `}` without escaping them, and dynamic Elixir expressions can still be used with `<%= ... %>` syntax
+
+- HEEx class attrs support lists, but you must **always** use list `[...]` syntax. You can use the class list syntax to conditionally add classes, **always do this for multiple class values**:
+
+      Text
+
+  and **always** wrap `if`'s inside `{...}` expressions with parens, like done above (`if(@other_condition, do: "...", else: "...")`)
+
+  and **never** do this, since it's invalid (note the missing `[` and `]`):
+
+       ...
+      => Raises compile syntax error on invalid HEEx attr syntax
+
+- **Never** use `<% Enum.each %>` or non-for comprehensions for generating template content, instead **always** use `<%= for item <- @collection do %>`
+- HEEx HTML comments use `<%!-- comment --%>`. **Always** use the HEEx HTML comment syntax for template comments (`<%!-- comment --%>`)
+- HEEx allows interpolation via `{...}` and `<%= ... %>`, but the `<%= %>` **only** works within tag bodies. **Always** use the `{...}` syntax for interpolation within tag attributes, and for interpolation of values within tag bodies. **Always** interpolate block constructs (if, cond, case, for) within tag bodies using `<%= ... %>`.
+
+  **Always** do this:
+
+      
+ {@my_assign} + <%= if @some_block_condition do %> + {@another_assign} + <% end %> +
+ + and **Never** do this – the program will terminate with a syntax error: + + <%!-- THIS IS INVALID NEVER EVER DO THIS --%> +
+ {if @invalid_block_construct do} + {end} +
+ + + +## Phoenix LiveView guidelines + +- **Never** use the deprecated `live_redirect` and `live_patch` functions, instead **always** use the `<.link navigate={href}>` and `<.link patch={href}>` in templates, and `push_navigate` and `push_patch` functions LiveViews +- **Avoid LiveComponent's** unless you have a strong, specific need for them +- LiveViews should be named like `AppWeb.WeatherLive`, with a `Live` suffix. When you go to add LiveView routes to the router, the default `:browser` scope is **already aliased** with the `AppWeb` module, so you can just do `live "/weather", WeatherLive` + +### LiveView streams + +- **Always** use LiveView streams for collections for assigning regular lists to avoid memory ballooning and runtime termination with the following operations: + - basic append of N items - `stream(socket, :messages, [new_msg])` + - resetting stream with new items - `stream(socket, :messages, [new_msg], reset: true)` (e.g. for filtering items) + - prepend to stream - `stream(socket, :messages, [new_msg], at: -1)` + - deleting items - `stream_delete(socket, :messages, msg)` + +- When using the `stream/3` interfaces in the LiveView, the LiveView template must 1) always set `phx-update="stream"` on the parent element, with a DOM id on the parent element like `id="messages"` and 2) consume the `@streams.stream_name` collection and use the id as the DOM id for each child. For a call like `stream(socket, :messages, [new_msg])` in the LiveView, the template would be: + +
+
+ {msg.text} +
+
+ +- LiveView streams are *not* enumerable, so you cannot use `Enum.filter/2` or `Enum.reject/2` on them. Instead, if you want to filter, prune, or refresh a list of items on the UI, you **must refetch the data and re-stream the entire stream collection, passing reset: true**: + + def handle_event("filter", %{"filter" => filter}, socket) do + # re-fetch the messages based on the filter + messages = list_messages(filter) + + {:noreply, + socket + |> assign(:messages_empty?, messages == []) + # reset the stream with the new messages + |> stream(:messages, messages, reset: true)} + end + +- LiveView streams *do not support counting or empty states*. If you need to display a count, you must track it using a separate assign. For empty states, you can use Tailwind classes: + +
+ +
+ {task.name} +
+
+ + The above only works if the empty state is the only HTML block alongside the stream for-comprehension. + +- When updating an assign that should change content inside any streamed item(s), you MUST re-stream the items + along with the updated assign: + + def handle_event("edit_message", %{"message_id" => message_id}, socket) do + message = Chat.get_message!(message_id) + edit_form = to_form(Chat.change_message(message, %{content: message.content})) + + # re-insert message so @editing_message_id toggle logic takes effect for that stream item + {:noreply, + socket + |> stream_insert(:messages, message) + |> assign(:editing_message_id, String.to_integer(message_id)) + |> assign(:edit_form, edit_form)} + end + + And in the template: + +
+
+ {message.username} + <%= if @editing_message_id == message.id do %> + <%!-- Edit mode --%> + <.form for={@edit_form} id="edit-form-#{message.id}" phx-submit="save_edit"> + ... + + <% end %> +
+
+ +- **Never** use the deprecated `phx-update="append"` or `phx-update="prepend"` for collections + +### LiveView JavaScript interop + +- Remember anytime you use `phx-hook="MyHook"` and that JS hook manages its own DOM, you **must** also set the `phx-update="ignore"` attribute +- **Always** provide an unique DOM id alongside `phx-hook` otherwise a compiler error will be raised + +LiveView hooks come in two flavors, 1) colocated js hooks for "inline" scripts defined inside HEEx, +and 2) external `phx-hook` annotations where JavaScript object literals are defined and passed to the `LiveSocket` constructor. + +#### Inline colocated js hooks + +**Never** write raw embedded ` + +- colocated hooks are automatically integrated into the app.js bundle +- colocated hooks names **MUST ALWAYS** start with a `.` prefix, i.e. `.PhoneNumber` + +#### External phx-hook + +External JS hooks (`
`) must be placed in `assets/js/` and passed to the +LiveSocket constructor: + + const MyHook = { + mounted() { ... } + } + let liveSocket = new LiveSocket("/live", Socket, { + hooks: { MyHook } + }); + +#### Pushing events between client and server + +Use LiveView's `push_event/3` when you need to push events/data to the client for a phx-hook to handle. +**Always** return or rebind the socket on `push_event/3` when pushing events: + + # re-bind socket so we maintain event state to be pushed + socket = push_event(socket, "my_event", %{...}) + + # or return the modified socket directly: + def handle_event("some_event", _, socket) do + {:noreply, push_event(socket, "my_event", %{...})} + end + +Pushed events can then be picked up in a JS hook with `this.handleEvent`: + + mounted() { + this.handleEvent("my_event", data => console.log("from server:", data)); + } + +Clients can also push an event to the server and receive a reply with `this.pushEvent`: + + mounted() { + this.el.addEventListener("click", e => { + this.pushEvent("my_event", { one: 1 }, reply => console.log("got reply from server:", reply)); + }) + } + +Where the server handled it via: + + def handle_event("my_event", %{"one" => 1}, socket) do + {:reply, %{two: 2}, socket} + end + +### LiveView tests + +- `Phoenix.LiveViewTest` module and `LazyHTML` (included) for making your assertions +- Form tests are driven by `Phoenix.LiveViewTest`'s `render_submit/2` and `render_change/2` functions +- Come up with a step-by-step test plan that splits major test cases into small, isolated files. You may start with simpler tests that verify content exists, gradually add interaction tests +- **Always reference the key element IDs you added in the LiveView templates in your tests** for `Phoenix.LiveViewTest` functions like `element/2`, `has_element/2`, selectors, etc +- **Never** tests again raw HTML, **always** use `element/2`, `has_element/2`, and similar: `assert has_element?(view, "#my-form")` +- Instead of relying on testing text content, which can change, favor testing for the presence of key elements +- Focus on testing outcomes rather than implementation details +- Be aware that `Phoenix.Component` functions like `<.form>` might produce different HTML than expected. Test against the output HTML structure, not your mental model of what you expect it to be +- When facing test failures with element selectors, add debug statements to print the actual HTML, but use `LazyHTML` selectors to limit the output, ie: + + html = render(view) + document = LazyHTML.from_fragment(html) + matches = LazyHTML.filter(document, "your-complex-selector") + IO.inspect(matches, label: "Matches") + +### Form handling + +#### Creating a form from params + +If you want to create a form based on `handle_event` params: + + def handle_event("submitted", params, socket) do + {:noreply, assign(socket, form: to_form(params))} + end + +When you pass a map to `to_form/1`, it assumes said map contains the form params, which are expected to have string keys. + +You can also specify a name to nest the params: + + def handle_event("submitted", %{"user" => user_params}, socket) do + {:noreply, assign(socket, form: to_form(user_params, as: :user))} + end + +#### Creating a form from changesets + +When using changesets, the underlying data, form params, and errors are retrieved from it. The `:as` option is automatically computed too. E.g. if you have a user schema: + + defmodule MyApp.Users.User do + use Ecto.Schema + ... + end + +And then you create a changeset that you pass to `to_form`: + + %MyApp.Users.User{} + |> Ecto.Changeset.change() + |> to_form() + +Once the form is submitted, the params will be available under `%{"user" => user_params}`. + +In the template, the form form assign can be passed to the `<.form>` function component: + + <.form for={@form} id="todo-form" phx-change="validate" phx-submit="save"> + <.input field={@form[:field]} type="text" /> + + +Always give the form an explicit, unique DOM ID, like `id="todo-form"`. + +#### Avoiding form errors + +**Always** use a form assigned via `to_form/2` in the LiveView, and the `<.input>` component in the template. In the template **always access forms this**: + + <%!-- ALWAYS do this (valid) --%> + <.form for={@form} id="my-form"> + <.input field={@form[:field]} type="text" /> + + +And **never** do this: + + <%!-- NEVER do this (invalid) --%> + <.form for={@changeset} id="my-form"> + <.input field={@changeset[:field]} type="text" /> + + +- You are FORBIDDEN from accessing the changeset in the template as it will cause errors +- **Never** use `<.form let={f} ...>` in the template, instead **always use `<.form for={@form} ...>`**, then drive all form references from the form assign as in `@form[:field]`. The UI should **always** be driven by a `to_form/2` assigned in the LiveView module that is derived from a changeset + + + \ No newline at end of file diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..56bc843 --- /dev/null +++ b/website/README.md @@ -0,0 +1,18 @@ +# Demo + +To start your Phoenix server: + +* Run `mix setup` to install and setup dependencies +* Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + +* Official website: https://www.phoenixframework.org/ +* Guides: https://hexdocs.pm/phoenix/overview.html +* Docs: https://hexdocs.pm/phoenix +* Forum: https://elixirforum.com/c/phoenix-forum +* Source: https://github.com/phoenixframework/phoenix diff --git a/website/assets/css/app.css b/website/assets/css/app.css new file mode 100644 index 0000000..15d153b --- /dev/null +++ b/website/assets/css/app.css @@ -0,0 +1,429 @@ +/* See the Tailwind configuration guide for advanced usage + https://tailwindcss.com/docs/configuration */ + +@import "tailwindcss" source(none); +@source "../css"; +@source "../js"; +@source "../../lib/website_web"; + +/* A Tailwind plugin that makes "hero-#{ICON}" classes available. + The heroicons installation itself is managed by your mix.exs */ +@plugin "../vendor/heroicons"; + +/* daisyUI Tailwind Plugin. You can update this file by fetching the latest version with: + curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui.js + Make sure to look at the daisyUI changelog: https://daisyui.com/docs/changelog/ */ +@plugin "../vendor/daisyui" { + themes: false; +} + +/* daisyUI theme plugin. You can update this file by fetching the latest version with: + curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui-theme.js + We ship with two themes, a light one inspired on Phoenix colors and a dark one inspired + on Elixir colors. Build your own at: https://daisyui.com/theme-generator/ */ +@plugin "../vendor/daisyui-theme" { + name: "dark"; + default: false; + prefersdark: true; + color-scheme: "dark"; + --color-base-100: oklch(30.33% 0.016 252.42); + --color-base-200: oklch(25.26% 0.014 253.1); + --color-base-300: oklch(20.15% 0.012 254.09); + --color-base-content: oklch(97.807% 0.029 256.847); + --color-primary: oklch(58% 0.233 277.117); + --color-primary-content: oklch(96% 0.018 272.314); + --color-secondary: oklch(58% 0.233 277.117); + --color-secondary-content: oklch(96% 0.018 272.314); + --color-accent: oklch(60% 0.25 292.717); + --color-accent-content: oklch(96% 0.016 293.756); + --color-neutral: oklch(37% 0.044 257.287); + --color-neutral-content: oklch(98% 0.003 247.858); + --color-info: oklch(58% 0.158 241.966); + --color-info-content: oklch(97% 0.013 236.62); + --color-success: oklch(60% 0.118 184.704); + --color-success-content: oklch(98% 0.014 180.72); + --color-warning: oklch(66% 0.179 58.318); + --color-warning-content: oklch(98% 0.022 95.277); + --color-error: oklch(58% 0.253 17.585); + --color-error-content: oklch(96% 0.015 12.422); + --radius-selector: 0.25rem; + --radius-field: 0.25rem; + --radius-box: 0.5rem; + --size-selector: 0.21875rem; + --size-field: 0.21875rem; + --border: 1.5px; + --depth: 1; + --noise: 0; +} + +@plugin "../vendor/daisyui-theme" { + name: "light"; + default: true; + prefersdark: false; + color-scheme: "light"; + --color-base-100: oklch(98% 0 0); + --color-base-200: oklch(96% 0.001 286.375); + --color-base-300: oklch(92% 0.004 286.32); + --color-base-content: oklch(21% 0.006 285.885); + --color-primary: oklch(70% 0.213 47.604); + --color-primary-content: oklch(98% 0.016 73.684); + --color-secondary: oklch(55% 0.027 264.364); + --color-secondary-content: oklch(98% 0.002 247.839); + --color-accent: oklch(0% 0 0); + --color-accent-content: oklch(100% 0 0); + --color-neutral: oklch(44% 0.017 285.786); + --color-neutral-content: oklch(98% 0 0); + --color-info: oklch(62% 0.214 259.815); + --color-info-content: oklch(97% 0.014 254.604); + --color-success: oklch(70% 0.14 182.503); + --color-success-content: oklch(98% 0.014 180.72); + --color-warning: oklch(66% 0.179 58.318); + --color-warning-content: oklch(98% 0.022 95.277); + --color-error: oklch(58% 0.253 17.585); + --color-error-content: oklch(96% 0.015 12.422); + --radius-selector: 0.25rem; + --radius-field: 0.25rem; + --radius-box: 0.5rem; + --size-selector: 0.21875rem; + --size-field: 0.21875rem; + --border: 1.5px; + --depth: 1; + --noise: 0; +} + +/* Add variants based on LiveView classes */ +@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &); +@custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &); +@custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &); + +/* Use the data attribute for dark mode */ +@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); + +/* Make LiveView wrapper divs transparent for layout */ +[data-phx-session], [data-phx-teleported-src] { display: contents } + +/* Restore the standard
collapse behavior for site disclosures. + Tailwind v4 + daisyUI preflight removes the user-agent rule that hides + non-summary children when the disclosure is closed. Scoped to + `.disclosure` so ad-hoc
usage elsewhere (e.g. dev widgets) + isn't silently affected — apply the class to any
that should + collapse natively. */ +details.disclosure:not([open]) > *:not(summary) { + display: none; +} + +/* Crescent Drop mark — theme-aware CSS variables consumed by the + inline SVG defined in DemoWeb.Layouts.lua_mark/1 and lua_orbit/1. + Light theme: near-black ink drop with a Phoenix-orange satellite. + Dark theme: Elixir-purple drop with a near-white satellite. */ +:root { + --lua-drop-from: oklch(22% 0.04 280); + --lua-drop-to: oklch(32% 0.06 280); + --lua-satellite: oklch(70% 0.213 47.604); +} +[data-theme=dark] { + --lua-drop-from: oklch(58% 0.233 277.117); + --lua-drop-to: oklch(60% 0.25 292.717); + --lua-satellite: oklch(95% 0.02 280); +} + +/* Orbital satellite — rotates around the SVG's geometric center. + `transform-box: view-box` resolves the origin in viewBox units so + `transform-origin: 32px 32px` is the center of our 64x64 viewBox. */ +.lua-orbit-satellite { + transform-box: view-box; + transform-origin: 32px 32px; +} + +@keyframes lua-orbit { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@media (prefers-reduced-motion: no-preference) { + .lua-orbit-satellite { + animation: lua-orbit 24s linear infinite; + animation-delay: -12s; + } +} + +/* Small mark dot — one orbit on load, continuous on hover. + Reuses the @keyframes lua-orbit defined above. Scoped via a + distinct class so the homepage's always-orbiting background mark + (.lua-orbit-satellite) is unaffected. */ +.lua-mark-satellite { + transform-box: view-box; + transform-origin: 32px 32px; +} + +.lua-mark { + transition: filter 120ms ease-out, transform 120ms ease-out; +} + +.group:hover .lua-mark { + filter: + drop-shadow(0 0 3px var(--lua-satellite)) + drop-shadow(0 0 9px color-mix(in oklch, var(--lua-satellite) 55%, transparent)); + transform: scale(1.06); +} + +@media (prefers-reduced-motion: no-preference) { + .lua-mark-satellite { + animation: lua-orbit 2.4s cubic-bezier(0.4, 0, 0.2, 1) 1; + } + + .group:hover .lua-mark-satellite { + animation: lua-orbit 1.6s linear infinite; + } +} + +/* This file is for your main application CSS */ + +/* Makeup syntax highlighting — mapped to daisyUI theme tokens so it + adapts to both `light` and `dark` themes without two stylesheets. + Token classes are Pygments-compatible. */ +.highlight { + color: var(--color-base-content); +} + +/* Comments */ +.highlight .c, +.highlight .c1, +.highlight .cm, +.highlight .cs, +.highlight .ch, +.highlight .cp, +.highlight .cpf { + color: color-mix(in oklch, var(--color-base-content) 40%, transparent); + font-style: italic; +} + +/* Keywords (do, end, defmodule, case, when…) */ +.highlight .k, +.highlight .kd, +.highlight .kn, +.highlight .kp, +.highlight .kr, +.highlight .kt { + color: var(--color-primary); + font-weight: 500; +} + +/* Pseudo keywords / builtin constants (true, false, nil) */ +.highlight .kc, +.highlight .bp { + color: var(--color-warning); +} + +/* Module names */ +.highlight .nc, +.highlight .nn { + color: var(--color-secondary); +} + +/* Function names */ +.highlight .nf, +.highlight .fm { + color: var(--color-info); +} + +/* Atoms (:foo) */ +.highlight .ss { + color: var(--color-accent); +} + +/* Strings */ +.highlight .s, +.highlight .s1, +.highlight .s2, +.highlight .sb, +.highlight .sc, +.highlight .sd, +.highlight .se, +.highlight .sh, +.highlight .si, +.highlight .sx, +.highlight .sr, +.highlight .dl { + color: var(--color-accent); +} + +/* String interpolation punctuation: visually quieter */ +.highlight .si .o, +.highlight .si .p { + color: color-mix(in oklch, var(--color-accent) 70%, transparent); +} + +/* Numbers */ +.highlight .m, +.highlight .mi, +.highlight .mf, +.highlight .mh, +.highlight .mo, +.highlight .mb, +.highlight .il { + color: var(--color-warning); +} + +/* Operators, punctuation: inherit but muted */ +.highlight .o, +.highlight .ow, +.highlight .p { + color: color-mix(in oklch, var(--color-base-content) 70%, transparent); +} + +/* Variables / identifiers */ +.highlight .n, +.highlight .nv, +.highlight .nb, +.highlight .nl, +.highlight .py { + color: var(--color-base-content); +} + +/* Module attributes (@moduledoc, @spec) */ +.highlight .na { + color: var(--color-info); +} + +/* Errors should not vanish silently */ +.highlight .err { + color: var(--color-error); +} + +/* Tippy.js — custom "lua" theme that follows DaisyUI's color variables + so the same tooltip styling works in both light and dark themes. */ +.tippy-box[data-theme~="lua"] { + background-color: var(--color-base-300); + color: var(--color-base-content); + border: 1px solid color-mix(in oklch, var(--color-base-content) 18%, transparent); + border-radius: 0.5rem; + box-shadow: + 0 10px 30px -10px color-mix(in oklch, black 40%, transparent), + 0 2px 6px -2px color-mix(in oklch, black 25%, transparent); + font-size: 12.5px; + line-height: 1.55; +} + +.tippy-box[data-theme~="lua"] > .tippy-content { + padding: 0.65rem 0.8rem; +} + +.tippy-box[data-theme~="lua"][data-placement^="top"] > .tippy-arrow::before { + border-top-color: var(--color-base-300); +} +.tippy-box[data-theme~="lua"][data-placement^="bottom"] > .tippy-arrow::before { + border-bottom-color: var(--color-base-300); +} +.tippy-box[data-theme~="lua"][data-placement^="left"] > .tippy-arrow::before { + border-left-color: var(--color-base-300); +} +.tippy-box[data-theme~="lua"][data-placement^="right"] > .tippy-arrow::before { + border-right-color: var(--color-base-300); +} + +/* Opcode tip card layout. */ +.lua-tip { + max-width: 280px; +} +.lua-tip-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 0.5rem; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + margin-bottom: 0.3rem; +} +.lua-tip-head > code:first-child { + font-weight: 700; + color: var(--color-primary); + font-size: 13px; +} +.lua-tip-sig { + font-size: 10.5px; + text-transform: uppercase; + letter-spacing: 0.05em; + color: color-mix(in oklch, var(--color-base-content) 55%, transparent); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; +} +.lua-tip-body { + color: color-mix(in oklch, var(--color-base-content) 85%, transparent); + margin-bottom: 0.4rem; +} +.lua-tip-link { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-size: 11.5px; + font-weight: 600; + color: var(--color-primary); + text-decoration: none; +} +.lua-tip-link:hover { + text-decoration: underline; +} + +/* Glossary tip — small text-only variant used for column headers, + meta pills, and the args-notation cell. */ +.lua-tip code { + background: color-mix(in oklch, var(--color-base-content) 10%, transparent); + padding: 0.05rem 0.3rem; + border-radius: 0.2rem; + font-size: 0.92em; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + color: var(--color-primary); +} + +.lua-tip strong { + font-weight: 600; + color: var(--color-base-content); +} + +.lua-tip-section { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 700; + color: color-mix(in oklch, var(--color-base-content) 55%, transparent); + margin-bottom: 0.45rem; +} + +.lua-tip-defs { + display: grid; + grid-template-columns: max-content 1fr; + column-gap: 0.7rem; + row-gap: 0.25rem; + margin: 0; +} +.lua-tip-defs dt { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 11px; + white-space: nowrap; +} +.lua-tip-defs dt code { + background: transparent; + padding: 0; + color: var(--color-primary); + font-size: 11px; +} +.lua-tip-defs dd { + margin: 0; + font-size: 11.5px; + color: color-mix(in oklch, var(--color-base-content) 80%, transparent); +} + +/* Bytecode row cross-highlight. Applied by the `BytecodeHighlight` + JS hook to every row whose `data-line` matches the line currently + under the mouse (or under the editor cursor). */ +tr.is-hovered { + background-color: color-mix(in oklch, var(--color-primary) 15%, transparent); + box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--color-primary) 40%, transparent); +} +tr.is-hovered:hover { + background-color: color-mix(in oklch, var(--color-primary) 20%, transparent); +} +tr.is-hovered .lua-row-line { + color: var(--color-primary); + font-weight: 600; +} + diff --git a/website/assets/js/app.js b/website/assets/js/app.js new file mode 100644 index 0000000..c567347 --- /dev/null +++ b/website/assets/js/app.js @@ -0,0 +1,710 @@ +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// +// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file. +// To load it, simply add a second `` to your `root.html.heex` file. + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" +import {hooks as colocatedHooks} from "phoenix-colocated/website" +import topbar from "../vendor/topbar" + +import {EditorView, keymap, lineNumbers, highlightActiveLine, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightSpecialChars} from "@codemirror/view" +import {EditorState, Compartment} from "@codemirror/state" +import {defaultKeymap, history, historyKeymap, indentWithTab} from "@codemirror/commands" +import {StreamLanguage, syntaxHighlighting, defaultHighlightStyle, indentOnInput, bracketMatching, foldKeymap} from "@codemirror/language" +import {closeBrackets, closeBracketsKeymap} from "@codemirror/autocomplete" +import {highlightSelectionMatches, searchKeymap} from "@codemirror/search" +import {lua} from "@codemirror/legacy-modes/mode/lua" +import {oneDark} from "@codemirror/theme-one-dark" +import tippy, {delegate} from "tippy.js" +import "tippy.js/dist/tippy.css" +import "tippy.js/animations/shift-away.css" + +const themeCompartment = new Compartment() + +const editorTheme = EditorView.theme({ + "&": { + height: "100%", + fontSize: "13px", + backgroundColor: "transparent", + }, + "&.cm-focused": { + outline: "none", + }, + ".cm-content": { + fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace", + padding: "12px 0", + caretColor: "var(--color-primary, #6366f1)", + }, + ".cm-scroller": { + fontFamily: "inherit", + overflow: "auto", + lineHeight: "1.55", + }, + ".cm-gutters": { + backgroundColor: "transparent", + border: "none", + color: "color-mix(in oklch, currentColor 35%, transparent)", + }, + ".cm-activeLineGutter, .cm-activeLine": { + backgroundColor: "color-mix(in oklch, currentColor 6%, transparent)", + }, + ".cm-lineNumbers .cm-gutterElement": { + padding: "0 12px 0 8px", + minWidth: "2.5em", + }, +}) + +function darkActive() { + const t = document.documentElement.dataset.theme + if (t === "dark") return true + if (t === "light") return false + return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches +} + +function themeExt() { + return darkActive() ? oneDark : [] +} + +const LuaEditor = { + mounted() { + const textarea = this.el.querySelector("textarea") + if (!textarea) return + + this.textarea = textarea + // Hide the underlying textarea but keep it in the form. + textarea.style.display = "none" + textarea.setAttribute("tabindex", "-1") + textarea.setAttribute("aria-hidden", "true") + + this.storageBase = this.el.dataset.storageKey || null + this.exampleId = this.el.dataset.exampleId || null + const computeStorageKey = () => + this.storageBase && this.exampleId ? `${this.storageBase}:${this.exampleId}` : null + this.storageKey = computeStorageKey() + + const readSaved = (key) => { + try { + return key ? window.localStorage.getItem(key) : null + } catch (e) { + return null + } + } + + const restoreFromStorage = (() => { + if (!this.storageKey) return null + const url = new URL(window.location.href) + if (url.searchParams.has("source")) return null + const saved = readSaved(this.storageKey) + if (!saved || saved === textarea.value) return null + return saved + })() + + const submitForm = () => { + const form = this.el.closest("form") + if (form) form.requestSubmit() + return true + } + + const syncToTextarea = () => { + const val = this.view.state.doc.toString() + if (this.textarea.value !== val) { + this.textarea.value = val + // Trigger phx-change on the form + this.textarea.dispatchEvent(new Event("input", {bubbles: true})) + } + if (this.storageKey) { + try { + window.localStorage.setItem(this.storageKey, val) + } catch (e) { + /* quota or private mode — ignore */ + } + } + } + + let lastLine = null + const broadcastLine = (n) => { + if (n == null) return + if (n === lastLine) return + lastLine = n + document.dispatchEvent( + new CustomEvent("lua-bytecode:highlight", {detail: {line: n}}) + ) + } + + const pushCursorLine = () => { + const state = this.view.state + const head = state.selection.main.head + broadcastLine(state.doc.lineAt(head).number) + } + + const pushHoverLine = (event) => { + if (!this.view) return + const pos = this.view.posAtCoords({x: event.clientX, y: event.clientY}) + if (pos == null) return + broadcastLine(this.view.state.doc.lineAt(pos).number) + } + + const clearHoverLine = () => { + if (lastLine === null) return + lastLine = null + document.dispatchEvent( + new CustomEvent("lua-bytecode:highlight", {detail: {line: null}}) + ) + } + + this.view = new EditorView({ + doc: textarea.value, + parent: this.el, + extensions: [ + lineNumbers(), + highlightSpecialChars(), + history(), + drawSelection(), + dropCursor(), + EditorState.allowMultipleSelections.of(true), + indentOnInput(), + bracketMatching(), + closeBrackets(), + rectangularSelection(), + crosshairCursor(), + highlightActiveLine(), + highlightSelectionMatches(), + StreamLanguage.define(lua), + syntaxHighlighting(defaultHighlightStyle, {fallback: true}), + keymap.of([ + {key: "Mod-Enter", run: submitForm, preventDefault: true}, + ...closeBracketsKeymap, + ...defaultKeymap, + ...searchKeymap, + ...historyKeymap, + ...foldKeymap, + indentWithTab, + ]), + EditorView.updateListener.of((update) => { + if (update.docChanged) syncToTextarea() + if (update.selectionSet && !update.docChanged) pushCursorLine() + }), + editorTheme, + themeCompartment.of(themeExt()), + ], + }) + + // Restore from localStorage if applicable, before sync. + if (restoreFromStorage) { + this.view.dispatch({ + changes: {from: 0, to: this.view.state.doc.length, insert: restoreFromStorage}, + }) + } + + // Sync initial value (in case textarea had different value) + syncToTextarea() + + // Dispatch a DOM event on every editor-line change so the bytecode + // panel can highlight the matching rows. Deduped by line; cleared + // when the mouse leaves the editor. + this.view.scrollDOM.addEventListener("mousemove", pushHoverLine) + this.view.scrollDOM.addEventListener("mouseleave", clearHoverLine) + this._hoverHandler = pushHoverLine + this._hoverLeaveHandler = clearHoverLine + + // Listen for server-pushed source updates (e.g. when loading an example). + // + // `example_id` rebinds the storage key so each example's edits are saved + // independently. `clear_storage` wipes any saved edits for that example, + // used by the Reset button to drop back to the pristine source. When + // switching examples (no clear_storage), prefer the user's saved edits + // for that example over the pushed default. + this.handleEvent("lua-editor:set-source", ({source, example_id, clear_storage, target}) => { + if (target && target !== this.el.id) return + if (example_id !== undefined) { + this.exampleId = example_id || null + this.storageKey = computeStorageKey() + } + if (clear_storage && this.storageKey) { + try { + window.localStorage.removeItem(this.storageKey) + } catch (e) { + /* ignore */ + } + } + let next = source + if (!clear_storage && this.storageKey) { + const saved = readSaved(this.storageKey) + if (saved) next = saved + } + const current = this.view.state.doc.toString() + if (current === next) return + this.view.dispatch({ + changes: {from: 0, to: this.view.state.doc.length, insert: next}, + }) + }) + + // Listen for client-side focus requests (e.g. clicking a bytecode row). + // The BytecodeHighlight hook dispatches this directly on document; no + // server round-trip. + this._onFocusLine = (e) => { + const lineNum = parseInt(e.detail?.line, 10) + if (!Number.isFinite(lineNum)) return + const doc = this.view.state.doc + if (lineNum < 1 || lineNum > doc.lines) return + const info = doc.line(lineNum) + this.view.dispatch({ + selection: {anchor: info.from}, + effects: EditorView.scrollIntoView(info.from, {y: "center"}), + }) + this.view.focus() + } + document.addEventListener("lua-editor:focus-line", this._onFocusLine) + + // Listen for theme changes on the element so highlight updates live. + this.themeObserver = new MutationObserver(() => { + this.view.dispatch({effects: themeCompartment.reconfigure(themeExt())}) + }) + this.themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme"], + }) + }, + + destroyed() { + if (this.themeObserver) this.themeObserver.disconnect() + if (this.view) { + if (this._hoverHandler) { + this.view.scrollDOM.removeEventListener("mousemove", this._hoverHandler) + } + if (this._hoverLeaveHandler) { + this.view.scrollDOM.removeEventListener("mouseleave", this._hoverLeaveHandler) + } + } + if (this._onFocusLine) { + document.removeEventListener("lua-editor:focus-line", this._onFocusLine) + } + if (this.view) this.view.destroy() + }, +} + +// Cross-highlights every bytecode row that shares a `data-line` with the +// row currently under the mouse, and dispatches a focus-line event on +// click so the editor scrolls to that source line. Also listens for +// `lua-bytecode:highlight` events from the editor hook so editor +// scrubbing drives the same highlight. +// +// Listeners attach to `this.el` (the scroll container) rather than the +// inner tbody so they survive re-renders when the user clicks Run. +const BytecodeHighlight = { + mounted() { + const root = this.el + let current = null + const setLine = (line) => { + const next = line == null || line === "" ? null : String(line) + if (next === current) return + if (current !== null) { + root + .querySelectorAll(`tr[data-line="${current}"]`) + .forEach((tr) => tr.classList.remove("is-hovered")) + } + if (next !== null) { + root + .querySelectorAll(`tr[data-line="${next}"]`) + .forEach((tr) => tr.classList.add("is-hovered")) + } + current = next + } + + this._onOver = (e) => { + const tr = e.target.closest("tr[data-line]") + if (!tr || !root.contains(tr)) return + setLine(tr.dataset.line) + } + this._onLeave = () => setLine(null) + this._onClick = (e) => { + if (e.target.closest("a")) return + const tr = e.target.closest("tr[data-line]") + if (!tr || !root.contains(tr)) return + const n = parseInt(tr.dataset.line, 10) + if (!Number.isFinite(n)) return + document.dispatchEvent( + new CustomEvent("lua-editor:focus-line", {detail: {line: n}}) + ) + } + this._onExternalHighlight = (e) => setLine(e.detail?.line) + + root.addEventListener("mouseover", this._onOver) + root.addEventListener("mouseleave", this._onLeave) + root.addEventListener("click", this._onClick) + document.addEventListener("lua-bytecode:highlight", this._onExternalHighlight) + }, + destroyed() { + if (this._onOver) this.el.removeEventListener("mouseover", this._onOver) + if (this._onLeave) this.el.removeEventListener("mouseleave", this._onLeave) + if (this._onClick) this.el.removeEventListener("click", this._onClick) + if (this._onExternalHighlight) { + document.removeEventListener( + "lua-bytecode:highlight", + this._onExternalHighlight + ) + } + }, +} + +// Manages a multi-snippet code block rendered by +// `DemoWeb.CoreComponents.code_block/1`. The component renders every +// snippet server-side, stacked in a single grid cell so the container +// reserves the tallest snippet's height. This code picks a random +// initial snippet and wires the dot buttons below the block for +// manual switching — no auto-advance, no page jumps. +// +// Lives outside the LiveView hooks because the marketing pages are +// controller-rendered, not LiveViews. +function startCodeRotator(el) { + if (el.__codeRotator) return + const snippets = Array.from(el.querySelectorAll("[data-snippet-index]")) + if (snippets.length < 2) return + + const filename = document.querySelector( + `[data-code-filename-for="${el.id}"]` + ) + const dotsContainer = document.querySelector( + `[data-code-dots-for="${el.id}"]` + ) + const dots = dotsContainer + ? Array.from(dotsContainer.querySelectorAll("[data-snippet-target]")) + : [] + + const show = (idx) => { + snippets.forEach((node, i) => { + const active = i === idx + node.classList.toggle("invisible", !active) + node.setAttribute("aria-hidden", active ? "false" : "true") + }) + dots.forEach((dot, i) => { + if (i === idx) { + dot.setAttribute("data-active", "") + } else { + dot.removeAttribute("data-active") + } + }) + if (filename) { + const label = snippets[idx].dataset.label + if (label) filename.textContent = label + } + } + + const initial = Math.floor(Math.random() * snippets.length) + show(initial) + + const onClick = (event) => { + const target = event.target.closest("[data-snippet-target]") + if (!target || !dotsContainer.contains(target)) return + const idx = parseInt(target.dataset.snippetTarget, 10) + if (Number.isFinite(idx)) show(idx) + } + + if (dotsContainer) dotsContainer.addEventListener("click", onClick) + + el.__codeRotator = { + stop() { + if (dotsContainer) dotsContainer.removeEventListener("click", onClick) + el.__codeRotator = null + }, + } +} + +function initCodeRotators(root = document) { + root.querySelectorAll("pre.highlight[data-rotate]").forEach(startCodeRotator) +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => initCodeRotators()) +} else { + initCodeRotators() +} +window.addEventListener("phx:page-loading-stop", () => initCodeRotators()) + +// Compresses a string with gzip and returns a URL-safe base64 string +// without padding. Used for `?source=` shareable playground links. +async function gzipBase64Url(text) { + if (typeof CompressionStream === "undefined") { + // Fallback for older browsers: raw base64 (no compression). + return btoa(unescape(encodeURIComponent(text))) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, "") + } + const bytes = new TextEncoder().encode(text) + const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip")) + const gzipped = new Uint8Array(await new Response(stream).arrayBuffer()) + let bin = "" + for (let i = 0; i < gzipped.length; i++) bin += String.fromCharCode(gzipped[i]) + return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") +} + +const ShareSnippet = { + mounted() { + this.el.addEventListener("click", async (e) => { + e.preventDefault() + const textarea = document.getElementById("lua-source") + if (!textarea) return + const label = this.el.querySelector("[data-share-label]") + const original = label ? label.textContent : null + try { + const encoded = await gzipBase64Url(textarea.value || "") + const url = new URL(window.location.href) + url.pathname = "/playground" + url.search = "?source=" + encoded + const shareUrl = url.toString() + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(shareUrl) + } else { + // Fallback: temporary textarea + execCommand + const t = document.createElement("textarea") + t.value = shareUrl + t.style.position = "fixed" + t.style.opacity = "0" + document.body.appendChild(t) + t.select() + document.execCommand("copy") + document.body.removeChild(t) + } + // Replace current URL silently so the share state survives reloads + window.history.replaceState({}, "", shareUrl) + if (label) { + label.textContent = "Copied!" + this.el.classList.add("text-success") + setTimeout(() => { + label.textContent = original + this.el.classList.remove("text-success") + }, 1600) + } + } catch (err) { + console.error("Share failed:", err) + if (label) label.textContent = "Failed" + } + }) + }, +} + +// Document-level copy handler. Works for both LiveView- and controller- +// rendered pages because it delegates on bubbling click events. +// +// Usage on any element: +// +// +// +// Add a child to get a transient "Copied!" label +// after a successful copy. +async function doCopy(el) { + let text = el.dataset.copy + if (!text && el.dataset.copyTarget) { + const target = document.querySelector(el.dataset.copyTarget) + if (target) text = target.innerText + } + if (!text) return + const label = el.querySelector("[data-copy-label]") + const original = label ? label.textContent : null + try { + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(text) + } else { + const t = document.createElement("textarea") + t.value = text + t.style.position = "fixed" + t.style.opacity = "0" + document.body.appendChild(t) + t.select() + document.execCommand("copy") + document.body.removeChild(t) + } + if (label) { + label.textContent = "Copied!" + el.classList.add("text-success") + setTimeout(() => { + label.textContent = original + el.classList.remove("text-success") + }, 1400) + } + } catch (err) { + console.error("Copy failed:", err) + } +} + +document.addEventListener("click", (e) => { + const btn = e.target.closest("[data-copy], [data-copy-target]") + if (!btn) return + e.preventDefault() + doCopy(btn) +}) + +// LiveView hook stub — still useful when a button needs the LiveView +// lifecycle (e.g. to fire pushEvent after a successful copy). +const CopyButton = { + mounted() { + // No-op: the document-level handler above already wires clicks. + }, +} + +// Tippy.js setup. One body-level delegator so tooltips survive LiveView +// re-renders without per-element binding. Three content sources: +// +// data-tip="text" plain string +// data-tip-html="#tpl-id" clone innerHTML of a hidden template element +// data-tip-op="opcode_name" look up window.__opcodeDocs[op] and build a card +// +function readOpcodeDocs() { + const el = document.getElementById("opcode-docs") + if (!el) return {} + try { + return JSON.parse(el.textContent) || {} + } catch (_e) { + return {} + } +} + +function escHtml(str) { + return String(str) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) +} + +// Markdown-lite: backticks → , **bold** → . Source string +// is HTML-escaped first so user-supplied content can never inject markup. +function inlineMd(str) { + return escHtml(str) + .replace(/\*\*([^*]+)\*\*/g, "$1") + .replace(/`([^`]+)`/g, "$1") +} + +function opcodeTipContent(op) { + const docs = readOpcodeDocs() + const entry = docs[op] + if (!entry) { + return `
${escHtml(op)}
` + } + const sig = entry.signature + ? `${escHtml(entry.signature)}` + : "" + return ` +
+ ` +} + +// Wrap a plain data-tip string in the same `.lua-tip` card so glossary +// tips and opcode cards share visual styling. +function plainTipContent(str) { + return `
${inlineMd(str)}
` +} + +tippy.setDefaultProps({ + theme: "lua", + animation: "shift-away", + delay: [120, 0], + duration: [150, 100], + allowHTML: true, + appendTo: () => document.body, +}) + +delegate(document.body, { + target: "[data-tip], [data-tip-html], [data-tip-op]", + interactive: true, + interactiveBorder: 12, + maxWidth: 320, + content(reference) { + if (reference.dataset.tipOp) { + return opcodeTipContent(reference.dataset.tipOp) + } + if (reference.dataset.tipHtml) { + const sel = reference.dataset.tipHtml + const tpl = sel.startsWith("#") ? document.querySelector(sel) : null + return tpl ? tpl.innerHTML : "" + } + return reference.dataset.tip ? plainTipContent(reference.dataset.tip) : "" + }, + onShow(instance) { + // Don't show if there's no content to render. + return !!instance.props.content + }, +}) + +const hooks = {...colocatedHooks, LuaEditor, ShareSnippet, CopyButton, BytecodeHighlight} + +const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +const liveSocket = new LiveSocket("/live", Socket, { + longPollFallbackMs: 2500, + params: {_csrf_token: csrfToken}, + hooks: hooks, +}) + +// Show progress bar on live navigation and form submits +topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) +window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) +window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket + +// The lines below enable quality of life phoenix_live_reload +// development features: +// +// 1. stream server logs to the browser console +// 2. click on elements to jump to their definitions in your code editor +// +if (process.env.NODE_ENV === "development") { + window.addEventListener("phx:live_reload:attached", ({detail: reloader}) => { + // Enable server log streaming to client. + // Disable with reloader.disableServerLogs() + reloader.enableServerLogs() + + // Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component + // + // * click with "c" key pressed to open at caller location + // * click with "d" key pressed to open at function component definition location + let keyDown + window.addEventListener("keydown", e => keyDown = e.key) + window.addEventListener("keyup", _e => keyDown = null) + window.addEventListener("click", e => { + if(keyDown === "c"){ + e.preventDefault() + e.stopImmediatePropagation() + reloader.openEditorAtCaller(e.target) + } else if(keyDown === "d"){ + e.preventDefault() + e.stopImmediatePropagation() + reloader.openEditorAtDef(e.target) + } + }, true) + + window.liveReloader = reloader + }) +} diff --git a/website/assets/package-lock.json b/website/assets/package-lock.json new file mode 100644 index 0000000..c8a8213 --- /dev/null +++ b/website/assets/package-lock.json @@ -0,0 +1,273 @@ +{ + "name": "website-assets", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "website-assets", + "version": "0.0.0", + "dependencies": { + "@codemirror/commands": "^6.6.0", + "@codemirror/language": "^6.10.2", + "@codemirror/legacy-modes": "^6.4.0", + "@codemirror/state": "^6.4.1", + "@codemirror/theme-one-dark": "^6.1.2", + "@codemirror/view": "^6.26.3", + "codemirror": "^6.0.1", + "phoenix": "../deps/phoenix", + "phoenix_html": "../deps/phoenix_html", + "phoenix_live_view": "../deps/phoenix_live_view", + "tippy.js": "^6.3.7" + } + }, + "../deps/phoenix": { + "version": "1.8.7", + "license": "MIT", + "devDependencies": { + "@babel/cli": "7.28.6", + "@babel/core": "7.29.0", + "@babel/preset-env": "7.29.3", + "@eslint/js": "^10.0.1", + "@stylistic/eslint-plugin": "^5.0.0", + "documentation": "^14.0.3", + "eslint": "10.2.1", + "eslint-plugin-jest": "29.15.2", + "jest": "^30.0.0", + "jest-environment-jsdom": "^30.0.0", + "jsdom": "^29.0.1", + "mock-socket": "^9.3.1" + } + }, + "../deps/phoenix_html": { + "version": "4.3.0" + }, + "../deps/phoenix_live_view": { + "version": "1.1.30", + "license": "MIT", + "dependencies": { + "morphdom": "2.7.8" + }, + "devDependencies": { + "@babel/cli": "7.27.2", + "@babel/core": "7.27.4", + "@babel/preset-env": "7.27.2", + "@babel/preset-typescript": "^7.27.1", + "@eslint/js": "^9.29.0", + "@playwright/test": "^1.56.1", + "@types/jest": "^30.0.0", + "@types/phoenix": "^1.6.6", + "css.escape": "^1.5.1", + "eslint": "9.29.0", + "eslint-plugin-jest": "28.14.0", + "eslint-plugin-playwright": "^2.2.0", + "globals": "^16.2.0", + "jest": "^30.0.0", + "jest-environment-jsdom": "^30.0.0", + "jest-monocart-coverage": "^1.1.1", + "monocart-reporter": "^2.9.21", + "phoenix": "1.7.21", + "prettier": "3.5.3", + "ts-jest": "^29.4.0", + "typescript": "^5.8.3", + "typescript-eslint": "^8.34.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.2", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.2.tgz", + "integrity": "sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", + "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.3.tgz", + "integrity": "sha512-xCsmIzH78MyWkib9jlPaaun57XNkfbMIhagfaZVd0iLTqlpw3jXaIcbZm72MTmmn64eTZpBVNjbyYh+QXnxRsg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.6.tgz", + "integrity": "sha512-6Kp7r6XfCi/D/5sdXieMfg9pJU1bUEx96WITuLU6ESaKizCz0QHFMjY/TaFSbigDdEAIgi93itLBIUETP4oK+A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.0.tgz", + "integrity": "sha512-ZvGm99wc/s2cITtMT15LFdn8aH/aS+V+DqyGq/N5ZlV5vWtH+nILvC2nw0zX7ByNoHHDZ2IxxdW38O0tc5nVHg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", + "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.0.tgz", + "integrity": "sha512-V7ZCLQO3Jus9hzh2jVCCPW3mO4IBMr43O37PqSUYautJSnnJF41YlgLw21x0fLJTYvJ+Vkm6Gp+qKGH9pltgXA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.6.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "node_modules/phoenix": { + "resolved": "../deps/phoenix", + "link": true + }, + "node_modules/phoenix_html": { + "resolved": "../deps/phoenix_html", + "link": true + }, + "node_modules/phoenix_live_view": { + "resolved": "../deps/phoenix_live_view", + "link": true + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + } + } +} diff --git a/website/assets/package.json b/website/assets/package.json new file mode 100644 index 0000000..3aa60b2 --- /dev/null +++ b/website/assets/package.json @@ -0,0 +1,18 @@ +{ + "name": "website-assets", + "version": "0.0.0", + "private": true, + "dependencies": { + "@codemirror/commands": "^6.6.0", + "@codemirror/language": "^6.10.2", + "@codemirror/legacy-modes": "^6.4.0", + "@codemirror/state": "^6.4.1", + "@codemirror/theme-one-dark": "^6.1.2", + "@codemirror/view": "^6.26.3", + "codemirror": "^6.0.1", + "phoenix": "../deps/phoenix", + "phoenix_html": "../deps/phoenix_html", + "phoenix_live_view": "../deps/phoenix_live_view", + "tippy.js": "^6.3.7" + } +} diff --git a/website/assets/tsconfig.json b/website/assets/tsconfig.json new file mode 100644 index 0000000..a9401b6 --- /dev/null +++ b/website/assets/tsconfig.json @@ -0,0 +1,32 @@ +// This file is needed on most editors to enable the intelligent autocompletion +// of LiveView's JavaScript API methods. You can safely delete it if you don't need it. +// +// Note: This file assumes a basic esbuild setup without node_modules. +// We include a generic paths alias to deps to mimic how esbuild resolves +// the Phoenix and LiveView JavaScript assets. +// If you have a package.json in your project, you should remove the +// paths configuration and instead add the phoenix dependencies to the +// dependencies section of your package.json: +// +// { +// ... +// "dependencies": { +// ..., +// "phoenix": "../deps/phoenix", +// "phoenix_html": "../deps/phoenix_html", +// "phoenix_live_view": "../deps/phoenix_live_view" +// } +// } +// +// Feel free to adjust this configuration however you need. +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": ["../deps/*"] + }, + "allowJs": true, + "noEmit": true + }, + "include": ["js/**/*"] +} diff --git a/website/assets/vendor/daisyui-theme.js b/website/assets/vendor/daisyui-theme.js new file mode 100644 index 0000000..169c806 --- /dev/null +++ b/website/assets/vendor/daisyui-theme.js @@ -0,0 +1,124 @@ +/** 🌼 + * @license MIT + * daisyUI bundle + * https://daisyui.com/ + */ + +var __defProp = Object.defineProperty; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __moduleCache = /* @__PURE__ */ new WeakMap; +var __toCommonJS = (from) => { + var entry = __moduleCache.get(from), desc; + if (entry) + return entry; + entry = __defProp({}, "__esModule", { value: true }); + if (from && typeof from === "object" || typeof from === "function") + __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable + })); + __moduleCache.set(from, entry); + return entry; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { + get: all[name], + enumerable: true, + configurable: true, + set: (newValue) => all[name] = () => newValue + }); +}; + +// packages/daisyui/theme/index.js +var exports_theme = {}; +__export(exports_theme, { + default: () => theme_default +}); +module.exports = __toCommonJS(exports_theme); + +// packages/daisyui/functions/plugin.js +var plugin = { + withOptions: (pluginFunction, configFunction = () => ({})) => { + const optionsFunction = (options) => { + const handler = pluginFunction(options); + const config = configFunction(options); + return { handler, config }; + }; + optionsFunction.__isOptionsFunction = true; + return optionsFunction; + } +}; + +// packages/daisyui/theme/object.js +var object_default = { cyberpunk: { "color-scheme": "light", "--color-base-100": "oklch(94.51% 0.179 104.32)", "--color-base-200": "oklch(91.51% 0.179 104.32)", "--color-base-300": "oklch(85.51% 0.179 104.32)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(74.22% 0.209 6.35)", "--color-primary-content": "oklch(14.844% 0.041 6.35)", "--color-secondary": "oklch(83.33% 0.184 204.72)", "--color-secondary-content": "oklch(16.666% 0.036 204.72)", "--color-accent": "oklch(71.86% 0.217 310.43)", "--color-accent-content": "oklch(14.372% 0.043 310.43)", "--color-neutral": "oklch(23.04% 0.065 269.31)", "--color-neutral-content": "oklch(94.51% 0.179 104.32)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, acid: { "color-scheme": "light", "--color-base-100": "oklch(98% 0 0)", "--color-base-200": "oklch(95% 0 0)", "--color-base-300": "oklch(91% 0 0)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(71.9% 0.357 330.759)", "--color-primary-content": "oklch(14.38% 0.071 330.759)", "--color-secondary": "oklch(73.37% 0.224 48.25)", "--color-secondary-content": "oklch(14.674% 0.044 48.25)", "--color-accent": "oklch(92.78% 0.264 122.962)", "--color-accent-content": "oklch(18.556% 0.052 122.962)", "--color-neutral": "oklch(21.31% 0.128 278.68)", "--color-neutral-content": "oklch(84.262% 0.025 278.68)", "--color-info": "oklch(60.72% 0.227 252.05)", "--color-info-content": "oklch(12.144% 0.045 252.05)", "--color-success": "oklch(85.72% 0.266 158.53)", "--color-success-content": "oklch(17.144% 0.053 158.53)", "--color-warning": "oklch(91.01% 0.212 100.5)", "--color-warning-content": "oklch(18.202% 0.042 100.5)", "--color-error": "oklch(64.84% 0.293 29.349)", "--color-error-content": "oklch(12.968% 0.058 29.349)", "--radius-selector": "1rem", "--radius-field": "1rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, black: { "color-scheme": "dark", "--color-base-100": "oklch(0% 0 0)", "--color-base-200": "oklch(19% 0 0)", "--color-base-300": "oklch(22% 0 0)", "--color-base-content": "oklch(87.609% 0 0)", "--color-primary": "oklch(35% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(35% 0 0)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(35% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(35% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(45.201% 0.313 264.052)", "--color-info-content": "oklch(89.04% 0.062 264.052)", "--color-success": "oklch(51.975% 0.176 142.495)", "--color-success-content": "oklch(90.395% 0.035 142.495)", "--color-warning": "oklch(96.798% 0.211 109.769)", "--color-warning-content": "oklch(19.359% 0.042 109.769)", "--color-error": "oklch(62.795% 0.257 29.233)", "--color-error-content": "oklch(12.559% 0.051 29.233)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dark: { "color-scheme": "dark", "--color-base-100": "oklch(25.33% 0.016 252.42)", "--color-base-200": "oklch(23.26% 0.014 253.1)", "--color-base-300": "oklch(21.15% 0.012 254.09)", "--color-base-content": "oklch(97.807% 0.029 256.847)", "--color-primary": "oklch(58% 0.233 277.117)", "--color-primary-content": "oklch(96% 0.018 272.314)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, light: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98% 0 0)", "--color-base-300": "oklch(95% 0 0)", "--color-base-content": "oklch(21% 0.006 285.885)", "--color-primary": "oklch(45% 0.24 277.023)", "--color-primary-content": "oklch(93% 0.034 272.788)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, luxury: { "color-scheme": "dark", "--color-base-100": "oklch(14.076% 0.004 285.822)", "--color-base-200": "oklch(20.219% 0.004 308.229)", "--color-base-300": "oklch(23.219% 0.004 308.229)", "--color-base-content": "oklch(75.687% 0.123 76.89)", "--color-primary": "oklch(100% 0 0)", "--color-primary-content": "oklch(20% 0 0)", "--color-secondary": "oklch(27.581% 0.064 261.069)", "--color-secondary-content": "oklch(85.516% 0.012 261.069)", "--color-accent": "oklch(36.674% 0.051 338.825)", "--color-accent-content": "oklch(87.334% 0.01 338.825)", "--color-neutral": "oklch(24.27% 0.057 59.825)", "--color-neutral-content": "oklch(93.203% 0.089 90.861)", "--color-info": "oklch(79.061% 0.121 237.133)", "--color-info-content": "oklch(15.812% 0.024 237.133)", "--color-success": "oklch(78.119% 0.192 132.154)", "--color-success-content": "oklch(15.623% 0.038 132.154)", "--color-warning": "oklch(86.127% 0.136 102.891)", "--color-warning-content": "oklch(17.225% 0.027 102.891)", "--color-error": "oklch(71.753% 0.176 22.568)", "--color-error-content": "oklch(14.35% 0.035 22.568)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, dracula: { "color-scheme": "dark", "--color-base-100": "oklch(28.822% 0.022 277.508)", "--color-base-200": "oklch(26.805% 0.02 277.508)", "--color-base-300": "oklch(24.787% 0.019 277.508)", "--color-base-content": "oklch(97.747% 0.007 106.545)", "--color-primary": "oklch(75.461% 0.183 346.812)", "--color-primary-content": "oklch(15.092% 0.036 346.812)", "--color-secondary": "oklch(74.202% 0.148 301.883)", "--color-secondary-content": "oklch(14.84% 0.029 301.883)", "--color-accent": "oklch(83.392% 0.124 66.558)", "--color-accent-content": "oklch(16.678% 0.024 66.558)", "--color-neutral": "oklch(39.445% 0.032 275.524)", "--color-neutral-content": "oklch(87.889% 0.006 275.524)", "--color-info": "oklch(88.263% 0.093 212.846)", "--color-info-content": "oklch(17.652% 0.018 212.846)", "--color-success": "oklch(87.099% 0.219 148.024)", "--color-success-content": "oklch(17.419% 0.043 148.024)", "--color-warning": "oklch(95.533% 0.134 112.757)", "--color-warning-content": "oklch(19.106% 0.026 112.757)", "--color-error": "oklch(68.22% 0.206 24.43)", "--color-error-content": "oklch(13.644% 0.041 24.43)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, retro: { "color-scheme": "light", "--color-base-100": "oklch(91.637% 0.034 90.515)", "--color-base-200": "oklch(88.272% 0.049 91.774)", "--color-base-300": "oklch(84.133% 0.065 90.856)", "--color-base-content": "oklch(41% 0.112 45.904)", "--color-primary": "oklch(80% 0.114 19.571)", "--color-primary-content": "oklch(39% 0.141 25.723)", "--color-secondary": "oklch(92% 0.084 155.995)", "--color-secondary-content": "oklch(44% 0.119 151.328)", "--color-accent": "oklch(68% 0.162 75.834)", "--color-accent-content": "oklch(41% 0.112 45.904)", "--color-neutral": "oklch(44% 0.011 73.639)", "--color-neutral-content": "oklch(86% 0.005 56.366)", "--color-info": "oklch(58% 0.158 241.966)", "--color-info-content": "oklch(96% 0.059 95.617)", "--color-success": "oklch(51% 0.096 186.391)", "--color-success-content": "oklch(96% 0.059 95.617)", "--color-warning": "oklch(64% 0.222 41.116)", "--color-warning-content": "oklch(96% 0.059 95.617)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(40% 0.123 38.172)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, lofi: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(94% 0 0)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(15.906% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(21.455% 0.001 17.278)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(26.861% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(0% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(79.54% 0.103 205.9)", "--color-info-content": "oklch(15.908% 0.02 205.9)", "--color-success": "oklch(90.13% 0.153 164.14)", "--color-success-content": "oklch(18.026% 0.03 164.14)", "--color-warning": "oklch(88.37% 0.135 79.94)", "--color-warning-content": "oklch(17.674% 0.027 79.94)", "--color-error": "oklch(78.66% 0.15 28.47)", "--color-error-content": "oklch(15.732% 0.03 28.47)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, valentine: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.014 343.198)", "--color-base-200": "oklch(94% 0.028 342.258)", "--color-base-300": "oklch(89% 0.061 343.231)", "--color-base-content": "oklch(52% 0.223 3.958)", "--color-primary": "oklch(65% 0.241 354.308)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(62% 0.265 303.9)", "--color-secondary-content": "oklch(97% 0.014 308.299)", "--color-accent": "oklch(82% 0.111 230.318)", "--color-accent-content": "oklch(39% 0.09 240.876)", "--color-neutral": "oklch(40% 0.153 2.432)", "--color-neutral-content": "oklch(89% 0.061 343.231)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(44% 0.11 240.79)", "--color-success": "oklch(84% 0.143 164.978)", "--color-success-content": "oklch(43% 0.095 166.913)", "--color-warning": "oklch(75% 0.183 55.934)", "--color-warning-content": "oklch(26% 0.079 36.259)", "--color-error": "oklch(63% 0.237 25.331)", "--color-error-content": "oklch(97% 0.013 17.38)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, nord: { "color-scheme": "light", "--color-base-100": "oklch(95.127% 0.007 260.731)", "--color-base-200": "oklch(93.299% 0.01 261.788)", "--color-base-300": "oklch(89.925% 0.016 262.749)", "--color-base-content": "oklch(32.437% 0.022 264.182)", "--color-primary": "oklch(59.435% 0.077 254.027)", "--color-primary-content": "oklch(11.887% 0.015 254.027)", "--color-secondary": "oklch(69.651% 0.059 248.687)", "--color-secondary-content": "oklch(13.93% 0.011 248.687)", "--color-accent": "oklch(77.464% 0.062 217.469)", "--color-accent-content": "oklch(15.492% 0.012 217.469)", "--color-neutral": "oklch(45.229% 0.035 264.131)", "--color-neutral-content": "oklch(89.925% 0.016 262.749)", "--color-info": "oklch(69.207% 0.062 332.664)", "--color-info-content": "oklch(13.841% 0.012 332.664)", "--color-success": "oklch(76.827% 0.074 131.063)", "--color-success-content": "oklch(15.365% 0.014 131.063)", "--color-warning": "oklch(85.486% 0.089 84.093)", "--color-warning-content": "oklch(17.097% 0.017 84.093)", "--color-error": "oklch(60.61% 0.12 15.341)", "--color-error-content": "oklch(12.122% 0.024 15.341)", "--radius-selector": "1rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, lemonade: { "color-scheme": "light", "--color-base-100": "oklch(98.71% 0.02 123.72)", "--color-base-200": "oklch(91.8% 0.018 123.72)", "--color-base-300": "oklch(84.89% 0.017 123.72)", "--color-base-content": "oklch(19.742% 0.004 123.72)", "--color-primary": "oklch(58.92% 0.199 134.6)", "--color-primary-content": "oklch(11.784% 0.039 134.6)", "--color-secondary": "oklch(77.75% 0.196 111.09)", "--color-secondary-content": "oklch(15.55% 0.039 111.09)", "--color-accent": "oklch(85.39% 0.201 100.73)", "--color-accent-content": "oklch(17.078% 0.04 100.73)", "--color-neutral": "oklch(30.98% 0.075 108.6)", "--color-neutral-content": "oklch(86.196% 0.015 108.6)", "--color-info": "oklch(86.19% 0.047 224.14)", "--color-info-content": "oklch(17.238% 0.009 224.14)", "--color-success": "oklch(86.19% 0.047 157.85)", "--color-success-content": "oklch(17.238% 0.009 157.85)", "--color-warning": "oklch(86.19% 0.047 102.15)", "--color-warning-content": "oklch(17.238% 0.009 102.15)", "--color-error": "oklch(86.19% 0.047 25.85)", "--color-error-content": "oklch(17.238% 0.009 25.85)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, garden: { "color-scheme": "light", "--color-base-100": "oklch(92.951% 0.002 17.197)", "--color-base-200": "oklch(86.445% 0.002 17.197)", "--color-base-300": "oklch(79.938% 0.001 17.197)", "--color-base-content": "oklch(16.961% 0.001 17.32)", "--color-primary": "oklch(62.45% 0.278 3.836)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(48.495% 0.11 355.095)", "--color-secondary-content": "oklch(89.699% 0.022 355.095)", "--color-accent": "oklch(56.273% 0.054 154.39)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(24.155% 0.049 89.07)", "--color-neutral-content": "oklch(92.951% 0.002 17.197)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, aqua: { "color-scheme": "dark", "--color-base-100": "oklch(37% 0.146 265.522)", "--color-base-200": "oklch(28% 0.091 267.935)", "--color-base-300": "oklch(22% 0.091 267.935)", "--color-base-content": "oklch(90% 0.058 230.902)", "--color-primary": "oklch(85.661% 0.144 198.645)", "--color-primary-content": "oklch(40.124% 0.068 197.603)", "--color-secondary": "oklch(60.682% 0.108 309.782)", "--color-secondary-content": "oklch(96% 0.016 293.756)", "--color-accent": "oklch(93.426% 0.102 94.555)", "--color-accent-content": "oklch(18.685% 0.02 94.555)", "--color-neutral": "oklch(27% 0.146 265.522)", "--color-neutral-content": "oklch(80% 0.146 265.522)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(27% 0.077 45.635)", "--color-error": "oklch(73.95% 0.19 27.33)", "--color-error-content": "oklch(14.79% 0.038 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, corporate: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(22.389% 0.031 278.072)", "--color-primary": "oklch(58% 0.158 241.966)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(55% 0.046 257.417)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(60% 0.118 184.704)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(0% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(60% 0.126 221.723)", "--color-info-content": "oklch(100% 0 0)", "--color-success": "oklch(62% 0.194 149.214)", "--color-success-content": "oklch(100% 0 0)", "--color-warning": "oklch(85% 0.199 91.936)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, pastel: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98.462% 0.001 247.838)", "--color-base-300": "oklch(92.462% 0.001 247.838)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(90% 0.063 306.703)", "--color-primary-content": "oklch(49% 0.265 301.924)", "--color-secondary": "oklch(89% 0.058 10.001)", "--color-secondary-content": "oklch(51% 0.222 16.935)", "--color-accent": "oklch(90% 0.093 164.15)", "--color-accent-content": "oklch(50% 0.118 165.612)", "--color-neutral": "oklch(55% 0.046 257.417)", "--color-neutral-content": "oklch(92% 0.013 255.508)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(52% 0.105 223.128)", "--color-success": "oklch(87% 0.15 154.449)", "--color-success-content": "oklch(52% 0.154 150.069)", "--color-warning": "oklch(83% 0.128 66.29)", "--color-warning-content": "oklch(55% 0.195 38.402)", "--color-error": "oklch(80% 0.114 19.571)", "--color-error-content": "oklch(50% 0.213 27.518)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "0", "--noise": "0" }, bumblebee: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(92% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(85% 0.199 91.936)", "--color-primary-content": "oklch(42% 0.095 57.708)", "--color-secondary": "oklch(75% 0.183 55.934)", "--color-secondary-content": "oklch(40% 0.123 38.172)", "--color-accent": "oklch(0% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(37% 0.01 67.558)", "--color-neutral-content": "oklch(92% 0.003 48.717)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(39% 0.09 240.876)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, coffee: { "color-scheme": "dark", "--color-base-100": "oklch(24% 0.023 329.708)", "--color-base-200": "oklch(21% 0.021 329.708)", "--color-base-300": "oklch(16% 0.019 329.708)", "--color-base-content": "oklch(72.354% 0.092 79.129)", "--color-primary": "oklch(71.996% 0.123 62.756)", "--color-primary-content": "oklch(14.399% 0.024 62.756)", "--color-secondary": "oklch(34.465% 0.029 199.194)", "--color-secondary-content": "oklch(86.893% 0.005 199.194)", "--color-accent": "oklch(42.621% 0.074 224.389)", "--color-accent-content": "oklch(88.524% 0.014 224.389)", "--color-neutral": "oklch(16.51% 0.015 326.261)", "--color-neutral-content": "oklch(83.302% 0.003 326.261)", "--color-info": "oklch(79.49% 0.063 184.558)", "--color-info-content": "oklch(15.898% 0.012 184.558)", "--color-success": "oklch(74.722% 0.072 131.116)", "--color-success-content": "oklch(14.944% 0.014 131.116)", "--color-warning": "oklch(88.15% 0.14 87.722)", "--color-warning-content": "oklch(17.63% 0.028 87.722)", "--color-error": "oklch(77.318% 0.128 31.871)", "--color-error-content": "oklch(15.463% 0.025 31.871)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, silk: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.0035 67.78)", "--color-base-200": "oklch(95% 0.0081 61.42)", "--color-base-300": "oklch(90% 0.0081 61.42)", "--color-base-content": "oklch(40% 0.0081 61.42)", "--color-primary": "oklch(23.27% 0.0249 284.3)", "--color-primary-content": "oklch(94.22% 0.2505 117.44)", "--color-secondary": "oklch(23.27% 0.0249 284.3)", "--color-secondary-content": "oklch(73.92% 0.2135 50.94)", "--color-accent": "oklch(23.27% 0.0249 284.3)", "--color-accent-content": "oklch(88.92% 0.2061 189.9)", "--color-neutral": "oklch(20% 0 0)", "--color-neutral-content": "oklch(80% 0.0081 61.42)", "--color-info": "oklch(80.39% 0.1148 241.68)", "--color-info-content": "oklch(30.39% 0.1148 241.68)", "--color-success": "oklch(83.92% 0.0901 136.87)", "--color-success-content": "oklch(23.92% 0.0901 136.87)", "--color-warning": "oklch(83.92% 0.1085 80)", "--color-warning-content": "oklch(43.92% 0.1085 80)", "--color-error": "oklch(75.1% 0.1814 22.37)", "--color-error-content": "oklch(35.1% 0.1814 22.37)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, sunset: { "color-scheme": "dark", "--color-base-100": "oklch(22% 0.019 237.69)", "--color-base-200": "oklch(20% 0.019 237.69)", "--color-base-300": "oklch(18% 0.019 237.69)", "--color-base-content": "oklch(77.383% 0.043 245.096)", "--color-primary": "oklch(74.703% 0.158 39.947)", "--color-primary-content": "oklch(14.94% 0.031 39.947)", "--color-secondary": "oklch(72.537% 0.177 2.72)", "--color-secondary-content": "oklch(14.507% 0.035 2.72)", "--color-accent": "oklch(71.294% 0.166 299.844)", "--color-accent-content": "oklch(14.258% 0.033 299.844)", "--color-neutral": "oklch(26% 0.019 237.69)", "--color-neutral-content": "oklch(70% 0.019 237.69)", "--color-info": "oklch(85.559% 0.085 206.015)", "--color-info-content": "oklch(17.111% 0.017 206.015)", "--color-success": "oklch(85.56% 0.085 144.778)", "--color-success-content": "oklch(17.112% 0.017 144.778)", "--color-warning": "oklch(85.569% 0.084 74.427)", "--color-warning-content": "oklch(17.113% 0.016 74.427)", "--color-error": "oklch(85.511% 0.078 16.886)", "--color-error-content": "oklch(17.102% 0.015 16.886)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, synthwave: { "color-scheme": "dark", "--color-base-100": "oklch(15% 0.09 281.288)", "--color-base-200": "oklch(20% 0.09 281.288)", "--color-base-300": "oklch(25% 0.09 281.288)", "--color-base-content": "oklch(78% 0.115 274.713)", "--color-primary": "oklch(71% 0.202 349.761)", "--color-primary-content": "oklch(28% 0.109 3.907)", "--color-secondary": "oklch(82% 0.111 230.318)", "--color-secondary-content": "oklch(29% 0.066 243.157)", "--color-accent": "oklch(75% 0.183 55.934)", "--color-accent-content": "oklch(26% 0.079 36.259)", "--color-neutral": "oklch(45% 0.24 277.023)", "--color-neutral-content": "oklch(87% 0.065 274.039)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(77% 0.152 181.912)", "--color-success-content": "oklch(27% 0.046 192.524)", "--color-warning": "oklch(90% 0.182 98.111)", "--color-warning-content": "oklch(42% 0.095 57.708)", "--color-error": "oklch(73.7% 0.121 32.639)", "--color-error-content": "oklch(23.501% 0.096 290.329)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dim: { "color-scheme": "dark", "--color-base-100": "oklch(30.857% 0.023 264.149)", "--color-base-200": "oklch(28.036% 0.019 264.182)", "--color-base-300": "oklch(26.346% 0.018 262.177)", "--color-base-content": "oklch(82.901% 0.031 222.959)", "--color-primary": "oklch(86.133% 0.141 139.549)", "--color-primary-content": "oklch(17.226% 0.028 139.549)", "--color-secondary": "oklch(73.375% 0.165 35.353)", "--color-secondary-content": "oklch(14.675% 0.033 35.353)", "--color-accent": "oklch(74.229% 0.133 311.379)", "--color-accent-content": "oklch(14.845% 0.026 311.379)", "--color-neutral": "oklch(24.731% 0.02 264.094)", "--color-neutral-content": "oklch(82.901% 0.031 222.959)", "--color-info": "oklch(86.078% 0.142 206.182)", "--color-info-content": "oklch(17.215% 0.028 206.182)", "--color-success": "oklch(86.171% 0.142 166.534)", "--color-success-content": "oklch(17.234% 0.028 166.534)", "--color-warning": "oklch(86.163% 0.142 94.818)", "--color-warning-content": "oklch(17.232% 0.028 94.818)", "--color-error": "oklch(82.418% 0.099 33.756)", "--color-error-content": "oklch(16.483% 0.019 33.756)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, abyss: { "color-scheme": "dark", "--color-base-100": "oklch(20% 0.08 209)", "--color-base-200": "oklch(15% 0.08 209)", "--color-base-300": "oklch(10% 0.08 209)", "--color-base-content": "oklch(90% 0.076 70.697)", "--color-primary": "oklch(92% 0.2653 125)", "--color-primary-content": "oklch(50% 0.2653 125)", "--color-secondary": "oklch(83.27% 0.0764 298.3)", "--color-secondary-content": "oklch(43.27% 0.0764 298.3)", "--color-accent": "oklch(43% 0 0)", "--color-accent-content": "oklch(98% 0 0)", "--color-neutral": "oklch(30% 0.08 209)", "--color-neutral-content": "oklch(90% 0.076 70.697)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(79% 0.209 151.711)", "--color-success-content": "oklch(26% 0.065 152.934)", "--color-warning": "oklch(84.8% 0.1962 84.62)", "--color-warning-content": "oklch(44.8% 0.1962 84.62)", "--color-error": "oklch(65% 0.1985 24.22)", "--color-error-content": "oklch(27% 0.1985 24.22)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, forest: { "color-scheme": "dark", "--color-base-100": "oklch(20.84% 0.008 17.911)", "--color-base-200": "oklch(18.522% 0.007 17.911)", "--color-base-300": "oklch(16.203% 0.007 17.911)", "--color-base-content": "oklch(83.768% 0.001 17.911)", "--color-primary": "oklch(68.628% 0.185 148.958)", "--color-primary-content": "oklch(0% 0 0)", "--color-secondary": "oklch(69.776% 0.135 168.327)", "--color-secondary-content": "oklch(13.955% 0.027 168.327)", "--color-accent": "oklch(70.628% 0.119 185.713)", "--color-accent-content": "oklch(14.125% 0.023 185.713)", "--color-neutral": "oklch(30.698% 0.039 171.364)", "--color-neutral-content": "oklch(86.139% 0.007 171.364)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, night: { "color-scheme": "dark", "--color-base-100": "oklch(20.768% 0.039 265.754)", "--color-base-200": "oklch(19.314% 0.037 265.754)", "--color-base-300": "oklch(17.86% 0.034 265.754)", "--color-base-content": "oklch(84.153% 0.007 265.754)", "--color-primary": "oklch(75.351% 0.138 232.661)", "--color-primary-content": "oklch(15.07% 0.027 232.661)", "--color-secondary": "oklch(68.011% 0.158 276.934)", "--color-secondary-content": "oklch(13.602% 0.031 276.934)", "--color-accent": "oklch(72.36% 0.176 350.048)", "--color-accent-content": "oklch(14.472% 0.035 350.048)", "--color-neutral": "oklch(27.949% 0.036 260.03)", "--color-neutral-content": "oklch(85.589% 0.007 260.03)", "--color-info": "oklch(68.455% 0.148 237.251)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(78.452% 0.132 181.911)", "--color-success-content": "oklch(15.69% 0.026 181.911)", "--color-warning": "oklch(83.242% 0.139 82.95)", "--color-warning-content": "oklch(16.648% 0.027 82.95)", "--color-error": "oklch(71.785% 0.17 13.118)", "--color-error-content": "oklch(14.357% 0.034 13.118)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, caramellatte: { "color-scheme": "light", "--color-base-100": "oklch(98% 0.016 73.684)", "--color-base-200": "oklch(95% 0.038 75.164)", "--color-base-300": "oklch(90% 0.076 70.697)", "--color-base-content": "oklch(40% 0.123 38.172)", "--color-primary": "oklch(0% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(22.45% 0.075 37.85)", "--color-secondary-content": "oklch(90% 0.076 70.697)", "--color-accent": "oklch(46.44% 0.111 37.85)", "--color-accent-content": "oklch(90% 0.076 70.697)", "--color-neutral": "oklch(55% 0.195 38.402)", "--color-neutral-content": "oklch(98% 0.016 73.684)", "--color-info": "oklch(42% 0.199 265.638)", "--color-info-content": "oklch(90% 0.076 70.697)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.076 70.697)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "1" }, autumn: { "color-scheme": "light", "--color-base-100": "oklch(95.814% 0 0)", "--color-base-200": "oklch(89.107% 0 0)", "--color-base-300": "oklch(82.4% 0 0)", "--color-base-content": "oklch(19.162% 0 0)", "--color-primary": "oklch(40.723% 0.161 17.53)", "--color-primary-content": "oklch(88.144% 0.032 17.53)", "--color-secondary": "oklch(61.676% 0.169 23.865)", "--color-secondary-content": "oklch(12.335% 0.033 23.865)", "--color-accent": "oklch(73.425% 0.094 60.729)", "--color-accent-content": "oklch(14.685% 0.018 60.729)", "--color-neutral": "oklch(54.367% 0.037 51.902)", "--color-neutral-content": "oklch(90.873% 0.007 51.902)", "--color-info": "oklch(69.224% 0.097 207.284)", "--color-info-content": "oklch(13.844% 0.019 207.284)", "--color-success": "oklch(60.995% 0.08 174.616)", "--color-success-content": "oklch(12.199% 0.016 174.616)", "--color-warning": "oklch(70.081% 0.164 56.844)", "--color-warning-content": "oklch(14.016% 0.032 56.844)", "--color-error": "oklch(53.07% 0.241 24.16)", "--color-error-content": "oklch(90.614% 0.048 24.16)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, emerald: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(35.519% 0.032 262.988)", "--color-primary": "oklch(76.662% 0.135 153.45)", "--color-primary-content": "oklch(33.387% 0.04 162.24)", "--color-secondary": "oklch(61.302% 0.202 261.294)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(72.772% 0.149 33.2)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(35.519% 0.032 262.988)", "--color-neutral-content": "oklch(98.462% 0.001 247.838)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, cupcake: { "color-scheme": "light", "--color-base-100": "oklch(97.788% 0.004 56.375)", "--color-base-200": "oklch(93.982% 0.007 61.449)", "--color-base-300": "oklch(91.586% 0.006 53.44)", "--color-base-content": "oklch(23.574% 0.066 313.189)", "--color-primary": "oklch(85% 0.138 181.071)", "--color-primary-content": "oklch(43% 0.078 188.216)", "--color-secondary": "oklch(89% 0.061 343.231)", "--color-secondary-content": "oklch(45% 0.187 3.815)", "--color-accent": "oklch(90% 0.076 70.697)", "--color-accent-content": "oklch(47% 0.157 37.304)", "--color-neutral": "oklch(27% 0.006 286.033)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(68% 0.169 237.323)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(69% 0.17 162.48)", "--color-success-content": "oklch(26% 0.051 172.552)", "--color-warning": "oklch(79% 0.184 86.047)", "--color-warning-content": "oklch(28% 0.066 53.813)", "--color-error": "oklch(64% 0.246 16.439)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, cmyk: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(95% 0 0)", "--color-base-300": "oklch(90% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(71.772% 0.133 239.443)", "--color-primary-content": "oklch(14.354% 0.026 239.443)", "--color-secondary": "oklch(64.476% 0.202 359.339)", "--color-secondary-content": "oklch(12.895% 0.04 359.339)", "--color-accent": "oklch(94.228% 0.189 105.306)", "--color-accent-content": "oklch(18.845% 0.037 105.306)", "--color-neutral": "oklch(21.778% 0 0)", "--color-neutral-content": "oklch(84.355% 0 0)", "--color-info": "oklch(68.475% 0.094 217.284)", "--color-info-content": "oklch(13.695% 0.018 217.284)", "--color-success": "oklch(46.949% 0.162 321.406)", "--color-success-content": "oklch(89.389% 0.032 321.406)", "--color-warning": "oklch(71.236% 0.159 52.023)", "--color-warning-content": "oklch(14.247% 0.031 52.023)", "--color-error": "oklch(62.013% 0.208 28.717)", "--color-error-content": "oklch(12.402% 0.041 28.717)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, business: { "color-scheme": "dark", "--color-base-100": "oklch(24.353% 0 0)", "--color-base-200": "oklch(22.648% 0 0)", "--color-base-300": "oklch(20.944% 0 0)", "--color-base-content": "oklch(84.87% 0 0)", "--color-primary": "oklch(41.703% 0.099 251.473)", "--color-primary-content": "oklch(88.34% 0.019 251.473)", "--color-secondary": "oklch(64.092% 0.027 229.389)", "--color-secondary-content": "oklch(12.818% 0.005 229.389)", "--color-accent": "oklch(67.271% 0.167 35.791)", "--color-accent-content": "oklch(13.454% 0.033 35.791)", "--color-neutral": "oklch(27.441% 0.013 253.041)", "--color-neutral-content": "oklch(85.488% 0.002 253.041)", "--color-info": "oklch(62.616% 0.143 240.033)", "--color-info-content": "oklch(12.523% 0.028 240.033)", "--color-success": "oklch(70.226% 0.094 156.596)", "--color-success-content": "oklch(14.045% 0.018 156.596)", "--color-warning": "oklch(77.482% 0.115 81.519)", "--color-warning-content": "oklch(15.496% 0.023 81.519)", "--color-error": "oklch(51.61% 0.146 29.674)", "--color-error-content": "oklch(90.322% 0.029 29.674)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, winter: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97.466% 0.011 259.822)", "--color-base-300": "oklch(93.268% 0.016 262.751)", "--color-base-content": "oklch(41.886% 0.053 255.824)", "--color-primary": "oklch(56.86% 0.255 257.57)", "--color-primary-content": "oklch(91.372% 0.051 257.57)", "--color-secondary": "oklch(42.551% 0.161 282.339)", "--color-secondary-content": "oklch(88.51% 0.032 282.339)", "--color-accent": "oklch(59.939% 0.191 335.171)", "--color-accent-content": "oklch(11.988% 0.038 335.171)", "--color-neutral": "oklch(19.616% 0.063 257.651)", "--color-neutral-content": "oklch(83.923% 0.012 257.651)", "--color-info": "oklch(88.127% 0.085 214.515)", "--color-info-content": "oklch(17.625% 0.017 214.515)", "--color-success": "oklch(80.494% 0.077 197.823)", "--color-success-content": "oklch(16.098% 0.015 197.823)", "--color-warning": "oklch(89.172% 0.045 71.47)", "--color-warning-content": "oklch(17.834% 0.009 71.47)", "--color-error": "oklch(73.092% 0.11 20.076)", "--color-error-content": "oklch(14.618% 0.022 20.076)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, halloween: { "color-scheme": "dark", "--color-base-100": "oklch(21% 0.006 56.043)", "--color-base-200": "oklch(14% 0.004 49.25)", "--color-base-300": "oklch(0% 0 0)", "--color-base-content": "oklch(84.955% 0 0)", "--color-primary": "oklch(77.48% 0.204 60.62)", "--color-primary-content": "oklch(19.693% 0.004 196.779)", "--color-secondary": "oklch(45.98% 0.248 305.03)", "--color-secondary-content": "oklch(89.196% 0.049 305.03)", "--color-accent": "oklch(64.8% 0.223 136.073)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(24.371% 0.046 65.681)", "--color-neutral-content": "oklch(84.874% 0.009 65.681)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(13.316% 0.031 58.318)", "--color-error": "oklch(65.72% 0.199 27.33)", "--color-error-content": "oklch(13.144% 0.039 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, fantasy: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(27.807% 0.029 256.847)", "--color-primary": "oklch(37.45% 0.189 325.02)", "--color-primary-content": "oklch(87.49% 0.037 325.02)", "--color-secondary": "oklch(53.92% 0.162 241.36)", "--color-secondary-content": "oklch(90.784% 0.032 241.36)", "--color-accent": "oklch(75.98% 0.204 56.72)", "--color-accent-content": "oklch(15.196% 0.04 56.72)", "--color-neutral": "oklch(27.807% 0.029 256.847)", "--color-neutral-content": "oklch(85.561% 0.005 256.847)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, wireframe: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(94% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(87% 0 0)", "--color-primary-content": "oklch(26% 0 0)", "--color-secondary": "oklch(87% 0 0)", "--color-secondary-content": "oklch(26% 0 0)", "--color-accent": "oklch(87% 0 0)", "--color-accent-content": "oklch(26% 0 0)", "--color-neutral": "oklch(87% 0 0)", "--color-neutral-content": "oklch(26% 0 0)", "--color-info": "oklch(44% 0.11 240.79)", "--color-info-content": "oklch(90% 0.058 230.902)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.093 164.15)", "--color-warning": "oklch(47% 0.137 46.201)", "--color-warning-content": "oklch(92% 0.12 95.746)", "--color-error": "oklch(44% 0.177 26.899)", "--color-error-content": "oklch(88% 0.062 18.334)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" } }; + +// packages/daisyui/theme/index.js +var theme_default = plugin.withOptions((options = {}) => { + return ({ addBase }) => { + const { + name = "custom-theme", + default: isDefault = false, + prefersdark = false, + "color-scheme": colorScheme = "normal", + root = ":root", + ...customThemeTokens + } = options; + let selector = `${root}:has(input.theme-controller[value=${name}]:checked),[data-theme="${name}"]`; + if (isDefault) { + selector = `:where(${root}),${selector}`; + } + let themeTokens = { ...customThemeTokens }; + if (object_default[name]) { + const builtinTheme = object_default[name]; + themeTokens = { + ...builtinTheme, + ...customThemeTokens, + "color-scheme": colorScheme || builtinTheme.colorScheme + }; + } + const baseStyles = { + [selector]: { + "color-scheme": themeTokens["color-scheme"] || colorScheme, + ...themeTokens + } + }; + if (prefersdark) { + addBase({ + "@media (prefers-color-scheme: dark)": { + [root]: baseStyles[selector] + } + }); + } + addBase(baseStyles); + }; +}); + + +/* + + MIT License + + Copyright (c) 2020 Pouya Saadeghi – https://daisyui.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ diff --git a/website/assets/vendor/daisyui.js b/website/assets/vendor/daisyui.js new file mode 100644 index 0000000..46bf6bf --- /dev/null +++ b/website/assets/vendor/daisyui.js @@ -0,0 +1,1031 @@ +/** 🌼 + * @license MIT + * daisyUI bundle + * https://daisyui.com/ + */ + +var __defProp = Object.defineProperty; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __moduleCache = /* @__PURE__ */ new WeakMap; +var __toCommonJS = (from) => { + var entry = __moduleCache.get(from), desc; + if (entry) + return entry; + entry = __defProp({}, "__esModule", { value: true }); + if (from && typeof from === "object" || typeof from === "function") + __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable + })); + __moduleCache.set(from, entry); + return entry; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { + get: all[name], + enumerable: true, + configurable: true, + set: (newValue) => all[name] = () => newValue + }); +}; + +// packages/daisyui/index.js +var exports_daisyui = {}; +__export(exports_daisyui, { + default: () => daisyui_default +}); +module.exports = __toCommonJS(exports_daisyui); + +// packages/daisyui/functions/themeOrder.js +var themeOrder_default = [ + "light", + "dark", + "cupcake", + "bumblebee", + "emerald", + "corporate", + "synthwave", + "retro", + "cyberpunk", + "valentine", + "halloween", + "garden", + "forest", + "aqua", + "lofi", + "pastel", + "fantasy", + "wireframe", + "black", + "luxury", + "dracula", + "cmyk", + "autumn", + "business", + "acid", + "lemonade", + "night", + "coffee", + "winter", + "dim", + "nord", + "sunset", + "caramellatte", + "abyss", + "silk" +]; + +// packages/daisyui/functions/pluginOptionsHandler.js +var pluginOptionsHandler = (() => { + let firstRun = true; + return (options, addBase, themesObject, packageVersion) => { + const { + logs = true, + root = ":root", + themes = ["light --default", "dark --prefersdark"], + include, + exclude, + prefix = "" + } = options || {}; + if (logs !== false && firstRun) { + console.log(`${atob("Lyoh")} ${decodeURIComponent("%F0%9F%8C%BC")} ${atob("ZGFpc3lVSQ==")} ${packageVersion} ${atob("Ki8=")}`); + firstRun = false; + } + const applyTheme = (themeName, flags) => { + const theme = themesObject[themeName]; + if (theme) { + let selector = `${root}:has(input.theme-controller[value=${themeName}]:checked),[data-theme=${themeName}]`; + if (flags.includes("--default")) { + selector = `:where(${root}),${selector}`; + } + addBase({ [selector]: theme }); + if (flags.includes("--prefersdark")) { + addBase({ "@media (prefers-color-scheme: dark)": { [root]: theme } }); + } + } + }; + if (themes === "all") { + if (themesObject["light"]) { + applyTheme("light", ["--default"]); + } + if (themesObject["dark"]) { + addBase({ "@media (prefers-color-scheme: dark)": { [root]: themesObject["dark"] } }); + } + themeOrder_default.forEach((themeName) => { + if (themesObject[themeName]) { + applyTheme(themeName, []); + } + }); + } else if (themes) { + const themeArray = Array.isArray(themes) ? themes : [themes]; + if (themeArray.length === 1 && themeArray[0].includes("--default")) { + const [themeName, ...flags] = themeArray[0].split(" "); + applyTheme(themeName, flags); + return { include, exclude, prefix }; + } + themeArray.forEach((themeOption) => { + const [themeName, ...flags] = themeOption.split(" "); + if (flags.includes("--default")) { + applyTheme(themeName, ["--default"]); + } + }); + themeArray.forEach((themeOption) => { + const [themeName, ...flags] = themeOption.split(" "); + if (flags.includes("--prefersdark")) { + addBase({ "@media (prefers-color-scheme: dark)": { [root]: themesObject[themeName] } }); + } + }); + themeArray.forEach((themeOption) => { + const [themeName] = themeOption.split(" "); + applyTheme(themeName, []); + }); + } + return { include, exclude, prefix }; + }; +})(); + +// packages/daisyui/functions/plugin.js +var plugin = { + withOptions: (pluginFunction, configFunction = () => ({})) => { + const optionsFunction = (options) => { + const handler = pluginFunction(options); + const config = configFunction(options); + return { handler, config }; + }; + optionsFunction.__isOptionsFunction = true; + return optionsFunction; + } +}; + +// packages/daisyui/functions/variables.js +var variables_default = { + colors: { + "base-100": "var(--color-base-100)", + "base-200": "var(--color-base-200)", + "base-300": "var(--color-base-300)", + "base-content": "var(--color-base-content)", + primary: "var(--color-primary)", + "primary-content": "var(--color-primary-content)", + secondary: "var(--color-secondary)", + "secondary-content": "var(--color-secondary-content)", + accent: "var(--color-accent)", + "accent-content": "var(--color-accent-content)", + neutral: "var(--color-neutral)", + "neutral-content": "var(--color-neutral-content)", + info: "var(--color-info)", + "info-content": "var(--color-info-content)", + success: "var(--color-success)", + "success-content": "var(--color-success-content)", + warning: "var(--color-warning)", + "warning-content": "var(--color-warning-content)", + error: "var(--color-error)", + "error-content": "var(--color-error-content)" + }, + borderRadius: { + selector: "var(--radius-selector)", + field: "var(--radius-field)", + box: "var(--radius-box)" + } +}; + +// packages/daisyui/theme/object.js +var object_default = { cyberpunk: { "color-scheme": "light", "--color-base-100": "oklch(94.51% 0.179 104.32)", "--color-base-200": "oklch(91.51% 0.179 104.32)", "--color-base-300": "oklch(85.51% 0.179 104.32)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(74.22% 0.209 6.35)", "--color-primary-content": "oklch(14.844% 0.041 6.35)", "--color-secondary": "oklch(83.33% 0.184 204.72)", "--color-secondary-content": "oklch(16.666% 0.036 204.72)", "--color-accent": "oklch(71.86% 0.217 310.43)", "--color-accent-content": "oklch(14.372% 0.043 310.43)", "--color-neutral": "oklch(23.04% 0.065 269.31)", "--color-neutral-content": "oklch(94.51% 0.179 104.32)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, acid: { "color-scheme": "light", "--color-base-100": "oklch(98% 0 0)", "--color-base-200": "oklch(95% 0 0)", "--color-base-300": "oklch(91% 0 0)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(71.9% 0.357 330.759)", "--color-primary-content": "oklch(14.38% 0.071 330.759)", "--color-secondary": "oklch(73.37% 0.224 48.25)", "--color-secondary-content": "oklch(14.674% 0.044 48.25)", "--color-accent": "oklch(92.78% 0.264 122.962)", "--color-accent-content": "oklch(18.556% 0.052 122.962)", "--color-neutral": "oklch(21.31% 0.128 278.68)", "--color-neutral-content": "oklch(84.262% 0.025 278.68)", "--color-info": "oklch(60.72% 0.227 252.05)", "--color-info-content": "oklch(12.144% 0.045 252.05)", "--color-success": "oklch(85.72% 0.266 158.53)", "--color-success-content": "oklch(17.144% 0.053 158.53)", "--color-warning": "oklch(91.01% 0.212 100.5)", "--color-warning-content": "oklch(18.202% 0.042 100.5)", "--color-error": "oklch(64.84% 0.293 29.349)", "--color-error-content": "oklch(12.968% 0.058 29.349)", "--radius-selector": "1rem", "--radius-field": "1rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, black: { "color-scheme": "dark", "--color-base-100": "oklch(0% 0 0)", "--color-base-200": "oklch(19% 0 0)", "--color-base-300": "oklch(22% 0 0)", "--color-base-content": "oklch(87.609% 0 0)", "--color-primary": "oklch(35% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(35% 0 0)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(35% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(35% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(45.201% 0.313 264.052)", "--color-info-content": "oklch(89.04% 0.062 264.052)", "--color-success": "oklch(51.975% 0.176 142.495)", "--color-success-content": "oklch(90.395% 0.035 142.495)", "--color-warning": "oklch(96.798% 0.211 109.769)", "--color-warning-content": "oklch(19.359% 0.042 109.769)", "--color-error": "oklch(62.795% 0.257 29.233)", "--color-error-content": "oklch(12.559% 0.051 29.233)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dark: { "color-scheme": "dark", "--color-base-100": "oklch(25.33% 0.016 252.42)", "--color-base-200": "oklch(23.26% 0.014 253.1)", "--color-base-300": "oklch(21.15% 0.012 254.09)", "--color-base-content": "oklch(97.807% 0.029 256.847)", "--color-primary": "oklch(58% 0.233 277.117)", "--color-primary-content": "oklch(96% 0.018 272.314)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, light: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98% 0 0)", "--color-base-300": "oklch(95% 0 0)", "--color-base-content": "oklch(21% 0.006 285.885)", "--color-primary": "oklch(45% 0.24 277.023)", "--color-primary-content": "oklch(93% 0.034 272.788)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, luxury: { "color-scheme": "dark", "--color-base-100": "oklch(14.076% 0.004 285.822)", "--color-base-200": "oklch(20.219% 0.004 308.229)", "--color-base-300": "oklch(23.219% 0.004 308.229)", "--color-base-content": "oklch(75.687% 0.123 76.89)", "--color-primary": "oklch(100% 0 0)", "--color-primary-content": "oklch(20% 0 0)", "--color-secondary": "oklch(27.581% 0.064 261.069)", "--color-secondary-content": "oklch(85.516% 0.012 261.069)", "--color-accent": "oklch(36.674% 0.051 338.825)", "--color-accent-content": "oklch(87.334% 0.01 338.825)", "--color-neutral": "oklch(24.27% 0.057 59.825)", "--color-neutral-content": "oklch(93.203% 0.089 90.861)", "--color-info": "oklch(79.061% 0.121 237.133)", "--color-info-content": "oklch(15.812% 0.024 237.133)", "--color-success": "oklch(78.119% 0.192 132.154)", "--color-success-content": "oklch(15.623% 0.038 132.154)", "--color-warning": "oklch(86.127% 0.136 102.891)", "--color-warning-content": "oklch(17.225% 0.027 102.891)", "--color-error": "oklch(71.753% 0.176 22.568)", "--color-error-content": "oklch(14.35% 0.035 22.568)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, dracula: { "color-scheme": "dark", "--color-base-100": "oklch(28.822% 0.022 277.508)", "--color-base-200": "oklch(26.805% 0.02 277.508)", "--color-base-300": "oklch(24.787% 0.019 277.508)", "--color-base-content": "oklch(97.747% 0.007 106.545)", "--color-primary": "oklch(75.461% 0.183 346.812)", "--color-primary-content": "oklch(15.092% 0.036 346.812)", "--color-secondary": "oklch(74.202% 0.148 301.883)", "--color-secondary-content": "oklch(14.84% 0.029 301.883)", "--color-accent": "oklch(83.392% 0.124 66.558)", "--color-accent-content": "oklch(16.678% 0.024 66.558)", "--color-neutral": "oklch(39.445% 0.032 275.524)", "--color-neutral-content": "oklch(87.889% 0.006 275.524)", "--color-info": "oklch(88.263% 0.093 212.846)", "--color-info-content": "oklch(17.652% 0.018 212.846)", "--color-success": "oklch(87.099% 0.219 148.024)", "--color-success-content": "oklch(17.419% 0.043 148.024)", "--color-warning": "oklch(95.533% 0.134 112.757)", "--color-warning-content": "oklch(19.106% 0.026 112.757)", "--color-error": "oklch(68.22% 0.206 24.43)", "--color-error-content": "oklch(13.644% 0.041 24.43)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, retro: { "color-scheme": "light", "--color-base-100": "oklch(91.637% 0.034 90.515)", "--color-base-200": "oklch(88.272% 0.049 91.774)", "--color-base-300": "oklch(84.133% 0.065 90.856)", "--color-base-content": "oklch(41% 0.112 45.904)", "--color-primary": "oklch(80% 0.114 19.571)", "--color-primary-content": "oklch(39% 0.141 25.723)", "--color-secondary": "oklch(92% 0.084 155.995)", "--color-secondary-content": "oklch(44% 0.119 151.328)", "--color-accent": "oklch(68% 0.162 75.834)", "--color-accent-content": "oklch(41% 0.112 45.904)", "--color-neutral": "oklch(44% 0.011 73.639)", "--color-neutral-content": "oklch(86% 0.005 56.366)", "--color-info": "oklch(58% 0.158 241.966)", "--color-info-content": "oklch(96% 0.059 95.617)", "--color-success": "oklch(51% 0.096 186.391)", "--color-success-content": "oklch(96% 0.059 95.617)", "--color-warning": "oklch(64% 0.222 41.116)", "--color-warning-content": "oklch(96% 0.059 95.617)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(40% 0.123 38.172)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, lofi: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(94% 0 0)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(15.906% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(21.455% 0.001 17.278)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(26.861% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(0% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(79.54% 0.103 205.9)", "--color-info-content": "oklch(15.908% 0.02 205.9)", "--color-success": "oklch(90.13% 0.153 164.14)", "--color-success-content": "oklch(18.026% 0.03 164.14)", "--color-warning": "oklch(88.37% 0.135 79.94)", "--color-warning-content": "oklch(17.674% 0.027 79.94)", "--color-error": "oklch(78.66% 0.15 28.47)", "--color-error-content": "oklch(15.732% 0.03 28.47)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, valentine: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.014 343.198)", "--color-base-200": "oklch(94% 0.028 342.258)", "--color-base-300": "oklch(89% 0.061 343.231)", "--color-base-content": "oklch(52% 0.223 3.958)", "--color-primary": "oklch(65% 0.241 354.308)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(62% 0.265 303.9)", "--color-secondary-content": "oklch(97% 0.014 308.299)", "--color-accent": "oklch(82% 0.111 230.318)", "--color-accent-content": "oklch(39% 0.09 240.876)", "--color-neutral": "oklch(40% 0.153 2.432)", "--color-neutral-content": "oklch(89% 0.061 343.231)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(44% 0.11 240.79)", "--color-success": "oklch(84% 0.143 164.978)", "--color-success-content": "oklch(43% 0.095 166.913)", "--color-warning": "oklch(75% 0.183 55.934)", "--color-warning-content": "oklch(26% 0.079 36.259)", "--color-error": "oklch(63% 0.237 25.331)", "--color-error-content": "oklch(97% 0.013 17.38)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, nord: { "color-scheme": "light", "--color-base-100": "oklch(95.127% 0.007 260.731)", "--color-base-200": "oklch(93.299% 0.01 261.788)", "--color-base-300": "oklch(89.925% 0.016 262.749)", "--color-base-content": "oklch(32.437% 0.022 264.182)", "--color-primary": "oklch(59.435% 0.077 254.027)", "--color-primary-content": "oklch(11.887% 0.015 254.027)", "--color-secondary": "oklch(69.651% 0.059 248.687)", "--color-secondary-content": "oklch(13.93% 0.011 248.687)", "--color-accent": "oklch(77.464% 0.062 217.469)", "--color-accent-content": "oklch(15.492% 0.012 217.469)", "--color-neutral": "oklch(45.229% 0.035 264.131)", "--color-neutral-content": "oklch(89.925% 0.016 262.749)", "--color-info": "oklch(69.207% 0.062 332.664)", "--color-info-content": "oklch(13.841% 0.012 332.664)", "--color-success": "oklch(76.827% 0.074 131.063)", "--color-success-content": "oklch(15.365% 0.014 131.063)", "--color-warning": "oklch(85.486% 0.089 84.093)", "--color-warning-content": "oklch(17.097% 0.017 84.093)", "--color-error": "oklch(60.61% 0.12 15.341)", "--color-error-content": "oklch(12.122% 0.024 15.341)", "--radius-selector": "1rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, lemonade: { "color-scheme": "light", "--color-base-100": "oklch(98.71% 0.02 123.72)", "--color-base-200": "oklch(91.8% 0.018 123.72)", "--color-base-300": "oklch(84.89% 0.017 123.72)", "--color-base-content": "oklch(19.742% 0.004 123.72)", "--color-primary": "oklch(58.92% 0.199 134.6)", "--color-primary-content": "oklch(11.784% 0.039 134.6)", "--color-secondary": "oklch(77.75% 0.196 111.09)", "--color-secondary-content": "oklch(15.55% 0.039 111.09)", "--color-accent": "oklch(85.39% 0.201 100.73)", "--color-accent-content": "oklch(17.078% 0.04 100.73)", "--color-neutral": "oklch(30.98% 0.075 108.6)", "--color-neutral-content": "oklch(86.196% 0.015 108.6)", "--color-info": "oklch(86.19% 0.047 224.14)", "--color-info-content": "oklch(17.238% 0.009 224.14)", "--color-success": "oklch(86.19% 0.047 157.85)", "--color-success-content": "oklch(17.238% 0.009 157.85)", "--color-warning": "oklch(86.19% 0.047 102.15)", "--color-warning-content": "oklch(17.238% 0.009 102.15)", "--color-error": "oklch(86.19% 0.047 25.85)", "--color-error-content": "oklch(17.238% 0.009 25.85)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, garden: { "color-scheme": "light", "--color-base-100": "oklch(92.951% 0.002 17.197)", "--color-base-200": "oklch(86.445% 0.002 17.197)", "--color-base-300": "oklch(79.938% 0.001 17.197)", "--color-base-content": "oklch(16.961% 0.001 17.32)", "--color-primary": "oklch(62.45% 0.278 3.836)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(48.495% 0.11 355.095)", "--color-secondary-content": "oklch(89.699% 0.022 355.095)", "--color-accent": "oklch(56.273% 0.054 154.39)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(24.155% 0.049 89.07)", "--color-neutral-content": "oklch(92.951% 0.002 17.197)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, aqua: { "color-scheme": "dark", "--color-base-100": "oklch(37% 0.146 265.522)", "--color-base-200": "oklch(28% 0.091 267.935)", "--color-base-300": "oklch(22% 0.091 267.935)", "--color-base-content": "oklch(90% 0.058 230.902)", "--color-primary": "oklch(85.661% 0.144 198.645)", "--color-primary-content": "oklch(40.124% 0.068 197.603)", "--color-secondary": "oklch(60.682% 0.108 309.782)", "--color-secondary-content": "oklch(96% 0.016 293.756)", "--color-accent": "oklch(93.426% 0.102 94.555)", "--color-accent-content": "oklch(18.685% 0.02 94.555)", "--color-neutral": "oklch(27% 0.146 265.522)", "--color-neutral-content": "oklch(80% 0.146 265.522)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(27% 0.077 45.635)", "--color-error": "oklch(73.95% 0.19 27.33)", "--color-error-content": "oklch(14.79% 0.038 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, corporate: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(22.389% 0.031 278.072)", "--color-primary": "oklch(58% 0.158 241.966)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(55% 0.046 257.417)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(60% 0.118 184.704)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(0% 0 0)", "--color-neutral-content": "oklch(100% 0 0)", "--color-info": "oklch(60% 0.126 221.723)", "--color-info-content": "oklch(100% 0 0)", "--color-success": "oklch(62% 0.194 149.214)", "--color-success-content": "oklch(100% 0 0)", "--color-warning": "oklch(85% 0.199 91.936)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, pastel: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98.462% 0.001 247.838)", "--color-base-300": "oklch(92.462% 0.001 247.838)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(90% 0.063 306.703)", "--color-primary-content": "oklch(49% 0.265 301.924)", "--color-secondary": "oklch(89% 0.058 10.001)", "--color-secondary-content": "oklch(51% 0.222 16.935)", "--color-accent": "oklch(90% 0.093 164.15)", "--color-accent-content": "oklch(50% 0.118 165.612)", "--color-neutral": "oklch(55% 0.046 257.417)", "--color-neutral-content": "oklch(92% 0.013 255.508)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(52% 0.105 223.128)", "--color-success": "oklch(87% 0.15 154.449)", "--color-success-content": "oklch(52% 0.154 150.069)", "--color-warning": "oklch(83% 0.128 66.29)", "--color-warning-content": "oklch(55% 0.195 38.402)", "--color-error": "oklch(80% 0.114 19.571)", "--color-error-content": "oklch(50% 0.213 27.518)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "0", "--noise": "0" }, bumblebee: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(92% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(85% 0.199 91.936)", "--color-primary-content": "oklch(42% 0.095 57.708)", "--color-secondary": "oklch(75% 0.183 55.934)", "--color-secondary-content": "oklch(40% 0.123 38.172)", "--color-accent": "oklch(0% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(37% 0.01 67.558)", "--color-neutral-content": "oklch(92% 0.003 48.717)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(39% 0.09 240.876)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, coffee: { "color-scheme": "dark", "--color-base-100": "oklch(24% 0.023 329.708)", "--color-base-200": "oklch(21% 0.021 329.708)", "--color-base-300": "oklch(16% 0.019 329.708)", "--color-base-content": "oklch(72.354% 0.092 79.129)", "--color-primary": "oklch(71.996% 0.123 62.756)", "--color-primary-content": "oklch(14.399% 0.024 62.756)", "--color-secondary": "oklch(34.465% 0.029 199.194)", "--color-secondary-content": "oklch(86.893% 0.005 199.194)", "--color-accent": "oklch(42.621% 0.074 224.389)", "--color-accent-content": "oklch(88.524% 0.014 224.389)", "--color-neutral": "oklch(16.51% 0.015 326.261)", "--color-neutral-content": "oklch(83.302% 0.003 326.261)", "--color-info": "oklch(79.49% 0.063 184.558)", "--color-info-content": "oklch(15.898% 0.012 184.558)", "--color-success": "oklch(74.722% 0.072 131.116)", "--color-success-content": "oklch(14.944% 0.014 131.116)", "--color-warning": "oklch(88.15% 0.14 87.722)", "--color-warning-content": "oklch(17.63% 0.028 87.722)", "--color-error": "oklch(77.318% 0.128 31.871)", "--color-error-content": "oklch(15.463% 0.025 31.871)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, silk: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.0035 67.78)", "--color-base-200": "oklch(95% 0.0081 61.42)", "--color-base-300": "oklch(90% 0.0081 61.42)", "--color-base-content": "oklch(40% 0.0081 61.42)", "--color-primary": "oklch(23.27% 0.0249 284.3)", "--color-primary-content": "oklch(94.22% 0.2505 117.44)", "--color-secondary": "oklch(23.27% 0.0249 284.3)", "--color-secondary-content": "oklch(73.92% 0.2135 50.94)", "--color-accent": "oklch(23.27% 0.0249 284.3)", "--color-accent-content": "oklch(88.92% 0.2061 189.9)", "--color-neutral": "oklch(20% 0 0)", "--color-neutral-content": "oklch(80% 0.0081 61.42)", "--color-info": "oklch(80.39% 0.1148 241.68)", "--color-info-content": "oklch(30.39% 0.1148 241.68)", "--color-success": "oklch(83.92% 0.0901 136.87)", "--color-success-content": "oklch(23.92% 0.0901 136.87)", "--color-warning": "oklch(83.92% 0.1085 80)", "--color-warning-content": "oklch(43.92% 0.1085 80)", "--color-error": "oklch(75.1% 0.1814 22.37)", "--color-error-content": "oklch(35.1% 0.1814 22.37)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, sunset: { "color-scheme": "dark", "--color-base-100": "oklch(22% 0.019 237.69)", "--color-base-200": "oklch(20% 0.019 237.69)", "--color-base-300": "oklch(18% 0.019 237.69)", "--color-base-content": "oklch(77.383% 0.043 245.096)", "--color-primary": "oklch(74.703% 0.158 39.947)", "--color-primary-content": "oklch(14.94% 0.031 39.947)", "--color-secondary": "oklch(72.537% 0.177 2.72)", "--color-secondary-content": "oklch(14.507% 0.035 2.72)", "--color-accent": "oklch(71.294% 0.166 299.844)", "--color-accent-content": "oklch(14.258% 0.033 299.844)", "--color-neutral": "oklch(26% 0.019 237.69)", "--color-neutral-content": "oklch(70% 0.019 237.69)", "--color-info": "oklch(85.559% 0.085 206.015)", "--color-info-content": "oklch(17.111% 0.017 206.015)", "--color-success": "oklch(85.56% 0.085 144.778)", "--color-success-content": "oklch(17.112% 0.017 144.778)", "--color-warning": "oklch(85.569% 0.084 74.427)", "--color-warning-content": "oklch(17.113% 0.016 74.427)", "--color-error": "oklch(85.511% 0.078 16.886)", "--color-error-content": "oklch(17.102% 0.015 16.886)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, synthwave: { "color-scheme": "dark", "--color-base-100": "oklch(15% 0.09 281.288)", "--color-base-200": "oklch(20% 0.09 281.288)", "--color-base-300": "oklch(25% 0.09 281.288)", "--color-base-content": "oklch(78% 0.115 274.713)", "--color-primary": "oklch(71% 0.202 349.761)", "--color-primary-content": "oklch(28% 0.109 3.907)", "--color-secondary": "oklch(82% 0.111 230.318)", "--color-secondary-content": "oklch(29% 0.066 243.157)", "--color-accent": "oklch(75% 0.183 55.934)", "--color-accent-content": "oklch(26% 0.079 36.259)", "--color-neutral": "oklch(45% 0.24 277.023)", "--color-neutral-content": "oklch(87% 0.065 274.039)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(77% 0.152 181.912)", "--color-success-content": "oklch(27% 0.046 192.524)", "--color-warning": "oklch(90% 0.182 98.111)", "--color-warning-content": "oklch(42% 0.095 57.708)", "--color-error": "oklch(73.7% 0.121 32.639)", "--color-error-content": "oklch(23.501% 0.096 290.329)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dim: { "color-scheme": "dark", "--color-base-100": "oklch(30.857% 0.023 264.149)", "--color-base-200": "oklch(28.036% 0.019 264.182)", "--color-base-300": "oklch(26.346% 0.018 262.177)", "--color-base-content": "oklch(82.901% 0.031 222.959)", "--color-primary": "oklch(86.133% 0.141 139.549)", "--color-primary-content": "oklch(17.226% 0.028 139.549)", "--color-secondary": "oklch(73.375% 0.165 35.353)", "--color-secondary-content": "oklch(14.675% 0.033 35.353)", "--color-accent": "oklch(74.229% 0.133 311.379)", "--color-accent-content": "oklch(14.845% 0.026 311.379)", "--color-neutral": "oklch(24.731% 0.02 264.094)", "--color-neutral-content": "oklch(82.901% 0.031 222.959)", "--color-info": "oklch(86.078% 0.142 206.182)", "--color-info-content": "oklch(17.215% 0.028 206.182)", "--color-success": "oklch(86.171% 0.142 166.534)", "--color-success-content": "oklch(17.234% 0.028 166.534)", "--color-warning": "oklch(86.163% 0.142 94.818)", "--color-warning-content": "oklch(17.232% 0.028 94.818)", "--color-error": "oklch(82.418% 0.099 33.756)", "--color-error-content": "oklch(16.483% 0.019 33.756)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, abyss: { "color-scheme": "dark", "--color-base-100": "oklch(20% 0.08 209)", "--color-base-200": "oklch(15% 0.08 209)", "--color-base-300": "oklch(10% 0.08 209)", "--color-base-content": "oklch(90% 0.076 70.697)", "--color-primary": "oklch(92% 0.2653 125)", "--color-primary-content": "oklch(50% 0.2653 125)", "--color-secondary": "oklch(83.27% 0.0764 298.3)", "--color-secondary-content": "oklch(43.27% 0.0764 298.3)", "--color-accent": "oklch(43% 0 0)", "--color-accent-content": "oklch(98% 0 0)", "--color-neutral": "oklch(30% 0.08 209)", "--color-neutral-content": "oklch(90% 0.076 70.697)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(79% 0.209 151.711)", "--color-success-content": "oklch(26% 0.065 152.934)", "--color-warning": "oklch(84.8% 0.1962 84.62)", "--color-warning-content": "oklch(44.8% 0.1962 84.62)", "--color-error": "oklch(65% 0.1985 24.22)", "--color-error-content": "oklch(27% 0.1985 24.22)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, forest: { "color-scheme": "dark", "--color-base-100": "oklch(20.84% 0.008 17.911)", "--color-base-200": "oklch(18.522% 0.007 17.911)", "--color-base-300": "oklch(16.203% 0.007 17.911)", "--color-base-content": "oklch(83.768% 0.001 17.911)", "--color-primary": "oklch(68.628% 0.185 148.958)", "--color-primary-content": "oklch(0% 0 0)", "--color-secondary": "oklch(69.776% 0.135 168.327)", "--color-secondary-content": "oklch(13.955% 0.027 168.327)", "--color-accent": "oklch(70.628% 0.119 185.713)", "--color-accent-content": "oklch(14.125% 0.023 185.713)", "--color-neutral": "oklch(30.698% 0.039 171.364)", "--color-neutral-content": "oklch(86.139% 0.007 171.364)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, night: { "color-scheme": "dark", "--color-base-100": "oklch(20.768% 0.039 265.754)", "--color-base-200": "oklch(19.314% 0.037 265.754)", "--color-base-300": "oklch(17.86% 0.034 265.754)", "--color-base-content": "oklch(84.153% 0.007 265.754)", "--color-primary": "oklch(75.351% 0.138 232.661)", "--color-primary-content": "oklch(15.07% 0.027 232.661)", "--color-secondary": "oklch(68.011% 0.158 276.934)", "--color-secondary-content": "oklch(13.602% 0.031 276.934)", "--color-accent": "oklch(72.36% 0.176 350.048)", "--color-accent-content": "oklch(14.472% 0.035 350.048)", "--color-neutral": "oklch(27.949% 0.036 260.03)", "--color-neutral-content": "oklch(85.589% 0.007 260.03)", "--color-info": "oklch(68.455% 0.148 237.251)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(78.452% 0.132 181.911)", "--color-success-content": "oklch(15.69% 0.026 181.911)", "--color-warning": "oklch(83.242% 0.139 82.95)", "--color-warning-content": "oklch(16.648% 0.027 82.95)", "--color-error": "oklch(71.785% 0.17 13.118)", "--color-error-content": "oklch(14.357% 0.034 13.118)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, caramellatte: { "color-scheme": "light", "--color-base-100": "oklch(98% 0.016 73.684)", "--color-base-200": "oklch(95% 0.038 75.164)", "--color-base-300": "oklch(90% 0.076 70.697)", "--color-base-content": "oklch(40% 0.123 38.172)", "--color-primary": "oklch(0% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(22.45% 0.075 37.85)", "--color-secondary-content": "oklch(90% 0.076 70.697)", "--color-accent": "oklch(46.44% 0.111 37.85)", "--color-accent-content": "oklch(90% 0.076 70.697)", "--color-neutral": "oklch(55% 0.195 38.402)", "--color-neutral-content": "oklch(98% 0.016 73.684)", "--color-info": "oklch(42% 0.199 265.638)", "--color-info-content": "oklch(90% 0.076 70.697)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.076 70.697)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "1" }, autumn: { "color-scheme": "light", "--color-base-100": "oklch(95.814% 0 0)", "--color-base-200": "oklch(89.107% 0 0)", "--color-base-300": "oklch(82.4% 0 0)", "--color-base-content": "oklch(19.162% 0 0)", "--color-primary": "oklch(40.723% 0.161 17.53)", "--color-primary-content": "oklch(88.144% 0.032 17.53)", "--color-secondary": "oklch(61.676% 0.169 23.865)", "--color-secondary-content": "oklch(12.335% 0.033 23.865)", "--color-accent": "oklch(73.425% 0.094 60.729)", "--color-accent-content": "oklch(14.685% 0.018 60.729)", "--color-neutral": "oklch(54.367% 0.037 51.902)", "--color-neutral-content": "oklch(90.873% 0.007 51.902)", "--color-info": "oklch(69.224% 0.097 207.284)", "--color-info-content": "oklch(13.844% 0.019 207.284)", "--color-success": "oklch(60.995% 0.08 174.616)", "--color-success-content": "oklch(12.199% 0.016 174.616)", "--color-warning": "oklch(70.081% 0.164 56.844)", "--color-warning-content": "oklch(14.016% 0.032 56.844)", "--color-error": "oklch(53.07% 0.241 24.16)", "--color-error-content": "oklch(90.614% 0.048 24.16)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, emerald: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(35.519% 0.032 262.988)", "--color-primary": "oklch(76.662% 0.135 153.45)", "--color-primary-content": "oklch(33.387% 0.04 162.24)", "--color-secondary": "oklch(61.302% 0.202 261.294)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(72.772% 0.149 33.2)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(35.519% 0.032 262.988)", "--color-neutral-content": "oklch(98.462% 0.001 247.838)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, cupcake: { "color-scheme": "light", "--color-base-100": "oklch(97.788% 0.004 56.375)", "--color-base-200": "oklch(93.982% 0.007 61.449)", "--color-base-300": "oklch(91.586% 0.006 53.44)", "--color-base-content": "oklch(23.574% 0.066 313.189)", "--color-primary": "oklch(85% 0.138 181.071)", "--color-primary-content": "oklch(43% 0.078 188.216)", "--color-secondary": "oklch(89% 0.061 343.231)", "--color-secondary-content": "oklch(45% 0.187 3.815)", "--color-accent": "oklch(90% 0.076 70.697)", "--color-accent-content": "oklch(47% 0.157 37.304)", "--color-neutral": "oklch(27% 0.006 286.033)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(68% 0.169 237.323)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(69% 0.17 162.48)", "--color-success-content": "oklch(26% 0.051 172.552)", "--color-warning": "oklch(79% 0.184 86.047)", "--color-warning-content": "oklch(28% 0.066 53.813)", "--color-error": "oklch(64% 0.246 16.439)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, cmyk: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(95% 0 0)", "--color-base-300": "oklch(90% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(71.772% 0.133 239.443)", "--color-primary-content": "oklch(14.354% 0.026 239.443)", "--color-secondary": "oklch(64.476% 0.202 359.339)", "--color-secondary-content": "oklch(12.895% 0.04 359.339)", "--color-accent": "oklch(94.228% 0.189 105.306)", "--color-accent-content": "oklch(18.845% 0.037 105.306)", "--color-neutral": "oklch(21.778% 0 0)", "--color-neutral-content": "oklch(84.355% 0 0)", "--color-info": "oklch(68.475% 0.094 217.284)", "--color-info-content": "oklch(13.695% 0.018 217.284)", "--color-success": "oklch(46.949% 0.162 321.406)", "--color-success-content": "oklch(89.389% 0.032 321.406)", "--color-warning": "oklch(71.236% 0.159 52.023)", "--color-warning-content": "oklch(14.247% 0.031 52.023)", "--color-error": "oklch(62.013% 0.208 28.717)", "--color-error-content": "oklch(12.402% 0.041 28.717)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, business: { "color-scheme": "dark", "--color-base-100": "oklch(24.353% 0 0)", "--color-base-200": "oklch(22.648% 0 0)", "--color-base-300": "oklch(20.944% 0 0)", "--color-base-content": "oklch(84.87% 0 0)", "--color-primary": "oklch(41.703% 0.099 251.473)", "--color-primary-content": "oklch(88.34% 0.019 251.473)", "--color-secondary": "oklch(64.092% 0.027 229.389)", "--color-secondary-content": "oklch(12.818% 0.005 229.389)", "--color-accent": "oklch(67.271% 0.167 35.791)", "--color-accent-content": "oklch(13.454% 0.033 35.791)", "--color-neutral": "oklch(27.441% 0.013 253.041)", "--color-neutral-content": "oklch(85.488% 0.002 253.041)", "--color-info": "oklch(62.616% 0.143 240.033)", "--color-info-content": "oklch(12.523% 0.028 240.033)", "--color-success": "oklch(70.226% 0.094 156.596)", "--color-success-content": "oklch(14.045% 0.018 156.596)", "--color-warning": "oklch(77.482% 0.115 81.519)", "--color-warning-content": "oklch(15.496% 0.023 81.519)", "--color-error": "oklch(51.61% 0.146 29.674)", "--color-error-content": "oklch(90.322% 0.029 29.674)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, winter: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97.466% 0.011 259.822)", "--color-base-300": "oklch(93.268% 0.016 262.751)", "--color-base-content": "oklch(41.886% 0.053 255.824)", "--color-primary": "oklch(56.86% 0.255 257.57)", "--color-primary-content": "oklch(91.372% 0.051 257.57)", "--color-secondary": "oklch(42.551% 0.161 282.339)", "--color-secondary-content": "oklch(88.51% 0.032 282.339)", "--color-accent": "oklch(59.939% 0.191 335.171)", "--color-accent-content": "oklch(11.988% 0.038 335.171)", "--color-neutral": "oklch(19.616% 0.063 257.651)", "--color-neutral-content": "oklch(83.923% 0.012 257.651)", "--color-info": "oklch(88.127% 0.085 214.515)", "--color-info-content": "oklch(17.625% 0.017 214.515)", "--color-success": "oklch(80.494% 0.077 197.823)", "--color-success-content": "oklch(16.098% 0.015 197.823)", "--color-warning": "oklch(89.172% 0.045 71.47)", "--color-warning-content": "oklch(17.834% 0.009 71.47)", "--color-error": "oklch(73.092% 0.11 20.076)", "--color-error-content": "oklch(14.618% 0.022 20.076)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, halloween: { "color-scheme": "dark", "--color-base-100": "oklch(21% 0.006 56.043)", "--color-base-200": "oklch(14% 0.004 49.25)", "--color-base-300": "oklch(0% 0 0)", "--color-base-content": "oklch(84.955% 0 0)", "--color-primary": "oklch(77.48% 0.204 60.62)", "--color-primary-content": "oklch(19.693% 0.004 196.779)", "--color-secondary": "oklch(45.98% 0.248 305.03)", "--color-secondary-content": "oklch(89.196% 0.049 305.03)", "--color-accent": "oklch(64.8% 0.223 136.073)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(24.371% 0.046 65.681)", "--color-neutral-content": "oklch(84.874% 0.009 65.681)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(13.316% 0.031 58.318)", "--color-error": "oklch(65.72% 0.199 27.33)", "--color-error-content": "oklch(13.144% 0.039 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, fantasy: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(93% 0 0)", "--color-base-300": "oklch(86% 0 0)", "--color-base-content": "oklch(27.807% 0.029 256.847)", "--color-primary": "oklch(37.45% 0.189 325.02)", "--color-primary-content": "oklch(87.49% 0.037 325.02)", "--color-secondary": "oklch(53.92% 0.162 241.36)", "--color-secondary-content": "oklch(90.784% 0.032 241.36)", "--color-accent": "oklch(75.98% 0.204 56.72)", "--color-accent-content": "oklch(15.196% 0.04 56.72)", "--color-neutral": "oklch(27.807% 0.029 256.847)", "--color-neutral-content": "oklch(85.561% 0.005 256.847)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, wireframe: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97% 0 0)", "--color-base-300": "oklch(94% 0 0)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(87% 0 0)", "--color-primary-content": "oklch(26% 0 0)", "--color-secondary": "oklch(87% 0 0)", "--color-secondary-content": "oklch(26% 0 0)", "--color-accent": "oklch(87% 0 0)", "--color-accent-content": "oklch(26% 0 0)", "--color-neutral": "oklch(87% 0 0)", "--color-neutral-content": "oklch(26% 0 0)", "--color-info": "oklch(44% 0.11 240.79)", "--color-info-content": "oklch(90% 0.058 230.902)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.093 164.15)", "--color-warning": "oklch(47% 0.137 46.201)", "--color-warning-content": "oklch(92% 0.12 95.746)", "--color-error": "oklch(44% 0.177 26.899)", "--color-error-content": "oklch(88% 0.062 18.334)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" } }; + +// packages/daisyui/base/rootscrolllock/object.js +var object_default2 = { ':root:has( .modal-open, .modal[open], .modal:target, .modal-toggle:checked, .drawer:not([class*="drawer-open"]) > .drawer-toggle:checked )': { overflow: "hidden" } }; + +// packages/daisyui/functions/addPrefix.js +var defaultExcludedPrefixes = ["color-", "size-", "radius-", "border", "depth", "noise"]; +var shouldExcludeVariable = (variableName, excludedPrefixes) => { + if (variableName.startsWith("tw")) { + return true; + } + return excludedPrefixes.some((excludedPrefix) => variableName.startsWith(excludedPrefix)); +}; +var prefixVariable = (variableName, prefix, excludedPrefixes) => { + if (shouldExcludeVariable(variableName, excludedPrefixes)) { + return variableName; + } + return `${prefix}${variableName}`; +}; +var getPrefixedSelector = (selector, prefix) => { + if (!selector.startsWith(".")) + return selector; + return `.${prefix}${selector.slice(1)}`; +}; +var getPrefixedKey = (key, prefix, excludedPrefixes) => { + const prefixAmpDot = prefix ? `&.${prefix}` : ""; + if (!prefix) + return key; + if (key.startsWith("--")) { + const variableName = key.slice(2); + return `--${prefixVariable(variableName, prefix, excludedPrefixes)}`; + } + if (key.startsWith("@") || key.startsWith("[")) { + return key; + } + if (key.startsWith("&")) { + if (key.match(/:[a-z-]+\(/)) { + return key.replace(/\.([\w-]+)/g, `.${prefix}$1`); + } + if (key.startsWith("&.")) { + return `${prefixAmpDot}${key.slice(2)}`; + } + return key.replace(/\.([\w-]+)/g, `.${prefix}$1`); + } + if (key.startsWith(":")) { + return key.replace(/\.([\w-]+)/g, `.${prefix}$1`); + } + if (key.includes(".") && !key.includes(" ") && !key.includes(">") && !key.includes("+") && !key.includes("~")) { + return key.split(".").filter(Boolean).map((part) => prefix + part).join(".").replace(/^/, "."); + } + if (key.includes(">") || key.includes("+") || key.includes("~")) { + if (key.includes(",")) { + return key.split(/\s*,\s*/).map((part) => { + return part.replace(/\.([\w-]+)/g, `.${prefix}$1`); + }).join(", "); + } + let processedKey = key.replace(/\.([\w-]+)/g, `.${prefix}$1`); + if (processedKey.startsWith(">") || processedKey.startsWith("+") || processedKey.startsWith("~")) { + processedKey = ` ${processedKey}`; + } + return processedKey; + } + if (key.includes(" ")) { + return key.split(/\s+/).map((part) => { + if (part.startsWith(".")) { + return getPrefixedSelector(part, prefix); + } + return part; + }).join(" "); + } + if (key.includes(":")) { + const [selector, ...pseudo] = key.split(":"); + if (selector.startsWith(".")) { + return `${getPrefixedSelector(selector, prefix)}:${pseudo.join(":")}`; + } + return key.replace(/\.([\w-]+)/g, `.${prefix}$1`); + } + if (key.startsWith(".")) { + return getPrefixedSelector(key, prefix); + } + return key; +}; +var processArrayValue = (value, prefix, excludedPrefixes) => { + return value.map((item) => { + if (typeof item === "string") { + if (item.startsWith(".")) { + return prefix ? `.${prefix}${item.slice(1)}` : item; + } + return processStringValue(item, prefix, excludedPrefixes); + } + return item; + }); +}; +var processStringValue = (value, prefix, excludedPrefixes) => { + if (prefix === 0) + return value; + return value.replace(/var\(--([^)]+)\)/g, (match, variableName) => { + if (shouldExcludeVariable(variableName, excludedPrefixes)) { + return match; + } + return `var(--${prefix}${variableName})`; + }); +}; +var processValue = (value, prefix, excludedPrefixes) => { + if (Array.isArray(value)) { + return processArrayValue(value, prefix, excludedPrefixes); + } else if (typeof value === "object" && value !== null) { + return addPrefix(value, prefix, excludedPrefixes); + } else if (typeof value === "string") { + return processStringValue(value, prefix, excludedPrefixes); + } else { + return value; + } +}; +var addPrefix = (obj, prefix, excludedPrefixes = defaultExcludedPrefixes) => { + return Object.entries(obj).reduce((result, [key, value]) => { + const newKey = getPrefixedKey(key, prefix, excludedPrefixes); + result[newKey] = processValue(value, prefix, excludedPrefixes); + return result; + }, {}); +}; + +// packages/daisyui/base/rootscrolllock/index.js +var rootscrolllock_default = ({ addBase, prefix = "" }) => { + const prefixedrootscrolllock = addPrefix(object_default2, prefix); + addBase({ ...prefixedrootscrolllock }); +}; + +// packages/daisyui/base/rootcolor/object.js +var object_default3 = { ":root, [data-theme]": { "background-color": "var(--root-bg, var(--color-base-100))", color: "var(--color-base-content)" } }; + +// packages/daisyui/base/rootcolor/index.js +var rootcolor_default = ({ addBase, prefix = "" }) => { + const prefixedrootcolor = addPrefix(object_default3, prefix); + addBase({ ...prefixedrootcolor }); +}; + +// packages/daisyui/base/scrollbar/object.js +var object_default4 = { ":root": { "scrollbar-color": "color-mix(in oklch, currentColor 35%, #0000) #0000" } }; + +// packages/daisyui/base/scrollbar/index.js +var scrollbar_default = ({ addBase, prefix = "" }) => { + const prefixedscrollbar = addPrefix(object_default4, prefix); + addBase({ ...prefixedscrollbar }); +}; + +// packages/daisyui/base/properties/object.js +var object_default5 = { "@property --radialprogress": { syntax: '""', inherits: "true", "initial-value": "0%" } }; + +// packages/daisyui/base/properties/index.js +var properties_default = ({ addBase, prefix = "" }) => { + const prefixedproperties = addPrefix(object_default5, prefix); + addBase({ ...prefixedproperties }); +}; + +// packages/daisyui/base/rootscrollgutter/object.js +var object_default6 = { ":where( :root:has( .modal-open, .modal[open], .modal:target, .modal-toggle:checked, .drawer:not(.drawer-open) > .drawer-toggle:checked ) )": { "scrollbar-gutter": "stable", "background-image": "linear-gradient(var(--color-base-100), var(--color-base-100))", "--root-bg": "color-mix(in srgb, var(--color-base-100), oklch(0% 0 0) 40%)" }, ":where(.modal[open], .modal-open, .modal-toggle:checked + .modal):not(.modal-start, .modal-end)": { "scrollbar-gutter": "stable" } }; + +// packages/daisyui/base/rootscrollgutter/index.js +var rootscrollgutter_default = ({ addBase, prefix = "" }) => { + const prefixedrootscrollgutter = addPrefix(object_default6, prefix); + addBase({ ...prefixedrootscrollgutter }); +}; + +// packages/daisyui/base/svg/object.js +var object_default7 = { ":root": { "--fx-noise": `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.34' numOctaves='4' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)' opacity='0.2'%3E%3C/rect%3E%3C/svg%3E")` }, ".chat": { "--mask-chat": `url("data:image/svg+xml,%3csvg width='13' height='13' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M0 11.5004C0 13.0004 2 13.0004 2 13.0004H12H13V0.00036329L12.5 0C12.5 0 11.977 2.09572 11.8581 2.50033C11.6075 3.35237 10.9149 4.22374 9 5.50036C6 7.50036 0 10.0004 0 11.5004Z'/%3e%3c/svg%3e")` } }; + +// packages/daisyui/base/svg/index.js +var svg_default = ({ addBase, prefix = "" }) => { + const prefixedsvg = addPrefix(object_default7, prefix); + addBase({ ...prefixedsvg }); +}; + +// packages/daisyui/components/drawer/object.js +var object_default8 = { ".drawer": { position: "relative", display: "grid", width: "100%", "grid-auto-columns": "max-content auto" }, ".drawer-content": { "grid-column-start": "2", "grid-row-start": "1", "min-width": "calc(0.25rem * 0)" }, ".drawer-side": { "pointer-events": "none", visibility: "hidden", position: "fixed", "inset-inline-start": "calc(0.25rem * 0)", top: "calc(0.25rem * 0)", "z-index": 1, "grid-column-start": "1", "grid-row-start": "1", display: "grid", width: "100%", "grid-template-columns": "repeat(1, minmax(0, 1fr))", "grid-template-rows": "repeat(1, minmax(0, 1fr))", "align-items": "flex-start", "justify-items": "start", "overflow-x": "hidden", "overflow-y": "hidden", "overscroll-behavior": "contain", opacity: "0%", transition: "opacity 0.2s ease-out 0.1s allow-discrete, visibility 0.3s ease-out 0.1s allow-discrete", height: ["100vh", "100dvh"], "> .drawer-overlay": { position: "sticky", top: "calc(0.25rem * 0)", cursor: "pointer", "place-self": "stretch", "background-color": "oklch(0% 0 0 / 40%)" }, "> *": { "grid-column-start": "1", "grid-row-start": "1" }, "> *:not(.drawer-overlay)": { "will-change": "transform", transition: "translate 0.3s ease-out", translate: "-100%", '[dir="rtl"] &': { translate: "100%" } } }, ".drawer-toggle": { position: "fixed", height: "calc(0.25rem * 0)", width: "calc(0.25rem * 0)", appearance: "none", opacity: "0%", "&:checked": { "& ~ .drawer-side": { "pointer-events": "auto", visibility: "visible", "overflow-y": "auto", opacity: "100%", "& > *:not(.drawer-overlay)": { translate: "0%" } } }, "&:focus-visible ~ .drawer-content label.drawer-button": { outline: "2px solid", "outline-offset": "2px" } }, ".drawer-end": { "grid-auto-columns": "auto max-content", "> .drawer-toggle": { "& ~ .drawer-content": { "grid-column-start": "1" }, "& ~ .drawer-side": { "grid-column-start": "2", "justify-items": "end" }, "& ~ .drawer-side > *:not(.drawer-overlay)": { translate: "100%", '[dir="rtl"] &': { translate: "-100%" } }, "&:checked ~ .drawer-side > *:not(.drawer-overlay)": { translate: "0%" } } }, ".drawer-open": { "> .drawer-side": { "overflow-y": "auto" }, "> .drawer-toggle": { display: "none", "& ~ .drawer-side": { "pointer-events": "auto", visibility: "visible", position: "sticky", display: "block", width: "auto", "overscroll-behavior": "auto", opacity: "100%", "& > .drawer-overlay": { cursor: "default", "background-color": "transparent" }, "& > *:not(.drawer-overlay)": { translate: "0%", '[dir="rtl"] &': { translate: "0%" } } }, "&:checked ~ .drawer-side": { "pointer-events": "auto", visibility: "visible" } } } }; + +// packages/daisyui/components/drawer/index.js +var drawer_default = ({ addComponents, prefix = "" }) => { + const prefixeddrawer = addPrefix(object_default8, prefix); + addComponents({ ...prefixeddrawer }); +}; + +// packages/daisyui/components/link/object.js +var object_default9 = { ".link": { cursor: "pointer", "text-decoration-line": "underline", "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible": { outline: "2px solid currentColor", "outline-offset": "2px" } }, ".link-hover": { "text-decoration-line": "none", "&:hover": { "@media (hover: hover)": { "text-decoration-line": "underline" } } }, ".link-primary": { color: "var(--color-primary)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-primary) 80%, #000)" } } }, ".link-secondary": { color: "var(--color-secondary)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-secondary) 80%, #000)" } } }, ".link-accent": { color: "var(--color-accent)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-accent) 80%, #000)" } } }, ".link-neutral": { color: "var(--color-neutral)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-neutral) 80%, #000)" } } }, ".link-success": { color: "var(--color-success)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-success) 80%, #000)" } } }, ".link-info": { color: "var(--color-info)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-info) 80%, #000)" } } }, ".link-warning": { color: "var(--color-warning)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-warning) 80%, #000)" } } }, ".link-error": { color: "var(--color-error)", "@media (hover: hover)": { "&:hover": { color: "color-mix(in oklab, var(--color-error) 80%, #000)" } } } }; + +// packages/daisyui/components/link/index.js +var link_default = ({ addComponents, prefix = "" }) => { + const prefixedlink = addPrefix(object_default9, prefix); + addComponents({ ...prefixedlink }); +}; + +// packages/daisyui/components/stat/object.js +var object_default10 = { ".stats": { position: "relative", display: "inline-grid", "grid-auto-flow": "column", "overflow-x": "auto", "border-radius": "var(--radius-box)" }, ".stat": { display: "inline-grid", width: "100%", "column-gap": "calc(0.25rem * 4)", "padding-inline": "calc(0.25rem * 6)", "padding-block": "calc(0.25rem * 4)", "grid-template-columns": "repeat(1, 1fr)", "&:not(:last-child)": { "border-inline-end": "var(--border) dashed color-mix(in oklab, currentColor 10%, #0000)", "border-block-end": "none" } }, ".stat-figure": { "grid-column-start": "2", "grid-row": "span 3 / span 3", "grid-row-start": "1", "place-self": "center", "justify-self": "flex-end" }, ".stat-title": { "grid-column-start": "1", "white-space": "nowrap", color: "color-mix(in oklab, var(--color-base-content) 60%, transparent)", "font-size": "0.75rem" }, ".stat-value": { "grid-column-start": "1", "white-space": "nowrap", "font-size": "2rem", "font-weight": 800 }, ".stat-desc": { "grid-column-start": "1", "white-space": "nowrap", color: "color-mix(in oklab, var(--color-base-content) 60%, transparent)", "font-size": "0.75rem" }, ".stat-actions": { "grid-column-start": "1", "white-space": "nowrap" }, ".stats-horizontal": { "grid-auto-flow": "column", "overflow-x": "auto", ".stat:not(:last-child)": { "border-inline-end": "var(--border) dashed color-mix(in oklab, currentColor 10%, #0000)", "border-block-end": "none" } }, ".stats-vertical": { "grid-auto-flow": "row", "overflow-y": "auto", ".stat:not(:last-child)": { "border-inline-end": "none", "border-block-end": "var(--border) dashed color-mix(in oklab, currentColor 10%, #0000)" } } }; + +// packages/daisyui/components/stat/index.js +var stat_default = ({ addComponents, prefix = "" }) => { + const prefixedstat = addPrefix(object_default10, prefix); + addComponents({ ...prefixedstat }); +}; + +// packages/daisyui/components/carousel/object.js +var object_default11 = { ".carousel": { display: "inline-flex", "overflow-x": "scroll", "scroll-snap-type": "x mandatory", "scroll-behavior": "smooth", "scrollbar-width": "none", "&::-webkit-scrollbar": { display: "none" } }, ".carousel-vertical": { "flex-direction": "column", "overflow-y": "scroll", "scroll-snap-type": "y mandatory" }, ".carousel-horizontal": { "flex-direction": "row", "overflow-x": "scroll", "scroll-snap-type": "x mandatory" }, ".carousel-item": { "box-sizing": "content-box", display: "flex", flex: "none", "scroll-snap-align": "start" }, ".carousel-start": { ".carousel-item": { "scroll-snap-align": "start" } }, ".carousel-center": { ".carousel-item": { "scroll-snap-align": "center" } }, ".carousel-end": { ".carousel-item": { "scroll-snap-align": "end" } } }; + +// packages/daisyui/components/carousel/index.js +var carousel_default = ({ addComponents, prefix = "" }) => { + const prefixedcarousel = addPrefix(object_default11, prefix); + addComponents({ ...prefixedcarousel }); +}; + +// packages/daisyui/components/divider/object.js +var object_default12 = { ".divider": { display: "flex", height: "calc(0.25rem * 4)", "flex-direction": "row", "align-items": "center", "align-self": "stretch", "white-space": "nowrap", margin: "var(--divider-m, 1rem 0)", "--divider-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", "&:before, &:after": { content: '""', height: "calc(0.25rem * 0.5)", width: "100%", "flex-grow": 1, "background-color": "var(--divider-color)" }, "@media print": { "&:before, &:after": { border: "0.5px solid" } }, "&:not(:empty)": { gap: "calc(0.25rem * 4)" } }, ".divider-horizontal": { "--divider-m": "0 1rem", "&.divider": { height: "auto", width: "calc(0.25rem * 4)", "flex-direction": "column", "&:before": { height: "100%", width: "calc(0.25rem * 0.5)" }, "&:after": { height: "100%", width: "calc(0.25rem * 0.5)" } } }, ".divider-vertical": { "--divider-m": "1rem 0", "&.divider": { height: "calc(0.25rem * 4)", width: "auto", "flex-direction": "row", "&:before": { height: "calc(0.25rem * 0.5)", width: "100%" }, "&:after": { height: "calc(0.25rem * 0.5)", width: "100%" } } }, ".divider-neutral": { "&:before, &:after": { "background-color": "var(--color-neutral)" } }, ".divider-primary": { "&:before, &:after": { "background-color": "var(--color-primary)" } }, ".divider-secondary": { "&:before, &:after": { "background-color": "var(--color-secondary)" } }, ".divider-accent": { "&:before, &:after": { "background-color": "var(--color-accent)" } }, ".divider-success": { "&:before, &:after": { "background-color": "var(--color-success)" } }, ".divider-warning": { "&:before, &:after": { "background-color": "var(--color-warning)" } }, ".divider-info": { "&:before, &:after": { "background-color": "var(--color-info)" } }, ".divider-error": { "&:before, &:after": { "background-color": "var(--color-error)" } }, ".divider-start:before": { display: "none" }, ".divider-end:after": { display: "none" } }; + +// packages/daisyui/components/divider/index.js +var divider_default = ({ addComponents, prefix = "" }) => { + const prefixeddivider = addPrefix(object_default12, prefix); + addComponents({ ...prefixeddivider }); +}; + +// packages/daisyui/components/mask/object.js +var object_default13 = { ".mask": { display: "inline-block", "vertical-align": "middle", "mask-size": "contain", "mask-repeat": "no-repeat", "mask-position": "center" }, ".mask-half-1": { "mask-size": "200%", "mask-position": ["left", "left"], '&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)': { "mask-position": "right" } }, ".mask-half-2": { "mask-size": "200%", "mask-position": ["right", "right"], '&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)': { "mask-position": "left" } }, ".mask-squircle": { "mask-image": `url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M100 0C20 0 0 20 0 100s20 100 100 100 100-20 100-100S180 0 100 0Z'/%3e%3c/svg%3e")` }, ".mask-decagon": { "mask-image": `url("data:image/svg+xml,%3csvg width='192' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 0 58.779 19.098 36.327 50v61.804l-36.327 50L96 200l-58.779-19.098-36.327-50V69.098l36.327-50z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-diamond": { "mask-image": `url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m100 0 100 100-100 100L0 100z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-heart": { "mask-image": `url("data:image/svg+xml,%3csvg width='200' height='185' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M100 184.606a15.384 15.384 0 0 1-8.653-2.678C53.565 156.28 37.205 138.695 28.182 127.7 8.952 104.264-.254 80.202.005 54.146.308 24.287 24.264 0 53.406 0c21.192 0 35.869 11.937 44.416 21.879a2.884 2.884 0 0 0 4.356 0C110.725 11.927 125.402 0 146.594 0c29.142 0 53.098 24.287 53.4 54.151.26 26.061-8.956 50.122-28.176 73.554-9.023 10.994-25.383 28.58-63.165 54.228a15.384 15.384 0 0 1-8.653 2.673Z' fill='black' fill-rule='nonzero'/%3e%3c/svg%3e")` }, ".mask-hexagon": { "mask-image": `url("data:image/svg+xml,%3csvg width='182' height='201' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M.3 65.486c0-9.196 6.687-20.063 14.211-25.078l61.86-35.946c8.36-5.016 20.899-5.016 29.258 0l61.86 35.946c8.36 5.015 14.211 15.882 14.211 25.078v71.055c0 9.196-6.687 20.063-14.211 25.079l-61.86 35.945c-8.36 4.18-20.899 4.18-29.258 0L14.51 161.62C6.151 157.44.3 145.737.3 136.54V65.486Z' fill='black' fill-rule='nonzero'/%3e%3c/svg%3e")` }, ".mask-hexagon-2": { "mask-image": `url("data:image/svg+xml,%3csvg width='200' height='182' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M64.786 181.4c-9.196 0-20.063-6.687-25.079-14.21L3.762 105.33c-5.016-8.36-5.016-20.9 0-29.259l35.945-61.86C44.723 5.851 55.59 0 64.786 0h71.055c9.196 0 20.063 6.688 25.079 14.211l35.945 61.86c4.18 8.36 4.18 20.899 0 29.258l-35.945 61.86c-4.18 8.36-15.883 14.211-25.079 14.211H64.786Z' fill='black' fill-rule='nonzero'/%3e%3c/svg%3e")` }, ".mask-circle": { "mask-image": `url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle fill='black' cx='100' cy='100' r='100' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-pentagon": { "mask-image": `url("data:image/svg+xml,%3csvg width='192' height='181' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 0 95.106 69.098-36.327 111.804H37.22L.894 69.098z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-star": { "mask-image": `url("data:image/svg+xml,%3csvg width='192' height='180' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 137.263-58.779 42.024 22.163-68.389L.894 68.481l72.476-.243L96 0l22.63 68.238 72.476.243-58.49 42.417 22.163 68.389z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-star-2": { "mask-image": `url("data:image/svg+xml,%3csvg width='192' height='180' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 153.044-58.779 26.243 7.02-63.513L.894 68.481l63.117-13.01L96 0l31.989 55.472 63.117 13.01-43.347 47.292 7.02 63.513z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-triangle": { "mask-image": `url("data:image/svg+xml,%3csvg width='174' height='149' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m87 148.476-86.603.185L43.86 74.423 87 0l43.14 74.423 43.463 74.238z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-triangle-2": { "mask-image": `url("data:image/svg+xml,%3csvg width='174' height='150' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m87 .738 86.603-.184-43.463 74.238L87 149.214 43.86 74.792.397.554z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-triangle-3": { "mask-image": `url("data:image/svg+xml,%3csvg width='150' height='174' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m149.369 87.107.185 86.603-74.239-43.463L.893 87.107l74.422-43.14L149.554.505z' fill-rule='evenodd'/%3e%3c/svg%3e")` }, ".mask-triangle-4": { "mask-image": `url("data:image/svg+xml,%3csvg width='150' height='174' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M.631 87.107.446.505l74.239 43.462 74.422 43.14-74.422 43.14L.446 173.71z' fill-rule='evenodd'/%3e%3c/svg%3e")` } }; + +// packages/daisyui/components/mask/index.js +var mask_default = ({ addComponents, prefix = "" }) => { + const prefixedmask = addPrefix(object_default13, prefix); + addComponents({ ...prefixedmask }); +}; + +// packages/daisyui/components/fieldset/object.js +var object_default14 = { ".fieldset": { display: "grid", gap: "calc(0.25rem * 1.5)", "padding-block": "calc(0.25rem * 1)", "font-size": "0.75rem", "grid-template-columns": "1fr", "grid-auto-rows": "max-content" }, ".fieldset-legend": { "margin-bottom": "calc(0.25rem * -1)", display: "flex", "align-items": "center", "justify-content": "space-between", gap: "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 2)", color: "var(--color-base-content)", "font-weight": 600 }, ".fieldset-label": { display: "flex", "align-items": "center", gap: "calc(0.25rem * 1.5)", color: "color-mix(in oklab, var(--color-base-content) 60%, transparent)", "&:has(input)": { cursor: "pointer" } } }; + +// packages/daisyui/components/fieldset/index.js +var fieldset_default = ({ addComponents, prefix = "" }) => { + const prefixedfieldset = addPrefix(object_default14, prefix); + addComponents({ ...prefixedfieldset }); +}; + +// packages/daisyui/components/dropdown/object.js +var object_default15 = { ".dropdown": { position: "relative", display: "inline-block", "position-area": "var(--anchor-v, bottom) var(--anchor-h, span-right)", "& > *:not(summary):focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, ".dropdown-content": { position: "absolute" }, "&:not(details, .dropdown-open, .dropdown-hover:hover, :focus-within)": { ".dropdown-content": { display: "none", "transform-origin": "top", opacity: "0%", scale: "95%" } }, "&[popover], .dropdown-content": { "z-index": 999, animation: "dropdown 0.2s", "transition-property": "opacity, scale, display", "transition-behavior": "allow-discrete", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)" }, "@starting-style": { "&[popover], .dropdown-content": { scale: "95%", opacity: 0 } }, "&.dropdown-open, &:not(.dropdown-hover):focus, &:focus-within": { "> [tabindex]:first-child": { "pointer-events": "none" }, ".dropdown-content": { opacity: "100%" } }, "&.dropdown-hover:hover": { ".dropdown-content": { opacity: "100%", scale: "100%" } }, "&:is(details)": { summary: { "&::-webkit-details-marker": { display: "none" } } }, "&.dropdown-open, &:focus, &:focus-within": { ".dropdown-content": { scale: "100%" } }, "&:where([popover])": { background: "#0000" }, "&[popover]": { position: "fixed", color: "inherit", "@supports not (position-area: bottom)": { margin: "auto", "&.dropdown-open:not(:popover-open)": { display: "none", "transform-origin": "top", opacity: "0%", scale: "95%" }, "&::backdrop": { "background-color": "color-mix(in oklab, #000 30%, #0000)" } }, "&:not(.dropdown-open, :popover-open)": { display: "none", "transform-origin": "top", opacity: "0%", scale: "95%" } } }, ".dropdown-start": { "--anchor-h": "span-right", ":where(.dropdown-content)": { "inset-inline-end": "auto" }, "&.dropdown-left": { "--anchor-h": "left", "--anchor-v": "span-bottom", ".dropdown-content": { top: "calc(0.25rem * 0)", bottom: "auto" } }, "&.dropdown-right": { "--anchor-h": "right", "--anchor-v": "span-bottom", ".dropdown-content": { top: "calc(0.25rem * 0)", bottom: "auto" } } }, ".dropdown-center": { "--anchor-h": "center", ":where(.dropdown-content)": { "inset-inline-end": "calc(1/2 * 100%)", translate: "50% 0", '[dir="rtl"] &': { translate: "-50% 0" } }, "&.dropdown-left": { "--anchor-h": "left", "--anchor-v": "center", ".dropdown-content": { top: "auto", bottom: "calc(1/2 * 100%)", translate: "0 50%" } }, "&.dropdown-right": { "--anchor-h": "right", "--anchor-v": "center", ".dropdown-content": { top: "auto", bottom: "calc(1/2 * 100%)", translate: "0 50%" } } }, ".dropdown-end": { "--anchor-h": "span-left", ":where(.dropdown-content)": { "inset-inline-end": "calc(0.25rem * 0)", translate: "0 0" }, "&.dropdown-left": { "--anchor-h": "left", "--anchor-v": "span-top", ".dropdown-content": { top: "auto", bottom: "calc(0.25rem * 0)" } }, "&.dropdown-right": { "--anchor-h": "right", "--anchor-v": "span-top", ".dropdown-content": { top: "auto", bottom: "calc(0.25rem * 0)" } } }, ".dropdown-left": { "--anchor-h": "left", "--anchor-v": "span-bottom", ".dropdown-content": { "inset-inline-end": "100%", top: "calc(0.25rem * 0)", bottom: "auto", "transform-origin": "right" } }, ".dropdown-right": { "--anchor-h": "right", "--anchor-v": "span-bottom", ".dropdown-content": { "inset-inline-start": "100%", top: "calc(0.25rem * 0)", bottom: "auto", "transform-origin": "left" } }, ".dropdown-bottom": { "--anchor-v": "bottom", ".dropdown-content": { top: "100%", bottom: "auto", "transform-origin": "top" } }, ".dropdown-top": { "--anchor-v": "top", ".dropdown-content": { top: "auto", bottom: "100%", "transform-origin": "bottom" } }, "@keyframes dropdown": { "0%": { opacity: 0 } } }; + +// packages/daisyui/components/dropdown/index.js +var dropdown_default = ({ addComponents, prefix = "" }) => { + const prefixeddropdown = addPrefix(object_default15, prefix); + addComponents({ ...prefixeddropdown }); +}; + +// packages/daisyui/components/card/object.js +var object_default16 = { ".card": { position: "relative", display: "flex", "flex-direction": "column", "border-radius": "var(--radius-box)", "outline-width": "2px", transition: "outline 0.2s ease-in-out", outline: "0 solid #0000", "outline-offset": "2px", "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible": { "outline-color": "currentColor" }, ":where(figure:first-child)": { overflow: "hidden", "border-start-start-radius": "inherit", "border-start-end-radius": "inherit", "border-end-start-radius": "unset", "border-end-end-radius": "unset" }, ":where(figure:last-child)": { overflow: "hidden", "border-start-start-radius": "unset", "border-start-end-radius": "unset", "border-end-start-radius": "inherit", "border-end-end-radius": "inherit" }, "&:where(.card-border)": { border: "var(--border) solid var(--color-base-200)" }, "&:where(.card-dash)": { border: "var(--border) dashed var(--color-base-200)" }, "&.image-full": { display: "grid", "> *": { "grid-column-start": "1", "grid-row-start": "1" }, "> .card-body": { position: "relative", color: "var(--color-neutral-content)" }, ":where(figure)": { overflow: "hidden", "border-radius": "inherit" }, "> figure img": { height: "100%", "object-fit": "cover", filter: "brightness(28%)" } }, figure: { display: "flex", "align-items": "center", "justify-content": "center" }, '&:has(> input:is(input[type="checkbox"], input[type="radio"]))': { cursor: "pointer", "user-select": "none" }, "&:has(> :checked)": { outline: "2px solid currentColor" } }, ".card-title": { display: "flex", "align-items": "center", gap: "calc(0.25rem * 2)", "font-size": "var(--cardtitle-fs, 1.125rem)", "font-weight": 600 }, ".card-body": { display: "flex", flex: "auto", "flex-direction": "column", gap: "calc(0.25rem * 2)", padding: "var(--card-p, 1.5rem)", "font-size": "var(--card-fs, 0.875rem)", ":where(p)": { "flex-grow": 1 } }, ".card-actions": { display: "flex", "flex-wrap": "wrap", "align-items": "flex-start", gap: "calc(0.25rem * 2)" }, ".card-xs": { ".card-body": { "--card-p": "0.5rem", "--card-fs": "0.6875rem" }, ".card-title": { "--cardtitle-fs": "0.875rem" } }, ".card-sm": { ".card-body": { "--card-p": "1rem", "--card-fs": "0.75rem" }, ".card-title": { "--cardtitle-fs": "1rem" } }, ".card-md": { ".card-body": { "--card-p": "1.5rem", "--card-fs": "0.875rem" }, ".card-title": { "--cardtitle-fs": "1.125rem" } }, ".card-lg": { ".card-body": { "--card-p": "2rem", "--card-fs": "1rem" }, ".card-title": { "--cardtitle-fs": "1.25rem" } }, ".card-xl": { ".card-body": { "--card-p": "2.5rem", "--card-fs": "1.125rem" }, ".card-title": { "--cardtitle-fs": "1.375rem" } }, ".card-side": { "align-items": "stretch", "flex-direction": "row", ":where(figure:first-child)": { overflow: "hidden", "border-start-start-radius": "inherit", "border-start-end-radius": "unset", "border-end-start-radius": "inherit", "border-end-end-radius": "unset" }, ":where(figure:last-child)": { overflow: "hidden", "border-start-start-radius": "unset", "border-start-end-radius": "inherit", "border-end-start-radius": "unset", "border-end-end-radius": "inherit" }, "figure > *": { "max-width": "unset" }, ":where(figure > *)": { width: "100%", height: "100%", "object-fit": "cover" } } }; + +// packages/daisyui/components/card/index.js +var card_default = ({ addComponents, prefix = "" }) => { + const prefixedcard = addPrefix(object_default16, prefix); + addComponents({ ...prefixedcard }); +}; + +// packages/daisyui/components/steps/object.js +var object_default17 = { ".steps": { display: "inline-grid", "grid-auto-flow": "column", overflow: "hidden", "overflow-x": "auto", "counter-reset": "step", "grid-auto-columns": "1fr", ".step": { display: "grid", "grid-template-columns": ["repeat(1, minmax(0, 1fr))", "auto"], "grid-template-rows": ["repeat(2, minmax(0, 1fr))", "40px 1fr"], "place-items": "center", "text-align": "center", "min-width": "4rem", "--step-bg": "var(--color-base-300)", "--step-fg": "var(--color-base-content)", "&:before": { top: "calc(0.25rem * 0)", "grid-column-start": "1", "grid-row-start": "1", height: "calc(0.25rem * 2)", width: "100%", border: "1px solid", color: "var(--step-bg)", "background-color": "var(--step-bg)", "--tw-content": '""', content: "var(--tw-content)", "margin-inline-start": "-100%" }, "> .step-icon, &:not(:has(.step-icon)):after": { content: "counter(step)", "counter-increment": "step", "z-index": 1, color: "var(--step-fg)", "background-color": "var(--step-bg)", border: "1px solid var(--step-bg)", position: "relative", "grid-column-start": "1", "grid-row-start": "1", display: "grid", height: "calc(0.25rem * 8)", width: "calc(0.25rem * 8)", "place-items": "center", "place-self": "center", "border-radius": "calc(infinity * 1px)" }, "&:first-child:before": { content: "none" }, "&[data-content]:after": { content: "attr(data-content)" } }, ".step-neutral": { "+ .step-neutral:before, &:after, > .step-icon": { "--step-bg": "var(--color-neutral)", "--step-fg": "var(--color-neutral-content)" } }, ".step-primary": { "+ .step-primary:before, &:after, > .step-icon": { "--step-bg": "var(--color-primary)", "--step-fg": "var(--color-primary-content)" } }, ".step-secondary": { "+ .step-secondary:before, &:after, > .step-icon": { "--step-bg": "var(--color-secondary)", "--step-fg": "var(--color-secondary-content)" } }, ".step-accent": { "+ .step-accent:before, &:after, > .step-icon": { "--step-bg": "var(--color-accent)", "--step-fg": "var(--color-accent-content)" } }, ".step-info": { "+ .step-info:before, &:after, > .step-icon": { "--step-bg": "var(--color-info)", "--step-fg": "var(--color-info-content)" } }, ".step-success": { "+ .step-success:before, &:after, > .step-icon": { "--step-bg": "var(--color-success)", "--step-fg": "var(--color-success-content)" } }, ".step-warning": { "+ .step-warning:before, &:after, > .step-icon": { "--step-bg": "var(--color-warning)", "--step-fg": "var(--color-warning-content)" } }, ".step-error": { "+ .step-error:before, &:after, > .step-icon": { "--step-bg": "var(--color-error)", "--step-fg": "var(--color-error-content)" } } }, ".steps-horizontal": { "grid-auto-columns": "1fr", display: "inline-grid", "grid-auto-flow": "column", overflow: "hidden", "overflow-x": "auto", ".step": { display: "grid", "grid-template-columns": ["repeat(1, minmax(0, 1fr))", "auto"], "grid-template-rows": ["repeat(2, minmax(0, 1fr))", "40px 1fr"], "place-items": "center", "text-align": "center", "min-width": "4rem", "&:before": { height: "calc(0.25rem * 2)", width: "100%", translate: "0", content: '""', "margin-inline-start": "-100%" }, '[dir="rtl"] &:before': { translate: "0" } } }, ".steps-vertical": { "grid-auto-rows": "1fr", "grid-auto-flow": "row", ".step": { display: "grid", "grid-template-columns": ["repeat(2, minmax(0, 1fr))", "40px 1fr"], "grid-template-rows": ["repeat(1, minmax(0, 1fr))", "auto"], gap: "0.5rem", "min-height": "4rem", "justify-items": "start", "&:before": { height: "100%", width: "calc(0.25rem * 2)", translate: "-50% -50%", "margin-inline-start": "50%" }, '[dir="rtl"] &:before': { translate: "50% -50%" } } } }; + +// packages/daisyui/components/steps/index.js +var steps_default = ({ addComponents, prefix = "" }) => { + const prefixedsteps = addPrefix(object_default17, prefix); + addComponents({ ...prefixedsteps }); +}; + +// packages/daisyui/components/alert/object.js +var object_default18 = { ".alert": { display: "grid", "align-items": "center", gap: "calc(0.25rem * 4)", "border-radius": "var(--radius-box)", "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 3)", color: "var(--color-base-content)", "background-color": "var(--alert-color, var(--color-base-200))", "justify-content": "start", "justify-items": "start", "grid-auto-flow": "column", "grid-template-columns": "auto", "text-align": "start", border: "var(--border) solid var(--color-base-200)", "font-size": "0.875rem", "line-height": "1.25rem", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)", "box-shadow": "0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * 0.08)) inset, 0 1px color-mix( in oklab, color-mix(in oklab, #000 20%, var(--alert-color, var(--color-base-200))) calc(var(--depth) * 20%), #0000 ), 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * 0.08))", "&:has(:nth-child(2))": { "grid-template-columns": "auto minmax(auto, 1fr)" }, "&.alert-outline": { "background-color": "transparent", color: "var(--alert-color)", "box-shadow": "none", "background-image": "none" }, "&.alert-dash": { "background-color": "transparent", color: "var(--alert-color)", "border-style": "dashed", "box-shadow": "none", "background-image": "none" }, "&.alert-soft": { color: "var(--alert-color, var(--color-base-content))", background: "color-mix( in oklab, var(--alert-color, var(--color-base-content)) 8%, var(--color-base-100) )", "border-color": "color-mix( in oklab, var(--alert-color, var(--color-base-content)) 10%, var(--color-base-100) )", "box-shadow": "none", "background-image": "none" } }, ".alert-info": { "border-color": "var(--color-info)", color: "var(--color-info-content)", "--alert-color": "var(--color-info)" }, ".alert-success": { "border-color": "var(--color-success)", color: "var(--color-success-content)", "--alert-color": "var(--color-success)" }, ".alert-warning": { "border-color": "var(--color-warning)", color: "var(--color-warning-content)", "--alert-color": "var(--color-warning)" }, ".alert-error": { "border-color": "var(--color-error)", color: "var(--color-error-content)", "--alert-color": "var(--color-error)" }, ".alert-vertical": { "justify-content": "center", "justify-items": "center", "grid-auto-flow": "row", "grid-template-columns": "auto", "text-align": "center", "&:has(:nth-child(2))": { "grid-template-columns": "auto" } }, ".alert-horizontal": { "justify-content": "start", "justify-items": "start", "grid-auto-flow": "column", "grid-template-columns": "auto", "text-align": "start", "&:has(:nth-child(2))": { "grid-template-columns": "auto minmax(auto, 1fr)" } } }; + +// packages/daisyui/components/alert/index.js +var alert_default = ({ addComponents, prefix = "" }) => { + const prefixedalert = addPrefix(object_default18, prefix); + addComponents({ ...prefixedalert }); +}; + +// packages/daisyui/components/kbd/object.js +var object_default19 = { ".kbd": { display: "inline-flex", "align-items": "center", "justify-content": "center", "border-radius": "var(--radius-field)", "background-color": "var(--color-base-200)", "vertical-align": "middle", "padding-left": "0.5em", "padding-right": "0.5em", border: "var(--border) solid color-mix(in srgb, var(--color-base-content) 20%, #0000)", "border-bottom": "calc(var(--border) + 1px) solid color-mix(in srgb, var(--color-base-content) 20%, #0000)", "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem", height: "var(--size)", "min-width": "var(--size)" }, ".kbd-xs": { "--size": "calc(var(--size-selector, 0.25rem) * 4)", "font-size": "0.625rem" }, ".kbd-sm": { "--size": "calc(var(--size-selector, 0.25rem) * 5)", "font-size": "0.75rem" }, ".kbd-md": { "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem" }, ".kbd-lg": { "--size": "calc(var(--size-selector, 0.25rem) * 7)", "font-size": "1rem" }, ".kbd-xl": { "--size": "calc(var(--size-selector, 0.25rem) * 8)", "font-size": "1.125rem" } }; + +// packages/daisyui/components/kbd/index.js +var kbd_default = ({ addComponents, prefix = "" }) => { + const prefixedkbd = addPrefix(object_default19, prefix); + addComponents({ ...prefixedkbd }); +}; + +// packages/daisyui/components/select/object.js +var object_default20 = { ".select": { border: "var(--border) solid #0000", position: "relative", display: "inline-flex", "flex-shrink": 1, appearance: "none", "align-items": "center", gap: "calc(0.25rem * 1.5)", "background-color": "var(--color-base-100)", "padding-inline-start": "calc(0.25rem * 4)", "padding-inline-end": "calc(0.25rem * 7)", "vertical-align": "middle", width: "clamp(3rem, 20rem, 100%)", height: "var(--size)", "font-size": "0.875rem", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "background-image": "linear-gradient(45deg, #0000 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, #0000 50%)", "background-position": "calc(100% - 20px) calc(1px + 50%), calc(100% - 16.1px) calc(1px + 50%)", "background-size": "4px 4px, 4px 4px", "background-repeat": "no-repeat", "text-overflow": "ellipsis", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset", "border-color": "var(--input-color)", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "--size": "calc(var(--size-field, 0.25rem) * 10)", '[dir="rtl"] &': { "background-position": "calc(0% + 12px) calc(1px + 50%), calc(0% + 16px) calc(1px + 50%)" }, select: { "margin-inline-start": "calc(0.25rem * -4)", "margin-inline-end": "calc(0.25rem * -7)", width: "calc(100% + 2.75rem)", appearance: "none", "padding-inline-start": "calc(0.25rem * 4)", "padding-inline-end": "calc(0.25rem * 7)", height: "calc(100% - 2px)", background: "inherit", "border-radius": "inherit", "border-style": "none", "&:focus, &:focus-within": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:not(:last-child)": { "margin-inline-end": "calc(0.25rem * -5.5)", "background-image": "none" } }, "&:focus, &:focus-within": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate", "z-index": 1 }, "&:has(> select[disabled]), &:is(:disabled, [disabled])": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" } }, "&:has(> select[disabled]) > select[disabled]": { cursor: "not-allowed" } }, ".select-ghost": { "background-color": "transparent", transition: "background-color 0.2s", "box-shadow": "none", "border-color": "#0000", "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } }, ".select-neutral": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } }, ".select-primary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } }, ".select-secondary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } }, ".select-accent": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } }, ".select-info": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } }, ".select-success": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } }, ".select-warning": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } }, ".select-error": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } }, ".select-xs": { "--size": "calc(var(--size-field, 0.25rem) * 6)", "font-size": "0.6875rem" }, ".select-sm": { "--size": "calc(var(--size-field, 0.25rem) * 8)", "font-size": "0.75rem" }, ".select-md": { "--size": "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem" }, ".select-lg": { "--size": "calc(var(--size-field, 0.25rem) * 12)", "font-size": "1.125rem" }, ".select-xl": { "--size": "calc(var(--size-field, 0.25rem) * 14)", "font-size": "1.375rem" } }; + +// packages/daisyui/components/select/index.js +var select_default = ({ addComponents, prefix = "" }) => { + const prefixedselect = addPrefix(object_default20, prefix); + addComponents({ ...prefixedselect }); +}; + +// packages/daisyui/components/progress/object.js +var object_default21 = { ".progress": { position: "relative", height: "calc(0.25rem * 2)", width: "100%", appearance: "none", overflow: "hidden", "border-radius": "var(--radius-box)", "background-color": "color-mix(in oklab, currentColor 20%, transparent)", color: "var(--color-base-content)", "&:indeterminate": { "background-image": "repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% )", "background-size": "200%", "background-position-x": "15%", animation: "progress 5s ease-in-out infinite", "@supports (-moz-appearance: none)": { "&::-moz-progress-bar": { "background-color": "transparent", "background-image": "repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% )", "background-size": "200%", "background-position-x": "15%", animation: "progress 5s ease-in-out infinite" } } }, "@supports (-moz-appearance: none)": { "&::-moz-progress-bar": { "border-radius": "var(--radius-box)", "background-color": "currentColor" } }, "@supports (-webkit-appearance: none)": { "&::-webkit-progress-bar": { "border-radius": "var(--radius-box)", "background-color": "transparent" }, "&::-webkit-progress-value": { "border-radius": "var(--radius-box)", "background-color": "currentColor" } } }, ".progress-primary": { color: "var(--color-primary)" }, ".progress-secondary": { color: "var(--color-secondary)" }, ".progress-accent": { color: "var(--color-accent)" }, ".progress-neutral": { color: "var(--color-neutral)" }, ".progress-info": { color: "var(--color-info)" }, ".progress-success": { color: "var(--color-success)" }, ".progress-warning": { color: "var(--color-warning)" }, ".progress-error": { color: "var(--color-error)" }, "@keyframes progress": { "50%": { "background-position-x": "-115%" } } }; + +// packages/daisyui/components/progress/index.js +var progress_default = ({ addComponents, prefix = "" }) => { + const prefixedprogress = addPrefix(object_default21, prefix); + addComponents({ ...prefixedprogress }); +}; + +// packages/daisyui/components/fileinput/object.js +var object_default22 = { ".file-input": { cursor: ["pointer", "pointer"], border: "var(--border) solid #0000", display: "inline-flex", appearance: "none", "align-items": "center", "background-color": "var(--color-base-100)", "vertical-align": "middle", "webkit-user-select": "none", "user-select": "none", width: "clamp(3rem, 20rem, 100%)", height: "var(--size)", "padding-inline-end": "0.75rem", "font-size": "0.875rem", "line-height": 2, "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "border-color": "var(--input-color)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "&::file-selector-button": { "margin-inline-end": "calc(0.25rem * 4)", cursor: "pointer", "padding-inline": "calc(0.25rem * 4)", "webkit-user-select": "none", "user-select": "none", height: "calc(100% + var(--border) * 2)", "margin-block": "calc(var(--border) * -1)", "margin-inline-start": "calc(var(--border) * -1)", "font-size": "0.875rem", color: "var(--btn-fg)", "border-width": "var(--border)", "border-style": "solid", "border-color": "var(--btn-border)", "border-start-start-radius": "calc(var(--join-ss, var(--radius-field) - var(--border)))", "border-end-start-radius": "calc(var(--join-es, var(--radius-field) - var(--border)))", "font-weight": 600, "background-color": "var(--btn-bg)", "background-size": "calc(var(--noise) * 100%)", "background-image": "var(--btn-noise)", "text-shadow": "0 0.5px oklch(1 0 0 / calc(var(--depth) * 0.15))", "box-shadow": "0 0.5px 0 0.5px color-mix( in oklab, color-mix(in oklab, white 30%, var(--btn-bg)) calc(var(--depth) * 20%), #0000 ) inset, var(--btn-shadow)", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--btn-bg": "var(--btn-color, var(--color-base-200))", "--btn-fg": "var(--color-base-content)", "--btn-border": "color-mix(in oklab, var(--btn-bg), #000 5%)", "--btn-shadow": `0 3px 2px -2px color-mix(in oklab, var(--btn-bg) 30%, #0000), + 0 4px 3px -2px color-mix(in oklab, var(--btn-bg) 30%, #0000)`, "--btn-noise": "var(--fx-noise)" }, "&:focus": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) 10%, #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate" }, "&:has(> input[disabled]), &:is(:disabled, [disabled])": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" }, "box-shadow": "none", color: "color-mix(in oklch, var(--color-base-content) 20%, #0000)", "&::file-selector-button": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", "--btn-border": "#0000", "--btn-noise": "none", "--btn-fg": "color-mix(in oklch, var(--color-base-content) 20%, #0000)" } } }, ".file-input-ghost": { "background-color": "transparent", transition: "background-color 0.2s", "box-shadow": "none", "border-color": "#0000", "&::file-selector-button": { "margin-inline-start": "calc(0.25rem * 0)", "margin-inline-end": "calc(0.25rem * 4)", height: "100%", cursor: "pointer", "padding-inline": "calc(0.25rem * 4)", "webkit-user-select": "none", "user-select": "none", "margin-block": "0", "border-start-end-radius": "calc(var(--join-ss, var(--radius-field) - var(--border)))", "border-end-end-radius": "calc(var(--join-es, var(--radius-field) - var(--border)))" }, "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } }, ".file-input-neutral": { "--btn-color": "var(--color-neutral)", "&::file-selector-button": { color: "var(--color-neutral-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } }, ".file-input-primary": { "--btn-color": "var(--color-primary)", "&::file-selector-button": { color: "var(--color-primary-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } }, ".file-input-secondary": { "--btn-color": "var(--color-secondary)", "&::file-selector-button": { color: "var(--color-secondary-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } }, ".file-input-accent": { "--btn-color": "var(--color-accent)", "&::file-selector-button": { color: "var(--color-accent-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } }, ".file-input-info": { "--btn-color": "var(--color-info)", "&::file-selector-button": { color: "var(--color-info-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } }, ".file-input-success": { "--btn-color": "var(--color-success)", "&::file-selector-button": { color: "var(--color-success-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } }, ".file-input-warning": { "--btn-color": "var(--color-warning)", "&::file-selector-button": { color: "var(--color-warning-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } }, ".file-input-error": { "--btn-color": "var(--color-error)", "&::file-selector-button": { color: "var(--color-error-content)" }, "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } }, ".file-input-xs": { "--size": "calc(var(--size-field, 0.25rem) * 6)", "font-size": "0.6875rem", "line-height": "1rem", "&::file-selector-button": { "font-size": "0.6875rem" } }, ".file-input-sm": { "--size": "calc(var(--size-field, 0.25rem) * 8)", "font-size": "0.75rem", "line-height": "1.5rem", "&::file-selector-button": { "font-size": "0.75rem" } }, ".file-input-md": { "--size": "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem", "line-height": 2, "&::file-selector-button": { "font-size": "0.875rem" } }, ".file-input-lg": { "--size": "calc(var(--size-field, 0.25rem) * 12)", "font-size": "1.125rem", "line-height": "2.5rem", "&::file-selector-button": { "font-size": "1.125rem" } }, ".file-input-xl": { "padding-inline-end": "calc(0.25rem * 6)", "--size": "calc(var(--size-field, 0.25rem) * 14)", "font-size": "1.125rem", "line-height": "3rem", "&::file-selector-button": { "font-size": "1.375rem" } } }; + +// packages/daisyui/components/fileinput/index.js +var fileinput_default = ({ addComponents, prefix = "" }) => { + const prefixedfileinput = addPrefix(object_default22, prefix); + addComponents({ ...prefixedfileinput }); +}; + +// packages/daisyui/components/modal/object.js +var object_default23 = { ".modal": { "pointer-events": "none", visibility: "hidden", position: "fixed", inset: "calc(0.25rem * 0)", margin: "calc(0.25rem * 0)", display: "grid", height: "100%", "max-height": "none", width: "100%", "max-width": "none", "align-items": "center", "justify-items": "center", "background-color": "transparent", padding: "calc(0.25rem * 0)", color: "inherit", "overflow-x": "hidden", transition: "translate 0.3s ease-out, visibility 0.3s allow-discrete, background-color 0.3s ease-out, opacity 0.1s ease-out", "overflow-y": "hidden", "overscroll-behavior": "contain", "z-index": 999, "&::backdrop": { display: "none" }, "&.modal-open, &[open], &:target": { "pointer-events": "auto", visibility: "visible", opacity: "100%", "background-color": "oklch(0% 0 0/ 0.4)", ".modal-box": { translate: "0 0", scale: "1", opacity: 1 } }, "@starting-style": { "&.modal-open, &[open], &:target": { visibility: "hidden", opacity: "0%" } } }, ".modal-action": { "margin-top": "calc(0.25rem * 6)", display: "flex", "justify-content": "flex-end", gap: "calc(0.25rem * 2)" }, ".modal-toggle": { position: "fixed", height: "calc(0.25rem * 0)", width: "calc(0.25rem * 0)", appearance: "none", opacity: "0%", "&:checked + .modal": { "pointer-events": "auto", visibility: "visible", opacity: "100%", "background-color": "oklch(0% 0 0/ 0.4)", ".modal-box": { translate: "0 0", scale: "1", opacity: 1 } }, "@starting-style": { "&:checked + .modal": { visibility: "hidden", opacity: "0%" } } }, ".modal-backdrop": { "grid-column-start": "1", "grid-row-start": "1", display: "grid", "align-self": "stretch", "justify-self": "stretch", color: "transparent", "z-index": -1, button: { cursor: "pointer" } }, ".modal-box": { "grid-column-start": "1", "grid-row-start": "1", "max-height": "100vh", width: "calc(11/12 * 100%)", "max-width": "32rem", "background-color": "var(--color-base-100)", padding: "calc(0.25rem * 6)", transition: "translate 0.3s ease-out, scale 0.3s ease-out, opacity 0.2s ease-out 0.05s, box-shadow 0.3s ease-out", "border-top-left-radius": "var(--modal-tl, var(--radius-box))", "border-top-right-radius": "var(--modal-tr, var(--radius-box))", "border-bottom-left-radius": "var(--modal-bl, var(--radius-box))", "border-bottom-right-radius": "var(--modal-br, var(--radius-box))", scale: "95%", opacity: 0, "box-shadow": "oklch(0% 0 0/ 0.25) 0px 25px 50px -12px", "overflow-y": "auto", "overscroll-behavior": "contain" }, ".modal-top": { "place-items": "start", ":where(.modal-box)": { height: "auto", width: "100%", "max-width": "none", "max-height": "calc(100vh - 5em)", translate: "0 -100%", scale: "1", "--modal-tl": "0", "--modal-tr": "0", "--modal-bl": "var(--radius-box)", "--modal-br": "var(--radius-box)" } }, ".modal-middle": { "place-items": "center", ":where(.modal-box)": { height: "auto", width: "calc(11/12 * 100%)", "max-width": "32rem", "max-height": "calc(100vh - 5em)", translate: "0 2%", scale: "98%", "--modal-tl": "var(--radius-box)", "--modal-tr": "var(--radius-box)", "--modal-bl": "var(--radius-box)", "--modal-br": "var(--radius-box)" } }, ".modal-bottom": { "place-items": "end", ":where(.modal-box)": { height: "auto", width: "100%", "max-width": "none", "max-height": "calc(100vh - 5em)", translate: "0 100%", scale: "1", "--modal-tl": "var(--radius-box)", "--modal-tr": "var(--radius-box)", "--modal-bl": "0", "--modal-br": "0" } }, ".modal-start": { "place-items": "start", ":where(.modal-box)": { height: "100vh", "max-height": "none", width: "auto", "max-width": "none", translate: "-100% 0", scale: "1", "--modal-tl": "0", "--modal-tr": "var(--radius-box)", "--modal-bl": "0", "--modal-br": "var(--radius-box)" } }, ".modal-end": { "place-items": "end", ":where(.modal-box)": { height: "100vh", "max-height": "none", width: "auto", "max-width": "none", translate: "100% 0", scale: "1", "--modal-tl": "var(--radius-box)", "--modal-tr": "0", "--modal-bl": "var(--radius-box)", "--modal-br": "0" } } }; + +// packages/daisyui/components/modal/index.js +var modal_default = ({ addComponents, prefix = "" }) => { + const prefixedmodal = addPrefix(object_default23, prefix); + addComponents({ ...prefixedmodal }); +}; + +// packages/daisyui/components/footer/object.js +var object_default24 = { ".footer": { display: "grid", width: "100%", "grid-auto-flow": "row", "place-items": "start", "column-gap": "calc(0.25rem * 4)", "row-gap": "calc(0.25rem * 10)", "font-size": "0.875rem", "line-height": "1.25rem", "& > *": { display: "grid", "place-items": "start", gap: "calc(0.25rem * 2)" }, "&.footer-center": { "grid-auto-flow": "column dense", "place-items": "center", "text-align": "center", "& > *": { "place-items": "center" } } }, ".footer-title": { "margin-bottom": "calc(0.25rem * 2)", "text-transform": "uppercase", opacity: "60%", "font-weight": 600 }, ".footer-horizontal": { "grid-auto-flow": "column", "&.footer-center": { "grid-auto-flow": "row dense" } }, ".footer-vertical": { "grid-auto-flow": "row", "&.footer-center": { "grid-auto-flow": "column dense" } } }; + +// packages/daisyui/components/footer/index.js +var footer_default = ({ addComponents, prefix = "" }) => { + const prefixedfooter = addPrefix(object_default24, prefix); + addComponents({ ...prefixedfooter }); +}; + +// packages/daisyui/components/table/object.js +var object_default25 = { ".table": { "font-size": "0.875rem", position: "relative", width: "100%", "border-radius": "var(--radius-box)", "text-align": "left", '&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)': { "text-align": "right" }, "tr.row-hover": { "&, &:nth-child(even)": { "&:hover": { "@media (hover: hover)": { "background-color": "var(--color-base-200)" } } } }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 3)", "vertical-align": "middle" }, ":where(thead, tfoot)": { "white-space": "nowrap", color: "color-mix(in oklab, var(--color-base-content) 60%, transparent)", "font-size": "0.875rem", "font-weight": 600 }, ":where(tfoot)": { "border-top": "var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000)" }, ":where(.table-pin-rows thead tr)": { position: "sticky", top: "calc(0.25rem * 0)", "z-index": 1, "background-color": "var(--color-base-100)" }, ":where(.table-pin-rows tfoot tr)": { position: "sticky", bottom: "calc(0.25rem * 0)", "z-index": 1, "background-color": "var(--color-base-100)" }, ":where(.table-pin-cols tr th)": { position: "sticky", right: "calc(0.25rem * 0)", left: "calc(0.25rem * 0)", "background-color": "var(--color-base-100)" }, ":where(thead tr, tbody tr:not(:last-child))": { "border-bottom": "var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000)" } }, ".table-zebra": { tbody: { tr: { "&:where(:nth-child(even))": { "background-color": "var(--color-base-200)", ":where(.table-pin-cols tr th)": { "background-color": "var(--color-base-200)" } }, "&.row-hover": { "&, &:where(:nth-child(even))": { "&:hover": { "@media (hover: hover)": { "background-color": "var(--color-base-300)" } } } } } } }, ".table-xs": { ":not(thead, tfoot) tr": { "font-size": "0.6875rem" }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 1)" } }, ".table-sm": { ":not(thead, tfoot) tr": { "font-size": "0.75rem" }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 2)" } }, ".table-md": { ":not(thead, tfoot) tr": { "font-size": "0.875rem" }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 3)" } }, ".table-lg": { ":not(thead, tfoot) tr": { "font-size": "1.125rem" }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 5)", "padding-block": "calc(0.25rem * 4)" } }, ".table-xl": { ":not(thead, tfoot) tr": { "font-size": "1.375rem" }, ":where(th, td)": { "padding-inline": "calc(0.25rem * 6)", "padding-block": "calc(0.25rem * 5)" } } }; + +// packages/daisyui/components/table/index.js +var table_default = ({ addComponents, prefix = "" }) => { + const prefixedtable = addPrefix(object_default25, prefix); + addComponents({ ...prefixedtable }); +}; + +// packages/daisyui/components/avatar/object.js +var object_default26 = { ".avatar-group": { display: "flex", overflow: "hidden", ":where(.avatar)": { overflow: "hidden", "border-radius": "calc(infinity * 1px)", border: "4px solid var(--color-base-100)" } }, ".avatar": { position: "relative", display: "inline-flex", "vertical-align": "middle", "& > div": { display: "block", "aspect-ratio": "1 / 1", overflow: "hidden" }, img: { height: "100%", width: "100%", "object-fit": "cover" } }, ".avatar-placeholder": { "& > div": { display: "flex", "align-items": "center", "justify-content": "center" } }, ".avatar-online": { "&:before": { content: '""', position: "absolute", "z-index": 1, display: "block", "border-radius": "calc(infinity * 1px)", "background-color": "var(--color-success)", outline: "2px solid var(--color-base-100)", width: "15%", height: "15%", top: "7%", right: "7%" } }, ".avatar-offline": { "&:before": { content: '""', position: "absolute", "z-index": 1, display: "block", "border-radius": "calc(infinity * 1px)", "background-color": "var(--color-base-300)", outline: "2px solid var(--color-base-100)", width: "15%", height: "15%", top: "7%", right: "7%" } } }; + +// packages/daisyui/components/avatar/index.js +var avatar_default = ({ addComponents, prefix = "" }) => { + const prefixedavatar = addPrefix(object_default26, prefix); + addComponents({ ...prefixedavatar }); +}; + +// packages/daisyui/components/input/object.js +var object_default27 = { ".input": { cursor: "text", border: "var(--border) solid #0000", position: "relative", display: "inline-flex", "flex-shrink": 1, appearance: "none", "align-items": "center", gap: "calc(0.25rem * 2)", "background-color": "var(--color-base-100)", "padding-inline": "calc(0.25rem * 3)", "vertical-align": "middle", "white-space": "nowrap", width: "clamp(3rem, 20rem, 100%)", height: "var(--size)", "font-size": "0.875rem", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "border-color": "var(--input-color)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "&:where(input)": { display: "inline-flex" }, ":where(input)": { display: "inline-flex", height: "100%", width: "100%", appearance: "none", "background-color": "transparent", border: "none", "&:focus, &:focus-within": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } } }, ':where(input[type="date"])': { display: "inline-block" }, "&:focus, &:focus-within": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate", "z-index": 1 }, "&:has(> input[disabled]), &:is(:disabled, [disabled])": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" }, "box-shadow": "none" }, "&:has(> input[disabled]) > input[disabled]": { cursor: "not-allowed" }, "&::-webkit-date-and-time-value": { "text-align": "inherit" }, '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } }, "&::-webkit-calendar-picker-indicator": { position: "absolute", "inset-inline-end": "0.75em" } }, ".input-ghost": { "background-color": "transparent", "box-shadow": "none", "border-color": "#0000", "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } }, ".input-neutral": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } }, ".input-primary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } }, ".input-secondary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } }, ".input-accent": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } }, ".input-info": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } }, ".input-success": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } }, ".input-warning": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } }, ".input-error": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } }, ".input-xs": { "--size": "calc(var(--size-field, 0.25rem) * 6)", "font-size": "0.6875rem", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -1)", "margin-inline-end": "calc(0.25rem * -3)" } } }, ".input-sm": { "--size": "calc(var(--size-field, 0.25rem) * 8)", "font-size": "0.75rem", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -2)", "margin-inline-end": "calc(0.25rem * -3)" } } }, ".input-md": { "--size": "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } } }, ".input-lg": { "--size": "calc(var(--size-field, 0.25rem) * 12)", "font-size": "1.125rem", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } } }, ".input-xl": { "--size": "calc(var(--size-field, 0.25rem) * 14)", "font-size": "1.375rem", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -4)", "margin-inline-end": "calc(0.25rem * -3)" } } } }; + +// packages/daisyui/components/input/index.js +var input_default = ({ addComponents, prefix = "" }) => { + const prefixedinput = addPrefix(object_default27, prefix); + addComponents({ ...prefixedinput }); +}; + +// packages/daisyui/components/checkbox/object.js +var object_default28 = { ".checkbox": { border: "var(--border) solid var(--input-color, color-mix(in oklab, var(--color-base-content) 20%, #0000))", position: "relative", "flex-shrink": 0, cursor: "pointer", appearance: "none", "border-radius": "var(--radius-selector)", padding: "calc(0.25rem * 1)", "vertical-align": "middle", color: "var(--color-base-content)", "box-shadow": "0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0 #0000 inset, 0 0 #0000", transition: "background-color 0.2s, box-shadow 0.2s", "--size": "calc(var(--size-selector, 0.25rem) * 6)", width: "var(--size)", height: "var(--size)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)", "&:before": { "--tw-content": '""', content: "var(--tw-content)", display: "block", width: "100%", height: "100%", rotate: "45deg", "background-color": "currentColor", opacity: "0%", transition: "clip-path 0.3s, opacity 0.1s, rotate 0.3s, translate 0.3s", "transition-delay": "0.1s", "clip-path": "polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 70% 80%, 70% 100%)", "box-shadow": "0px 3px 0 0px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset", "font-size": "1rem", "line-height": 0.75 }, "&:focus-visible": { outline: "2px solid var(--input-color, currentColor)", "outline-offset": "2px" }, '&:checked, &[aria-checked="true"]': { "background-color": "var(--input-color, #0000)", "box-shadow": "0 0 #0000 inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1))", "&:before": { "clip-path": "polygon(20% 100%, 20% 80%, 50% 80%, 50% 0%, 70% 0%, 70% 100%)", opacity: "100%" }, "@media (forced-colors: active)": { "&:before": { rotate: "0deg", "background-color": "transparent", "--tw-content": '"✔︎"', "clip-path": "none" } }, "@media print": { "&:before": { rotate: "0deg", "background-color": "transparent", "--tw-content": '"✔︎"', "clip-path": "none" } } }, "&:indeterminate": { "&:before": { rotate: "0deg", opacity: "100%", translate: "0 -35%", "clip-path": "polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 80% 80%, 80% 100%)" } } }, ".checkbox-primary": { color: "var(--color-primary-content)", "--input-color": "var(--color-primary)" }, ".checkbox-secondary": { color: "var(--color-secondary-content)", "--input-color": "var(--color-secondary)" }, ".checkbox-accent": { color: "var(--color-accent-content)", "--input-color": "var(--color-accent)" }, ".checkbox-neutral": { color: "var(--color-neutral-content)", "--input-color": "var(--color-neutral)" }, ".checkbox-info": { color: "var(--color-info-content)", "--input-color": "var(--color-info)" }, ".checkbox-success": { color: "var(--color-success-content)", "--input-color": "var(--color-success)" }, ".checkbox-warning": { color: "var(--color-warning-content)", "--input-color": "var(--color-warning)" }, ".checkbox-error": { color: "var(--color-error-content)", "--input-color": "var(--color-error)" }, ".checkbox:disabled": { cursor: "not-allowed", opacity: "20%" }, ".checkbox-xs": { padding: "0.125rem", "--size": "calc(var(--size-selector, 0.25rem) * 4)" }, ".checkbox-sm": { padding: "0.1875rem", "--size": "calc(var(--size-selector, 0.25rem) * 5)" }, ".checkbox-md": { padding: "0.25rem", "--size": "calc(var(--size-selector, 0.25rem) * 6)" }, ".checkbox-lg": { padding: "0.3125rem", "--size": "calc(var(--size-selector, 0.25rem) * 7)" }, ".checkbox-xl": { padding: "0.375rem", "--size": "calc(var(--size-selector, 0.25rem) * 8)" } }; + +// packages/daisyui/components/checkbox/index.js +var checkbox_default = ({ addComponents, prefix = "" }) => { + const prefixedcheckbox = addPrefix(object_default28, prefix); + addComponents({ ...prefixedcheckbox }); +}; + +// packages/daisyui/components/badge/object.js +var object_default29 = { ".badge": { display: "inline-flex", "align-items": "center", "justify-content": "center", gap: "calc(0.25rem * 2)", "border-radius": "var(--radius-selector)", "vertical-align": "middle", color: "var(--badge-fg)", border: "var(--border) solid var(--badge-color, var(--color-base-200))", "font-size": "0.875rem", width: "fit-content", "padding-inline": "calc(0.25rem * 3 - var(--border))", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)", "background-color": "var(--badge-bg)", "--badge-bg": "var(--badge-color, var(--color-base-100))", "--badge-fg": "var(--color-base-content)", "--size": "calc(var(--size-selector, 0.25rem) * 6)", height: "var(--size)", "&.badge-outline": { "--badge-fg": "var(--badge-color)", "--badge-bg": "#0000", "background-image": "none", "border-color": "currentColor" }, "&.badge-dash": { "--badge-fg": "var(--badge-color)", "--badge-bg": "#0000", "background-image": "none", "border-color": "currentColor", "border-style": "dashed" }, "&.badge-soft": { color: "var(--badge-color, var(--color-base-content))", "background-color": "color-mix( in oklab, var(--badge-color, var(--color-base-content)) 8%, var(--color-base-100) )", "border-color": "color-mix( in oklab, var(--badge-color, var(--color-base-content)) 10%, var(--color-base-100) )", "background-image": "none" } }, ".badge-primary": { "--badge-color": "var(--color-primary)", "--badge-fg": "var(--color-primary-content)" }, ".badge-secondary": { "--badge-color": "var(--color-secondary)", "--badge-fg": "var(--color-secondary-content)" }, ".badge-accent": { "--badge-color": "var(--color-accent)", "--badge-fg": "var(--color-accent-content)" }, ".badge-neutral": { "--badge-color": "var(--color-neutral)", "--badge-fg": "var(--color-neutral-content)" }, ".badge-info": { "--badge-color": "var(--color-info)", "--badge-fg": "var(--color-info-content)" }, ".badge-success": { "--badge-color": "var(--color-success)", "--badge-fg": "var(--color-success-content)" }, ".badge-warning": { "--badge-color": "var(--color-warning)", "--badge-fg": "var(--color-warning-content)" }, ".badge-error": { "--badge-color": "var(--color-error)", "--badge-fg": "var(--color-error-content)" }, ".badge-ghost": { "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "var(--color-base-content)", "background-image": "none" }, ".badge-xs": { "--size": "calc(var(--size-selector, 0.25rem) * 4)", "font-size": "0.625rem", "padding-inline": "calc(0.25rem * 2 - var(--border))" }, ".badge-sm": { "--size": "calc(var(--size-selector, 0.25rem) * 5)", "font-size": "0.75rem", "padding-inline": "calc(0.25rem * 2.5 - var(--border))" }, ".badge-md": { "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem", "padding-inline": "calc(0.25rem * 3 - var(--border))" }, ".badge-lg": { "--size": "calc(var(--size-selector, 0.25rem) * 7)", "font-size": "1rem", "padding-inline": "calc(0.25rem * 3.5 - var(--border))" }, ".badge-xl": { "--size": "calc(var(--size-selector, 0.25rem) * 8)", "font-size": "1.125rem", "padding-inline": "calc(0.25rem * 4 - var(--border))" } }; + +// packages/daisyui/components/badge/index.js +var badge_default = ({ addComponents, prefix = "" }) => { + const prefixedbadge = addPrefix(object_default29, prefix); + addComponents({ ...prefixedbadge }); +}; + +// packages/daisyui/components/status/object.js +var object_default30 = { ".status": { display: "inline-block", "aspect-ratio": "1 / 1", width: "calc(0.25rem * 2)", height: "calc(0.25rem * 2)", "border-radius": "var(--radius-selector)", "background-color": "color-mix(in oklab, var(--color-base-content) 20%, transparent)", "background-position": "center", "background-repeat": "no-repeat", "vertical-align": "middle", color: "color-mix(in srgb, #000 30%, transparent)", "@supports (color: color-mix(in lab, red, red))": { color: "color-mix(in oklab, var(--color-black) 30%, transparent)" }, "background-image": "radial-gradient( circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * 0.5)), #0000 )", "box-shadow": "0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), #0000)" }, ".status-primary": { "background-color": "var(--color-primary)", color: "var(--color-primary)" }, ".status-secondary": { "background-color": "var(--color-secondary)", color: "var(--color-secondary)" }, ".status-accent": { "background-color": "var(--color-accent)", color: "var(--color-accent)" }, ".status-neutral": { "background-color": "var(--color-neutral)", color: "var(--color-neutral)" }, ".status-info": { "background-color": "var(--color-info)", color: "var(--color-info)" }, ".status-success": { "background-color": "var(--color-success)", color: "var(--color-success)" }, ".status-warning": { "background-color": "var(--color-warning)", color: "var(--color-warning)" }, ".status-error": { "background-color": "var(--color-error)", color: "var(--color-error)" }, ".status-xs": { width: "calc(0.25rem * 0.5)", height: "calc(0.25rem * 0.5)" }, ".status-sm": { width: "calc(0.25rem * 1)", height: "calc(0.25rem * 1)" }, ".status-md": { width: "calc(0.25rem * 2)", height: "calc(0.25rem * 2)" }, ".status-lg": { width: "calc(0.25rem * 3)", height: "calc(0.25rem * 3)" }, ".status-xl": { width: "calc(0.25rem * 4)", height: "calc(0.25rem * 4)" } }; + +// packages/daisyui/components/status/index.js +var status_default = ({ addComponents, prefix = "" }) => { + const prefixedstatus = addPrefix(object_default30, prefix); + addComponents({ ...prefixedstatus }); +}; + +// packages/daisyui/components/diff/object.js +var object_default31 = { ".diff": { position: "relative", display: "grid", width: "100%", overflow: "hidden", "webkit-user-select": "none", "user-select": "none", direction: "ltr", "container-type": "inline-size", "grid-template-columns": "auto 1fr", "&:focus-visible, &:has(.diff-item-1:focus-visible)": { "outline-style": "var(--tw-outline-style)", "outline-width": "2px", "outline-offset": "1px", "outline-color": "var(--color-base-content)" }, "&:focus-visible": { "outline-style": "var(--tw-outline-style)", "outline-width": "2px", "outline-offset": "1px", "outline-color": "var(--color-base-content)", ".diff-resizer": { "min-width": "90cqi", "max-width": "90cqi" } }, "&:has(.diff-item-2:focus-visible)": { "outline-style": "var(--tw-outline-style)", "outline-width": "2px", "outline-offset": "1px", ".diff-resizer": { "min-width": "10cqi", "max-width": "10cqi" } }, "@supports (-webkit-overflow-scrolling: touch) and (overflow: -webkit-paged-x)": { "&:focus": { ".diff-resizer": { "min-width": "10cqi", "max-width": "10cqi" } }, "&:has(.diff-item-1:focus)": { ".diff-resizer": { "min-width": "90cqi", "max-width": "90cqi" } } } }, ".diff-resizer": { position: "relative", top: "calc(1/2 * 100%)", "z-index": 1, "grid-column-start": "1", "grid-row-start": "1", height: "calc(0.25rem * 2)", width: "50cqi", "max-width": "calc(100cqi - 1rem)", "min-width": "1rem", resize: "horizontal", overflow: "hidden", opacity: "0%", transform: "scaleY(3) translate(0.35rem, 0.08rem)", cursor: "ew-resize", "transform-origin": "100% 100%", "clip-path": "inset(calc(100% - 0.75rem) 0 0 calc(100% - 0.75rem))", transition: "min-width 0.3s ease-out, max-width 0.3s ease-out" }, ".diff-item-2": { position: "relative", "grid-column-start": "1", "grid-row-start": "1", "&:after": { "pointer-events": "none", position: "absolute", top: "calc(1/2 * 100%)", right: "1px", bottom: "calc(0.25rem * 0)", "z-index": 2, "border-radius": "calc(infinity * 1px)", "background-color": "color-mix(in oklab, var(--color-base-100) 50%, transparent)", width: "1.2rem", height: "1.8rem", border: "2px solid var(--color-base-100)", content: '""', outline: "1px solid color-mix(in oklab, var(--color-base-content) 5%, #0000)", "outline-offset": "-3px", "backdrop-filter": "blur(8px)", "box-shadow": "0 1px 2px 0 oklch(0% 0 0 / 0.1)", translate: "50% -50%" }, "> *": { "pointer-events": "none", position: "absolute", top: "calc(0.25rem * 0)", bottom: "calc(0.25rem * 0)", left: "calc(0.25rem * 0)", height: "100%", width: "100cqi", "max-width": "none", "object-fit": "cover", "object-position": "center" }, "@supports (-webkit-overflow-scrolling: touch) and (overflow: -webkit-paged-x)": { "&:after": { content: "none" } } }, ".diff-item-1": { position: "relative", "z-index": 1, "grid-column-start": "1", "grid-row-start": "1", overflow: "hidden", "border-right": "2px solid var(--color-base-100)", "> *": { "pointer-events": "none", position: "absolute", top: "calc(0.25rem * 0)", bottom: "calc(0.25rem * 0)", left: "calc(0.25rem * 0)", height: "100%", width: "100cqi", "max-width": "none", "object-fit": "cover", "object-position": "center" } } }; + +// packages/daisyui/components/diff/index.js +var diff_default = ({ addComponents, prefix = "" }) => { + const prefixeddiff = addPrefix(object_default31, prefix); + addComponents({ ...prefixeddiff }); +}; + +// packages/daisyui/components/hero/object.js +var object_default32 = { ".hero": { display: "grid", width: "100%", "place-items": "center", "background-size": "cover", "background-position": "center", "& > *": { "grid-column-start": "1", "grid-row-start": "1" } }, ".hero-overlay": { "grid-column-start": "1", "grid-row-start": "1", height: "100%", width: "100%", "background-color": "color-mix(in oklab, var(--color-neutral) 50%, transparent)" }, ".hero-content": { isolation: "isolate", display: "flex", "max-width": "80rem", "align-items": "center", "justify-content": "center", gap: "calc(0.25rem * 4)", padding: "calc(0.25rem * 4)" } }; + +// packages/daisyui/components/hero/index.js +var hero_default = ({ addComponents, prefix = "" }) => { + const prefixedhero = addPrefix(object_default32, prefix); + addComponents({ ...prefixedhero }); +}; + +// packages/daisyui/components/toggle/object.js +var object_default33 = { ".toggle": { border: "var(--border) solid currentColor", color: "var(--input-color)", position: "relative", display: "inline-grid", "flex-shrink": 0, cursor: "pointer", appearance: "none", "place-content": "center", "vertical-align": "middle", "webkit-user-select": "none", "user-select": "none", "grid-template-columns": "0fr 1fr 1fr", "--radius-selector-max": `calc( + var(--radius-selector) + var(--radius-selector) + var(--radius-selector) + )`, "border-radius": "calc( var(--radius-selector) + min(var(--toggle-p), var(--radius-selector-max)) + min(var(--border), var(--radius-selector-max)) )", padding: "var(--toggle-p)", "box-shadow": "0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000) inset", transition: "color 0.3s, grid-template-columns 0.2s", "--input-color": "color-mix(in oklab, var(--color-base-content) 50%, #0000)", "--toggle-p": "calc(var(--size) * 0.125)", "--size": "calc(var(--size-selector, 0.25rem) * 6)", width: "calc((var(--size) * 2) - (var(--border) + var(--toggle-p)) * 2)", height: "var(--size)", "> *": { "z-index": 1, "grid-column": "span 1 / span 1", "grid-column-start": "2", "grid-row-start": "1", height: "100%", cursor: "pointer", appearance: "none", "background-color": "transparent", padding: "calc(0.25rem * 0.5)", transition: "opacity 0.2s, rotate 0.4s", border: "none", "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:nth-child(2)": { color: "var(--color-base-100)", rotate: "0deg" }, "&:nth-child(3)": { color: "var(--color-base-100)", opacity: "0%", rotate: "-15deg" } }, "&:has(:checked)": { "> :nth-child(2)": { opacity: "0%", rotate: "15deg" }, "> :nth-child(3)": { opacity: "100%", rotate: "0deg" } }, "&:before": { position: "relative", "inset-inline-start": "calc(0.25rem * 0)", "grid-column-start": "2", "grid-row-start": "1", "aspect-ratio": "1 / 1", height: "100%", "border-radius": "var(--radius-selector)", "background-color": "currentColor", translate: "0", "--tw-content": '""', content: "var(--tw-content)", transition: "background-color 0.1s, translate 0.2s, inset-inline-start 0.2s", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)" }, "@media (forced-colors: active)": { "&:before": { "outline-style": "var(--tw-outline-style)", "outline-width": "1px", "outline-offset": "calc(1px * -1)" } }, "@media print": { "&:before": { outline: "0.25rem solid", "outline-offset": "-1rem" } }, "&:focus-visible, &:has(:focus-visible)": { outline: "2px solid currentColor", "outline-offset": "2px" }, '&:checked, &[aria-checked="true"], &:has(> input:checked)': { "grid-template-columns": "1fr 1fr 0fr", "background-color": "var(--color-base-100)", "--input-color": "var(--color-base-content)", "&:before": { "background-color": "currentColor" }, "@starting-style": { "&:before": { opacity: 0 } } }, "&:indeterminate": { "grid-template-columns": "0.5fr 1fr 0.5fr" }, "&:disabled": { cursor: "not-allowed", opacity: "30%", "&:before": { "background-color": "transparent", border: "var(--border) solid currentColor" } } }, ".toggle-primary": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-primary)" } }, ".toggle-secondary": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-secondary)" } }, ".toggle-accent": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-accent)" } }, ".toggle-neutral": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-neutral)" } }, ".toggle-success": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-success)" } }, ".toggle-warning": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-warning)" } }, ".toggle-info": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-info)" } }, ".toggle-error": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-error)" } }, ".toggle-xs": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 4)" } }, ".toggle-sm": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 5)" } }, ".toggle-md": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 6)" } }, ".toggle-lg": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 7)" } }, ".toggle-xl": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 8)" } } }; + +// packages/daisyui/components/toggle/index.js +var toggle_default = ({ addComponents, prefix = "" }) => { + const prefixedtoggle = addPrefix(object_default33, prefix); + addComponents({ ...prefixedtoggle }); +}; + +// packages/daisyui/components/stack/object.js +var object_default34 = { ".stack": { display: "inline-grid", "grid-template-columns": "3px 4px 1fr 4px 3px", "grid-template-rows": "3px 4px 1fr 4px 3px", "& > *": { height: "100%", width: "100%", "&:nth-child(n + 2)": { width: "100%", opacity: "70%" }, "&:nth-child(2)": { "z-index": 2, opacity: "90%" }, "&:nth-child(1)": { "z-index": 3, width: "100%" } }, "&, &.stack-bottom": { "> *": { "grid-column": "3 / 4", "grid-row": "3 / 6", "&:nth-child(2)": { "grid-column": "2 / 5", "grid-row": "2 / 5" }, "&:nth-child(1)": { "grid-column": "1 / 6", "grid-row": "1 / 4" } } }, "&.stack-top": { "> *": { "grid-column": "3 / 4", "grid-row": "1 / 4", "&:nth-child(2)": { "grid-column": "2 / 5", "grid-row": "2 / 5" }, "&:nth-child(1)": { "grid-column": "1 / 6", "grid-row": "3 / 6" } } }, "&.stack-start": { "> *": { "grid-column": "1 / 4", "grid-row": "3 / 4", "&:nth-child(2)": { "grid-column": "2 / 5", "grid-row": "2 / 5" }, "&:nth-child(1)": { "grid-column": "3 / 6", "grid-row": "1 / 6" } } }, "&.stack-end": { "> *": { "grid-column": "3 / 6", "grid-row": "3 / 4", "&:nth-child(2)": { "grid-column": "2 / 5", "grid-row": "2 / 5" }, "&:nth-child(1)": { "grid-column": "1 / 4", "grid-row": "1 / 6" } } } } }; + +// packages/daisyui/components/stack/index.js +var stack_default = ({ addComponents, prefix = "" }) => { + const prefixedstack = addPrefix(object_default34, prefix); + addComponents({ ...prefixedstack }); +}; + +// packages/daisyui/components/navbar/object.js +var object_default35 = { ".navbar": { display: "flex", width: "100%", "align-items": "center", padding: "0.5rem", "min-height": "4rem" }, ".navbar-start": { display: "inline-flex", "align-items": "center", width: "50%", "justify-content": "flex-start" }, ".navbar-center": { display: "inline-flex", "align-items": "center", "flex-shrink": 0 }, ".navbar-end": { display: "inline-flex", "align-items": "center", width: "50%", "justify-content": "flex-end" } }; + +// packages/daisyui/components/navbar/index.js +var navbar_default = ({ addComponents, prefix = "" }) => { + const prefixednavbar = addPrefix(object_default35, prefix); + addComponents({ ...prefixednavbar }); +}; + +// packages/daisyui/components/label/object.js +var object_default36 = { ".label": { display: "inline-flex", "align-items": "center", gap: "calc(0.25rem * 1.5)", "white-space": "nowrap", color: "color-mix(in oklab, currentColor 60%, transparent)", "&:has(input)": { cursor: "pointer" }, "&:is(.input > *, .select > *)": { display: "flex", height: "calc(100% - 0.5rem)", "align-items": "center", "padding-inline": "calc(0.25rem * 3)", "white-space": "nowrap", "font-size": "inherit", "&:first-child": { "margin-inline-start": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * 3)", "border-inline-end": "var(--border) solid color-mix(in oklab, currentColor 10%, #0000)" }, "&:last-child": { "margin-inline-start": "calc(0.25rem * 3)", "margin-inline-end": "calc(0.25rem * -3)", "border-inline-start": "var(--border) solid color-mix(in oklab, currentColor 10%, #0000)" } } }, ".floating-label": { position: "relative", display: "block", input: { display: "block", "&::placeholder": { transition: "top 0.1s ease-out, translate 0.1s ease-out, scale 0.1s ease-out, opacity 0.1s ease-out" } }, textarea: { "&::placeholder": { transition: "top 0.1s ease-out, translate 0.1s ease-out, scale 0.1s ease-out, opacity 0.1s ease-out" } }, "> span": { position: "absolute", "inset-inline-start": "calc(0.25rem * 3)", "z-index": 1, "background-color": "var(--color-base-100)", "padding-inline": "calc(0.25rem * 1)", opacity: "0%", "font-size": "0.875rem", top: "calc(var(--size-field, 0.25rem) * 10 / 2)", "line-height": 1, "border-radius": "2px", "pointer-events": "none", translate: "0 -50%", transition: "top 0.1s ease-out, translate 0.1s ease-out, scale 0.1s ease-out, opacity 0.1s ease-out" }, "&:focus-within, &:not(:has(input:placeholder-shown, textarea:placeholder-shown))": { "::placeholder": { opacity: "0%", top: "0", translate: "-12.5% calc(-50% - 0.125em)", scale: "0.75", "pointer-events": "auto" }, "> span": { opacity: "100%", top: "0", translate: "-12.5% calc(-50% - 0.125em)", scale: "0.75", "pointer-events": "auto", "z-index": 2 } }, "&:has(:disabled, [disabled])": { "> span": { opacity: "0%" } }, "&:has(.input-xs, .select-xs, .textarea-xs) span": { "font-size": "0.6875rem", top: "calc(var(--size-field, 0.25rem) * 6 / 2)" }, "&:has(.input-sm, .select-sm, .textarea-sm) span": { "font-size": "0.75rem", top: "calc(var(--size-field, 0.25rem) * 8 / 2)" }, "&:has(.input-md, .select-md, .textarea-md) span": { "font-size": "0.875rem", top: "calc(var(--size-field, 0.25rem) * 10 / 2)" }, "&:has(.input-lg, .select-lg, .textarea-lg) span": { "font-size": "1.125rem", top: "calc(var(--size-field, 0.25rem) * 12 / 2)" }, "&:has(.input-xl, .select-xl, .textarea-xl) span": { "font-size": "1.375rem", top: "calc(var(--size-field, 0.25rem) * 14 / 2)" } } }; + +// packages/daisyui/components/label/index.js +var label_default = ({ addComponents, prefix = "" }) => { + const prefixedlabel = addPrefix(object_default36, prefix); + addComponents({ ...prefixedlabel }); +}; + +// packages/daisyui/components/menu/object.js +var object_default37 = { ".menu": { display: "flex", width: "fit-content", "flex-direction": "column", "flex-wrap": "wrap", padding: "calc(0.25rem * 2)", "--menu-active-fg": "var(--color-neutral-content)", "--menu-active-bg": "var(--color-neutral)", "font-size": "0.875rem", ":where(li ul)": { position: "relative", "margin-inline-start": "calc(0.25rem * 4)", "padding-inline-start": "calc(0.25rem * 2)", "white-space": "nowrap", "&:before": { position: "absolute", "inset-inline-start": "calc(0.25rem * 0)", top: "calc(0.25rem * 3)", bottom: "calc(0.25rem * 3)", "background-color": "var(--color-base-content)", opacity: "10%", width: "var(--border)", content: '""' } }, ":where(li > .menu-dropdown:not(.menu-dropdown-show))": { display: "none" }, ":where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { display: "grid", "grid-auto-flow": "column", "align-content": "flex-start", "align-items": "center", gap: "calc(0.25rem * 2)", "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1.5)", "text-align": "start", "transition-property": "color, background-color, box-shadow", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0, 0, 0.2, 1)", "grid-auto-columns": "minmax(auto, max-content) auto max-content", "text-wrap": "balance", "user-select": "none" }, ":where(li > details > summary)": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" }, "&::-webkit-details-marker": { display: "none" } }, ":where(li > details > summary), :where(li > .menu-dropdown-toggle)": { "&:after": { "justify-self": "flex-end", display: "block", height: "0.375rem", width: "0.375rem", rotate: "-135deg", translate: "0 -1px", "transition-property": "rotate, translate", "transition-duration": "0.2s", content: '""', "transform-origin": "50% 50%", "box-shadow": "2px 2px inset", "pointer-events": "none" } }, ":where(li > details[open] > summary):after, :where(li > .menu-dropdown-toggle.menu-dropdown-show):after": { rotate: "45deg", translate: "0 1px" }, ":where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title), li:not(.menu-title, .disabled) > details > summary:not(.menu-title) ):not(.menu-active, :active, .btn)": { "&.menu-focus, &:focus-visible": { cursor: "pointer", "background-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", color: "var(--color-base-content)", "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } } }, ":where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title):not(.menu-active, :active, .btn):hover, li:not(.menu-title, .disabled) > details > summary:not(.menu-title):not(.menu-active, :active, .btn):hover )": { cursor: "pointer", "background-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" }, "box-shadow": "0 1px oklch(0% 0 0 / 0.01) inset, 0 -1px oklch(100% 0 0 / 0.01) inset" }, ":where(li:empty)": { "background-color": "var(--color-base-content)", opacity: "10%", margin: "0.5rem 1rem", height: "1px" }, ":where(li)": { position: "relative", display: "flex", "flex-shrink": 0, "flex-direction": "column", "flex-wrap": "wrap", "align-items": "stretch", ".badge": { "justify-self": "flex-end" }, "& > *:not(ul, .menu-title, details, .btn):active, & > *:not(ul, .menu-title, details, .btn).menu-active, & > details > summary:active": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" }, color: "var(--menu-active-fg)", "background-color": "var(--menu-active-bg)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)", "&:not(&:active)": { "box-shadow": "0 2px calc(var(--depth) * 3px) -2px var(--menu-active-bg)" } }, "&.menu-disabled": { "pointer-events": "none", color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" } }, ".dropdown:focus-within": { ".menu-dropdown-toggle:after": { rotate: "45deg", translate: "0 1px" } }, ".dropdown-content": { "margin-top": "calc(0.25rem * 2)", padding: "calc(0.25rem * 2)", "&:before": { display: "none" } } }, ".menu-title": { "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 2)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "font-size": "0.875rem", "font-weight": 600 }, ".menu-horizontal": { display: "inline-flex", "flex-direction": "row", "& > li:not(.menu-title) > details > ul": { position: "absolute", "margin-inline-start": "calc(0.25rem * 0)", "margin-top": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 2)", "padding-inline-end": "calc(0.25rem * 2)" }, "& > li > details > ul": { "&:before": { content: "none" } }, ":where(& > li:not(.menu-title) > details > ul)": { "border-radius": "var(--radius-box)", "background-color": "var(--color-base-100)", "box-shadow": "0 1px 3px 0 oklch(0% 0 0/0.1), 0 1px 2px -1px oklch(0% 0 0/0.1)" } }, ".menu-vertical": { display: "inline-flex", "flex-direction": "column", "& > li:not(.menu-title) > details > ul": { position: "relative", "margin-inline-start": "calc(0.25rem * 4)", "margin-top": "calc(0.25rem * 0)", "padding-block": "calc(0.25rem * 0)", "padding-inline-end": "calc(0.25rem * 0)" } }, ".menu-xs": { ":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 1)", "font-size": "0.6875rem" }, ".menu-title": { "padding-inline": "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 1)" } }, ".menu-sm": { ":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 2.5)", "padding-block": "calc(0.25rem * 1)", "font-size": "0.75rem" }, ".menu-title": { "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 2)" } }, ".menu-md": { ":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1.5)", "font-size": "0.875rem" }, ".menu-title": { "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 2)" } }, ".menu-lg": { ":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 1.5)", "font-size": "1.125rem" }, ".menu-title": { "padding-inline": "calc(0.25rem * 6)", "padding-block": "calc(0.25rem * 3)" } }, ".menu-xl": { ":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))": { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 5)", "padding-block": "calc(0.25rem * 1.5)", "font-size": "1.375rem" }, ".menu-title": { "padding-inline": "calc(0.25rem * 6)", "padding-block": "calc(0.25rem * 3)" } } }; + +// packages/daisyui/components/menu/index.js +var menu_default = ({ addComponents, prefix = "" }) => { + const prefixedmenu = addPrefix(object_default37, prefix); + addComponents({ ...prefixedmenu }); +}; + +// packages/daisyui/components/toast/object.js +var object_default38 = { ".toast": { position: "fixed", "inset-inline-start": "auto", "inset-inline-end": "calc(0.25rem * 4)", top: "auto", bottom: "calc(0.25rem * 4)", display: "flex", "flex-direction": "column", gap: "calc(0.25rem * 2)", "background-color": "transparent", translate: "var(--toast-x, 0) var(--toast-y, 0)", width: "max-content", "max-width": "calc(100vw - 2rem)", "& > *": { animation: "toast 0.25s ease-out" }, "&:where(.toast-start)": { "inset-inline-start": "calc(0.25rem * 4)", "inset-inline-end": "auto", "--toast-x": "0" }, "&:where(.toast-center)": { "inset-inline-start": "calc(1/2 * 100%)", "inset-inline-end": "calc(1/2 * 100%)", "--toast-x": "-50%" }, "&:where(.toast-end)": { "inset-inline-start": "auto", "inset-inline-end": "calc(0.25rem * 4)", "--toast-x": "0" }, "&:where(.toast-bottom)": { top: "auto", bottom: "calc(0.25rem * 4)", "--toast-y": "0" }, "&:where(.toast-middle)": { top: "calc(1/2 * 100%)", bottom: "auto", "--toast-y": "-50%" }, "&:where(.toast-top)": { top: "calc(0.25rem * 4)", bottom: "auto", "--toast-y": "0" } }, "@keyframes toast": { "0%": { scale: "0.9", opacity: 0 }, "100%": { scale: "1", opacity: 1 } } }; + +// packages/daisyui/components/toast/index.js +var toast_default = ({ addComponents, prefix = "" }) => { + const prefixedtoast = addPrefix(object_default38, prefix); + addComponents({ ...prefixedtoast }); +}; + +// packages/daisyui/components/button/object.js +var object_default39 = { ":where(.btn)": { width: "unset" }, ".btn": { display: "inline-flex", "flex-shrink": 0, cursor: "pointer", "flex-wrap": "nowrap", "align-items": "center", "justify-content": "center", gap: "calc(0.25rem * 1.5)", "text-align": "center", "vertical-align": "middle", "outline-offset": "2px", "webkit-user-select": "none", "user-select": "none", "padding-inline": "var(--btn-p)", color: "var(--btn-fg)", "--tw-prose-links": "var(--btn-fg)", height: "var(--size)", "font-size": "var(--fontsize, 0.875rem)", "font-weight": 600, "outline-color": "var(--btn-color, var(--color-base-content))", "transition-property": "color, background-color, border-color, box-shadow", "transition-timing-function": "cubic-bezier(0, 0, 0.2, 1)", "transition-duration": "0.2s", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "background-color": "var(--btn-bg)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--btn-noise)", "border-width": "var(--border)", "border-style": "solid", "border-color": "var(--btn-border)", "text-shadow": "0 0.5px oklch(100% 0 0 / calc(var(--depth) * 0.15))", "touch-action": "manipulation", "box-shadow": "0 0.5px 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 6%)) inset, var(--btn-shadow)", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--btn-bg": "var(--btn-color, var(--color-base-200))", "--btn-fg": "var(--color-base-content)", "--btn-p": "1rem", "--btn-border": "color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%))", "--btn-shadow": `0 3px 2px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000), + 0 4px 3px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000)`, "--btn-noise": "var(--fx-noise)", ".prose &": { "text-decoration-line": "none" }, "@media (hover: hover)": { "&:hover": { "--btn-bg": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)" } }, "&:focus-visible": { "outline-width": "2px", "outline-style": "solid", isolation: "isolate" }, "&:active:not(.btn-active)": { translate: "0 0.5px", "--btn-bg": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 5%)", "--btn-border": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)", "--btn-shadow": "0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0)" }, "&:is(:disabled, [disabled], .btn-disabled)": { "&:not(.btn-link, .btn-ghost)": { "background-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", "box-shadow": "none" }, "pointer-events": "none", "--btn-border": "#0000", "--btn-noise": "none", "--btn-fg": "color-mix(in oklch, var(--color-base-content) 20%, #0000)", "@media (hover: hover)": { "&:hover": { "pointer-events": "none", "background-color": "color-mix(in oklab, var(--color-neutral) 20%, transparent)", "--btn-border": "#0000", "--btn-fg": "color-mix(in oklch, var(--color-base-content) 20%, #0000)" } } }, '&:is(input[type="checkbox"], input[type="radio"])': { appearance: "none", "&::after": { content: "attr(aria-label)" } }, "&:where(input:checked:not(.filter .btn))": { "--btn-color": "var(--color-primary)", "--btn-fg": "var(--color-primary-content)", isolation: "isolate" } }, ".btn-active": { "--btn-bg": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)", "--btn-shadow": "0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0)", isolation: "isolate" }, ".btn-primary": { "--btn-color": "var(--color-primary)", "--btn-fg": "var(--color-primary-content)" }, ".btn-secondary": { "--btn-color": "var(--color-secondary)", "--btn-fg": "var(--color-secondary-content)" }, ".btn-accent": { "--btn-color": "var(--color-accent)", "--btn-fg": "var(--color-accent-content)" }, ".btn-neutral": { "--btn-color": "var(--color-neutral)", "--btn-fg": "var(--color-neutral-content)" }, ".btn-info": { "--btn-color": "var(--color-info)", "--btn-fg": "var(--color-info-content)" }, ".btn-success": { "--btn-color": "var(--color-success)", "--btn-fg": "var(--color-success-content)" }, ".btn-warning": { "--btn-color": "var(--color-warning)", "--btn-fg": "var(--color-warning-content)" }, ".btn-error": { "--btn-color": "var(--color-error)", "--btn-fg": "var(--color-error-content)" }, ".btn-ghost": { "&:not(.btn-active, :hover, :active:focus, :focus-visible)": { "--btn-shadow": '""', "--btn-bg": "#0000", "--btn-border": "#0000", "--btn-noise": "none", "&:not(:disabled, [disabled], .btn-disabled)": { "outline-color": "currentColor", "--btn-fg": "currentColor" } } }, ".btn-link": { "text-decoration-line": "underline", "outline-color": "currentColor", "--btn-border": "#0000", "--btn-bg": "#0000", "--btn-fg": "var(--color-primary)", "--btn-noise": "none", "--btn-shadow": '""', "&:is(.btn-active, :hover, :active:focus, :focus-visible)": { "text-decoration-line": "underline", "--btn-border": "#0000", "--btn-bg": "#0000" } }, ".btn-outline": { "&:not( .btn-active, :hover, :active:focus, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )": { "--btn-shadow": '""', "--btn-bg": "#0000", "--btn-fg": "var(--btn-color)", "--btn-border": "var(--btn-color)", "--btn-noise": "none" }, "@media (hover: none)": { "&:hover:not( .btn-active, :active, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )": { "--btn-shadow": '""', "--btn-bg": "#0000", "--btn-fg": "var(--btn-color)", "--btn-border": "var(--btn-color)", "--btn-noise": "none" } } }, ".btn-dash": { "&:not( .btn-active, :hover, :active:focus, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )": { "--btn-shadow": '""', "border-style": "dashed", "--btn-bg": "#0000", "--btn-fg": "var(--btn-color)", "--btn-border": "var(--btn-color)", "--btn-noise": "none" }, "@media (hover: none)": { "&:hover:not( .btn-active, :active, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )": { "--btn-shadow": '""', "border-style": "dashed", "--btn-bg": "#0000", "--btn-fg": "var(--btn-color)", "--btn-border": "var(--btn-color)", "--btn-noise": "none" } } }, ".btn-soft": { "&:not(.btn-active, :hover, :active:focus, :focus-visible, :disabled, [disabled], .btn-disabled)": { "--btn-shadow": '""', "--btn-fg": "var(--btn-color, var(--color-base-content))", "--btn-bg": `color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 8%, + var(--color-base-100) + )`, "--btn-border": `color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 10%, + var(--color-base-100) + )`, "--btn-noise": "none" }, "@media (hover: none)": { "&:hover:not(.btn-active, :active, :focus-visible, :disabled, [disabled], .btn-disabled)": { "--btn-shadow": '""', "--btn-fg": "var(--btn-color, var(--color-base-content))", "--btn-bg": `color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 8%, + var(--color-base-100) + )`, "--btn-border": `color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 10%, + var(--color-base-100) + )`, "--btn-noise": "none" } } }, ".btn-xs": { "--fontsize": "0.6875rem", "--btn-p": "0.5rem", "--size": "calc(var(--size-field, 0.25rem) * 6)" }, ".btn-sm": { "--fontsize": "0.75rem", "--btn-p": "0.75rem", "--size": "calc(var(--size-field, 0.25rem) * 8)" }, ".btn-md": { "--fontsize": "0.875rem", "--btn-p": "1rem", "--size": "calc(var(--size-field, 0.25rem) * 10)" }, ".btn-lg": { "--fontsize": "1.125rem", "--btn-p": "1.25rem", "--size": "calc(var(--size-field, 0.25rem) * 12)" }, ".btn-xl": { "--fontsize": "1.375rem", "--btn-p": "1.5rem", "--size": "calc(var(--size-field, 0.25rem) * 14)" }, ".btn-square": { "padding-inline": "calc(0.25rem * 0)", width: "var(--size)", height: "var(--size)" }, ".btn-circle": { "border-radius": "calc(infinity * 1px)", "padding-inline": "calc(0.25rem * 0)", width: "var(--size)", height: "var(--size)" }, ".btn-wide": { width: "100%", "max-width": "calc(0.25rem * 64)" }, ".btn-block": { width: "100%" } }; + +// packages/daisyui/components/button/index.js +var button_default = ({ addComponents, prefix = "" }) => { + const prefixedbutton = addPrefix(object_default39, prefix); + addComponents({ ...prefixedbutton }); +}; + +// packages/daisyui/components/list/object.js +var object_default40 = { ".list": { display: "flex", "flex-direction": "column", "font-size": "0.875rem", ":where(.list-row)": { "--list-grid-cols": "minmax(0, auto) 1fr", position: "relative", display: "grid", "grid-auto-flow": "column", gap: "calc(0.25rem * 4)", "border-radius": "var(--radius-box)", padding: "calc(0.25rem * 4)", "word-break": "break-word", "grid-template-columns": "var(--list-grid-cols)", "&:has(.list-col-grow:nth-child(1))": { "--list-grid-cols": "1fr" }, "&:has(.list-col-grow:nth-child(2))": { "--list-grid-cols": "minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(3))": { "--list-grid-cols": "minmax(0, auto) minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(4))": { "--list-grid-cols": "minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(5))": { "--list-grid-cols": "minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(6))": { "--list-grid-cols": `minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) + minmax(0, auto) 1fr` }, ":not(.list-col-wrap)": { "grid-row-start": "1" } }, "& > :not(:last-child)": { "&.list-row, .list-row": { "&:after": { content: '""', "border-bottom": "var(--border) solid", "inset-inline": "var(--radius-box)", position: "absolute", bottom: "calc(0.25rem * 0)", "border-color": "color-mix(in oklab, var(--color-base-content) 5%, transparent)" } } } }, ".list-col-wrap": { "grid-row-start": "2" } }; + +// packages/daisyui/components/list/index.js +var list_default = ({ addComponents, prefix = "" }) => { + const prefixedlist = addPrefix(object_default40, prefix); + addComponents({ ...prefixedlist }); +}; + +// packages/daisyui/components/mockup/object.js +var object_default41 = { ".mockup-code": { position: "relative", overflow: "hidden", "overflow-x": "auto", "border-radius": "var(--radius-box)", "background-color": "var(--color-neutral)", "padding-block": "calc(0.25rem * 5)", color: "var(--color-neutral-content)", "font-size": "0.875rem", direction: "ltr", "&:before": { content: '""', "margin-bottom": "calc(0.25rem * 4)", display: "block", height: "calc(0.25rem * 3)", width: "calc(0.25rem * 3)", "border-radius": "calc(infinity * 1px)", opacity: "30%", "box-shadow": "1.4em 0, 2.8em 0, 4.2em 0" }, pre: { "padding-right": "calc(0.25rem * 5)", "&:before": { content: '""', "margin-right": "2ch" }, "&[data-prefix]": { "&:before": { content: "attr(data-prefix)", display: "inline-block", width: "calc(0.25rem * 8)", "text-align": "right", opacity: "50%" } } } }, ".mockup-window": { position: "relative", display: "flex", "flex-direction": "column", overflow: "hidden", "overflow-x": "auto", "border-radius": "var(--radius-box)", "padding-top": "calc(0.25rem * 5)", "&:before": { content: '""', "margin-bottom": "calc(0.25rem * 4)", display: "block", "aspect-ratio": "1 / 1", height: "calc(0.25rem * 3)", "flex-shrink": 0, "align-self": "flex-start", "border-radius": "calc(infinity * 1px)", opacity: "30%", "box-shadow": "1.4em 0, 2.8em 0, 4.2em 0" }, '[dir="rtl"] &:before': { "align-self": "flex-end" }, "pre[data-prefix]": { "&:before": { content: "attr(data-prefix)", display: "inline-block", "text-align": "right" } } }, ".mockup-browser": { position: "relative", overflow: "hidden", "overflow-x": "auto", "border-radius": "var(--radius-box)", "pre[data-prefix]": { "&:before": { content: "attr(data-prefix)", display: "inline-block", "text-align": "right" } }, ".mockup-browser-toolbar": { "margin-block": "calc(0.25rem * 3)", display: "inline-flex", width: "100%", "align-items": "center", "padding-right": "1.4em", '&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *)': { "flex-direction": "row-reverse" }, "&:before": { content: '""', "margin-right": "4.8rem", display: "inline-block", "aspect-ratio": "1 / 1", height: "calc(0.25rem * 3)", "border-radius": "calc(infinity * 1px)", opacity: "30%", "box-shadow": "1.4em 0, 2.8em 0, 4.2em 0" }, ".input": { "margin-inline": "auto", display: "flex", height: "100%", "align-items": "center", gap: "calc(0.25rem * 2)", overflow: "hidden", "background-color": "var(--color-base-200)", "text-overflow": "ellipsis", "white-space": "nowrap", "font-size": "0.75rem", direction: "ltr", "&:before": { content: '""', width: "calc(0.25rem * 4)", height: "calc(0.25rem * 4)", opacity: "30%", "background-image": `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='currentColor' class='size-4'%3E%3Cpath fill-rule='evenodd' d='M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z' clip-rule='evenodd' /%3E%3C/svg%3E%0A")` } } } }, ".mockup-phone": { display: "inline-grid", "justify-items": "center", border: "6px solid #6b6b6b", "border-radius": "65px", "background-color": "#000", padding: "11px", overflow: "hidden" }, ".mockup-phone-camera": { "grid-column": "1/1", "grid-row": "1/1", background: "#000", height: "32px", width: "126px", "border-radius": "17px", "z-index": 1, "margin-top": "6px" }, ".mockup-phone-display": { "grid-column": "1/1", "grid-row": "1/1", overflow: "hidden", "border-radius": "49px", width: "390px", height: "845px" } }; + +// packages/daisyui/components/mockup/index.js +var mockup_default = ({ addComponents, prefix = "" }) => { + const prefixedmockup = addPrefix(object_default41, prefix); + addComponents({ ...prefixedmockup }); +}; + +// packages/daisyui/components/calendar/object.js +var object_default42 = { ".cally": { "font-size": "0.7rem", "&::part(container)": { padding: "0.5rem 1rem", "user-select": "none" }, "::part(th)": { "font-weight": "normal", "block-size": "auto" }, "&::part(header)": { direction: "ltr" }, "::part(head)": { opacity: 0.5, "font-size": "0.7rem" }, "&::part(button)": { "border-radius": "var(--radius-field)", border: "none", padding: "0.5rem", background: "#0000" }, "&::part(button):hover": { background: "var(--color-base-200)" }, "::part(day)": { "border-radius": "var(--radius-field)", "font-size": "0.7rem" }, "::part(button day today)": { background: "var(--color-primary)", color: "var(--color-primary-content)" }, "::part(selected)": { color: "var(--color-base-100)", background: "var(--color-base-content)", "border-radius": "var(--radius-field)" }, "::part(range-inner)": { "border-radius": "0" }, "::part(range-start)": { "border-start-end-radius": "0", "border-end-end-radius": "0" }, "::part(range-end)": { "border-start-start-radius": "0", "border-end-start-radius": "0" }, "::part(range-start range-end)": { "border-radius": "var(--radius-field)" }, "calendar-month": { width: "100%" } }, ".react-day-picker": { "user-select": "none", "background-color": "var(--color-base-100)", "border-radius": "var(--radius-box)", border: "var(--border) solid var(--color-base-200)", "font-size": "0.75rem", display: "inline-block", position: "relative", overflow: "clip", '&[dir="rtl"]': { ".rdp-nav": { ".rdp-chevron": { "transform-origin": "50%", transform: "rotate(180deg)" } } }, "*": { "box-sizing": "border-box" }, ".rdp-day": { width: "2.25rem", height: "2.25rem", "text-align": "center" }, ".rdp-day_button": { cursor: "pointer", font: "inherit", color: "inherit", width: "2.25rem", height: "2.25rem", border: "2px solid #0000", "border-radius": "var(--radius-field)", background: "0 0", "justify-content": "center", "align-items": "center", margin: "0", padding: "0", display: "flex", "&:disabled": { cursor: "revert" }, "&:hover": { "background-color": "var(--color-base-200)" } }, ".rdp-caption_label": { "z-index": 1, "white-space": "nowrap", border: "0", "align-items": "center", display: "inline-flex", position: "relative" }, ".rdp-button_next": { "border-radius": "var(--radius-field)", "&:hover": { "background-color": "var(--color-base-200)" } }, ".rdp-button_previous": { "border-radius": "var(--radius-field)", "&:hover": { "background-color": "var(--color-base-200)" } }, ".rdp-button_next, .rdp-button_previous": { cursor: "pointer", font: "inherit", color: "inherit", appearance: "none", width: "2.25rem", height: "2.25rem", background: "0 0", border: "none", "justify-content": "center", "align-items": "center", margin: "0", padding: "0", display: "inline-flex", position: "relative", "&:disabled": { cursor: "revert", opacity: 0.5 } }, ".rdp-chevron": { fill: "var(--color-base-content)", width: "1rem", height: "1rem", display: "inline-block" }, ".rdp-dropdowns": { "align-items": "center", gap: "0.5rem", display: "inline-flex", position: "relative" }, ".rdp-dropdown": { "z-index": 2, opacity: 0, appearance: "none", cursor: "inherit", "line-height": "inherit", border: "none", width: "100%", margin: "0", padding: "0", position: "absolute", "inset-block": "0", "inset-inline-start": "0", "&:focus-visible": { "~ .rdp-caption_label": { outline: ["5px auto highlight", "5px auto -webkit-focus-ring-color"] } } }, ".rdp-dropdown_root": { "align-items": "center", display: "inline-flex", position: "relative", '&[data-disabled="true"]': { ".rdp-chevron": { opacity: 0.5 } } }, ".rdp-month_caption": { height: "2.75rem", "font-size": "0.75rem", "font-weight": "inherit", "place-content": "center", display: "flex" }, ".rdp-months": { gap: "2rem", "flex-wrap": "wrap", "max-width": "fit-content", padding: "0.5rem", display: "flex", position: "relative" }, ".rdp-month_grid": { "border-collapse": "collapse" }, ".rdp-nav": { height: "2.75rem", "inset-block-start": "0", "inset-inline-end": "0", "justify-content": "space-between", "align-items": "center", width: "100%", "padding-inline": "0.5rem", display: "flex", position: "absolute", top: "0.25rem" }, ".rdp-weekday": { opacity: 0.6, padding: "0.5rem 0rem", "text-align": "center", "font-size": "smaller", "font-weight": 500 }, ".rdp-week_number": { opacity: 0.6, height: "2.25rem", width: "2.25rem", border: "none", "border-radius": "100%", "text-align": "center", "font-size": "small", "font-weight": 400 }, ".rdp-today:not(.rdp-outside)": { ".rdp-day_button": { background: "var(--color-primary)", color: "var(--color-primary-content)" } }, ".rdp-selected": { "font-weight": "inherit", "font-size": "0.75rem", ".rdp-day_button": { color: "var(--color-base-100)", "background-color": "var(--color-base-content)", "border-radius": "var(--radius-field)", border: "none", "&:hover": { "background-color": "var(--color-base-content)" } } }, ".rdp-outside": { opacity: 0.75 }, ".rdp-disabled": { opacity: 0.5 }, ".rdp-hidden": { visibility: "hidden", color: "var(--color-base-content)" }, ".rdp-range_start": { ".rdp-day_button": { "border-radius": "var(--radius-field) 0 0 var(--radius-field)" } }, ".rdp-range_start .rdp-day_button": { "background-color": "var(--color-base-content)", color: "var(--color-base-content)" }, ".rdp-range_middle": { "background-color": "var(--color-base-200)" }, ".rdp-range_middle .rdp-day_button": { border: "unset", "border-radius": "unset", color: "inherit" }, ".rdp-range_end": { color: "var(--color-base-content)", ".rdp-day_button": { "border-radius": "0 var(--radius-field) var(--radius-field) 0" } }, ".rdp-range_end .rdp-day_button": { color: "var(--color-base-content)", "background-color": "var(--color-base-content)" }, ".rdp-range_start.rdp-range_end": { background: "revert" }, ".rdp-focusable": { cursor: "pointer" }, ".rdp-footer": { "border-top": "var(--border) solid var(--color-base-200)", padding: "0.5rem" } }, ".pika-single": { "&:is(div)": { "user-select": "none", "font-size": "0.75rem", "z-index": 999, display: "inline-block", position: "relative", color: "var(--color-base-content)", "background-color": "var(--color-base-100)", "border-radius": "var(--radius-box)", border: "var(--border) solid var(--color-base-200)", padding: "0.5rem", "&:before, &:after": { content: '""', display: "table" }, "&:after": { clear: "both" }, "&.is-hidden": { display: "none" }, "&.is-bound": { position: "absolute" }, ".pika-lendar": { "css-float": "left" }, ".pika-title": { position: "relative", "text-align": "center", select: { cursor: "pointer", position: "absolute", "z-index": 999, margin: "0", left: "0", top: "5px", opacity: 0 } }, ".pika-label": { display: "inline-block", position: "relative", "z-index": 999, overflow: "hidden", margin: "0", padding: "5px 3px", "background-color": "var(--color-base-100)" }, ".pika-prev, .pika-next": { display: "block", cursor: "pointer", position: "absolute", top: "0", outline: "none", border: "0", width: "2.25rem", height: "2.25rem", color: "#0000", "font-size": "1.2em", "border-radius": "var(--radius-field)", "&:hover": { "background-color": "var(--color-base-200)" }, "&.is-disabled": { cursor: "default", opacity: 0.2 }, "&:before": { display: "inline-block", width: "2.25rem", height: "2.25rem", "line-height": 2.25, color: "var(--color-base-content)" } }, ".pika-prev": { left: "0", "&:before": { content: '"‹"' } }, ".pika-next": { right: "0", "&:before": { content: '"›"' } }, ".pika-select": { display: "inline-block" }, ".pika-table": { width: "100%", "border-collapse": "collapse", "border-spacing": "0", border: "0", "th, td": { padding: "0" }, th: { opacity: 0.6, "text-align": "center", width: "2.25rem", height: "2.25rem" } }, ".pika-button": { cursor: "pointer", display: "block", outline: "none", border: "0", margin: "0", width: "2.25rem", height: "2.25rem", padding: "5px", "text-align": ["right", "center"] }, ".pika-week": { color: "var(--color-base-content)" }, ".is-today": { ".pika-button": { background: "var(--color-primary)", color: "var(--color-primary-content)" } }, ".is-selected, .has-event": { ".pika-button": { "&, &:hover": { color: "var(--color-base-100)", "background-color": "var(--color-base-content)", "border-radius": "var(--radius-field)" } } }, ".has-event": { ".pika-button": { background: "var(--color-base-primary)" } }, ".is-disabled, .is-inrange": { ".pika-button": { background: "var(--color-base-primary)" } }, ".is-startrange": { ".pika-button": { color: "var(--color-base-100)", background: "var(--color-base-content)", "border-radius": "var(--radius-field)" } }, ".is-endrange": { ".pika-button": { color: "var(--color-base-100)", background: "var(--color-base-content)", "border-radius": "var(--radius-field)" } }, ".is-disabled": { ".pika-button": { "pointer-events": "none", cursor: "default", color: "var(--color-base-content)", opacity: 0.3 } }, ".is-outside-current-month": { ".pika-button": { color: "var(--color-base-content)", opacity: 0.3 } }, ".is-selection-disabled": { "pointer-events": "none", cursor: "default" }, ".pika-button:hover, .pika-row.pick-whole-week:hover .pika-button": { color: "var(--color-base-content)", "background-color": "var(--color-base-200)", "border-radius": "var(--radius-field)" }, ".pika-table abbr": { "text-decoration": "none", "font-weight": "normal" } } } }; + +// packages/daisyui/components/calendar/index.js +var calendar_default = ({ addComponents, prefix = "" }) => { + const prefixedcalendar = addPrefix(object_default42, prefix); + addComponents({ ...prefixedcalendar }); +}; + +// packages/daisyui/components/indicator/object.js +var object_default43 = { ".indicator": { position: "relative", display: "inline-flex", width: "max-content", ":where(.indicator-item)": { "z-index": 1, position: "absolute", "white-space": "nowrap", top: "var(--inidicator-t, 0)", bottom: "var(--inidicator-b, auto)", left: "var(--inidicator-s, auto)", right: "var(--inidicator-e, 0)", translate: "var(--inidicator-x, 50%) var(--indicator-y, -50%)" } }, ".indicator-start": { "--inidicator-s": "0", "--inidicator-e": "auto", "--inidicator-x": "-50%" }, ".indicator-center": { "--inidicator-s": "50%", "--inidicator-e": "50%", "--inidicator-x": "-50%", '[dir="rtl"] &': { "--inidicator-x": "50%" } }, ".indicator-end": { "--inidicator-s": "auto", "--inidicator-e": "0", "--inidicator-x": "50%" }, ".indicator-bottom": { "--inidicator-t": "auto", "--inidicator-b": "0", "--indicator-y": "50%" }, ".indicator-middle": { "--inidicator-t": "50%", "--inidicator-b": "50%", "--indicator-y": "-50%" }, ".indicator-top": { "--inidicator-t": "0", "--inidicator-b": "auto", "--indicator-y": "-50%" } }; + +// packages/daisyui/components/indicator/index.js +var indicator_default = ({ addComponents, prefix = "" }) => { + const prefixedindicator = addPrefix(object_default43, prefix); + addComponents({ ...prefixedindicator }); +}; + +// packages/daisyui/components/rating/object.js +var object_default44 = { ".rating": { position: "relative", display: "inline-flex", "vertical-align": "middle", "& input": { border: "none", appearance: "none" }, ":where(*)": { animation: "rating 0.25s ease-out", height: "calc(0.25rem * 6)", width: "calc(0.25rem * 6)", "border-radius": "0", "background-color": "var(--color-base-content)", opacity: "20%", "&:is(input)": { cursor: "pointer" } }, "& .rating-hidden": { width: "calc(0.25rem * 2)", "background-color": "transparent" }, 'input[type="radio"]:checked': { "background-image": "none" }, "*": { '&:checked, &[aria-checked="true"], &[aria-current="true"], &:has(~ *:checked, ~ *[aria-checked="true"], ~ *[aria-current="true"])': { opacity: "100%" }, "&:focus-visible": { transition: "scale 0.2s ease-out", scale: "1.1" } }, "& *:active:focus": { animation: "none", scale: "1.1" }, "&.rating-xs :where(*:not(.rating-hidden))": { width: "calc(0.25rem * 4)", height: "calc(0.25rem * 4)" }, "&.rating-sm :where(*:not(.rating-hidden))": { width: "calc(0.25rem * 5)", height: "calc(0.25rem * 5)" }, "&.rating-md :where(*:not(.rating-hidden))": { width: "calc(0.25rem * 6)", height: "calc(0.25rem * 6)" }, "&.rating-lg :where(*:not(.rating-hidden))": { width: "calc(0.25rem * 7)", height: "calc(0.25rem * 7)" }, "&.rating-xl :where(*:not(.rating-hidden))": { width: "calc(0.25rem * 8)", height: "calc(0.25rem * 8)" } }, ".rating-half": { ":where(*:not(.rating-hidden))": { width: "calc(0.25rem * 3)" }, "&.rating-xs *:not(.rating-hidden)": { width: "calc(0.25rem * 2)" }, "&.rating-sm *:not(.rating-hidden)": { width: "calc(0.25rem * 2.5)" }, "&.rating-md *:not(.rating-hidden)": { width: "calc(0.25rem * 3)" }, "&.rating-lg *:not(.rating-hidden)": { width: ".875rem" }, "&.rating-xl *:not(.rating-hidden)": { width: "calc(0.25rem * 4)" } }, "@keyframes rating": { "0%, 40%": { scale: "1.1", filter: "brightness(1.05) contrast(1.05)" } } }; + +// packages/daisyui/components/rating/index.js +var rating_default = ({ addComponents, prefix = "" }) => { + const prefixedrating = addPrefix(object_default44, prefix); + addComponents({ ...prefixedrating }); +}; + +// packages/daisyui/components/tab/object.js +var object_default45 = { ".tabs": { display: "flex", "flex-wrap": "wrap", "--tabs-height": "auto", "--tabs-direction": "row", height: "var(--tabs-height)", "flex-direction": "var(--tabs-direction)" }, ".tab": { position: "relative", display: "inline-flex", cursor: "pointer", appearance: "none", "flex-wrap": "wrap", "align-items": "center", "justify-content": "center", "text-align": "center", "webkit-user-select": "none", "user-select": "none", "&:hover": { "@media (hover: hover)": { color: "var(--color-base-content)" } }, "--tab-p": "1rem", "--tab-bg": "var(--color-base-100)", "--tab-border-color": "var(--color-base-300)", "--tab-radius-ss": "0", "--tab-radius-se": "0", "--tab-radius-es": "0", "--tab-radius-ee": "0", "--tab-order": "0", "--tab-radius-min": "calc(0.75rem - var(--border))", "border-color": "#0000", order: "var(--tab-order)", height: "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem", "padding-inline-start": "var(--tab-p)", "padding-inline-end": "var(--tab-p)", '&:is(input[type="radio"])': { "min-width": "fit-content", "&:after": { content: "attr(aria-label)" } }, "&:is(label)": { position: "relative", input: { position: "absolute", inset: "calc(0.25rem * 0)", cursor: "pointer", appearance: "none", opacity: "0%" } }, '&:checked, &:is(label:has(:checked)), &:is(.tab-active, [aria-selected="true"])': { "& + .tab-content": { display: "block", height: "100%" } }, '&:not(:checked, label:has(:checked), :hover, .tab-active, [aria-selected="true"])': { color: "color-mix(in oklab, var(--color-base-content) 50%, transparent)" }, "&:not(input):empty": { "flex-grow": 1, cursor: "default" }, "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible, &:is(label:has(:checked:focus-visible))": { outline: "2px solid currentColor", "outline-offset": "-5px" }, "&[disabled]": { "pointer-events": "none", opacity: "40%" } }, ".tab-disabled": { "pointer-events": "none", opacity: "40%" }, ".tabs-border": { ".tab": { "--tab-border-color": "#0000 #0000 var(--tab-border-color) #0000", position: "relative", "border-radius": "var(--radius-field)", "&:before": { "--tw-content": '""', content: "var(--tw-content)", "background-color": "var(--tab-border-color)", transition: "background-color 0.2s ease", width: "80%", height: "3px", "border-radius": "var(--radius-field)", bottom: "0", left: "10%", position: "absolute" }, '&:is(.tab-active, [aria-selected="true"]):not(.tab-disabled, [disabled]), &:is(input:checked), &:is(label:has(:checked))': { "&:before": { "--tab-border-color": "currentColor", "border-top": "3px solid" } } } }, ".tabs-lift": { "--tabs-height": "auto", "--tabs-direction": "row", "> .tab": { "--tab-border": "0 0 var(--border) 0", "--tab-radius-ss": "min(var(--radius-field), var(--tab-radius-min))", "--tab-radius-se": "min(var(--radius-field), var(--tab-radius-min))", "--tab-radius-es": "0", "--tab-radius-ee": "0", "--tab-paddings": "var(--border) var(--tab-p) 0 var(--tab-p)", "--tab-border-colors": "#0000 #0000 var(--tab-border-color) #0000", "--tab-corner-width": "calc(100% + min(var(--radius-field), var(--tab-radius-min)) * 2)", "--tab-corner-height": "min(var(--radius-field), var(--tab-radius-min))", "--tab-corner-position": "top left, top right", "border-width": "var(--tab-border)", "border-start-start-radius": "var(--tab-radius-ss)", "border-start-end-radius": "var(--tab-radius-se)", "border-end-start-radius": "var(--tab-radius-es)", "border-end-end-radius": "var(--tab-radius-ee)", padding: "var(--tab-paddings)", "border-color": "var(--tab-border-colors)", '&:is(.tab-active, [aria-selected="true"]):not(.tab-disabled, [disabled]), &:is(input:checked, label:has(:checked))': { "--tab-border": "var(--border) var(--border) 0 var(--border)", "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 + var(--tab-border-color)`, "--tab-paddings": `0 calc(var(--tab-p) - var(--border)) var(--border) + calc(var(--tab-p) - var(--border))`, "--tab-inset": "auto auto 0 auto", "--tab-grad": "calc(69% - var(--border))", "--radius-start": `radial-gradient( + circle at top left, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )`, "--radius-end": `radial-gradient( + circle at top right, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )`, "background-color": "var(--tab-bg)", "&:before": { "z-index": 1, content: '""', display: "block", position: "absolute", width: "var(--tab-corner-width)", height: "var(--tab-corner-height)", "background-position": "var(--tab-corner-position)", "background-image": "var(--radius-start), var(--radius-end)", "background-size": "min(var(--radius-field), var(--tab-radius-min)) min(var(--radius-field), var(--tab-radius-min))", "background-repeat": "no-repeat", inset: "var(--tab-inset)" }, "&:first-child:before": { "--radius-start": "none" }, '[dir="rtl"] &:first-child:before': { transform: "rotateY(180deg)" }, "&:last-child:before": { "--radius-end": "none" }, '[dir="rtl"] &:last-child:before': { transform: "rotateY(180deg)" } } }, "&:has(.tab-content)": { "> .tab:first-child": { '&:not(.tab-active, [aria-selected="true"])': { "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 + var(--tab-border-color)` } } }, ".tab-content": { "--tabcontent-margin": "calc(-1 * var(--border)) 0 0 0", "--tabcontent-radius-ss": "0", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "var(--radius-box)", "--tabcontent-radius-ee": "var(--radius-box)" }, ':checked, label:has(:checked), :is(.tab-active, [aria-selected="true"])': { "& + .tab-content": { "&:nth-child(1), &:nth-child(n + 3)": { "--tabcontent-radius-ss": "var(--radius-box)" } } } }, ".tabs-top": { "--tabs-height": "auto", "--tabs-direction": "row", ".tab": { "--tab-order": "0", "--tab-border": "0 0 var(--border) 0", "--tab-radius-ss": "min(var(--radius-field), var(--tab-radius-min))", "--tab-radius-se": "min(var(--radius-field), var(--tab-radius-min))", "--tab-radius-es": "0", "--tab-radius-ee": "0", "--tab-paddings": "var(--border) var(--tab-p) 0 var(--tab-p)", "--tab-border-colors": "#0000 #0000 var(--tab-border-color) #0000", "--tab-corner-width": "calc(100% + min(var(--radius-field), var(--tab-radius-min)) * 2)", "--tab-corner-height": "min(var(--radius-field), var(--tab-radius-min))", "--tab-corner-position": "top left, top right", '&:is(.tab-active, [aria-selected="true"]):not(.tab-disabled, [disabled]), &:is(input:checked), &:is(label:has(:checked))': { "--tab-border": "var(--border) var(--border) 0 var(--border)", "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 + var(--tab-border-color)`, "--tab-paddings": `0 calc(var(--tab-p) - var(--border)) var(--border) + calc(var(--tab-p) - var(--border))`, "--tab-inset": "auto auto 0 auto", "--radius-start": `radial-gradient( + circle at top left, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )`, "--radius-end": `radial-gradient( + circle at top right, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )` } }, "&:has(.tab-content)": { "> .tab:first-child": { '&:not(.tab-active, [aria-selected="true"])': { "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 + var(--tab-border-color)` } } }, ".tab-content": { "--tabcontent-order": "1", "--tabcontent-margin": "calc(-1 * var(--border)) 0 0 0", "--tabcontent-radius-ss": "0", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "var(--radius-box)", "--tabcontent-radius-ee": "var(--radius-box)" }, ':checked, label:has(:checked), :is(.tab-active, [aria-selected="true"])': { "& + .tab-content": { "&:nth-child(1), &:nth-child(n + 3)": { "--tabcontent-radius-ss": "var(--radius-box)" } } } }, ".tabs-bottom": { "--tabs-height": "auto", "--tabs-direction": "row", ".tab": { "--tab-order": "1", "--tab-border": "var(--border) 0 0 0", "--tab-radius-ss": "0", "--tab-radius-se": "0", "--tab-radius-es": "min(var(--radius-field), var(--tab-radius-min))", "--tab-radius-ee": "min(var(--radius-field), var(--tab-radius-min))", "--tab-border-colors": "var(--tab-border-color) #0000 #0000 #0000", "--tab-paddings": "0 var(--tab-p) var(--border) var(--tab-p)", "--tab-corner-width": "calc(100% + min(var(--radius-field), var(--tab-radius-min)) * 2)", "--tab-corner-height": "min(var(--radius-field), var(--tab-radius-min))", "--tab-corner-position": "top left, top right", '&:is(.tab-active, [aria-selected="true"]):not(.tab-disabled, [disabled]), &:is(input:checked), &:is(label:has(:checked))': { "--tab-border": "0 var(--border) var(--border) var(--border)", "--tab-border-colors": `#0000 var(--tab-border-color) var(--tab-border-color) + var(--tab-border-color)`, "--tab-paddings": `var(--border) calc(var(--tab-p) - var(--border)) 0 + calc(var(--tab-p) - var(--border))`, "--tab-inset": "0 auto auto auto", "--radius-start": `radial-gradient( + circle at bottom left, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )`, "--radius-end": `radial-gradient( + circle at bottom right, + #0000 var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--border)), + var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px) + )` } }, "&:has(.tab-content)": { "> .tab:first-child": { '&:not(.tab-active, [aria-selected="true"])': { "--tab-border-colors": `#0000 var(--tab-border-color) var(--tab-border-color) + var(--tab-border-color)` } } }, ".tab-content": { "--tabcontent-order": "0", "--tabcontent-margin": "0 0 calc(-1 * var(--border)) 0", "--tabcontent-radius-ss": "var(--radius-box)", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "0", "--tabcontent-radius-ee": "var(--radius-box)" }, '> :checked, > :is(label:has(:checked)), > :is(.tab-active, [aria-selected="true"])': { "& + .tab-content:not(:nth-child(2))": { "--tabcontent-radius-es": "var(--radius-box)" } } }, ".tabs-box": { "background-color": "var(--color-base-200)", padding: "calc(0.25rem * 1)", "--tabs-box-radius": "calc(var(--radius-field) + var(--radius-field) + var(--radius-field))", "border-radius": "calc(var(--radius-field) + min(0.25rem, var(--tabs-box-radius)))", "box-shadow": "0 -0.5px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0.5px oklch(0% 0 0 / calc(var(--depth) * 0.05)) inset", ".tab": { "border-radius": "var(--radius-field)", "border-style": "none", "&:focus-visible, &:is(label:has(:checked:focus-visible))": { "outline-offset": "2px" } }, '> :is(.tab-active, [aria-selected="true"]):not(.tab-disabled, [disabled]), > :is(input:checked), > :is(label:has(:checked))': { "background-color": "var(--tab-bg, var(--color-base-100))", "box-shadow": "0 1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px 1px -1px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 50%), #0000), 0 1px 6px -4px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 100%), #0000)", "@media (forced-colors: active)": { border: "1px solid" } } }, ".tab-content": { order: [1, "var(--tabcontent-order)"], display: "none", "border-color": "transparent", "--tabcontent-radius-ss": "0", "--tabcontent-radius-se": "0", "--tabcontent-radius-es": "0", "--tabcontent-radius-ee": "0", "--tabcontent-order": "1", width: "100%", margin: "var(--tabcontent-margin)", "border-width": "var(--border)", "border-start-start-radius": "var(--tabcontent-radius-ss)", "border-start-end-radius": "var(--tabcontent-radius-se)", "border-end-start-radius": "var(--tabcontent-radius-es)", "border-end-end-radius": "var(--tabcontent-radius-ee)" }, ".tabs-xs": { ":where(.tab)": { height: "calc(var(--size-field, 0.25rem) * 6)", "font-size": "0.75rem", "--tab-p": "0.375rem", "--tab-radius-min": "calc(0.5rem - var(--border))" } }, ".tabs-sm": { ":where(.tab)": { height: "calc(var(--size-field, 0.25rem) * 8)", "font-size": "0.875rem", "--tab-p": "0.5rem", "--tab-radius-min": "calc(0.5rem - var(--border))" } }, ".tabs-md": { ":where(.tab)": { height: "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem", "--tab-p": "0.75rem", "--tab-radius-min": "calc(0.75rem - var(--border))" } }, ".tabs-lg": { ":where(.tab)": { height: "calc(var(--size-field, 0.25rem) * 12)", "font-size": "1.125rem", "--tab-p": "1rem", "--tab-radius-min": "calc(1.5rem - var(--border))" } }, ".tabs-xl": { ":where(.tab)": { height: "calc(var(--size-field, 0.25rem) * 14)", "font-size": "1.125rem", "--tab-p": "1.25rem", "--tab-radius-min": "calc(2rem - var(--border))" } } }; + +// packages/daisyui/components/tab/index.js +var tab_default = ({ addComponents, prefix = "" }) => { + const prefixedtab = addPrefix(object_default45, prefix); + addComponents({ ...prefixedtab }); +}; + +// packages/daisyui/components/filter/object.js +var object_default46 = { ".filter": { display: "flex", "flex-wrap": "wrap", 'input[type="radio"]': { width: "auto" }, input: { overflow: "hidden", opacity: "100%", scale: "1", transition: "margin 0.1s, opacity 0.3s, padding 0.3s, border-width 0.1s", "&:not(:last-child)": { "margin-inline-end": "calc(0.25rem * 1)" }, "&.filter-reset": { "aspect-ratio": "1 / 1", "&::after": { content: '"×"' } } }, "&:not(:has(input:checked:not(.filter-reset)))": { '.filter-reset, input[type="reset"]': { scale: "0", "border-width": "0", "margin-inline": "calc(0.25rem * 0)", width: "calc(0.25rem * 0)", "padding-inline": "calc(0.25rem * 0)", opacity: "0%" } }, "&:has(input:checked:not(.filter-reset))": { 'input:not(:checked, .filter-reset, input[type="reset"])': { scale: "0", "border-width": "0", "margin-inline": "calc(0.25rem * 0)", width: "calc(0.25rem * 0)", "padding-inline": "calc(0.25rem * 0)", opacity: "0%" } } } }; + +// packages/daisyui/components/filter/index.js +var filter_default = ({ addComponents, prefix = "" }) => { + const prefixedfilter = addPrefix(object_default46, prefix); + addComponents({ ...prefixedfilter }); +}; + +// packages/daisyui/components/chat/object.js +var object_default47 = { ".chat": { display: "grid", "column-gap": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1)" }, ".chat-bubble": { position: "relative", display: "block", width: "fit-content", "border-radius": "var(--radius-field)", "background-color": "var(--color-base-300)", "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 2)", color: "var(--color-base-content)", "grid-row-end": "3", "min-height": "2rem", "min-width": "2.5rem", "max-width": "90%", "&:before": { position: "absolute", bottom: "calc(0.25rem * 0)", height: "calc(0.25rem * 3)", width: "calc(0.25rem * 3)", "background-color": "inherit", content: '""', "mask-repeat": "no-repeat", "mask-image": "var(--mask-chat)", "mask-position": "0px -1px", "mask-size": "13px" } }, ".chat-bubble-primary": { "background-color": "var(--color-primary)", color: "var(--color-primary-content)" }, ".chat-bubble-secondary": { "background-color": "var(--color-secondary)", color: "var(--color-secondary-content)" }, ".chat-bubble-accent": { "background-color": "var(--color-accent)", color: "var(--color-accent-content)" }, ".chat-bubble-neutral": { "background-color": "var(--color-neutral)", color: "var(--color-neutral-content)" }, ".chat-bubble-info": { "background-color": "var(--color-info)", color: "var(--color-info-content)" }, ".chat-bubble-success": { "background-color": "var(--color-success)", color: "var(--color-success-content)" }, ".chat-bubble-warning": { "background-color": "var(--color-warning)", color: "var(--color-warning-content)" }, ".chat-bubble-error": { "background-color": "var(--color-error)", color: "var(--color-error-content)" }, ".chat-image": { "grid-row": "span 2 / span 2", "align-self": "flex-end" }, ".chat-header": { "grid-row-start": "1", display: "flex", gap: "calc(0.25rem * 1)", "font-size": "0.6875rem" }, ".chat-footer": { "grid-row-start": "3", display: "flex", gap: "calc(0.25rem * 1)", "font-size": "0.6875rem" }, ".chat-start": { "place-items": "start", "grid-template-columns": "auto 1fr", ".chat-header": { "grid-column-start": "2" }, ".chat-footer": { "grid-column-start": "2" }, ".chat-image": { "grid-column-start": "1" }, ".chat-bubble": { "grid-column-start": "2", "border-end-start-radius": "0", "&:before": { transform: "rotateY(0deg)", "inset-inline-start": "-0.75rem" }, '[dir="rtl"] &:before': { transform: "rotateY(180deg)" } } }, ".chat-end": { "place-items": "end", "grid-template-columns": "1fr auto", ".chat-header": { "grid-column-start": "1" }, ".chat-footer": { "grid-column-start": "1" }, ".chat-image": { "grid-column-start": "2" }, ".chat-bubble": { "grid-column-start": "1", "border-end-end-radius": "0", "&:before": { transform: "rotateY(180deg)", "inset-inline-start": "100%" }, '[dir="rtl"] &:before': { transform: "rotateY(0deg)" } } } }; + +// packages/daisyui/components/chat/index.js +var chat_default = ({ addComponents, prefix = "" }) => { + const prefixedchat = addPrefix(object_default47, prefix); + addComponents({ ...prefixedchat }); +}; + +// packages/daisyui/components/radialprogress/object.js +var object_default48 = { ".radial-progress": { position: "relative", display: "inline-grid", height: "var(--size)", width: "var(--size)", "place-content": "center", "border-radius": "calc(infinity * 1px)", "background-color": "transparent", "vertical-align": "middle", "box-sizing": "content-box", "--value": "0", "--size": "5rem", "--thickness": "calc(var(--size) / 10)", "--radialprogress": "calc(var(--value) * 1%)", transition: "--radialprogress 0.3s linear", "&:before": { position: "absolute", inset: "calc(0.25rem * 0)", "border-radius": "calc(infinity * 1px)", content: '""', background: "radial-gradient(farthest-side, currentColor 98%, #0000) top/var(--thickness) var(--thickness) no-repeat, conic-gradient(currentColor var(--radialprogress), #0000 0)", "webkit-mask": "radial-gradient( farthest-side, #0000 calc(100% - var(--thickness)), #000 calc(100% + 0.5px - var(--thickness)) )", mask: "radial-gradient( farthest-side, #0000 calc(100% - var(--thickness)), #000 calc(100% + 0.5px - var(--thickness)) )" }, "&:after": { position: "absolute", "border-radius": "calc(infinity * 1px)", "background-color": "currentColor", transition: "transform 0.3s linear", content: '""', inset: "calc(50% - var(--thickness) / 2)", transform: "rotate(calc(var(--value) * 3.6deg - 90deg)) translate(calc(var(--size) / 2 - 50%))" } } }; + +// packages/daisyui/components/radialprogress/index.js +var radialprogress_default = ({ addComponents, prefix = "" }) => { + const prefixedradialprogress = addPrefix(object_default48, prefix); + addComponents({ ...prefixedradialprogress }); +}; + +// packages/daisyui/components/countdown/object.js +var object_default49 = { ".countdown": { display: "inline-flex", "&.countdown": { "line-height": "1em" }, "& > *": { display: "inline-block", "overflow-y": "hidden", height: "1em", "&:before": { position: "relative", content: '"00\\A 01\\A 02\\A 03\\A 04\\A 05\\A 06\\A 07\\A 08\\A 09\\A 10\\A 11\\A 12\\A 13\\A 14\\A 15\\A 16\\A 17\\A 18\\A 19\\A 20\\A 21\\A 22\\A 23\\A 24\\A 25\\A 26\\A 27\\A 28\\A 29\\A 30\\A 31\\A 32\\A 33\\A 34\\A 35\\A 36\\A 37\\A 38\\A 39\\A 40\\A 41\\A 42\\A 43\\A 44\\A 45\\A 46\\A 47\\A 48\\A 49\\A 50\\A 51\\A 52\\A 53\\A 54\\A 55\\A 56\\A 57\\A 58\\A 59\\A 60\\A 61\\A 62\\A 63\\A 64\\A 65\\A 66\\A 67\\A 68\\A 69\\A 70\\A 71\\A 72\\A 73\\A 74\\A 75\\A 76\\A 77\\A 78\\A 79\\A 80\\A 81\\A 82\\A 83\\A 84\\A 85\\A 86\\A 87\\A 88\\A 89\\A 90\\A 91\\A 92\\A 93\\A 94\\A 95\\A 96\\A 97\\A 98\\A 99\\A"', "white-space": "pre", top: "calc(var(--value) * -1em)", "text-align": "center", transition: "all 1s cubic-bezier(1, 0, 0, 1)" } } } }; + +// packages/daisyui/components/countdown/index.js +var countdown_default = ({ addComponents, prefix = "" }) => { + const prefixedcountdown = addPrefix(object_default49, prefix); + addComponents({ ...prefixedcountdown }); +}; + +// packages/daisyui/components/tooltip/object.js +var object_default50 = { ".tooltip": { position: "relative", display: "inline-block", "--tt-bg": "var(--color-neutral)", "--tt-off": "calc(100% + 0.5rem)", "--tt-tail": "calc(100% + 1px + 0.25rem)", "> :where(.tooltip-content), &:where([data-tip]):before": { position: "absolute", "max-width": "20rem", "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 1)", "text-align": "center", "white-space": "normal", color: "var(--color-neutral-content)", opacity: "0%", "font-size": "0.875rem", "line-height": 1.25, transition: "opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms", "background-color": "var(--tt-bg)", width: "max-content", "pointer-events": "none", "z-index": 1, "--tw-content": "attr(data-tip)", content: "var(--tw-content)" }, "&:after": { position: ["absolute", "absolute"], opacity: "0%", "background-color": "var(--tt-bg)", transition: "opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms", content: '""', "pointer-events": "none", width: "0.625rem", height: "0.25rem", display: "block", "mask-repeat": "no-repeat", "mask-position": "-1px 0", "--mask-tooltip": `url("data:image/svg+xml,%3Csvg width='10' height='4' viewBox='0 0 8 4' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.500009 1C3.5 1 3.00001 4 5.00001 4C7 4 6.5 1 9.5 1C10 1 10 0.499897 10 0H0C-1.99338e-08 0.5 0 1 0.500009 1Z' fill='black'/%3E%3C/svg%3E%0A")`, "mask-image": "var(--mask-tooltip)" }, '&.tooltip-open, &[data-tip]:not([data-tip=""]):hover, &:not(:has(.tooltip-content:empty)):has(.tooltip-content):hover, &:has(:focus-visible)': { "> .tooltip-content, &[data-tip]:before, &:after": { opacity: "100%", "--tt-pos": "0rem", transition: "opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1) 0s, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1) 0ms" } } }, ".tooltip, .tooltip-top": { "> .tooltip-content, &[data-tip]:before": { transform: "translateX(-50%) translateY(var(--tt-pos, 0.25rem))", inset: "auto auto var(--tt-off) 50%" }, "&:after": { transform: "translateX(-50%) translateY(var(--tt-pos, 0.25rem))", inset: "auto auto var(--tt-tail) 50%" } }, ".tooltip-bottom": { "> .tooltip-content, &[data-tip]:before": { transform: "translateX(-50%) translateY(var(--tt-pos, -0.25rem))", inset: "var(--tt-off) auto auto 50%" }, "&:after": { transform: "translateX(-50%) translateY(var(--tt-pos, -0.25rem)) rotate(180deg)", inset: "var(--tt-tail) auto auto 50%" } }, ".tooltip-left": { "> .tooltip-content, &[data-tip]:before": { transform: "translateX(calc(var(--tt-pos, 0.25rem) - 0.25rem)) translateY(-50%)", inset: "50% var(--tt-off) auto auto" }, "&:after": { transform: "translateX(var(--tt-pos, 0.25rem)) translateY(-50%) rotate(-90deg)", inset: "50% calc(var(--tt-tail) + 1px) auto auto" } }, ".tooltip-right": { "> .tooltip-content, &[data-tip]:before": { transform: "translateX(calc(var(--tt-pos, -0.25rem) + 0.25rem)) translateY(-50%)", inset: "50% auto auto var(--tt-off)" }, "&:after": { transform: "translateX(var(--tt-pos, -0.25rem)) translateY(-50%) rotate(90deg)", inset: "50% auto auto calc(var(--tt-tail) + 1px)" } }, ".tooltip-primary": { "--tt-bg": "var(--color-primary)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-primary-content)" } }, ".tooltip-secondary": { "--tt-bg": "var(--color-secondary)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-secondary-content)" } }, ".tooltip-accent": { "--tt-bg": "var(--color-accent)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-accent-content)" } }, ".tooltip-info": { "--tt-bg": "var(--color-info)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-info-content)" } }, ".tooltip-success": { "--tt-bg": "var(--color-success)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-success-content)" } }, ".tooltip-warning": { "--tt-bg": "var(--color-warning)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-warning-content)" } }, ".tooltip-error": { "--tt-bg": "var(--color-error)", "> .tooltip-content, &[data-tip]:before": { color: "var(--color-error-content)" } } }; + +// packages/daisyui/components/tooltip/index.js +var tooltip_default = ({ addComponents, prefix = "" }) => { + const prefixedtooltip = addPrefix(object_default50, prefix); + addComponents({ ...prefixedtooltip }); +}; + +// packages/daisyui/components/timeline/object.js +var object_default51 = { ".timeline": { position: "relative", display: "flex", "> li": { position: "relative", display: "grid", "flex-shrink": 0, "align-items": "center", "grid-template-rows": "var(--timeline-row-start, minmax(0, 1fr)) auto var( --timeline-row-end, minmax(0, 1fr) )", "grid-template-columns": "var(--timeline-col-start, minmax(0, 1fr)) auto var( --timeline-col-end, minmax(0, 1fr) )", "> hr": { border: "none", width: "100%", "&:first-child": { "grid-column-start": "1", "grid-row-start": "2" }, "&:last-child": { "grid-column-start": "3", "grid-column-end": "none", "grid-row-start": "2", "grid-row-end": "auto" }, "@media print": { border: "0.1px solid var(--color-base-300)" } } }, ":where(hr)": { height: "calc(0.25rem * 1)", "background-color": "var(--color-base-300)" }, "&:has(.timeline-middle hr)": { "&:first-child": { "border-start-start-radius": "0", "border-end-start-radius": "0", "border-start-end-radius": "var(--radius-selector)", "border-end-end-radius": "var(--radius-selector)" }, "&:last-child": { "border-start-start-radius": "var(--radius-selector)", "border-end-start-radius": "var(--radius-selector)", "border-start-end-radius": "0", "border-end-end-radius": "0" } }, "&:not(:has(.timeline-middle))": { ":first-child hr:last-child": { "border-start-start-radius": "var(--radius-selector)", "border-end-start-radius": "var(--radius-selector)", "border-start-end-radius": "0", "border-end-end-radius": "0" }, ":last-child hr:first-child": { "border-start-start-radius": "0", "border-end-start-radius": "0", "border-start-end-radius": "var(--radius-selector)", "border-end-end-radius": "var(--radius-selector)" } } }, ".timeline-box": { border: "var(--border) solid", "border-radius": "var(--radius-box)", "border-color": "var(--color-base-300)", "background-color": "var(--color-base-100)", "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 2)", "font-size": "0.75rem", "box-shadow": "0 1px 2px 0 oklch(0% 0 0/0.05)" }, ".timeline-start": { "grid-column-start": "1", "grid-column-end": "4", "grid-row-start": "1", "grid-row-end": "2", margin: "calc(0.25rem * 1)", "align-self": "flex-end", "justify-self": "center" }, ".timeline-middle": { "grid-column-start": "2", "grid-row-start": "2" }, ".timeline-end": { "grid-column-start": "1", "grid-column-end": "4", "grid-row-start": "3", "grid-row-end": "4", margin: "calc(0.25rem * 1)", "align-self": "flex-start", "justify-self": "center" }, ".timeline-compact": { "--timeline-row-start": "0", ".timeline-start": { "grid-column-start": "1", "grid-column-end": "4", "grid-row-start": "3", "grid-row-end": "4", "align-self": "flex-start", "justify-self": "center" }, "li:has(.timeline-start)": { ".timeline-end": { "grid-column-start": "none", "grid-row-start": "auto" } }, "&.timeline-vertical": { "> li": { "--timeline-col-start": "0" }, ".timeline-start": { "grid-column-start": "3", "grid-column-end": "4", "grid-row-start": "1", "grid-row-end": "4", "align-self": "center", "justify-self": "flex-start" }, "li:has(.timeline-start)": { ".timeline-end": { "grid-column-start": "auto", "grid-row-start": "none" } } } }, ".timeline-snap-icon": { "> li": { "--timeline-col-start": "0.5rem", "--timeline-row-start": "minmax(0, 1fr)" } }, ".timeline-vertical": { "flex-direction": "column", "> li": { "justify-items": "center", "--timeline-row-start": "minmax(0, 1fr)", "--timeline-row-end": "minmax(0, 1fr)", "> hr": { height: "100%", width: "calc(0.25rem * 1)", "&:first-child": { "grid-column-start": "2", "grid-row-start": "1" }, "&:last-child": { "grid-column-start": "2", "grid-column-end": "auto", "grid-row-start": "3", "grid-row-end": "none" } } }, ".timeline-start": { "grid-column-start": "1", "grid-column-end": "2", "grid-row-start": "1", "grid-row-end": "4", "align-self": "center", "justify-self": "flex-end" }, ".timeline-end": { "grid-column-start": "3", "grid-column-end": "4", "grid-row-start": "1", "grid-row-end": "4", "align-self": "center", "justify-self": "flex-start" }, "&:has(.timeline-middle)": { "> li": { "> hr": { "&:first-child": { "border-top-left-radius": "0", "border-top-right-radius": "0", "border-bottom-right-radius": "var(--radius-selector)", "border-bottom-left-radius": "var(--radius-selector)" }, "&:last-child": { "border-top-left-radius": "var(--radius-selector)", "border-top-right-radius": "var(--radius-selector)", "border-bottom-right-radius": "0", "border-bottom-left-radius": "0" } } } }, "&:not(:has(.timeline-middle))": { ":first-child": { "> hr:last-child": { "border-top-left-radius": "var(--radius-selector)", "border-top-right-radius": "var(--radius-selector)", "border-bottom-right-radius": "0", "border-bottom-left-radius": "0" } }, ":last-child": { "> hr:first-child": { "border-top-left-radius": "0", "border-top-right-radius": "0", "border-bottom-right-radius": "var(--radius-selector)", "border-bottom-left-radius": "var(--radius-selector)" } } }, "&.timeline-snap-icon": { "> li": { "--timeline-col-start": "minmax(0, 1fr)", "--timeline-row-start": "0.5rem" } } }, ".timeline-horizontal": { "flex-direction": "row", "> li": { "align-items": "center", "> hr": { height: "calc(0.25rem * 1)", width: "100%", "&:first-child": { "grid-column-start": "1", "grid-row-start": "2" }, "&:last-child": { "grid-column-start": "3", "grid-column-end": "none", "grid-row-start": "2", "grid-row-end": "auto" } } }, ".timeline-start": { "grid-column-start": "1", "grid-column-end": "4", "grid-row-start": "1", "grid-row-end": "2", "align-self": "flex-end", "justify-self": "center" }, ".timeline-end": { "grid-column-start": "1", "grid-column-end": "4", "grid-row-start": "3", "grid-row-end": "4", "align-self": "flex-start", "justify-self": "center" }, "&:has(.timeline-middle)": { "> li": { "> hr": { "&:first-child": { "border-start-start-radius": "0", "border-end-start-radius": "0", "border-start-end-radius": "var(--radius-selector)", "border-end-end-radius": "var(--radius-selector)" }, "&:last-child": { "border-start-start-radius": "var(--radius-selector)", "border-end-start-radius": "var(--radius-selector)", "border-start-end-radius": "0", "border-end-end-radius": "0" } } } }, "&:not(:has(.timeline-middle))": { ":first-child": { "> hr:last-child": { "border-start-start-radius": "var(--radius-selector)", "border-end-start-radius": "var(--radius-selector)", "border-start-end-radius": "0", "border-end-end-radius": "0" } }, ":last-child": { "> hr:first-child": { "border-start-start-radius": "0", "border-end-start-radius": "0", "border-start-end-radius": "var(--radius-selector)", "border-end-end-radius": "var(--radius-selector)" } } } } }; + +// packages/daisyui/components/timeline/index.js +var timeline_default = ({ addComponents, prefix = "" }) => { + const prefixedtimeline = addPrefix(object_default51, prefix); + addComponents({ ...prefixedtimeline }); +}; + +// packages/daisyui/components/textarea/object.js +var object_default52 = { ".textarea": { border: "var(--border) solid #0000", "min-height": "calc(0.25rem * 20)", "flex-shrink": 1, appearance: "none", "border-radius": "var(--radius-field)", "background-color": "var(--color-base-100)", "padding-block": "calc(0.25rem * 2)", "vertical-align": "middle", width: "clamp(3rem, 20rem, 100%)", "padding-inline-start": "0.75rem", "padding-inline-end": "0.75rem", "font-size": "0.875rem", "border-color": "var(--input-color)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", textarea: { appearance: "none", "background-color": "transparent", border: "none", "&:focus, &:focus-within": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } } }, "&:focus, &:focus-within": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate" }, "&:has(> textarea[disabled]), &:is(:disabled, [disabled])": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" }, "box-shadow": "none" }, "&:has(> textarea[disabled]) > textarea[disabled]": { cursor: "not-allowed" } }, ".textarea-ghost": { "background-color": "transparent", "box-shadow": "none", "border-color": "#0000", "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } }, ".textarea-neutral": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } }, ".textarea-primary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } }, ".textarea-secondary": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } }, ".textarea-accent": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } }, ".textarea-info": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } }, ".textarea-success": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } }, ".textarea-warning": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } }, ".textarea-error": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } }, ".textarea-xs": { "font-size": "0.6875rem" }, ".textarea-sm": { "font-size": "0.75rem" }, ".textarea-md": { "font-size": "0.875rem" }, ".textarea-lg": { "font-size": "1.125rem" }, ".textarea-xl": { "font-size": "1.375rem" } }; + +// packages/daisyui/components/textarea/index.js +var textarea_default = ({ addComponents, prefix = "" }) => { + const prefixedtextarea = addPrefix(object_default52, prefix); + addComponents({ ...prefixedtextarea }); +}; + +// packages/daisyui/components/range/object.js +var object_default53 = { ".range": { appearance: "none", "webkit-appearance": "none", "--range-thumb": "var(--color-base-100)", "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 6)", "--range-progress": "currentColor", "--range-fill": "1", "--range-p": "0.25rem", "--range-bg": "color-mix(in oklab, currentColor 10%, #0000)", cursor: "pointer", overflow: "hidden", "background-color": "transparent", "vertical-align": "middle", width: "clamp(3rem, 20rem, 100%)", "--radius-selector-max": `calc( + var(--radius-selector) + var(--radius-selector) + var(--radius-selector) + )`, "border-radius": "calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))", border: "none", height: "var(--range-thumb-size)", '[dir="rtl"] &': { "--range-dir": "-1" }, "&:focus": { outline: "none" }, "&:focus-visible": { outline: "2px solid", "outline-offset": "2px" }, "&::-webkit-slider-runnable-track": { width: "100%", "background-color": "var(--range-bg)", "border-radius": "var(--radius-selector)", height: "calc(var(--range-thumb-size) * 0.5)" }, "@media (forced-colors: active)": [{ "&::-webkit-slider-runnable-track": { border: "1px solid" } }, { "&::-moz-range-track": { border: "1px solid" } }], "&::-webkit-slider-thumb": { position: "relative", "box-sizing": "border-box", "border-radius": "calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))", "background-color": "currentColor", height: "var(--range-thumb-size)", width: "var(--range-thumb-size)", border: "var(--range-p) solid", appearance: "none", "webkit-appearance": "none", top: "50%", color: "var(--range-progress)", transform: "translateY(-50%)", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill))" }, "&::-moz-range-track": { width: "100%", "background-color": "var(--range-bg)", "border-radius": "var(--radius-selector)", height: "calc(var(--range-thumb-size) * 0.5)" }, "&::-moz-range-thumb": { position: "relative", "box-sizing": "border-box", "border-radius": "calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))", "background-color": "currentColor", height: "var(--range-thumb-size)", width: "var(--range-thumb-size)", border: "var(--range-p) solid", top: "50%", color: "var(--range-progress)", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill))" }, "&:disabled": { cursor: "not-allowed", opacity: "30%" } }, ".range-primary": { color: "var(--color-primary)", "--range-thumb": "var(--color-primary-content)" }, ".range-secondary": { color: "var(--color-secondary)", "--range-thumb": "var(--color-secondary-content)" }, ".range-accent": { color: "var(--color-accent)", "--range-thumb": "var(--color-accent-content)" }, ".range-neutral": { color: "var(--color-neutral)", "--range-thumb": "var(--color-neutral-content)" }, ".range-success": { color: "var(--color-success)", "--range-thumb": "var(--color-success-content)" }, ".range-warning": { color: "var(--color-warning)", "--range-thumb": "var(--color-warning-content)" }, ".range-info": { color: "var(--color-info)", "--range-thumb": "var(--color-info-content)" }, ".range-error": { color: "var(--color-error)", "--range-thumb": "var(--color-error-content)" }, ".range-xs": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 4)" }, ".range-sm": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 5)" }, ".range-md": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 6)" }, ".range-lg": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 7)" }, ".range-xl": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 8)" } }; + +// packages/daisyui/components/range/index.js +var range_default = ({ addComponents, prefix = "" }) => { + const prefixedrange = addPrefix(object_default53, prefix); + addComponents({ ...prefixedrange }); +}; + +// packages/daisyui/components/dock/object.js +var object_default54 = { ".dock": { position: "fixed", right: "calc(0.25rem * 0)", bottom: "calc(0.25rem * 0)", left: "calc(0.25rem * 0)", "z-index": 1, display: "flex", width: "100%", "flex-direction": "row", "align-items": "center", "justify-content": "space-around", "background-color": "var(--color-base-100)", padding: "calc(0.25rem * 2)", color: "currentColor", "border-top": "0.5px solid color-mix(in oklab, var(--color-base-content) 5%, #0000)", height: ["4rem", "calc(4rem + env(safe-area-inset-bottom))"], "padding-bottom": "env(safe-area-inset-bottom)", "> *": { position: "relative", "margin-bottom": "calc(0.25rem * 2)", display: "flex", height: "100%", "max-width": "calc(0.25rem * 32)", "flex-shrink": 1, "flex-basis": "100%", cursor: "pointer", "flex-direction": "column", "align-items": "center", "justify-content": "center", gap: "1px", "border-radius": "var(--radius-box)", "background-color": "transparent", transition: "opacity 0.2s ease-out", "@media (hover: hover)": { "&:hover": { opacity: "80%" } }, '&[aria-disabled="true"], &[disabled]': { "&, &:hover": { "pointer-events": "none", color: "color-mix(in oklab, var(--color-base-content) 10%, transparent)", opacity: "100%" } }, ".dock-label": { "font-size": "0.6875rem" }, "&:after": { content: '""', position: "absolute", height: "calc(0.25rem * 1)", width: "calc(0.25rem * 6)", "border-radius": "calc(infinity * 1px)", "background-color": "transparent", bottom: "0.2rem", "border-top": "3px solid transparent", transition: "background-color 0.1s ease-out, text-color 0.1s ease-out, width 0.1s ease-out" } } }, ".dock-active": { "&:after": { width: "calc(0.25rem * 10)", "background-color": "currentColor", color: "currentColor" } }, ".dock-xs": { height: ["3rem", "calc(3rem + env(safe-area-inset-bottom))"], ".dock-active": { "&:after": { bottom: "-0.1rem" } }, ".dock-label": { "font-size": "0.625rem" } }, ".dock-sm": { height: ["calc(0.25rem * 14)", "3.5rem", "calc(3.5rem + env(safe-area-inset-bottom))"], ".dock-active": { "&:after": { bottom: "-0.1rem" } }, ".dock-label": { "font-size": "0.625rem" } }, ".dock-md": { height: ["4rem", "calc(4rem + env(safe-area-inset-bottom))"], ".dock-label": { "font-size": "0.6875rem" } }, ".dock-lg": { height: ["4.5rem", "calc(4.5rem + env(safe-area-inset-bottom))"], ".dock-active": { "&:after": { bottom: "0.4rem" } }, ".dock-label": { "font-size": "0.6875rem" } }, ".dock-xl": { height: ["5rem", "calc(5rem + env(safe-area-inset-bottom))"], ".dock-active": { "&:after": { bottom: "0.4rem" } }, ".dock-label": { "font-size": "0.75rem" } } }; + +// packages/daisyui/components/dock/index.js +var dock_default = ({ addComponents, prefix = "" }) => { + const prefixeddock = addPrefix(object_default54, prefix); + addComponents({ ...prefixeddock }); +}; + +// packages/daisyui/components/breadcrumbs/object.js +var object_default55 = { ".breadcrumbs": { "max-width": "100%", "overflow-x": "auto", "padding-block": "calc(0.25rem * 2)", "> menu, > ul, > ol": { display: "flex", "min-height": "min-content", "align-items": "center", "white-space": "nowrap", "> li": { display: "flex", "align-items": "center", "> *": { display: "flex", cursor: "pointer", "align-items": "center", gap: "calc(0.25rem * 2)", "&:hover": { "@media (hover: hover)": { "text-decoration-line": "underline" } }, "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible": { outline: "2px solid currentColor", "outline-offset": "2px" } }, "& + *:before": { content: '""', "margin-right": "calc(0.25rem * 3)", "margin-left": "calc(0.25rem * 2)", display: "block", height: "calc(0.25rem * 1.5)", width: "calc(0.25rem * 1.5)", opacity: "40%", rotate: "45deg", "border-top": "1px solid", "border-right": "1px solid", "background-color": "#0000" }, '[dir="rtl"] & + *:before': { rotate: "-135deg" } } } } }; + +// packages/daisyui/components/breadcrumbs/index.js +var breadcrumbs_default = ({ addComponents, prefix = "" }) => { + const prefixedbreadcrumbs = addPrefix(object_default55, prefix); + addComponents({ ...prefixedbreadcrumbs }); +}; + +// packages/daisyui/components/radio/object.js +var object_default56 = { ".radio": { position: "relative", "flex-shrink": 0, cursor: "pointer", appearance: "none", "border-radius": "calc(infinity * 1px)", padding: "calc(0.25rem * 1)", "vertical-align": "middle", border: "var(--border) solid var(--input-color, color-mix(in srgb, currentColor 20%, #0000))", "box-shadow": "0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset", "--size": "calc(var(--size-selector, 0.25rem) * 6)", width: "var(--size)", height: "var(--size)", color: "var(--input-color, currentColor)", "&:before": { display: "block", width: "100%", height: "100%", "border-radius": "calc(infinity * 1px)", "--tw-content": '""', content: "var(--tw-content)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)" }, "&:focus-visible": { outline: "2px solid currentColor" }, '&:checked, &[aria-checked="true"]': { animation: "radio 0.2s ease-out", "border-color": "currentColor", "background-color": "var(--color-base-100)", "&:before": { "background-color": "currentColor", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1))" }, "@media (forced-colors: active)": { "&:before": { "outline-style": "var(--tw-outline-style)", "outline-width": "1px", "outline-offset": "calc(1px * -1)" } }, "@media print": { "&:before": { outline: "0.25rem solid", "outline-offset": "-1rem" } } } }, ".radio-primary": { "--input-color": "var(--color-primary)" }, ".radio-secondary": { "--input-color": "var(--color-secondary)" }, ".radio-accent": { "--input-color": "var(--color-accent)" }, ".radio-neutral": { "--input-color": "var(--color-neutral)" }, ".radio-info": { "--input-color": "var(--color-info)" }, ".radio-success": { "--input-color": "var(--color-success)" }, ".radio-warning": { "--input-color": "var(--color-warning)" }, ".radio-error": { "--input-color": "var(--color-error)" }, ".radio:disabled": { cursor: "not-allowed", opacity: "20%" }, ".radio-xs": { padding: "0.125rem", '&:is([type="radio"])': { "--size": "calc(var(--size-selector, 0.25rem) * 4)" } }, ".radio-sm": { padding: "0.1875rem", '&:is([type="radio"])': { "--size": "calc(var(--size-selector, 0.25rem) * 5)" } }, ".radio-md": { padding: "0.25rem", '&:is([type="radio"])': { "--size": "calc(var(--size-selector, 0.25rem) * 6)" } }, ".radio-lg": { padding: "0.3125rem", '&:is([type="radio"])': { "--size": "calc(var(--size-selector, 0.25rem) * 7)" } }, ".radio-xl": { padding: "0.375rem", '&:is([type="radio"])': { "--size": "calc(var(--size-selector, 0.25rem) * 8)" } }, "@keyframes radio": { "0%": { padding: "5px" }, "50%": { padding: "3px" } } }; + +// packages/daisyui/components/radio/index.js +var radio_default = ({ addComponents, prefix = "" }) => { + const prefixedradio = addPrefix(object_default56, prefix); + addComponents({ ...prefixedradio }); +}; + +// packages/daisyui/components/skeleton/object.js +var object_default57 = { ".skeleton": { "border-radius": "var(--radius-box)", "background-color": "var(--color-base-300)", "@media (prefers-reduced-motion: reduce)": { "transition-duration": "15s" }, "will-change": "background-position", animation: "skeleton 1.8s ease-in-out infinite", "background-image": "linear-gradient( 105deg, #0000 0% 40%, var(--color-base-100) 50%, #0000 60% 100% )", "background-size": "200% auto", "background-repeat": "no-repeat", "background-position-x": "-50%" }, "@keyframes skeleton": { "0%": { "background-position": "150%" }, "100%": { "background-position": "-50%" } } }; + +// packages/daisyui/components/skeleton/index.js +var skeleton_default = ({ addComponents, prefix = "" }) => { + const prefixedskeleton = addPrefix(object_default57, prefix); + addComponents({ ...prefixedskeleton }); +}; + +// packages/daisyui/components/loading/object.js +var object_default58 = { ".loading": { "pointer-events": "none", display: "inline-block", "aspect-ratio": "1 / 1", "background-color": "currentColor", "vertical-align": "middle", width: "calc(var(--size-selector, 0.25rem) * 6)", "mask-size": "100%", "mask-repeat": "no-repeat", "mask-position": "center", "mask-image": `url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E")` }, ".loading-spinner": { "mask-image": `url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E")` }, ".loading-dots": { "mask-image": `url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='4' cy='12' r='3'%3E%3Canimate attributeName='cy' values='12;6;12;12' keyTimes='0;0.286;0.571;1' dur='1.05s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1'/%3E%3C/circle%3E%3Ccircle cx='12' cy='12' r='3'%3E%3Canimate attributeName='cy' values='12;6;12;12' keyTimes='0;0.286;0.571;1' dur='1.05s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1' begin='0.1s'/%3E%3C/circle%3E%3Ccircle cx='20' cy='12' r='3'%3E%3Canimate attributeName='cy' values='12;6;12;12' keyTimes='0;0.286;0.571;1' dur='1.05s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1' begin='0.2s'/%3E%3C/circle%3E%3C/svg%3E")` }, ".loading-ring": { "mask-image": `url("data:image/svg+xml,%3Csvg width='44' height='44' viewBox='0 0 44 44' xmlns='http://www.w3.org/2000/svg' stroke='white'%3E%3Cg fill='none' fill-rule='evenodd' stroke-width='2'%3E%3Ccircle cx='22' cy='22' r='1'%3E%3Canimate attributeName='r' begin='0s' dur='1.8s' values='1;20' calcMode='spline' keyTimes='0;1' keySplines='0.165,0.84,0.44,1' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-opacity' begin='0s' dur='1.8s' values='1;0' calcMode='spline' keyTimes='0;1' keySplines='0.3,0.61,0.355,1' repeatCount='indefinite'/%3E%3C/circle%3E%3Ccircle cx='22' cy='22' r='1'%3E%3Canimate attributeName='r' begin='-0.9s' dur='1.8s' values='1;20' calcMode='spline' keyTimes='0;1' keySplines='0.165,0.84,0.44,1' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-opacity' begin='-0.9s' dur='1.8s' values='1;0' calcMode='spline' keyTimes='0;1' keySplines='0.3,0.61,0.355,1' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E")` }, ".loading-ball": { "mask-image": `url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cellipse cx='12' cy='5' rx='4' ry='4'%3E%3Canimate attributeName='cy' values='5;20;20.5;20;5' keyTimes='0;0.469;0.5;0.531;1' dur='.8s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1'/%3E%3Canimate attributeName='rx' values='4;4;4.8;4;4' keyTimes='0;0.469;0.5;0.531;1' dur='.8s' repeatCount='indefinite'/%3E%3Canimate attributeName='ry' values='4;4;3;4;4' keyTimes='0;0.469;0.5;0.531;1' dur='.8s' repeatCount='indefinite'/%3E%3C/ellipse%3E%3C/svg%3E")` }, ".loading-bars": { "mask-image": `url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1' y='1' width='6' height='22'%3E%3Canimate attributeName='y' values='1;5;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite'/%3E%3Canimate attributeName='height' values='22;14;22' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite'/%3E%3Canimate attributeName='opacity' values='1;0.2;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite'/%3E%3C/rect%3E%3Crect x='9' y='1' width='6' height='22'%3E%3Canimate attributeName='y' values='1;5;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.65s'/%3E%3Canimate attributeName='height' values='22;14;22' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.65s'/%3E%3Canimate attributeName='opacity' values='1;0.2;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.65s'/%3E%3C/rect%3E%3Crect x='17' y='1' width='6' height='22'%3E%3Canimate attributeName='y' values='1;5;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.5s'/%3E%3Canimate attributeName='height' values='22;14;22' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.5s'/%3E%3Canimate attributeName='opacity' values='1;0.2;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.5s'/%3E%3C/rect%3E%3C/svg%3E")` }, ".loading-infinity": { "mask-image": `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' style='shape-rendering:auto;' width='200px' height='200px' viewBox='0 0 100 100' preserveAspectRatio='xMidYMid'%3E%3Cpath fill='none' stroke='black' stroke-width='10' stroke-dasharray='205.271 51.318' d='M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z' stroke-linecap='round' style='transform:scale(0.8);transform-origin:50px 50px'%3E%3Canimate attributeName='stroke-dashoffset' repeatCount='indefinite' dur='2s' keyTimes='0;1' values='0;256.589'/%3E%3C/path%3E%3C/svg%3E")` }, ".loading-xs": { width: "calc(var(--size-selector, 0.25rem) * 4)" }, ".loading-sm": { width: "calc(var(--size-selector, 0.25rem) * 5)" }, ".loading-md": { width: "calc(var(--size-selector, 0.25rem) * 6)" }, ".loading-lg": { width: "calc(var(--size-selector, 0.25rem) * 7)" }, ".loading-xl": { width: "calc(var(--size-selector, 0.25rem) * 8)" } }; + +// packages/daisyui/components/loading/index.js +var loading_default = ({ addComponents, prefix = "" }) => { + const prefixedloading = addPrefix(object_default58, prefix); + addComponents({ ...prefixedloading }); +}; + +// packages/daisyui/components/validator/object.js +var object_default59 = { ".validator": { "&:user-valid, &:has(:user-valid)": { '&, &:focus, &:checked, &[aria-checked="true"], &:focus-within': { "--input-color": "var(--color-success)" } }, "&:user-invalid, &:has(:user-invalid), &[aria-invalid]": { '&, &:focus, &:checked, &[aria-checked="true"], &:focus-within': { "--input-color": "var(--color-error)" }, "& ~ .validator-hint": { visibility: "visible", display: "block", color: "var(--color-error)" } } }, ".validator-hint": { visibility: "hidden", "margin-top": "calc(0.25rem * 2)", "font-size": "0.75rem" } }; + +// packages/daisyui/components/validator/index.js +var validator_default = ({ addComponents, prefix = "" }) => { + const prefixedvalidator = addPrefix(object_default59, prefix); + addComponents({ ...prefixedvalidator }); +}; + +// packages/daisyui/components/collapse/object.js +var object_default60 = { ".collapse:not(td, tr, colgroup)": { visibility: "visible" }, ".collapse": { position: "relative", display: "grid", overflow: "hidden", "border-radius": "var(--radius-box, 1rem)", width: "100%", "grid-template-rows": "max-content 0fr", transition: "grid-template-rows 0.2s", isolation: "isolate", '> input:is([type="checkbox"], [type="radio"])': { "grid-column-start": "1", "grid-row-start": "1", appearance: "none", opacity: 0, "z-index": 1, width: "100%", padding: "1rem", "padding-inline-end": "3rem", "min-height": "3.75rem", transition: "background-color 0.2s ease-out" }, '&:is([open], :focus:not(.collapse-close)), &:not(.collapse-close):has(> input:is([type="checkbox"], [type="radio"]):checked)': { "grid-template-rows": "max-content 1fr" }, '&:is([open], :focus:not(.collapse-close)) > .collapse-content, &:not(.collapse-close) > :where(input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-content)': { visibility: "visible", "min-height": "fit-content" }, '&:focus-visible, &:has(> input:is([type="checkbox"], [type="radio"]):focus-visible)': { "outline-color": "var(--color-base-content)", "outline-style": "solid", "outline-width": "2px", "outline-offset": "2px" }, "&:not(.collapse-close)": { '> input[type="checkbox"], > input[type="radio"]:not(:checked), > .collapse-title': { cursor: "pointer" } }, "&:focus:not(.collapse-close, .collapse[open]) > .collapse-title": { cursor: "unset" }, '&:is([open], :focus:not(.collapse-close)) > :where(.collapse-content), &:not(.collapse-close) > :where(input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-content)': { "padding-bottom": "1rem", transition: "padding 0.2s ease-out, background-color 0.2s ease-out" }, "&:is([open])": { "&.collapse-arrow": { "> .collapse-title:after": { transform: "translateY(-50%) rotate(225deg)" } } }, "&.collapse-open": { "&.collapse-arrow": { "> .collapse-title:after": { transform: "translateY(-50%) rotate(225deg)" } }, "&.collapse-plus": { "> .collapse-title:after": { content: '"−"' } } }, "&.collapse-arrow:focus:not(.collapse-close)": { "> .collapse-title:after": { transform: "translateY(-50%) rotate(225deg)" } }, "&.collapse-arrow:not(.collapse-close)": { '> input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-title:after': { transform: "translateY(-50%) rotate(225deg)" } }, "&[open]": { "&.collapse-plus": { "> .collapse-title:after": { content: '"−"' } } }, "&.collapse-plus:focus:not(.collapse-close)": { "> .collapse-title:after": { content: '"−"' } }, "&.collapse-plus:not(.collapse-close)": { '> input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-title:after': { content: '"−"' } } }, ".collapse-title, .collapse-content": { "grid-column-start": "1", "grid-row-start": "1" }, ".collapse-content": { visibility: "hidden", "grid-column-start": "1", "grid-row-start": "2", "min-height": "0", "padding-left": "1rem", "padding-right": "1rem", cursor: "unset", transition: "visibility 0.2s, padding 0.2s ease-out, background-color 0.2s ease-out" }, ".collapse:is(details)": { width: "100%", "& summary": { position: "relative", display: "block", "&::-webkit-details-marker": { display: "none" } } }, ".collapse:is(details) summary": { outline: "none" }, ".collapse-arrow": { "> .collapse-title:after": { position: "absolute", display: "block", height: "0.5rem", width: "0.5rem", transform: "translateY(-100%) rotate(45deg)", "transition-property": "all", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)", "transition-duration": "0.2s", top: "1.9rem", "inset-inline-end": "1.4rem", content: '""', "transform-origin": "75% 75%", "box-shadow": "2px 2px", "pointer-events": "none" } }, ".collapse-plus": { "> .collapse-title:after": { position: "absolute", display: "block", height: "0.5rem", width: "0.5rem", "transition-property": "all", "transition-duration": "300ms", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)", top: "0.9rem", "inset-inline-end": "1.4rem", content: '"+"', "pointer-events": "none" } }, ".collapse-title": { position: "relative", width: "100%", padding: "1rem", "padding-inline-end": "3rem", "min-height": "3.75rem", transition: "background-color 0.2s ease-out" }, ".collapse-open": { "grid-template-rows": "max-content 1fr", "> .collapse-content": { visibility: "visible", "min-height": "fit-content", "padding-bottom": "1rem", transition: "padding 0.2s ease-out, background-color 0.2s ease-out" } } }; + +// packages/daisyui/components/collapse/index.js +var collapse_default = ({ addComponents, prefix = "" }) => { + const prefixedcollapse = addPrefix(object_default60, prefix); + addComponents({ ...prefixedcollapse }); +}; + +// packages/daisyui/components/swap/object.js +var object_default61 = { ".swap": { position: "relative", display: "inline-grid", cursor: "pointer", "place-content": "center", "vertical-align": "middle", "webkit-user-select": "none", "user-select": "none", input: { appearance: "none", border: "none" }, "> *": { "grid-column-start": "1", "grid-row-start": "1", "transition-property": "transform, rotate, opacity", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0, 0, 0.2, 1)" }, ".swap-on, .swap-indeterminate, input:indeterminate ~ .swap-on": { opacity: "0%" }, "input:is(:checked, :indeterminate)": { "& ~ .swap-off": { opacity: "0%" } }, "input:checked ~ .swap-on, input:indeterminate ~ .swap-indeterminate": { opacity: "100%", "backface-visibility": "visible" } }, ".swap-active": { ".swap-off": { opacity: "0%" }, ".swap-on": { opacity: "100%" } }, ".swap-rotate": { ".swap-on, input:indeterminate ~ .swap-on": { rotate: "45deg" }, "input:is(:checked, :indeterminate) ~ .swap-on, &.swap-active .swap-on": { rotate: "0deg" }, "input:is(:checked, :indeterminate) ~ .swap-off, &.swap-active .swap-off": { rotate: "calc(45deg * -1)" } }, ".swap-flip": { "transform-style": "preserve-3d", perspective: "20rem", ".swap-on, .swap-indeterminate, input:indeterminate ~ .swap-on": { transform: "rotateY(180deg)", "backface-visibility": "hidden" }, "input:is(:checked, :indeterminate) ~ .swap-on, &.swap-active .swap-on": { transform: "rotateY(0deg)" }, "input:is(:checked, :indeterminate) ~ .swap-off, &.swap-active .swap-off": { transform: "rotateY(-180deg)", "backface-visibility": "hidden", opacity: "100%" } } }; + +// packages/daisyui/components/swap/index.js +var swap_default = ({ addComponents, prefix = "" }) => { + const prefixedswap = addPrefix(object_default61, prefix); + addComponents({ ...prefixedswap }); +}; + +// packages/daisyui/utilities/typography/object.js +var object_default62 = { ":root .prose": { "--tw-prose-body": "color-mix(in oklab, var(--color-base-content) 80%, #0000)", "--tw-prose-headings": "var(--color-base-content)", "--tw-prose-lead": "var(--color-base-content)", "--tw-prose-links": "var(--color-base-content)", "--tw-prose-bold": "var(--color-base-content)", "--tw-prose-counters": "var(--color-base-content)", "--tw-prose-bullets": "color-mix(in oklab, var(--color-base-content) 50%, #0000)", "--tw-prose-hr": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "--tw-prose-quotes": "var(--color-base-content)", "--tw-prose-quote-borders": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "--tw-prose-captions": "color-mix(in oklab, var(--color-base-content) 50%, #0000)", "--tw-prose-code": "var(--color-base-content)", "--tw-prose-pre-code": "var(--color-neutral-content)", "--tw-prose-pre-bg": "var(--color-neutral)", "--tw-prose-th-borders": "color-mix(in oklab, var(--color-base-content) 50%, #0000)", "--tw-prose-td-borders": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "--tw-prose-kbd": "color-mix(in oklab, var(--color-base-content) 80%, #0000)", ":where(code):not(pre > code)": { "background-color": "var(--color-base-200)", "border-radius": "var(--radius-selector)", border: "var(--border) solid var(--color-base-300)", "padding-inline": "0.5em", "font-weight": "inherit", "&:before, &:after": { display: "none" } } } }; + +// packages/daisyui/utilities/typography/index.js +var typography_default = ({ addUtilities, prefix = "" }) => { + const prefixedtypography = addPrefix(object_default62, prefix); + addUtilities({ ...prefixedtypography }); +}; + +// packages/daisyui/utilities/glass/object.js +var object_default63 = { ".glass": { border: "none", "backdrop-filter": "blur(var(--glass-blur, 40px))", "background-color": "#0000", "background-image": "linear-gradient( 135deg, oklch(100% 0 0 / var(--glass-opacity, 30%)) 0%, oklch(0% 0 0 / 0%) 100% ), linear-gradient( var(--glass-reflect-degree, 100deg), oklch(100% 0 0 / var(--glass-reflect-opacity, 5%)) 25%, oklch(0% 0 0 / 0%) 25% )", "box-shadow": "0 0 0 1px oklch(100% 0 0 / var(--glass-border-opacity, 20%)) inset, 0 0 0 2px oklch(0% 0 0 / 5%)", "text-shadow": "0 1px oklch(0% 0 0 / var(--glass-text-shadow-opacity, 5%))" } }; + +// packages/daisyui/utilities/glass/index.js +var glass_default = ({ addUtilities, prefix = "" }) => { + const prefixedglass = addPrefix(object_default63, prefix); + addUtilities({ ...prefixedglass }); +}; + +// packages/daisyui/utilities/join/object.js +var object_default64 = { ".join": { display: "inline-flex", "align-items": "stretch", "--join-ss": "0", "--join-se": "0", "--join-es": "0", "--join-ee": "0", ":where(.join-item)": { "border-start-start-radius": "var(--join-ss, 0)", "border-start-end-radius": "var(--join-se, 0)", "border-end-start-radius": "var(--join-es, 0)", "border-end-end-radius": "var(--join-ee, 0)", "*": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, "> .join-item:where(:first-child)": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" }, ":first-child:not(:last-child)": { ":where(.join-item)": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" } }, "> .join-item:where(:last-child)": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ":where(.join-item)": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" } }, "> .join-item:where(:only-child)": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ":where(.join-item)": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } } }, ".join-item": { "&:where(*:not(:first-child, :disabled, [disabled], .btn-disabled))": { "margin-inline-start": "calc(var(--border, 1px) * -1)", "margin-block-start": "0" } }, ".join-vertical": { "flex-direction": "column", "> .join-item:first-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "0" }, ":first-child:not(:last-child)": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "0" } }, "> .join-item:last-child": { "--join-ss": "0", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ".join-item": { "--join-ss": "0", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, "> .join-item:only-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, ".join-item": { "&:where(*:not(:first-child))": { "margin-inline-start": "0", "margin-block-start": "calc(var(--border, 1px) * -1)" } } }, ".join-horizontal": { "flex-direction": "row", "> .join-item:first-child": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" }, ":first-child:not(:last-child)": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" } }, "> .join-item:last-child": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ".join-item": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" } }, "> .join-item:only-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, ".join-item": { "&:where(*:not(:first-child))": { "margin-inline-start": "calc(var(--border, 1px) * -1)", "margin-block-start": "0" } } } }; + +// packages/daisyui/utilities/join/index.js +var join_default = ({ addUtilities, prefix = "" }) => { + const prefixedjoin = addPrefix(object_default64, prefix); + addUtilities({ ...prefixedjoin }); +}; + +// packages/daisyui/utilities/radius/object.js +var object_default65 = { ".rounded-box": { "border-radius": "var(--radius-box)" }, ".rounded-field": { "border-radius": "var(--radius-field)" }, ".rounded-selector": { "border-radius": "var(--radius-selector)" }, ".rounded-t-box": { "border-top-left-radius": "var(--radius-box)", "border-top-right-radius": "var(--radius-box)" }, ".rounded-b-box": { "border-bottom-left-radius": "var(--radius-box)", "border-bottom-right-radius": "var(--radius-box)" }, ".rounded-l-box": { "border-top-left-radius": "var(--radius-box)", "border-bottom-left-radius": "var(--radius-box)" }, ".rounded-r-box": { "border-top-right-radius": "var(--radius-box)", "border-bottom-right-radius": "var(--radius-box)" }, ".rounded-tl-box": { "border-top-left-radius": "var(--radius-box)" }, ".rounded-tr-box": { "border-top-right-radius": "var(--radius-box)" }, ".rounded-br-box": { "border-bottom-right-radius": "var(--radius-box)" }, ".rounded-bl-box": { "border-bottom-left-radius": "var(--radius-box)" }, ".rounded-t-field": { "border-top-left-radius": "var(--radius-field)", "border-top-right-radius": "var(--radius-field)" }, ".rounded-b-field": { "border-bottom-left-radius": "var(--radius-field)", "border-bottom-right-radius": "var(--radius-field)" }, ".rounded-l-field": { "border-top-left-radius": "var(--radius-field)", "border-bottom-left-radius": "var(--radius-field)" }, ".rounded-r-field": { "border-top-right-radius": "var(--radius-field)", "border-bottom-right-radius": "var(--radius-field)" }, ".rounded-tl-field": { "border-top-left-radius": "var(--radius-field)" }, ".rounded-tr-field": { "border-top-right-radius": "var(--radius-field)" }, ".rounded-br-field": { "border-bottom-right-radius": "var(--radius-field)" }, ".rounded-bl-field": { "border-bottom-left-radius": "var(--radius-field)" }, ".rounded-t-selector": { "border-top-left-radius": "var(--radius-selector)", "border-top-right-radius": "var(--radius-selector)" }, ".rounded-b-selector": { "border-bottom-left-radius": "var(--radius-selector)", "border-bottom-right-radius": "var(--radius-selector)" }, ".rounded-l-selector": { "border-top-left-radius": "var(--radius-selector)", "border-bottom-left-radius": "var(--radius-selector)" }, ".rounded-r-selector": { "border-top-right-radius": "var(--radius-selector)", "border-bottom-right-radius": "var(--radius-selector)" }, ".rounded-tl-selector": { "border-top-left-radius": "var(--radius-selector)" }, ".rounded-tr-selector": { "border-top-right-radius": "var(--radius-selector)" }, ".rounded-br-selector": { "border-bottom-right-radius": "var(--radius-selector)" }, ".rounded-bl-selector": { "border-bottom-left-radius": "var(--radius-selector)" } }; + +// packages/daisyui/utilities/radius/index.js +var radius_default = ({ addUtilities, prefix = "" }) => { + const prefixedradius = addPrefix(object_default65, prefix); + addUtilities({ ...prefixedradius }); +}; + +// packages/daisyui/imports.js +var base = { rootscrolllock: rootscrolllock_default, rootcolor: rootcolor_default, scrollbar: scrollbar_default, properties: properties_default, rootscrollgutter: rootscrollgutter_default, svg: svg_default }; +var components = { drawer: drawer_default, link: link_default, stat: stat_default, carousel: carousel_default, divider: divider_default, mask: mask_default, fieldset: fieldset_default, dropdown: dropdown_default, card: card_default, steps: steps_default, alert: alert_default, kbd: kbd_default, select: select_default, progress: progress_default, fileinput: fileinput_default, modal: modal_default, footer: footer_default, table: table_default, avatar: avatar_default, input: input_default, checkbox: checkbox_default, badge: badge_default, status: status_default, diff: diff_default, hero: hero_default, toggle: toggle_default, stack: stack_default, navbar: navbar_default, label: label_default, menu: menu_default, toast: toast_default, button: button_default, list: list_default, mockup: mockup_default, calendar: calendar_default, indicator: indicator_default, rating: rating_default, tab: tab_default, filter: filter_default, chat: chat_default, radialprogress: radialprogress_default, countdown: countdown_default, tooltip: tooltip_default, timeline: timeline_default, textarea: textarea_default, range: range_default, dock: dock_default, breadcrumbs: breadcrumbs_default, radio: radio_default, skeleton: skeleton_default, loading: loading_default, validator: validator_default, collapse: collapse_default, swap: swap_default }; +var utilities = { typography: typography_default, glass: glass_default, join: join_default, radius: radius_default }; + +// packages/daisyui/index.js +var version = "5.0.35"; +var daisyui_default = plugin.withOptions((options) => { + return ({ addBase, addComponents, addUtilities }) => { + const { + include, + exclude, + prefix = "" + } = pluginOptionsHandler(options, addBase, object_default, version); + const shouldIncludeItem = (name) => { + if (include && exclude) { + return include.includes(name) && !exclude.includes(name); + } + if (include) { + return include.includes(name); + } + if (exclude) { + return !exclude.includes(name); + } + return true; + }; + Object.entries(base).forEach(([name, item]) => { + if (!shouldIncludeItem(name)) + return; + item({ addBase, prefix }); + }); + Object.entries(components).forEach(([name, item]) => { + if (!shouldIncludeItem(name)) + return; + item({ addComponents, prefix }); + }); + Object.entries(utilities).forEach(([name, item]) => { + if (!shouldIncludeItem(name)) + return; + item({ addUtilities, prefix }); + }); + }; +}, () => ({ + theme: { + extend: variables_default + } +})); + + +/* + + MIT License + + Copyright (c) 2020 Pouya Saadeghi – https://daisyui.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ diff --git a/website/assets/vendor/heroicons.js b/website/assets/vendor/heroicons.js new file mode 100644 index 0000000..296f80e --- /dev/null +++ b/website/assets/vendor/heroicons.js @@ -0,0 +1,43 @@ +const plugin = require("tailwindcss/plugin") +const fs = require("fs") +const path = require("path") + +module.exports = plugin(function({matchComponents, theme}) { + let iconsDir = path.join(__dirname, "../../deps/heroicons/optimized") + let values = {} + let icons = [ + ["", "/24/outline"], + ["-solid", "/24/solid"], + ["-mini", "/20/solid"], + ["-micro", "/16/solid"] + ] + icons.forEach(([suffix, dir]) => { + fs.readdirSync(path.join(iconsDir, dir)).forEach(file => { + let name = path.basename(file, ".svg") + suffix + values[name] = {name, fullPath: path.join(iconsDir, dir, file)} + }) + }) + matchComponents({ + "hero": ({name, fullPath}) => { + let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") + content = encodeURIComponent(content) + let size = theme("spacing.6") + if (name.endsWith("-mini")) { + size = theme("spacing.5") + } else if (name.endsWith("-micro")) { + size = theme("spacing.4") + } + return { + [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, + "-webkit-mask": `var(--hero-${name})`, + "mask": `var(--hero-${name})`, + "mask-repeat": "no-repeat", + "background-color": "currentColor", + "vertical-align": "middle", + "display": "inline-block", + "width": size, + "height": size + } + } + }, {values}) +}) diff --git a/website/assets/vendor/topbar.js b/website/assets/vendor/topbar.js new file mode 100644 index 0000000..0552337 --- /dev/null +++ b/website/assets/vendor/topbar.js @@ -0,0 +1,138 @@ +/** + * @license MIT + * topbar 3.0.0 + * http://buunguyen.github.io/topbar + * Copyright (c) 2024 Buu Nguyen + */ +(function (window, document) { + "use strict"; + + var canvas, + currentProgress, + showing, + progressTimerId = null, + fadeTimerId = null, + delayTimerId = null, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false); + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); + else elem["on" + type] = handler; + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth; + canvas.height = options.barThickness * 5; // need space for shadow + + var ctx = canvas.getContext("2d"); + ctx.shadowBlur = options.shadowBlur; + ctx.shadowColor = options.shadowColor; + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]); + ctx.lineWidth = options.barThickness; + ctx.beginPath(); + ctx.moveTo(0, options.barThickness / 2); + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ); + ctx.strokeStyle = lineGradient; + ctx.stroke(); + }, + createCanvas = function () { + canvas = document.createElement("canvas"); + var style = canvas.style; + style.position = "fixed"; + style.top = style.left = style.right = style.margin = style.padding = 0; + style.zIndex = 100001; + style.display = "none"; + if (options.className) canvas.classList.add(options.className); + addEvent(window, "resize", repaint); + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key]; + }, + show: function (delay) { + if (showing) return; + if (delay) { + if (delayTimerId) return; + delayTimerId = setTimeout(() => topbar.show(), delay); + } else { + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + if (!canvas.parentElement) document.body.appendChild(canvas); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); + } + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress; + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to); + } + currentProgress = to > 1 ? 1 : to; + repaint(); + return currentProgress; + }, + hide: function () { + clearTimeout(delayTimerId); + delayTimerId = null; + if (!showing) return; + showing = false; + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId); + progressTimerId = null; + } + (function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05; + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none"; + fadeTimerId = null; + return; + } + } + fadeTimerId = window.requestAnimationFrame(loop); + })(); + }, + }; + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar; + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar; + }); + } else { + this.topbar = topbar; + } +}.call(this, window, document)); diff --git a/website/config/config.exs b/website/config/config.exs new file mode 100644 index 0000000..d37d33c --- /dev/null +++ b/website/config/config.exs @@ -0,0 +1,65 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +config :website, + namespace: Demo, + generators: [timestamp_type: :utc_datetime] + +# Configure the endpoint +config :website, DemoWeb.Endpoint, + url: [host: "localhost"], + adapter: Bandit.PhoenixAdapter, + render_errors: [ + formats: [html: DemoWeb.ErrorHTML, json: DemoWeb.ErrorJSON], + layout: false + ], + pubsub_server: Demo.PubSub, + live_view: [signing_salt: "X+z9lCVz"] + +# Configure the mailer +# +# By default it uses the "Local" adapter which stores the emails +# locally. You can see the emails in your browser, at "/dev/mailbox". +# +# For production it's recommended to configure a different adapter +# at the `config/runtime.exs`. +config :website, Demo.Mailer, adapter: Swoosh.Adapters.Local + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.25.4", + website: [ + args: + ~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]} + ] + +# Configure tailwind (the version is required) +config :tailwind, + version: "4.1.12", + website: [ + args: ~w( + --input=assets/css/app.css + --output=priv/static/assets/css/app.css + ), + cd: Path.expand("..", __DIR__) + ] + +# Configure Elixir's Logger +config :logger, :default_formatter, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/website/config/dev.exs b/website/config/dev.exs new file mode 100644 index 0000000..117ecc4 --- /dev/null +++ b/website/config/dev.exs @@ -0,0 +1,82 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we can use it +# to bundle .js and .css sources. +config :website, DemoWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "Tc4MuO4fBQdoicv8o7TBbFW/FeR+l1ttH1rwIUScVcbxwNZnKHmZRuiceT3idqnZ", + watchers: [ + esbuild: {Esbuild, :install_and_run, [:website, ~w(--sourcemap=inline --watch)]}, + tailwind: {Tailwind, :install_and_run, [:website, ~w(--watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Reload browser tabs when matching files change. +config :website, DemoWeb.Endpoint, + live_reload: [ + web_console_logger: true, + patterns: [ + # Static assets, except user uploads + ~r"priv/static/(?!uploads/).*\.(js|css|png|jpeg|jpg|gif|svg)$", + # Gettext translations + ~r"priv/gettext/.*\.po$", + # Router, Controllers, LiveViews and LiveComponents + ~r"lib/website_web/router\.ex$", + ~r"lib/website_web/(controllers|live|components)/.*\.(ex|heex)$" + ] + ] + +# Enable dev routes for dashboard and mailbox +config :website, dev_routes: true + +# Do not include metadata nor timestamps in development logs +config :logger, :default_formatter, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime + +config :phoenix_live_view, + # Include debug annotations and locations in rendered markup. + # Changing this configuration will require mix clean and a full recompile. + debug_heex_annotations: true, + debug_attributes: true, + # Enable helpful, but potentially expensive runtime checks + enable_expensive_runtime_checks: true + +# Disable swoosh api client as it is only required for production adapters. +config :swoosh, :api_client, false diff --git a/website/config/prod.exs b/website/config/prod.exs new file mode 100644 index 0000000..29b501e --- /dev/null +++ b/website/config/prod.exs @@ -0,0 +1,32 @@ +import Config + +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix assets.deploy` task, +# which you should run after static files are built and +# before starting your production server. +config :website, DemoWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" + +# Force using SSL in production. This also sets the "strict-security-transport" header, +# known as HSTS. If you have a health check endpoint, you may want to exclude it below. +# Note `:force_ssl` is required to be set at compile-time. +config :website, DemoWeb.Endpoint, + force_ssl: [ + rewrite_on: [:x_forwarded_proto], + exclude: [ + paths: ["/health"], + hosts: ["localhost", "127.0.0.1"] + ] + ] + +# Configure Swoosh API Client +config :swoosh, api_client: Swoosh.ApiClient.Req + +# Disable Swoosh Local Memory Storage +config :swoosh, local: false + +# Do not print debug messages in production +config :logger, level: :info + +# Runtime production configuration, including reading +# of environment variables, is done on config/runtime.exs. diff --git a/website/config/runtime.exs b/website/config/runtime.exs new file mode 100644 index 0000000..18c6172 --- /dev/null +++ b/website/config/runtime.exs @@ -0,0 +1,106 @@ +import Config + +# config/runtime.exs is executed for all environments, including +# during releases. It is executed after compilation and before the +# system starts, so it is typically used to load production configuration +# and secrets from environment variables or elsewhere. Do not define +# any compile-time configuration in here, as it won't be applied. +# The block below contains prod specific runtime configuration. + +# ## Using releases +# +# If you use `mix release`, you need to explicitly enable the server +# by passing the PHX_SERVER=true when you start it: +# +# PHX_SERVER=true bin/website start +# +# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` +# script that automatically sets the env var above. +if System.get_env("PHX_SERVER") do + config :website, DemoWeb.Endpoint, server: true +end + +config :website, DemoWeb.Endpoint, http: [port: String.to_integer(System.get_env("PORT", "4000"))] + +if config_env() == :prod do + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + host = System.get_env("PHX_HOST") || "example.com" + + config :website, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") + + config :website, DemoWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + check_origin: [ + "https://#{host}", + "https://www.#{host}" + ], + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0 + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0} + ], + secret_key_base: secret_key_base + + # ## SSL Support + # + # To get SSL working, you will need to add the `https` key + # to your endpoint configuration: + # + # config :website, DemoWeb.Endpoint, + # https: [ + # ..., + # port: 443, + # cipher_suite: :strong, + # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), + # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") + # ] + # + # The `cipher_suite` is set to `:strong` to support only the + # latest and more secure SSL ciphers. This means old browsers + # and clients may not be supported. You can set it to + # `:compatible` for wider support. + # + # `:keyfile` and `:certfile` expect an absolute path to the key + # and cert in disk or a relative path inside priv, for example + # "priv/ssl/server.key". For all supported SSL configuration + # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 + # + # We also recommend setting `force_ssl` in your config/prod.exs, + # ensuring no data is ever sent via http, always redirecting to https: + # + # config :website, DemoWeb.Endpoint, + # force_ssl: [hsts: true] + # + # Check `Plug.SSL` for all available options in `force_ssl`. + + # ## Configuring the mailer + # + # In production you need to configure the mailer to use a different adapter. + # Here is an example configuration for Mailgun: + # + # config :website, Demo.Mailer, + # adapter: Swoosh.Adapters.Mailgun, + # api_key: System.get_env("MAILGUN_API_KEY"), + # domain: System.get_env("MAILGUN_DOMAIN") + # + # Most non-SMTP adapters require an API client. Swoosh supports Req, Hackney, + # and Finch out-of-the-box. This configuration is typically done at + # compile-time in your config/prod.exs: + # + # config :swoosh, :api_client, Swoosh.ApiClient.Req + # + # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. +end diff --git a/website/config/test.exs b/website/config/test.exs new file mode 100644 index 0000000..4bc9e1a --- /dev/null +++ b/website/config/test.exs @@ -0,0 +1,28 @@ +import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :website, DemoWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "E2HJYcgXOpdGGAsp1J7epDZtW0nxvFKTlCRTBd5DkY52UxOWWKJjfUm4/RrR2gzb", + server: false + +# In test we don't send emails +config :website, Demo.Mailer, adapter: Swoosh.Adapters.Test + +# Disable swoosh api client as it is only required for production adapters +config :swoosh, :api_client, false + +# Print only warnings and errors during test +config :logger, level: :warning + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime + +# Enable helpful, but potentially expensive runtime checks +config :phoenix_live_view, + enable_expensive_runtime_checks: true + +# Sort query params output of verified routes for robust url comparisons +config :phoenix, + sort_verified_routes_query_params: true diff --git a/website/lib/website.ex b/website/lib/website.ex new file mode 100644 index 0000000..3f1ac75 --- /dev/null +++ b/website/lib/website.ex @@ -0,0 +1,9 @@ +defmodule Demo do + @moduledoc """ + Demo keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/website/lib/website/application.ex b/website/lib/website/application.ex new file mode 100644 index 0000000..d344fcb --- /dev/null +++ b/website/lib/website/application.ex @@ -0,0 +1,33 @@ +defmodule Demo.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + DemoWeb.Telemetry, + {DNSCluster, query: Application.get_env(:website, :dns_cluster_query) || :ignore}, + {Phoenix.PubSub, name: Demo.PubSub}, + # Start a worker by calling: Demo.Worker.start_link(arg) + # {Demo.Worker, arg}, + # Start to serve requests, typically the last entry + DemoWeb.Endpoint + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Demo.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + DemoWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/website/lib/website/lua_sandbox.ex b/website/lib/website/lua_sandbox.ex new file mode 100644 index 0000000..2e8a9d1 --- /dev/null +++ b/website/lib/website/lua_sandbox.ex @@ -0,0 +1,1536 @@ +defmodule Website.LuaSandbox do + @moduledoc """ + Safe(-ish) execution wrapper around `Lua.eval!/2`. + + Synchronously evaluates user-submitted Lua snippets and captures any + output produced via the `print` builtin. The host VM is sandboxed via + the library's default deny-list (no `io.*`, `os.*`, `require`, + `package`, `load`, etc.). + + This module is intentionally process-free: timeout enforcement and + task lifecycle are the caller's responsibility (the LiveView wraps + `run/1` in `start_async/3` and cancels it on a timer). + """ + + import Lua, only: [sigil_LUA: 2] + + alias Lua.Compiler.Prototype + + @doc """ + Compiles a Lua snippet into a `Lua.Chunk` (without running it) and + returns the chunk plus the disassembled prototype tree. Returns + `{:error, formatted_messages}` if parsing or compilation fails. + """ + @spec compile(String.t()) :: + {:ok, Lua.Chunk.t(), [map()]} | {:error, [String.t()]} + def compile(source) when is_binary(source) do + case Lua.parse_chunk(source) do + {:ok, %Lua.Chunk{prototype: proto} = chunk} -> + {:ok, chunk, disassemble(proto)} + + {:error, messages} -> + {:error, messages} + end + end + + @doc """ + Executes a Lua snippet under a sandboxed VM, capturing `print` output, + returned values, and any error. Returns a `t:result/0` map. + """ + @type result :: %{ + status: :ok | :error | :timeout, + output: String.t(), + returns: [term()], + error: nil | String.t(), + duration_us: non_neg_integer(), + bytecode: [map()] + } + + @spec run(String.t()) :: result() + def run(source) when is_binary(source), do: do_run(source) + + defp do_run(source) do + started = System.monotonic_time(:microsecond) + output_pid = start_output_collector() + + lua = + Lua.new() + |> Lua.set!([:print], fn args -> + line = + args + |> Enum.map(&to_lua_string/1) + |> Enum.join("\t") + + send(output_pid, {:line, line}) + [] + end) + + try do + bytecode = + case Lua.parse_chunk(source) do + {:ok, %Lua.Chunk{prototype: proto}} -> disassemble(proto) + _ -> [] + end + + {results, _lua} = Lua.eval!(lua, source) + + %{ + status: :ok, + output: collect_output(output_pid), + returns: Enum.map(results, &inspect_value/1), + error: nil, + duration_us: System.monotonic_time(:microsecond) - started, + bytecode: bytecode + } + rescue + e in [Lua.CompilerException, Lua.RuntimeException] -> + %{ + status: :error, + output: collect_output(output_pid), + returns: [], + error: e |> Exception.message() |> strip_ansi(), + duration_us: System.monotonic_time(:microsecond) - started, + bytecode: [] + } + + e -> + %{ + status: :error, + output: collect_output(output_pid), + returns: [], + error: e |> Exception.message() |> strip_ansi(), + duration_us: System.monotonic_time(:microsecond) - started, + bytecode: [] + } + end + end + + defp strip_ansi(s) when is_binary(s) do + String.replace(s, ~r/\e\[[\d;]*[\x40-\x7E]/, "") + end + + defp start_output_collector do + spawn_link(fn -> collect_loop([]) end) + end + + defp collect_loop(acc) do + receive do + {:line, line} -> + collect_loop([line | acc]) + + {:dump, from} -> + send(from, {:lines, Enum.reverse(acc)}) + end + end + + defp collect_output(pid) do + send(pid, {:dump, self()}) + + receive do + {:lines, lines} -> Enum.join(lines, "\n") + after + 50 -> "" + end + end + + defp to_lua_string(nil), do: "nil" + defp to_lua_string(true), do: "true" + defp to_lua_string(false), do: "false" + defp to_lua_string(s) when is_binary(s), do: s + defp to_lua_string(n) when is_integer(n), do: Integer.to_string(n) + + defp to_lua_string(n) when is_float(n) do + if n == trunc(n) and abs(n) < 1.0e15 do + "#{trunc(n)}.0" + else + Float.to_string(n) + end + end + + defp to_lua_string({:tref, id}), do: "table: 0x#{Integer.to_string(id, 16)}" + defp to_lua_string(other), do: inspect(other) + + defp inspect_value(v), do: inspect(v, pretty: true, limit: 50) + + # ---- Disassembly ---- + + @doc """ + Walks a `%Lua.Compiler.Prototype{}` (and its nested prototypes) and + returns a flat list of "proto blocks" suitable for rendering in a + compiler-explorer view. + + Each block looks like: + + %{ + index: 0, + name: "main chunk" | "function #1", + param_count: 0, + is_vararg: true, + max_registers: 4, + instructions: [ + %{pc: 0, line: nil, op: :load_env, args: [0], pretty: "load_env r0"}, + ... + ] + } + """ + def disassemble(%Prototype{} = proto) do + {blocks, _} = walk_proto(proto, "main chunk", 0, []) + Enum.reverse(blocks) + end + + defp walk_proto(%Prototype{} = proto, name, next_index, acc) do + {instrs, _last_line} = + Enum.map_reduce(Enum.with_index(proto.instructions), nil, fn {ins, pc}, line -> + case ins do + {:source_line, ln, _file} -> + {%{pc: pc, line: ln, op: :source_line, args: [ln], pretty: "; line #{ln}"}, ln} + + tuple when is_tuple(tuple) -> + [op | args] = Tuple.to_list(tuple) + {%{pc: pc, line: line, op: op, args: args, pretty: pretty_ins(op, args)}, line} + + atom when is_atom(atom) -> + {%{pc: pc, line: line, op: atom, args: [], pretty: Atom.to_string(atom)}, line} + end + end) + + block = %{ + index: next_index, + name: name, + param_count: proto.param_count, + is_vararg: proto.is_vararg, + max_registers: proto.max_registers, + upvalue_count: length(proto.upvalue_descriptors), + source: proto.source, + lines: proto.lines, + instructions: instrs + } + + acc = [block | acc] + next_index = next_index + 1 + + Enum.reduce(Enum.with_index(proto.prototypes), {acc, next_index}, fn {child, child_local_idx}, + {acc, next_idx} -> + name = "function ##{next_idx} (proto[#{child_local_idx}])" + walk_proto(child, name, next_idx, acc) + end) + end + + defp pretty_ins(op, args), do: "#{op} #{format_op_args(op, args)}" + + # All-register triadic arithmetic and comparison ops + defp format_op_args(op, [a, b, c]) + when op in [ + :add, + :subtract, + :multiply, + :divide, + :floor_divide, + :modulo, + :power, + :concatenate, + :bitwise_and, + :bitwise_or, + :bitwise_xor, + :shift_left, + :shift_right, + :equal, + :less_than, + :less_equal + ], + do: "r#{a}, r#{b}, r#{c}" + + defp format_op_args(op, [a, b]) + when op in [:negate, :not, :length, :bitwise_not, :move], + do: "r#{a}, r#{b}" + + defp format_op_args(:load_constant, [d, val]), do: "r#{d}, #{format_lit(val)}" + defp format_op_args(:load_nil, [d, count]), do: "r#{d}, #{count}" + defp format_op_args(:load_boolean, [d, v]), do: "r#{d}, #{v}" + defp format_op_args(:load_env, [d]), do: "r#{d}" + defp format_op_args(:get_upvalue, [d, idx]), do: "r#{d}, up[#{idx}]" + defp format_op_args(:set_upvalue, [idx, s]), do: "up[#{idx}], r#{s}" + defp format_op_args(:get_open_upvalue, [d, r]), do: "r#{d}, r#{r}" + defp format_op_args(:set_open_upvalue, [r, s]), do: "r#{r}, r#{s}" + defp format_op_args(:get_global, [d, name]), do: ~s|r#{d}, _G["#{name}"]| + defp format_op_args(:set_global, [name, s]), do: ~s|_G["#{name}"], r#{s}| + defp format_op_args(:new_table, [d, a, h]), do: "r#{d}, array=#{a}, hash=#{h}" + defp format_op_args(:get_table, [d, t, k | _]), do: "r#{d}, r#{t}[#{pretty_arg(k)}]" + defp format_op_args(:set_table, [t, k, v | _]), do: "r#{t}[#{pretty_arg(k)}], r#{v}" + defp format_op_args(:get_field, [d, t, name | _]), do: ~s|r#{d}, r#{t}.#{name}| + defp format_op_args(:set_field, [t, name, v | _]), do: ~s|r#{t}.#{name}, r#{v}| + + defp format_op_args(:set_list, [t, s, c, o]), + do: "r#{t}, start=#{s}, count=#{count(c)}, off=#{o}" + + defp format_op_args(:call, [b, ac, rc | _]), + do: "r#{b}, args=#{count(ac)}, results=#{count(rc)}" + + defp format_op_args(:tail_call, [b, ac | _]), do: "r#{b}, args=#{count(ac)}" + defp format_op_args(:return, [b, c]), do: "r#{b}, count=#{count(c)}" + defp format_op_args(:return_vararg, _), do: "(varargs)" + defp format_op_args(:vararg, [b, c]), do: "r#{b}, count=#{count(c)}" + defp format_op_args(:self, [b, o, name | _]), do: "r#{b}, r#{o}, .#{name}" + defp format_op_args(:closure, [d, idx]), do: "r#{d}, proto[#{idx}]" + defp format_op_args(:test, [r | _]), do: "r#{r}" + defp format_op_args(:test_true, [r | _]), do: "r#{r}" + defp format_op_args(:test_and, [d, s | _]), do: "r#{d}, r#{s}" + defp format_op_args(:test_or, [d, s | _]), do: "r#{d}, r#{s}" + defp format_op_args(:numeric_for, [b | _]), do: "r#{b}" + defp format_op_args(:generic_for, [b, vc | _]), do: "r#{b}, vars=#{vc}" + defp format_op_args(:scope, [n | _]), do: "registers=#{n}" + defp format_op_args(:source_line, [ln]), do: "line #{ln}" + defp format_op_args(_op, args), do: args |> Enum.map(&pretty_arg/1) |> Enum.join(", ") + + defp pretty_arg({:constant, val}), do: format_lit(val) + defp pretty_arg({:global, name}), do: ~s|<#{name}>| + defp pretty_arg(atom) when is_atom(atom), do: inspect(atom) + defp pretty_arg(n) when is_integer(n), do: Integer.to_string(n) + defp pretty_arg(other), do: inspect(other, limit: 25) + + defp format_lit(val) when is_binary(val), do: inspect(val) + defp format_lit(val), do: inspect(val, limit: 20) + + defp count({:multi, n}), do: "multi(#{n})" + defp count(:varargs), do: "..." + defp count(n) when is_integer(n), do: Integer.to_string(n) + defp count(other), do: inspect(other) + + @doc """ + Returns the Lua snippets that are rendered on the marketing home page + (currently the compiler-explorer card). Same shape as `examples/0` so + the example test loop can iterate over every snippet uniformly. + """ + def home_snippets do + [ + %{ + id: "home-fib", + title: "Hero compiler-explorer", + source: """ + local function fib(n) + if n < 2 then return n end + return fib(n - 1) + + fib(n - 2) + end + + return fib(15) + """ + } + ] + end + + @doc """ + Returns a list of canonical example snippets for the playground/tour. + Each snippet has an id, title, blurb, and Lua source. + """ + def examples do + [ + %{ + id: "hello", + title: "Hello, Lua", + blurb: "Your first Lua program on the BEAM.", + source: ~s|print("Hello, Lua on the BEAM!")\nreturn 42\n|, + chunk: ~LUA""" + print("Hello, Lua on the BEAM!") + return 42 + """c + }, + %{ + id: "fib", + title: "Recursive Fibonacci", + blurb: "Classic recursion. Watch the closure prototype and tail-calls in the bytecode.", + source: """ + local function fib(n) + if n < 2 then return n end + return fib(n - 1) + fib(n - 2) + end + + for i = 0, 10 do + print(i, fib(i)) + end + + return fib(15) + """, + chunk: ~LUA""" + local function fib(n) + if n < 2 then return n end + return fib(n - 1) + fib(n - 2) + end + + for i = 0, 10 do + print(i, fib(i)) + end + + return fib(15) + """c + }, + %{ + id: "tables", + title: "Tables & iteration", + blurb: "Lua's one true data structure. Mix array and hash parts freely.", + source: """ + local people = { + { name = "Joe Armstrong", role = "co-creator of Erlang" }, + { name = "Robert Virding", role = "co-creator of Erlang" }, + { name = "Mike Williams", role = "co-creator of Erlang" }, + { name = "José Valim", role = "creator of Elixir" }, + { name = "Chris McCord", role = "creator of Phoenix" }, + } + + for i, p in ipairs(people) do + print(i, p.name, "->", p.role) + end + + return #people + """, + chunk: ~LUA""" + local people = { + { name = "Joe Armstrong", role = "co-creator of Erlang" }, + { name = "Robert Virding", role = "co-creator of Erlang" }, + { name = "Mike Williams", role = "co-creator of Erlang" }, + { name = "José Valim", role = "creator of Elixir" }, + { name = "Chris McCord", role = "creator of Phoenix" }, + } + + for i, p in ipairs(people) do + print(i, p.name, "->", p.role) + end + + return #people + """c + }, + %{ + id: "closures", + title: "Closures & upvalues", + blurb: "Counter factory. See how upvalues are captured in the bytecode.", + source: """ + local function make_counter(start) + local n = start or 0 + return function() + n = n + 1 + return n + end + end + + local c = make_counter(10) + print(c(), c(), c()) + return c() + """, + chunk: ~LUA""" + local function make_counter(start) + local n = start or 0 + return function() + n = n + 1 + return n + end + end + + local c = make_counter(10) + print(c(), c(), c()) + return c() + """c + }, + %{ + id: "patterns", + title: "String patterns", + blurb: "Lua's tiny but mighty pattern engine. No regex needed.", + source: """ + local s = "the quick brown fox" + for word in string.gmatch(s, "%a+") do + print(word, #word) + end + + return (string.gsub(s, "(%a+)", function(w) + return w:upper() + end)) + """, + chunk: ~LUA""" + local s = "the quick brown fox" + for word in string.gmatch(s, "%a+") do + print(word, #word) + end + + return (string.gsub(s, "(%a+)", function(w) + return w:upper() + end)) + """c + }, + %{ + id: "metatables", + title: "Metatables", + blurb: "Operator overloading via __add. Lua's secret weapon for DSLs.", + source: """ + local Vec = {} + Vec.__index = Vec + Vec.__add = function(a, b) + return setmetatable({ x = a.x + b.x, y = a.y + b.y }, Vec) + end + Vec.__tostring = function(v) + return string.format("(%g, %g)", v.x, v.y) + end + + local function vec(x, y) + return setmetatable({ x = x, y = y }, Vec) + end + + local a = vec(1, 2) + local b = vec(3, 4) + print(tostring(a + b)) + return (a + b).x, (a + b).y + """, + chunk: ~LUA""" + local Vec = {} + Vec.__index = Vec + Vec.__add = function(a, b) + return setmetatable({ x = a.x + b.x, y = a.y + b.y }, Vec) + end + Vec.__tostring = function(v) + return string.format("(%g, %g)", v.x, v.y) + end + + local function vec(x, y) + return setmetatable({ x = x, y = y }, Vec) + end + + local a = vec(1, 2) + local b = vec(3, 4) + print(tostring(a + b)) + return (a + b).x, (a + b).y + """c + }, + %{ + id: "sandbox", + title: "Sandbox escape", + blurb: + "Watch the VM refuse to run dangerous stdlib calls. This is the reason it's agent-ready.", + source: """ + -- The host process never had to defend itself. + local ok, err = pcall(function() + return os.execute("rm -rf /") + end) + print("os.execute ok?", ok) + print("err:", err) + + local ok2 = pcall(io.open, "/etc/passwd", "r") + print("io.open ok?", ok2) + + return ok, ok2 + """, + chunk: ~LUA""" + -- The host process never had to defend itself. + local ok, err = pcall(function() + return os.execute("rm -rf /") + end) + print("os.execute ok?", ok) + print("err:", err) + + local ok2 = pcall(io.open, "/etc/passwd", "r") + print("io.open ok?", ok2) + + return ok, ok2 + """c + }, + %{ + id: "error", + title: "Compile error", + blurb: "See the friendly compiler error path.", + expect: :compile_error, + source: """ + local x = 10 + local y = 20 + return x + + """ + }, + %{ + id: "runtime-error", + title: "Runtime error", + blurb: "Watch the VM blame the offending local by name, with a real stack trace.", + expect: :runtime_error, + source: """ + local function greet(person) + return "hi, " .. person.name + end + + local visitors = { + { name = "Ada" }, + { name = "Joe" }, + nil, -- oops! + } + + print(greet(visitors[1])) + print(greet(visitors[2])) + print(greet(visitors[3])) -- boom + """, + chunk: ~LUA""" + local function greet(person) + return "hi, " .. person.name + end + + local visitors = { + { name = "Ada" }, + { name = "Joe" }, + nil, -- oops! + } + + print(greet(visitors[1])) + print(greet(visitors[2])) + print(greet(visitors[3])) -- boom + """c + } + ] + end + + @doc """ + Returns the pre-disassembled bytecode blocks for an example by id. + + Examples carry a `~LUA"..."c` chunk compiled at module compile time, + so this is a cheap disassembly walk — no parsing. Returns `[]` for + unknown ids or examples that intentionally omit a chunk (e.g. the + compile-error showcase). + """ + @spec example_blocks(String.t()) :: [map()] + def example_blocks(id) when is_binary(id) do + case Enum.find(examples(), &(&1.id == id)) do + %{chunk: %Lua.Chunk{prototype: proto}} -> disassemble(proto) + _ -> [] + end + end + + @doc """ + Ordered chapter metadata for the tour. Each entry is `{slug, title}`. + + The sidebar in `DemoWeb.TourLive` groups lessons by `lesson.chapter` + using this list as the section order. + """ + def chapters do + [ + {:basics, "Language basics"}, + {:idioms, "Idioms & deeper language"}, + {:stdlib, "The standard library"}, + {:integration, "Lua.ex integration"}, + {:internals, "Under the hood"} + ] + end + + @doc """ + Human title for a chapter slug. Returns `nil` for unknown slugs so the + template can no-op rather than crashing on stale data. + """ + def chapter_title(chapter) when is_atom(chapter) do + Enum.find_value(chapters(), fn {slug, title} -> slug == chapter && title end) + end + + @doc """ + Ordered list of tour lessons. + + Each lesson is a map with at least `:slug`, `:title`, `:objective`, + `:body`, `:chapter`. Optional keys: `:source` (Lua snippet for the + runnable editor; omit for prose-only lessons), `:elixir_source` + (the host-side companion shown above the Lua pane in Chapter IV), + `:exercise`, `:see_also` (list of related slugs), `:runnable` + (default `true`; `false` makes the editor display-only and skips + it in `lua_examples_test.exs`), and `:expect` + (`:ok | :compile_error | :runtime_error`, default `:ok`). + + Quality rubric (enforced in `lua_examples_test.exs`): + + * `:title` ≤ 32 chars + * `:body` ≤ 90 words + * `:source` ≤ 18 lines (≤ 12 for `chapter: :integration`) + * Every concept named in `:objective` and `:body` must be + demonstrated in `:source` (human-reviewed) + * `:exercise` must require a concept beyond what `:source` shows + """ + def tour_lessons do + [ + # ----- Chapter I: Language basics ----- + %{ + slug: "values", + title: "Values & types", + chapter: :basics, + objective: "Recognise Lua's eight types and how integer/float numbers behave.", + body: """ + Lua has just eight types: `nil`, `boolean`, `number`, `string`, + `function`, `userdata`, `thread`, and `table`. Numbers split into + two subtypes (64-bit *integer* and *float*) and Lua tracks which + is which. Strings are interned immutable byte sequences. + """, + exercise: + "Add a line that prints `type(3.0 == 3)` and predict the result before running.", + source: """ + print(type(nil), type(true), type(1), type(1.5)) + print(type("hi"), type(print), type({})) + print(math.type(1), math.type(1.0)) -- integer float + return 1 + 2, 1 / 2, 1 // 2 + """, + see_also: ["math-and-numbers"] + }, + %{ + slug: "variables", + title: "Locals & assignment", + chapter: :basics, + objective: "Declare locals, swap with one statement, and scope with `do…end`.", + body: """ + Bare assignment creates a *global*, which is almost never what you + want. Use `local` for everything except top-level configuration. + Multiple assignment is first-class: `a, b = 1, 2` (and + `a, b = b, a` swaps without a temp). `do…end` opens a fresh + scope; locals declared inside vanish after `end`. + """, + exercise: + "Drop the `local` keyword from `x, y` and re-run. Lua quietly created a global. Print `_G.x` to confirm.", + source: """ + local x, y = 1, 2 + print(x, y) + + x, y = y, x -- swap with no temp + print(x, y) + + local outer = "outside" + do + local inner = "inside the block" + print(outer, inner) + end + print(outer, inner) -- `inner` is nil here + return x, y + """ + }, + %{ + slug: "truthiness", + title: "Truthiness & `and`/`or`", + chapter: :basics, + objective: "Use Lua's truthiness rule and the operand-returning behaviour of `and`/`or`.", + body: """ + Only `nil` and `false` are falsy. `0`, `""`, and even `{}` are + all truthy, which surprises everyone. `and`/`or` don't return + booleans; they return whichever *operand* decided the result. + `x or default` is the canonical default-value idiom; + `(cond and a) or b` is Lua's ternary expression. + """, + exercise: + "Make `(false and \"a\") or \"b\"` return `\"b\"`. Now make it return `false` when the first branch is false. Why is `(c and a) or b` unsafe when `a` can be `false`?", + source: """ + print(0 and "zero is truthy") + print("" and "empty string is truthy") + print({} and "empty table is truthy too") + + local name = nil + print(name or "anonymous") -- "anonymous" + + local age = 21 + local label = (age >= 18 and "adult") or "minor" + print(label) + return label + """ + }, + %{ + slug: "control-flow", + title: "Control flow", + chapter: :basics, + objective: "Use `if`, `while`, `repeat..until`, and numeric `for` with explicit steps.", + body: """ + `if`/`elseif`/`else` are statements, not expressions. `while` + checks before entering the body; `repeat..until` checks after, + so the body always runs at least once. Numeric `for` takes + `start, stop[, step]`; a negative step counts down. See + *Iteration* for the generic `for`. + """, + exercise: + "Rewrite the `while` loop as a `repeat..until`. Then flip the initial value of `i` to `5`. `while` skips, `repeat` still runs once.", + source: """ + local function sign(n) + if n > 0 then return 1 + elseif n < 0 then return -1 + else return 0 end + end + + local i = 0 + while i < 3 do i = i + 1; print("while", i) end + + repeat + i = i - 1 + until i == 0 + print("after repeat: i =", i) + + for j = 10, 6, -2 do print("for", j) end + return sign(-42) + """ + }, + %{ + slug: "tables", + title: "Tables are everything", + chapter: :basics, + objective: + "Build arrays, records, and nested tables, and know what `#t` actually counts.", + body: """ + Tables are *the* data structure: arrays, hash maps, records, + modules, all tables. Indexed from `1` by convention. `#t` is + defined for *sequences* (`{v1, v2, ..., vn}` with no nils) and + unspecified for sparse tables. Mix array and hash keys in one + literal. + """, + exercise: + "Add `user.prefs.shortcuts = { save = \"⌘S\" }` and print `user.prefs.shortcuts.save`. Then add `user[5] = \"trailing\"`. What does `#user` return now?", + source: """ + local user = { + "first row", -- user[1] + "second row", -- user[2] + name = "Ada", -- user.name + prefs = { theme = "dark" }, -- nested table + } + + print(user[1], user.name, user.prefs.theme, #user) + + user.prefs.theme = "light" + print(user.prefs.theme) + + local sparse = { [1] = "a", [3] = "c" } + print("#sparse =", #sparse, "(implementation-defined)") + return user.name + """ + }, + %{ + slug: "iteration", + title: "Iteration", + chapter: :basics, + objective: + "Use `pairs` for hash walks, `ipairs` for sequences, and write your own iterator.", + body: """ + `ipairs` walks the array part from `1` and stops at the first + `nil`. `pairs` walks every key in unspecified order. The generic + `for` accepts any *iterator function*. `range(n)` below shows + how to write one yourself: return `(iter, state, control)` and + Lua takes care of the rest. + """, + exercise: + "Add a `step` parameter to `range` so `range(10, 2)` yields `1, 3, 5, 7, 9` with their squares.", + source: """ + local t = { 10, 20, 30, name = "trio" } + for i, v in ipairs(t) do print("ipairs", i, v) end + for k, v in pairs(t) do print("pairs ", k, v) end + + local function range(n) + return function(_, i) + i = i + 1 + if i <= n then return i, i * i end + end, nil, 0 + end + + for i, sq in range(4) do print("range", i, sq) end + return "done" + """, + see_also: ["coroutines", "varargs"] + }, + %{ + slug: "functions", + title: "First-class functions", + chapter: :basics, + objective: "Pass and return functions, capture multiple returns, and discard with `_`.", + body: """ + Functions are values: pass them, return them, store them in + tables. Multiple return values are first-class. Assigning fewer + names drops extras (`local q = divmod(17, 5)`). Use `_` as a + discard. Only the *last* call in an expression list flattens; + wrap in parens to truncate to one. + """, + exercise: + "Wrap the last call as `(divmod(100, 7))`. The remainder vanishes from the return; the extra parens force single-value context.", + source: """ + local function divmod(a, b) + return a // b, a % b + end + + local q, r = divmod(17, 5) + print("q, r =", q, r) + + local _, only_r = divmod(17, 5) + print("only r =", only_r) + + -- Only the last call flattens; otherwise extras are dropped. + local pair = { divmod(17, 5) } + print("#pair =", #pair, pair[1], pair[2]) + return divmod(100, 7) + """ + }, + %{ + slug: "varargs", + title: "Varargs & multiple returns", + chapter: :basics, + objective: "Use `...` to accept variable arguments and forward them with `select`.", + body: """ + A function declared with `...` receives any number of extra + arguments. `select("#", ...)` is the count; `select(n, ...)` is + the tail starting at position `n`. Multiple return values + flatten when they're the last expression in a call. + """, + exercise: + "Add a `min(...)` function next to `sum` that returns the smallest argument. Use `math.huge` as the seed.", + source: """ + local function sum(...) + local n = select("#", ...) + local total = 0 + for i = 1, n do total = total + select(i, ...) end + return total, n + end + + print(sum(1, 2, 3, 4, 5)) -- 15, 5 + print(sum()) -- 0, 0 + return sum(10, 20, 30) + """ + }, + + # ----- Chapter II: Idioms & deeper language ----- + %{ + slug: "closures", + title: "Closures & upvalues", + chapter: :idioms, + objective: + "Capture an outer-scope binding and watch the `closure` op build the function at runtime.", + body: """ + Inner functions capture outer locals *by reference*. These + captured bindings are called *upvalues*. Two closures over the + same local share that storage. Run this snippet and toggle + Bytecode to see the `closure` opcode and the upvalue + descriptors on the inner prototype. + """, + exercise: + "Make `make_adder` return *two* closures, one that adds and one that subtracts. They should share the same `n`.", + source: """ + local function make_adder(n) + return function(x) return x + n end + end + + local add5 = make_adder(5) + print(add5(10), add5(100)) + return add5(0) + """, + see_also: ["bytecode", "method-syntax"] + }, + %{ + slug: "method-syntax", + title: "Method syntax: `:` vs `.`", + chapter: :idioms, + objective: "Read OO-style Lua and know when `self` is implicitly passed.", + body: """ + `obj:method(args)` is sugar for `obj.method(obj, args)`. The + colon implicitly threads `obj` as the first argument. Use `:` + when calling methods, and `function T:foo(...)` when declaring + them. The two forms below produce the same bytecode. + """, + exercise: "Toggle Bytecode and compare the disassembly for the two `hello` calls.", + source: """ + local Greeter = { lang = "en" } + + function Greeter:hello(name) + return self.lang .. ":hello " .. name + end + + local g = Greeter + print(g:hello("Ada")) -- method call + print(g.hello(g, "Joe")) -- equivalent + return g:hello("Linus") + """ + }, + %{ + slug: "metatables-index", + title: "Metatables: `__index`", + chapter: :idioms, + objective: + "Use `__index` (table or function) for fallback lookup and single-inheritance chains.", + body: """ + Every table can have a *metatable*. When a key is missing on + `t`, Lua consults `t`'s metatable's `__index`. If `__index` is a + table, lookup recurses there. That's how OO inheritance works. + If `__index` is a function, it's called with `(t, key)`. That's + how computed/lazy lookups work. + """, + exercise: + "Toggle Bytecode and find the `get_table` op for `rex.kingdom`. It misses on `rex` and `Dog` and lands on `Animal` via two metatable hops.", + source: """ + local Animal = { kingdom = "Animalia" } + function Animal:describe() + return self.name .. " is a " .. self.species + end + + -- Dog inherits from Animal; Rex inherits from Dog. + local Dog = setmetatable({ species = "dog" }, { __index = Animal }) + local rex = setmetatable({ name = "Rex" }, { __index = Dog }) + print(rex.kingdom) -- found two hops up on Animal + print(rex:describe()) -- method found on Animal + + -- __index as a function: compute on demand. + local squares = setmetatable({}, { __index = function(_, n) return n * n end }) + print(squares[7], squares[12]) + return rex:describe() + """, + see_also: ["metatables-ops", "bytecode"] + }, + %{ + slug: "metatables-ops", + title: "Operator overloading", + chapter: :idioms, + objective: + "Implement `__add`, `__eq`, `__tostring`, and `__call` so a Vector feels like a native value.", + body: """ + Metamethods customise operators (`__add`, `__sub`, `__mul`, + `__div`, `__unm`, `__pow`, `__concat`), equality (`__eq`), + ordering (`__lt`, `__le`), length (`__len`), stringification + (`__tostring`), and the call protocol (`__call`). Pair them + with `__index = self` to make instance methods discoverable. + """, + exercise: + "Add a `__sub` metamethod and a `:dot(other)` method. Use them: `print((a - b):dot(a + b))`.", + source: """ + local Vec = {} + Vec.__index = Vec + Vec.__add = function(a, b) return Vec.new(a.x + b.x, a.y + b.y) end + Vec.__eq = function(a, b) return a.x == b.x and a.y == b.y end + Vec.__tostring = function(v) return string.format("(%g, %g)", v.x, v.y) end + Vec.__call = function(_, x, y) return Vec.new(x, y) end + + function Vec.new(x, y) return setmetatable({ x = x, y = y }, Vec) end + function Vec:length() return math.sqrt(self.x ^ 2 + self.y ^ 2) end + + local a, b = Vec.new(1, 2), Vec.new(3, 4) + print(tostring(a + b)) + print((a + b) == Vec.new(4, 6)) + print(string.format("|a| = %.3f", a:length())) + return tostring(a + b) + """, + see_also: ["metatables-index"] + }, + %{ + slug: "errors", + title: "Errors, `pcall`, `xpcall`", + chapter: :idioms, + objective: + "Raise errors as strings *or* tables, catch them with `pcall`, and add a handler with `xpcall`.", + body: """ + `error(value)` raises. The value is usually a string, but a + table works and is great for structured failures. `pcall(f, …)` + returns `(true, ret…)` or `(false, err)`. `xpcall(f, handler, + …)` lets you attach context (e.g. a traceback) *before* the + stack unwinds. + """, + exercise: + "Replace `handler` with one that returns `{ wrapped = true, original = tostring(e) }` and read `wrapped.wrapped` from the caller side.", + source: """ + local function risky(x) + if x < 0 then error("negative: " .. x) end + if x == 0 then error({ code = "ZERO", retry = false }) end + return math.sqrt(x) + end + + local ok, err = pcall(risky, -1) + print(ok, err) + + local ok2, payload = pcall(risky, 0) + print(ok2, payload.code, payload.retry) + + local function handler(e) return "oops: " .. tostring(e) end + local ok3, wrapped = xpcall(risky, handler, -9) + print(ok3, wrapped) + return pcall(risky, 16) -- true, 4.0 + """, + see_also: ["errors-host"] + }, + %{ + slug: "coroutines", + title: "Coroutines (preview)", + chapter: :idioms, + runnable: false, + objective: + "Read the coroutine API and see why it's the engine behind stateful iterators.", + body: """ + Coroutines are cooperative threads inside a single OS thread: + `create` builds one, `resume` runs it, `yield` suspends it and + passes values back to the caller. They're the canonical engine + for stateful iterators and generator pipelines. The snippet + below is canonical Lua 5.3. Coroutines are on the Lua.ex + roadmap, so this lesson is read-only for now. + """, + exercise: + "Re-read *Iteration*: `range(n)` reached the same generator-shape result by capturing state in a closure instead of yielding from a coroutine.", + source: """ + local function producer(n) + for i = 1, n do + coroutine.yield(i * i) + end + end + + -- coroutine.wrap returns an iterator-shaped function. + local squares = coroutine.wrap(function() producer(5) end) + for v in squares do print(v) end + + -- coroutine.create returns a handle you drive with resume. + local co = coroutine.create(function() + print("step 1"); coroutine.yield() + print("step 2"); coroutine.yield() + print("step 3") + end) + coroutine.resume(co); coroutine.resume(co); coroutine.resume(co) + """, + see_also: ["iteration"] + }, + + # ----- Chapter III: The standard library ----- + %{ + slug: "strings", + title: "Strings & patterns", + chapter: :stdlib, + objective: + "Format, slice, and concatenate strings, then capture matches with `gmatch` and `gsub`.", + body: """ + `..` concatenates, `#s` is byte length (not codepoints), + `string.format` is `printf`. Patterns are *not* regex: `%a` + letters, `%d` digits, `%s` spaces, `.` any, `%p` punctuation. + Quantifiers: `*` 0+, `+` 1+, `?` optional, `-` shortest match. + `gmatch` yields each match; `gsub` rewrites with a string or + function. + """, + exercise: + "Use `gsub` with a function replacement to capitalise every word: `\"hello there\"` → `\"Hello There\"`. Hint: pattern `(%a)(%a*)`.", + source: """ + local s = "Hello, World!" + print(#s, s:upper(), s:sub(1, 5)) + print(string.format("len=%d, first=%q", #s, s:sub(1, 5))) + + -- Captures: pattern groups in parens become return values. + print(string.match("admin42", "(%a+)(%d+)")) + print(string.match("2026-05-25", "(%d+)%-(%d+)%-(%d+)")) + + -- gmatch iterates every match. + for word in string.gmatch("the quick brown fox", "%a+") do + print(word) + end + + -- gsub with a function replacement. + print((string.gsub("snake_case", "_(%a)", function(c) return c:upper() end))) + return string.format("%d words", 4) + """ + }, + %{ + slug: "tables-stdlib", + title: "Working with tables", + chapter: :stdlib, + objective: + "Use `table.insert`, `remove`, `sort`, `concat`, and `unpack` to manipulate sequences.", + body: """ + `table.insert(t, v)` pushes; `table.insert(t, pos, v)` inserts + at `pos`. `table.remove(t)` pops the tail. `table.concat(t, + sep)` is the fast string-buffer pattern. `table.sort(t[, cmp])` + sorts in place. `table.unpack(t)` spreads a table back into a + value list. + """, + exercise: + "Sort `{3, 1, 4, 1, 5, 9, 2, 6}` so even numbers come before odd, and within each group ascending. Use a single comparator function.", + source: """ + local t = { "b", "d", "a" } + table.insert(t, "c") -- push to tail + table.insert(t, 1, "_") -- insert at head + print(table.concat(t, ",")) + + table.sort(t) -- ascending strings + print(table.concat(t, ",")) + + table.sort(t, function(a, b) return a > b end) + print(table.concat(t, ",")) + + local popped = table.remove(t) -- pop tail + print("popped:", popped) + + local x, y, z = table.unpack({ 10, 20, 30 }) + return x + y + z -- 60 + """ + }, + %{ + slug: "math-and-numbers", + title: "Numbers: int & float", + chapter: :stdlib, + objective: + "Tell integers from floats, convert between them, and know which operator returns which.", + body: """ + Lua 5.3 has two number subtypes: 64-bit *integer* and *float* + (double). `/` always returns a float; `//` floor-divides and + stays integer when both operands are integers. `math.type(x)` + reports the subtype; `math.tointeger(x)` narrows or returns + `nil` when the value isn't representable as an integer. + """, + exercise: + "Predict `math.type(1/1)` and `math.type(2^10)`. Then run and confirm. Why does `2^10` *not* return an integer?", + source: """ + print(math.type(1), math.type(1.0)) -- integer float + print(math.type(1 + 1), math.type(1 + 1.0)) -- integer float + + print(7 / 2) -- 3.5 (/ always returns float) + print(7 // 2) -- 3 (int // int = int) + print(7.0 // 2) -- 3.0 (any float = float) + print(-7 // 2) -- -4 (floor, not truncate) + + print(math.tointeger(3.0)) -- 3 + print(math.tointeger(3.5)) -- nil (not representable) + print(tonumber("42")) -- 42 (string -> number) + + return math.maxinteger, math.mininteger + """, + see_also: ["values"] + }, + %{ + slug: "sandbox", + title: "The sandbox", + chapter: :stdlib, + objective: "See what's blocked by default. This is why the VM is agent-ready.", + body: """ + This playground runs in a sandboxed VM: dangerous functions + like `os.execute`, `io.open`, `require`, and `load` are stubbed + to raise. Those are the libraries a host application typically + locks down before exposing a scripting surface to users or + LLMs. *Chapter IV* shows how to expose your *own* safe + functions back into Lua. + """, + exercise: + "Try your own escape: `io.open('/etc/passwd', 'r')` or `require('os')`. Same outcome. Now check the *Embedding* lesson to see how to allow specific paths through.", + source: """ + local ok, err = pcall(function() + return os.execute("rm -rf /") + end) + print("os.execute ok?", ok) + print("err:", err) + + -- The names exist, but they refuse to do real work: + local ok2 = pcall(io.open, "/etc/passwd", "r") + print("io.open ok?", ok2) + + return ok, ok2 + """, + see_also: ["host-intro", "deflua"] + }, + + # ----- Chapter IV: Lua.ex integration (dual-pane) ----- + %{ + slug: "host-intro", + title: "Embedding Lua in Elixir", + chapter: :integration, + objective: "Initialize a sandboxed VM and evaluate a Lua snippet from your Elixir app.", + body: """ + `Lua.new()` returns a sandboxed VM. `Lua.eval!/2` runs a + snippet and returns `{results, lua}`. The updated VM threads + through, so every call yields a state you keep using. Multiple + return values from Lua come back as an Elixir list. + """, + exercise: + "Change the snippet to return three values, including a table `{ ok = true }`. The Elixir caller gets a 3-element list with the table at the end.", + elixir_source: """ + lua = Lua.new() + + {results, lua} = Lua.eval!(lua, \"\"\" + return 1 + 2, "hello" + \"\"\") + + results + # => [3, "hello"] + """, + source: """ + return 1 + 2, "hello" + """, + see_also: ["set-and-get", "call-function"] + }, + %{ + slug: "set-and-get", + title: "Reading & writing state", + chapter: :integration, + objective: + "Define globals from Elixir with `Lua.set!` and read them back with `Lua.get!`.", + body: """ + `Lua.set!(lua, [:greeting], "hi")` writes a global. `Lua.get!( + lua, [:total])` reads one. Paths nest into tables: + `Lua.set!(lua, [:cfg, :api_key], …)` writes to `cfg.api_key`. + After `eval!`, anything the script wrote to a global is + readable from Elixir. + """, + exercise: + "Add a nested-path read on the Elixir side: `Lua.set!(lua, [:cfg, :rate], 0.08)`, then use `cfg.rate` in the Lua snippet to compute tax.", + elixir_source: """ + lua = + Lua.new() + |> Lua.set!([:discount_pct], 15) + |> Lua.set!([:prices], [10, 20, 30]) + + {_, lua} = Lua.eval!(lua, source) + Lua.get!(lua, [:final_total]) + # => 51.0 + """, + source: """ + -- discount_pct and prices come from the Elixir host (Lua.set!). + -- The stubs let this snippet run standalone in the playground. + discount_pct = discount_pct or 15 + prices = prices or { 10, 20, 30 } + + local total = 0 + for _, p in ipairs(prices) do + total = total + p * (1 - discount_pct / 100) + end + + final_total = total -- host reads this back with Lua.get! + return total + """, + see_also: ["host-intro", "deflua"] + }, + %{ + slug: "deflua", + title: "Exposing Elixir with `deflua`", + chapter: :integration, + objective: + "Define a module with `use Lua.API` + `deflua`, then load it with `Lua.load_api/2`.", + body: """ + `deflua` turns an Elixir function into a Lua-callable. The + optional `scope:` puts it under a namespace, so `scope: + \"pricing\"` exposes `pricing.discount(…)`. + `Lua.load_api(lua, MyModule)` registers the module on the VM. + The Lua side just calls a function; the Elixir side runs the + body. + """, + exercise: + "Add `deflua tax(amount, rate)` to `Pricing`, returning `amount * rate`. Call `pricing.tax(85, 0.08)` from Lua. Remember to extend the playground stub too.", + elixir_source: """ + defmodule Pricing do + use Lua.API, scope: "pricing" + + deflua discount(amount, pct) do + amount * (1 - pct / 100) + end + end + + lua = Lua.new() |> Lua.load_api(Pricing) + {[total], _} = Lua.eval!(lua, ~S|return pricing.discount(100, 15)|) + # total = 85.0 + """, + source: """ + -- In your host, Lua.load_api(Pricing) registers `pricing.discount`. + -- Stub it here so the snippet runs in the standalone playground. + pricing = pricing or { + discount = function(amount, pct) return amount * (1 - pct / 100) end, + } + + print(string.format("$%d after 15%%: $%.2f", 100, pricing.discount(100, 15))) + print(string.format("$%d after 30%%: $%.2f", 80, pricing.discount(80, 30))) + return pricing.discount(250, 10) + """, + see_also: ["put-private", "call-function"] + }, + %{ + slug: "call-function", + title: "Calling Lua from Elixir", + chapter: :integration, + objective: + "Define a Lua function in a snippet, then invoke it from Elixir with `call_function!`.", + body: """ + `Lua.call_function!(lua, [:name], [arg1, arg2])` calls a Lua + function from Elixir. Path tuples work too: `[:string, :upper]` + reaches stdlib. This is the pattern when your script is + *configuration*: it defines hooks (`on_request`, `pricing`, …) + and your Elixir code drives the loop. + """, + exercise: + "Define a second function `farewell(name)` that returns `\"bye, \" .. name`. The host could now drive a request/response cycle through two hooks defined in one script.", + elixir_source: """ + {_, lua} = Lua.eval!(Lua.new(), \"\"\" + function greet(name) return "hi, " .. name end + \"\"\") + + Lua.call_function!(lua, [:greet], ["Ada"]) + # => ["hi, Ada"] + + # Stdlib paths work too: + Lua.call_function!(lua, [:string, :upper], ["heya"]) + # => ["HEYA"] + """, + source: """ + -- The host will call greet/1 by name after this snippet runs. + function greet(name) + return "hi, " .. name + end + + -- For the playground, drive it ourselves so you see the output. + print(greet("Ada")) + print(greet("Joe")) + return greet("Linus") + """, + see_also: ["host-intro", "deflua"] + }, + %{ + slug: "put-private", + title: "Private host context", + chapter: :integration, + objective: + "Pass authenticated context to `deflua` handlers without exposing it to Lua scripts.", + body: """ + `Lua.put_private(lua, :user_id, 42)` stores host state inside + the VM that *Lua scripts cannot see*. In a `deflua` body taking + `state`, `Lua.get_private!(state, :user_id)` reads it back. + This is the pattern for multi-tenant sandboxes: auth, tenant + id, and API keys stay on the host side; the script sees only + return values. + """, + exercise: + "Print `type(user_id)` from Lua. It stays `nil`. Now write a malicious-looking snippet that *tries* to read the user id (`return _G.user_id`) and confirm it can't.", + elixir_source: """ + defmodule Account do + use Lua.API, scope: "account" + + deflua balance(), state do + user_id = Lua.get_private!(state, :user_id) + {[fetch_balance(user_id)], state} + end + end + + lua = + Lua.new() + |> Lua.load_api(Account) + |> Lua.put_private(:user_id, 42) + + {[bal], _} = Lua.eval!(lua, "return account.balance()") + """, + source: """ + -- account.balance is a deflua handler that reads user_id via + -- Lua.get_private!. Lua sees only the return value. + account = account or { balance = function() return 12847 end } + + local b = account.balance() + print("balance:", b) + print("can Lua see user_id?", type(user_id)) -- nil; private! + return b + """, + see_also: ["deflua", "sandbox"] + }, + %{ + slug: "sigil", + title: "The `~LUA` sigil", + chapter: :integration, + objective: "Validate Lua at Elixir compile time and pre-compile chunks for hot paths.", + body: """ + `~LUA"..."` parses your Lua at *Elixir compile time*. A typo + crashes `mix compile`, not your release on a Tuesday. The `c` + modifier emits a pre-compiled `%Lua.Chunk{}`; `Lua.eval!(lua, + chunk)` skips the parser at runtime. Use this for repeatedly + executed snippets and config-as-code. + """, + exercise: + "Drop the closing `end` of an inner function and run. Lua.ex compiles fine here (the playground re-parses every Run), but as a `~LUA` body the same typo crashes `mix compile`.", + elixir_source: """ + defmodule Money do + import Lua + + # Parsed at Elixir compile time; runtime still parses. + def discount_lua, + do: ~LUA"return amount * (1 - pct/100)" + + # Parsed AND compiled at Elixir compile time. Re-runs skip + # the parser entirely. + def discount_chunk, + do: ~LUA"return amount * (1 - pct/100)"c + end + + lua = Lua.new() |> Lua.set!([:amount], 100) |> Lua.set!([:pct], 15) + {[total], _} = Lua.eval!(lua, Money.discount_chunk()) + """, + source: """ + -- The body of the ~LUA chunk above. Edit and re-run. + -- In a real app, `amount` and `pct` come from Lua.set! first. + amount = amount or 100 + pct = pct or 15 + return amount * (1 - pct / 100) + """, + see_also: ["host-intro", "bytecode"] + }, + %{ + slug: "errors-host", + title: "Errors across the boundary", + chapter: :integration, + expect: :runtime_error, + objective: + "Catch `Lua.RuntimeException` on the Elixir side. Line, source, and call stack come along.", + body: """ + Runtime errors in Lua raise `Lua.RuntimeException` on the + Elixir side. The exception carries the offending line, the + source snippet, and the call stack. `try/rescue` catches it; + `Exception.message/1` formats the pretty version you see in + this playground. + """, + exercise: + "Change `nil` to `{ missing = { field = 42 } }` and the error disappears: you get `42` back. Now toggle Bytecode and find the `get_table` op that the runtime stack points at.", + elixir_source: """ + try do + Lua.eval!(Lua.new(), \"\"\" + local function deep(x) return x.missing.field end + deep(nil) + \"\"\") + rescue + e in Lua.RuntimeException -> + IO.puts(Exception.message(e)) + {:error, e.line, e.source} + end + """, + source: """ + -- Run this to see the same error the Elixir caller rescues. + local function inner(x) + return x.missing.field + end + + local function middle(x) + return inner(x) -- error propagates up the stack + end + + return middle(nil) + """, + see_also: ["errors"] + }, + + # ----- Chapter V: Under the hood ----- + %{ + slug: "bytecode", + title: "The bytecode model", + chapter: :internals, + objective: + "Read a disassembled prototype: instructions, registers, upvalues, and nested protos.", + body: """ + The compiler lowers Lua to a stream of *register-based* + opcodes. No labels, no PC-relative jumps. Each function + becomes a `%Lua.Compiler.Prototype{}` with its own register + window and upvalue descriptors. Toggle Bytecode to see the + layout. Full reference at [`/reference/opcodes`](/reference/opcodes). + """, + exercise: + "Toggle Bytecode. Count the prototypes (`function #N` headers). Find the `closure` op that builds the inner function and the `get_upvalue` op that reads `seed`.", + source: """ + -- Two prototypes nest below. `outer` captures `seed` as an + -- upvalue, and the inner closure captures it again. Open + -- Bytecode and look for: load_constant, closure, get_upvalue, + -- call, return. + local function outer(seed) + return function(x) return x + seed end + end + + local add10 = outer(10) + print(add10(5), add10(100)) + return add10(0) + """, + see_also: ["closures", "metatables-index"] + }, + %{ + slug: "next-steps", + title: "Where to go from here", + chapter: :internals, + objective: "Pick the next stop: playground, opcode reference, or the canonical Lua book.", + body: """ + You've seen the language, the standard library, the host + integration, and the bytecode pipeline. Three places to go + next: the [Playground](/playground) for an empty editor, the + [opcode reference](/reference/opcodes) for the VM internals, + and [Programming in Lua](https://www.lua.org/pil/), the + canonical reference written by the language's authors. + """ + } + ] + end +end diff --git a/website/lib/website/mailer.ex b/website/lib/website/mailer.ex new file mode 100644 index 0000000..8e1ec1d --- /dev/null +++ b/website/lib/website/mailer.ex @@ -0,0 +1,3 @@ +defmodule Demo.Mailer do + use Swoosh.Mailer, otp_app: :website +end diff --git a/website/lib/website_web.ex b/website/lib/website_web.ex new file mode 100644 index 0000000..1526230 --- /dev/null +++ b/website/lib/website_web.ex @@ -0,0 +1,114 @@ +defmodule DemoWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, components, channels, and so on. + + This can be used in your application as: + + use DemoWeb, :controller + use DemoWeb, :html + + The definitions below will be executed for every controller, + component, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define additional modules and import + those modules here. + """ + + def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + + def router do + quote do + use Phoenix.Router, helpers: false + + # Import common connection and controller functions to use in pipelines + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + end + end + + def controller do + quote do + use Phoenix.Controller, formats: [:html, :json] + + use Gettext, backend: DemoWeb.Gettext + + import Plug.Conn + + unquote(verified_routes()) + end + end + + def live_view do + quote do + use Phoenix.LiveView + + unquote(html_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(html_helpers()) + end + end + + def html do + quote do + use Phoenix.Component + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_csrf_token: 0, view_module: 1, view_template: 1] + + # Include general helpers for rendering HTML + unquote(html_helpers()) + end + end + + defp html_helpers do + quote do + # Translation + use Gettext, backend: DemoWeb.Gettext + + # HTML escaping functionality + import Phoenix.HTML + # Core UI components + import DemoWeb.CoreComponents + + # Common modules used in templates + alias Phoenix.LiveView.JS + alias DemoWeb.Layouts + + # Routes generation with the ~p sigil + unquote(verified_routes()) + end + end + + def verified_routes do + quote do + use Phoenix.VerifiedRoutes, + endpoint: DemoWeb.Endpoint, + router: DemoWeb.Router, + statics: DemoWeb.static_paths() + end + end + + @doc """ + When used, dispatch to the appropriate controller/live_view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/website/lib/website_web/bytecode.ex b/website/lib/website_web/bytecode.ex new file mode 100644 index 0000000..8bcac04 --- /dev/null +++ b/website/lib/website_web/bytecode.ex @@ -0,0 +1,379 @@ +defmodule DemoWeb.Bytecode do + @moduledoc """ + Shared helpers for rendering register-based bytecode in HEEx templates. + + Centralises opcode colouring (`op_class/1`) and per-opcode argument + formatting (`format_args/2`) so the home-page teaser, playground, + tour, and opcode reference page all stay in sync. + """ + + @doc """ + Returns the Tailwind/DaisyUI class string for an opcode, used to + colour-code the mnemonic in disassembly listings. + """ + def op_class(:source_line), do: "text-base-content/40" + + def op_class(op) when op in [:return, :return_vararg, :tail_call], + do: "text-accent font-semibold" + + def op_class(op) when op in [:call, :closure, :self, :vararg], + do: "text-primary font-semibold" + + def op_class(op) + when op in [ + :test, + :test_true, + :test_and, + :test_or, + :while_loop, + :repeat_loop, + :numeric_for, + :generic_for, + :break, + :scope + ], + do: "text-warning font-semibold" + + def op_class(op) + when op in [ + :add, + :subtract, + :multiply, + :divide, + :floor_divide, + :modulo, + :power, + :concatenate, + :negate, + :equal, + :less_than, + :less_equal, + :length, + :not, + :bitwise_and, + :bitwise_or, + :bitwise_xor, + :shift_left, + :shift_right, + :bitwise_not + ], + do: "text-secondary font-semibold" + + def op_class(op) + when op in [:new_table, :set_list, :get_table, :set_table, :get_field, :set_field], + do: "text-info font-semibold" + + def op_class(_), do: "text-success font-semibold" + + @doc """ + Renders the argument list for an opcode as a human-readable string. + + Mirrors the formatting used by `Website.LuaSandbox.disassemble/1`'s + `:pretty` field, but exposed separately so callers can colour the + op mnemonic independently. + """ + def format_args(op, args), do: do_format(op, args) + + defp do_format(op, [a, b, c]) + when op in [ + :add, + :subtract, + :multiply, + :divide, + :floor_divide, + :modulo, + :power, + :concatenate, + :bitwise_and, + :bitwise_or, + :bitwise_xor, + :shift_left, + :shift_right, + :equal, + :less_than, + :less_equal + ], + do: "r#{a}, r#{b}, r#{c}" + + defp do_format(op, [a, b]) + when op in [:negate, :not, :length, :bitwise_not, :move], + do: "r#{a}, r#{b}" + + defp do_format(:load_constant, [d, val]), do: "r#{d}, #{format_lit(val)}" + defp do_format(:load_nil, [d, count]), do: "r#{d}, #{count}" + defp do_format(:load_boolean, [d, v]), do: "r#{d}, #{v}" + defp do_format(:load_env, [d]), do: "r#{d}" + defp do_format(:get_upvalue, [d, idx]), do: "r#{d}, up[#{idx}]" + defp do_format(:set_upvalue, [idx, s]), do: "up[#{idx}], r#{s}" + defp do_format(:get_open_upvalue, [d, r]), do: "r#{d}, r#{r}" + defp do_format(:set_open_upvalue, [r, s]), do: "r#{r}, r#{s}" + defp do_format(:get_global, [d, name]), do: ~s|r#{d}, _G["#{name}"]| + defp do_format(:set_global, [name, s]), do: ~s|_G["#{name}"], r#{s}| + defp do_format(:new_table, [d, a, h]), do: "r#{d}, array=#{a}, hash=#{h}" + defp do_format(:get_table, [d, t, k | _]), do: "r#{d}, r#{t}[#{format_arg(k)}]" + defp do_format(:set_table, [t, k, v | _]), do: "r#{t}[#{format_arg(k)}], r#{v}" + defp do_format(:get_field, [d, t, name | _]), do: ~s|r#{d}, r#{t}.#{name}| + defp do_format(:set_field, [t, name, v | _]), do: ~s|r#{t}.#{name}, r#{v}| + + defp do_format(:set_list, [t, s, c, o]), + do: "r#{t}, start=#{s}, count=#{c}, off=#{o}" + + defp do_format(:call, [b, ac, rc | _]), + do: "r#{b}, args=#{count_fmt(ac)}, results=#{count_fmt(rc)}" + + defp do_format(:tail_call, [b, ac | _]), do: "r#{b}, args=#{count_fmt(ac)}" + defp do_format(:return, [b, c]), do: "r#{b}, count=#{count_fmt(c)}" + defp do_format(:return_vararg, _), do: "(varargs)" + defp do_format(:vararg, [b, c]), do: "r#{b}, count=#{count_fmt(c)}" + defp do_format(:self, [b, o, name | _]), do: "r#{b}, r#{o}, .#{name}" + defp do_format(:closure, [d, idx]), do: "r#{d}, proto[#{idx}]" + defp do_format(:test, [r | _]), do: "r#{r}" + defp do_format(:test_true, [r | _]), do: "r#{r}" + defp do_format(:test_and, [d, s | _]), do: "r#{d}, r#{s}" + defp do_format(:test_or, [d, s | _]), do: "r#{d}, r#{s}" + defp do_format(:numeric_for, [b | _]), do: "r#{b}" + defp do_format(:generic_for, [b, vc | _]), do: "r#{b}, vars=#{vc}" + defp do_format(:scope, [n | _]), do: "registers=#{n}" + defp do_format(:source_line, [ln]), do: "line #{ln}" + defp do_format(_, args), do: args |> Enum.map(&format_arg/1) |> Enum.join(", ") + + defp format_arg({:constant, val}), do: format_lit(val) + defp format_arg({:global, name}), do: ~s|<#{name}>| + defp format_arg(atom) when is_atom(atom), do: inspect(atom) + defp format_arg(n) when is_integer(n), do: Integer.to_string(n) + defp format_arg(other), do: inspect(other, limit: 20) + + defp format_lit(val) when is_binary(val), do: inspect(val) + defp format_lit(val), do: inspect(val, limit: 20) + + defp count_fmt({:multi, n}), do: "multi(#{n})" + defp count_fmt(:varargs), do: "..." + defp count_fmt(n) when is_integer(n), do: Integer.to_string(n) + defp count_fmt(other), do: inspect(other) + + @doc """ + Returns a curated map of opcode → short human-readable description. + + Used by the opcode reference page and as tooltip text in the + playground. Not exhaustive; covers the ops users will actually see + in the playground examples. + """ + def opcode_doc(op) do + Map.get(opcode_docs(), op) + end + + def opcode_docs do + %{ + load_env: + "Put the globals table (`_ENV`, where `print`, `math`, etc. live) into a register so later instructions can look things up in it.", + load_constant: + "Put a literal value — a number, string, `nil`, or boolean — into a register. Constants are baked into the bytecode at compile time.", + load_nil: + "Set a run of registers to `nil` in one shot (used when declaring locals without values).", + load_boolean: "Put `true` or `false` into a register.", + move: "Copy the value in one register into another. Like `local x = y`.", + get_global: "Read a global variable (e.g. `print`) by name into a register.", + set_global: "Write a register's value to a global variable.", + get_upvalue: + "Read an upvalue — a variable this function captured from the enclosing function — into a register.", + set_upvalue: "Write a register's value back to a captured upvalue.", + get_open_upvalue: + "Read a captured local that's still living on the parent's stack (parent hasn't returned yet).", + set_open_upvalue: + "Write to a captured local that's still living on the parent's stack (parent hasn't returned yet).", + new_table: + "Create a fresh empty table. The compiler pre-sizes it based on how many array entries and hash keys it expects.", + get_table: "Read `t[k]` (any key) and put the result in a register.", + set_table: "Write a register's value to `t[k]` (any key).", + get_field: + "Read `t.name` — same as `get_table` but specialised for string keys. The VM uses a faster path here.", + set_field: "Write `t.name` — fast path for string-keyed assignment.", + set_list: + "Bulk-populate the array part of a table. Used for table literals like `{1, 2, 3}` so the VM can write all entries in one instruction.", + self: + "Set up an `obj:method(args)` call. Copies `obj` into one register and `obj.method` into the next, so the call instruction can use both.", + closure: + "Build a closure from a nested function definition, capturing its upvalues from the surrounding scope. This is what makes `function() ... end` actually produce a callable value.", + call: + "Call a function. The function and its arguments must already be in consecutive registers. Returns are written back to those same registers.", + tail_call: + "Call a function in tail position — reuses the current function's stack frame instead of growing it. Lets recursive functions run without exhausting the stack.", + return: + "Return zero or more values from this function. Values come from a run of consecutive registers.", + return_vararg: "Return whatever `...` arguments this function received, unchanged.", + vararg: + "Expand `...` into a run of consecutive registers so following instructions can use them.", + add: "Compute `a + b` and write the result to a register.", + subtract: "Compute `a - b` and write the result to a register.", + multiply: "Compute `a * b` and write the result to a register.", + divide: "Float division: `a / b`. Always returns a float, even for whole numbers.", + floor_divide: "Integer floor division: `a // b`. Drops the fractional part.", + modulo: "Compute `a % b` (remainder after floor division).", + power: "Compute `a ^ b` (exponentiation). Always returns a float.", + negate: "Compute `-x`.", + concatenate: "Join two strings with `..` and write the result to a register.", + length: + "Compute `#x` — string length, array length of a table, or the result of the `__len` metamethod.", + not: "Compute `not x` — flip a truthy value to `false` or a falsy one to `true`.", + equal: "Compare `a == b` and write `true` or `false` to a register.", + less_than: "Compare `a < b` and write the boolean result.", + less_equal: "Compare `a <= b` and write the boolean result.", + bitwise_and: "Compute `a & b` (bitwise AND).", + bitwise_or: "Compute `a | b` (bitwise OR).", + bitwise_xor: "Compute `a ~ b` (bitwise XOR — the binary `~`).", + bitwise_not: "Compute `~x` (bitwise NOT — the unary `~`).", + shift_left: "Compute `a << b` (left shift).", + shift_right: "Compute `a >> b` (right shift).", + test: + "Branch on truthiness: if the register is truthy, execute the next continuation; otherwise skip it. Powers `if` / `elseif` / `while` conditions.", + test_true: "Inverse of `test`: continue if truthy, skip if falsy.", + test_and: + "Short-circuit AND. If the source is falsy, copy it to the destination and skip the rest of the expression; otherwise keep going.", + test_or: + "Short-circuit OR. If the source is truthy, copy it to the destination and skip the rest; otherwise keep going.", + while_loop: "Top of a `while` loop: test the condition, run the body, jump back.", + repeat_loop: "Top of a `repeat … until` loop: run the body, test the condition, jump back.", + numeric_for: + "A `for i = start, stop, step do` loop. Steps the loop variable and continues until it passes `stop`.", + generic_for: + "A `for k, v in iter do` loop. Calls the iterator function on each iteration and binds its return values.", + break: "Jump out of the nearest enclosing loop. Implements Lua's `break`.", + scope: + "Open a new block scope and reserve registers for its locals. Bytecode-level mirror of a `do … end` block.", + source_line: + "Pseudo-instruction. Marks where in your source code the next batch of instructions came from — used for line numbers in error messages and the cross-highlight in this panel." + } + end + + @doc """ + Compact mnemonic signature for an opcode (e.g. `"rD, rS"`). Used both + on the opcode reference cards and inside playground tooltips. + """ + def op_signature(op) do + case op do + :load_constant -> + "rD, K" + + :load_nil -> + "rD, N" + + :load_boolean -> + "rD, bool" + + :load_env -> + "rD" + + :move -> + "rD, rS" + + :get_global -> + "rD, name" + + :set_global -> + "name, rS" + + :get_upvalue -> + "rD, up[i]" + + :set_upvalue -> + "up[i], rS" + + :get_open_upvalue -> + "rD, rS" + + :set_open_upvalue -> + "rD, rS" + + :new_table -> + "rD, array, hash" + + :get_table -> + "rD, rT, k" + + :set_table -> + "rT, k, rV" + + :get_field -> + "rD, rT, name" + + :set_field -> + "rT, name, rV" + + :set_list -> + "rT, start, count, off" + + :self -> + "rD, rO, name" + + :closure -> + "rD, proto[i]" + + :call -> + "rB, argc, resc" + + :tail_call -> + "rB, argc" + + :return -> + "rB, count" + + :return_vararg -> + "(varargs)" + + :vararg -> + "rB, count" + + :test -> + "rR" + + :test_true -> + "rR" + + :test_and -> + "rD, rS" + + :test_or -> + "rD, rS" + + :numeric_for -> + "rB" + + :generic_for -> + "rB, vars" + + :scope -> + "registers" + + :source_line -> + "line" + + op when op in [:add, :subtract, :multiply, :divide, :floor_divide, :modulo, :power] -> + "rD, rA, rB" + + op when op in [:concatenate, :bitwise_and, :bitwise_or, :bitwise_xor] -> + "rD, rA, rB" + + op when op in [:shift_left, :shift_right] -> + "rD, rA, rB" + + op when op in [:equal, :less_than, :less_equal] -> + "rD, rA, rB" + + op when op in [:negate, :not, :length, :bitwise_not] -> + "rD, rS" + + _ -> + "" + end + end + + @doc """ + Map of opcode → `%{doc, signature}` ready to JSON-encode into the page + for client-side tooltip rendering. + """ + def opcode_tooltip_map do + for {op, doc} <- opcode_docs(), into: %{} do + {op, %{doc: doc, signature: op_signature(op)}} + end + end +end diff --git a/website/lib/website_web/components/bytecode_components.ex b/website/lib/website_web/components/bytecode_components.ex new file mode 100644 index 0000000..d7fa87b --- /dev/null +++ b/website/lib/website_web/components/bytecode_components.ex @@ -0,0 +1,262 @@ +defmodule DemoWeb.BytecodeComponents do + @moduledoc """ + Reusable HEEx components for rendering register-based bytecode + disassembly. Used by the playground (`/playground`) and the tour + (`/tour/:slug`) so the column layout, colour scheme, opcode tooltips, + and meta-pill set stay in sync between the two surfaces. + + All tooltips are powered by the body-level Tippy.js delegator in + `assets/js/app.js` — emit `data-tip`, `data-tip-html`, or + `data-tip-op` attributes and the tooltip renders automatically. + """ + use Phoenix.Component + + alias DemoWeb.Bytecode + + @doc """ + Renders the opcode-doc JSON blob the JS tooltip layer reads to + build per-opcode cards. Drop one of these onto any page that uses + `bytecode_panel/1` or `bytecode_table/1`. + """ + def opcode_docs_script(assigns) do + assigns = + assign_new(assigns, :json, fn -> + Jason.encode!(Bytecode.opcode_tooltip_map()) + end) + + ~H""" + + + """ + end + + defp args_glossary_tip(assigns) do + ~H""" +
+
Reading the arguments
+
+
r0, r1, …
+
register slots — scratch space the VM uses for locals and temporaries
+
up[0]
+
upvalue — a variable captured from the enclosing function
+
_G["x"]
+
global variable named x
+
proto[0]
+
a nested function definition inside this one
+
"foo", 42
+
a constant literal baked into the bytecode
+
multi(n)
+
multiple return values (n of them)
+
...
+
varargs — the rest of the arguments
+
+
+ """ + end + + attr :label, :string, required: true + attr :value, :any, required: true + attr :tip, :string, default: nil + + @doc """ + Small label/value pill used to summarise a prototype's metadata + (`params`, `vararg`, `registers`, `upvalues`). + """ + def meta_pill(assigns) do + ~H""" +
+
+ {@label} +
+
{@value}
+
+ """ + end + + attr :blocks, :list, required: true + attr :selected, :integer, default: 0 + attr :variant, :atom, default: :full, values: [:full, :compact] + attr :max_height, :string, default: "max-h-[520px]" + attr :show_block_tabs, :boolean, default: true + attr :show_meta_pills, :boolean, default: true + attr :show_legend, :boolean, default: true + attr :empty_label, :string, default: "Bytecode appears here after a successful parse." + attr :cross_highlight?, :boolean, default: true + + @doc """ + The full bytecode panel: header, block tabs, meta pills, legend, + and instruction table with tooltips. + + Both the playground and tour render this. Pass `variant: :compact` + on the tour for a denser layout that omits the per-prototype meta + grid and the column header strip. + """ + def bytecode_panel(assigns) do + ~H""" +
+ <.opcode_docs_script /> + +
+
+ Bytecode · Lua.Compiler.Prototype +
+
+ <%= if @blocks != [] do %> + {length(@blocks)} proto{if length(@blocks) != 1, do: "s"} + <% else %> + — + <% end %> +
+
+ + <%= if @blocks == [] do %> +
+ {@empty_label} +
+ <% else %> + <%= if @show_block_tabs and length(@blocks) > 1 do %> +
+ <%= for block <- @blocks do %> + + <% end %> +
+ <% end %> + + <%= for block <- @blocks, block.index == @selected do %> + <%= if @show_meta_pills do %> +
+ <.meta_pill + label="params" + value={block.param_count} + tip="**Parameters.** How many named arguments this function declares (e.g. `function f(a, b)` has 2). Extra args beyond this collect into `...` if vararg is `yes`." + /> + <.meta_pill + label="vararg" + value={if block.is_vararg, do: "yes", else: "no"} + tip="**Varargs.** Whether this function accepts `...` (extra arguments past the declared params). The top-level chunk is always vararg." + /> + <.meta_pill + label="registers" + value={block.max_registers} + tip="**Registers.** How many slots this function reserves on the VM's stack. Think of them as numbered scratch boxes (`r0`, `r1`, …) where the VM stashes locals and intermediate values." + /> + <.meta_pill + label="upvalues" + value={block.upvalue_count} + tip="**Upvalues.** Variables this function borrows from the enclosing function. When you define a closure, every outer variable it touches becomes an upvalue." + /> +
+ <% end %> + + <%= if @show_legend and @cross_highlight? do %> +
+ + + Hover a row to highlight every instruction from the same source line. Click to jump the editor there. +
+ <% end %> + +
+ + <%= if @variant == :full do %> + + + + + + + + <% end %> + + <%= for ins <- block.instructions do %> + + + + + + <% end %> + +
+ PC + + Line + + Instruction +
+ {pad_pc(ins.pc)} + + <%= if ins.line do %> + L{ins.line} + <% end %> + + <.opcode_link op={ins.op} /> + + {" "}{Bytecode.format_args(ins.op, ins.args)} + +
+
+ <% end %> + <% end %> +
+ """ + end + + attr :op, :atom, required: true + + @doc """ + An opcode mnemonic rendered as an anchor to the reference page. The + Tippy delegator picks up `data-tip-op` and renders the rich card. + """ + def opcode_link(assigns) do + ~H""" + + {@op} + + """ + end + + defp pad_pc(n), do: n |> Integer.to_string() |> String.pad_leading(3, "0") + + defp row_classes(ins, cross_highlight?) do + [ + "border-b border-base-300/30 hover:bg-base-300/30", + ins.line && cross_highlight? && "cursor-pointer", + ins.op == :source_line && "text-base-content/40 italic" + ] + end +end diff --git a/website/lib/website_web/components/core_components.ex b/website/lib/website_web/components/core_components.ex new file mode 100644 index 0000000..aff6633 --- /dev/null +++ b/website/lib/website_web/components/core_components.ex @@ -0,0 +1,588 @@ +defmodule DemoWeb.CoreComponents do + @moduledoc """ + Provides core UI components. + + At first glance, this module may seem daunting, but its goal is to provide + core building blocks for your application, such as tables, forms, and + inputs. The components consist mostly of markup and are well-documented + with doc strings and declarative assigns. You may customize and style + them in any way you want, based on your application growth and needs. + + The foundation for styling is Tailwind CSS, a utility-first CSS framework, + augmented with daisyUI, a Tailwind CSS plugin that provides UI components + and themes. Here are useful references: + + * [daisyUI](https://daisyui.com/docs/intro/) - a good place to get + started and see the available components. + + * [Tailwind CSS](https://tailwindcss.com) - the foundational framework + we build on. You will use it for layout, sizing, flexbox, grid, and + spacing. + + * [Heroicons](https://heroicons.com) - see `icon/1` for usage. + + * [Phoenix.Component](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html) - + the component system used by Phoenix. Some components, such as `<.link>` + and `<.form>`, are defined there. + + """ + use Phoenix.Component + use Gettext, backend: DemoWeb.Gettext + + alias Phoenix.LiveView.JS + + @doc """ + Renders flash notices. + + ## Examples + + <.flash kind={:info} flash={@flash} /> + <.flash + id="welcome-back" + kind={:info} + phx-mounted={show("#welcome-back") |> JS.remove_attribute("hidden")} + hidden + > + Welcome Back! + + """ + attr :id, :string, doc: "the optional id of flash container" + attr :flash, :map, default: %{}, doc: "the map of flash messages to display" + attr :title, :string, default: nil + attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" + attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container" + + slot :inner_block, doc: "the optional inner block that renders the flash message" + + def flash(assigns) do + assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end) + + ~H""" +
hide("##{@id}")} + role="alert" + class="toast toast-top toast-end z-50" + {@rest} + > +
+ <.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" /> + <.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" /> +
+

{@title}

+

{msg}

+
+
+ +
+
+ """ + end + + @doc """ + Renders a button with navigation support. + + ## Examples + + <.button>Send! + <.button phx-click="go" variant="primary">Send! + <.button navigate={~p"/"}>Home + """ + attr :rest, :global, include: ~w(href navigate patch method download name value disabled) + attr :class, :any + attr :variant, :string, values: ~w(primary) + slot :inner_block, required: true + + def button(%{rest: rest} = assigns) do + variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"} + + assigns = + assign_new(assigns, :class, fn -> + ["btn", Map.fetch!(variants, assigns[:variant])] + end) + + if rest[:href] || rest[:navigate] || rest[:patch] do + ~H""" + <.link class={@class} {@rest}> + {render_slot(@inner_block)} + + """ + else + ~H""" + + """ + end + end + + @doc """ + Renders an input with label and error messages. + + A `Phoenix.HTML.FormField` may be passed as argument, + which is used to retrieve the input name, id, and values. + Otherwise all attributes may be passed explicitly. + + ## Types + + This function accepts all HTML input types, considering that: + + * You may also set `type="select"` to render a ` + """ + end + + def input(%{type: "checkbox"} = assigns) do + assigns = + assign_new(assigns, :checked, fn -> + Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value]) + end) + + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + def input(%{type: "select"} = assigns) do + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + def input(%{type: "textarea"} = assigns) do + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + # All other inputs text, datetime-local, url, password, etc. are handled here... + def input(assigns) do + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + # Helper used by inputs to generate form errors + defp error(assigns) do + ~H""" +

+ <.icon name="hero-exclamation-circle" class="size-5" /> + {render_slot(@inner_block)} +

+ """ + end + + @doc """ + Renders a header with title. + """ + slot :inner_block, required: true + slot :subtitle + slot :actions + + def header(assigns) do + ~H""" +
+
+

+ {render_slot(@inner_block)} +

+

+ {render_slot(@subtitle)} +

+
+
{render_slot(@actions)}
+
+ """ + end + + @doc """ + Renders a table with generic styling. + + ## Examples + + <.table id="users" rows={@users}> + <:col :let={user} label="id">{user.id} + <:col :let={user} label="username">{user.username} + + """ + attr :id, :string, required: true + attr :rows, :list, required: true + attr :row_id, :any, default: nil, doc: "the function for generating the row id" + attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" + + attr :row_item, :any, + default: &Function.identity/1, + doc: "the function for mapping each row before calling the :col and :action slots" + + slot :col, required: true do + attr :label, :string + end + + slot :action, doc: "the slot for showing user actions in the last table column" + + def table(assigns) do + assigns = + with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do + assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) + end + + ~H""" + + + + + + + + + + + + + +
{col[:label]} + {gettext("Actions")} +
+ {render_slot(col, @row_item.(row))} + +
+ <%= for action <- @action do %> + {render_slot(action, @row_item.(row))} + <% end %> +
+
+ """ + end + + @doc """ + Renders a data list. + + ## Examples + + <.list> + <:item title="Title">{@post.title} + <:item title="Views">{@post.views} + + """ + slot :item, required: true do + attr :title, :string, required: true + end + + def list(assigns) do + ~H""" +
    +
  • +
    +
    {item.title}
    +
    {render_slot(item)}
    +
    +
  • +
+ """ + end + + @doc """ + Renders a [Heroicon](https://heroicons.com). + + Heroicons come in three styles – outline, solid, and mini. + By default, the outline style is used, but solid and mini may + be applied by using the `-solid` and `-mini` suffix. + + You can customize the size and colors of the icons by setting + width, height, and background color classes. + + Icons are extracted from the `deps/heroicons` directory and bundled within + your compiled app.css by the plugin in `assets/vendor/heroicons.js`. + + ## Examples + + <.icon name="hero-x-mark" /> + <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" /> + """ + attr :name, :string, required: true + attr :class, :any, default: "size-4" + + def icon(%{name: "hero-" <> _} = assigns) do + ~H""" + + """ + end + + @doc """ + Renders a syntax-highlighted code block, optionally rotating through + several snippets. + + ## Examples + + <.code_block language={:elixir} source={~S\"\"\" + defmodule Foo do + end + \"\"\"} /> + + <.code_block + language={:elixir} + rotate + interval={4500} + snippets={[ + %{label: "queue.exs", source: "..."}, + %{label: "filter.exs", source: "..."} + ]} + /> + + When `snippets` is given, all snippets are rendered server-side and + stacked in a CSS grid cell so the container reserves the height of + the tallest snippet. The `CodeRotator` JS hook picks a random initial + snippet and renders dots below the code for manual switching. Each + snippet may include an optional `:label` that swaps into the chrome + filename slot. + """ + attr :id, :string, required: true + attr :language, :atom, default: :elixir + attr :source, :string, default: nil + attr :snippets, :list, default: nil + attr :rotate, :boolean, default: false + attr :class, :string, default: "p-5 font-mono text-[13px] leading-6 overflow-x-auto" + attr :rest, :global + + def code_block(assigns) do + snippets = + cond do + is_list(assigns[:snippets]) and assigns[:snippets] != [] -> assigns.snippets + is_binary(assigns[:source]) -> [%{source: assigns.source}] + true -> [] + end + + rotate? = assigns[:rotate] and length(snippets) > 1 + + assigns = + assigns + |> assign(:resolved_snippets, Enum.with_index(snippets)) + |> assign(:rotate?, rotate?) + + ~H""" +
+
+
+ +
+
+
+ """ + end + + ## JS Commands + + def show(js \\ %JS{}, selector) do + JS.show(js, + to: selector, + time: 300, + transition: + {"transition-all ease-out duration-300", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", + "opacity-100 translate-y-0 sm:scale-100"} + ) + end + + def hide(js \\ %JS{}, selector) do + JS.hide(js, + to: selector, + time: 200, + transition: + {"transition-all ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} + ) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # However the error messages in our forms and APIs are generated + # dynamically, so we need to translate them by calling Gettext + # with our gettext backend as first argument. Translations are + # available in the errors.po file (as we use the "errors" domain). + if count = opts[:count] do + Gettext.dngettext(DemoWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(DemoWeb.Gettext, "errors", msg, opts) + end + end + + @doc """ + Translates the errors for a field from a keyword list of errors. + """ + def translate_errors(errors, field) when is_list(errors) do + for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) + end +end diff --git a/website/lib/website_web/components/layouts.ex b/website/lib/website_web/components/layouts.ex new file mode 100644 index 0000000..8681429 --- /dev/null +++ b/website/lib/website_web/components/layouts.ex @@ -0,0 +1,553 @@ +defmodule DemoWeb.Layouts do + @moduledoc """ + This module holds layouts and related functionality + used by your application. + """ + use DemoWeb, :html + + # Embed all files in layouts/* within this module. + # The default root.html.heex file contains the HTML + # skeleton of your application, namely HTML headers + # and other static content. + embed_templates "layouts/*" + + @doc """ + Renders your app layout. + + This function is typically invoked from every template, + and it often contains your application menu, sidebar, + or similar. + + ## Examples + + +

Content

+
+ + """ + attr :flash, :map, required: true, doc: "the map of flash messages" + + attr :current_scope, :map, + default: nil, + doc: "the current [scope](https://hexdocs.pm/phoenix/scopes.html)" + + slot :inner_block, required: true + + attr :active, :atom, default: nil + + def app(assigns) do + ~H""" + <.site_nav active={@active} /> +
+ {render_slot(@inner_block)} +
+ <.site_footer /> + <.flash_group flash={@flash} /> + """ + end + + attr :active, :atom, default: nil + + def site_nav(assigns) do + ~H""" +
+
+ + <.lua_mark class="h-8 w-8" /> + + Lua.ex + + + + + +
+ + + <.theme_toggle class="hidden md:flex" /> + + Try it <.icon name="hero-arrow-right-micro" class="size-4" /> + + + +
+
+ """ + end + + attr :href, :string, required: true + attr :active, :boolean, default: false + slot :inner_block, required: true + + def nav_link(assigns) do + ~H""" + <.link + navigate={@href} + class={[ + "btn btn-ghost btn-sm font-medium", + @active && "text-primary bg-primary/10" + ]} + > + {render_slot(@inner_block)} + + """ + end + + def site_footer(assigns) do + ~H""" +
+
+
+
+
+ <.lua_mark class="h-6 w-6" /> + + Lua.ex + +
+

+ An Elixir-native Lua 5.3 VM. Scriptable, sandboxed, agent-ready. +

+
+ + <.footer_section title="Product"> +
  • + <.link navigate={~p"/playground"} class="text-base-content/70 hover:text-primary"> + Playground + +
  • +
  • + <.link navigate={~p"/tour"} class="text-base-content/70 hover:text-primary"> + Tour + +
  • +
  • + <.link + navigate={~p"/reference/opcodes"} + class="text-base-content/70 hover:text-primary" + > + Opcode reference + +
  • +
  • + <.link navigate={~p"/about"} class="text-base-content/70 hover:text-primary"> + About + +
  • + + + <.footer_section title="Resources"> +
  • + + HexDocs + +
  • +
  • + + Hex.pm + +
  • +
  • + + Changelog + +
  • +
  • + + Roadmap + +
  • + + + <.footer_section title="Community"> +
  • + + GitHub + +
  • +
  • + + Report an issue + +
  • +
  • + + Elixir Forum + +
  • + +
    + +
    + + Built at + + TV Labs + + · standing on the shoulders of + + Luerl + + and three decades of Lua. + + Apache 2.0 licensed +
    +
    +
    + """ + end + + attr :title, :string, required: true + slot :inner_block, required: true + + defp footer_section(assigns) do + ~H""" +
    + + + {@title} + + <.icon + name="hero-chevron-down-micro" + class="size-5 text-base-content/60 transition-transform" + /> + +
      + {render_slot(@inner_block)} +
    +
    + + + """ + end + + attr :class, :string, default: "" + + def lua_mark(assigns) do + ~H""" + + """ + end + + attr :class, :string, default: "" + + @doc """ + TV Labs wordmark (brand mark + "tv labs" lockup). Color inherits from `currentColor`. + """ + def tvlabs_wordmark(assigns) do + ~H""" + + + + + + + + + + """ + end + + attr :class, :string, default: "" + + def lua_orbit(assigns) do + ~H""" + + """ + end + + @doc """ + Shows the flash group with standard titles and content. + + ## Examples + + <.flash_group flash={@flash} /> + """ + attr :flash, :map, required: true, doc: "the map of flash messages" + attr :id, :string, default: "flash-group", doc: "the optional id of flash container" + + def flash_group(assigns) do + ~H""" +
    + <.flash kind={:info} flash={@flash} /> + <.flash kind={:error} flash={@flash} /> + + <.flash + id="client-error" + kind={:error} + title={gettext("We can't find the internet")} + phx-disconnected={show(".phx-client-error #client-error") |> JS.remove_attribute("hidden")} + phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})} + hidden + > + {gettext("Attempting to reconnect")} + <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" /> + + + <.flash + id="server-error" + kind={:error} + title={gettext("Something went wrong!")} + phx-disconnected={show(".phx-server-error #server-error") |> JS.remove_attribute("hidden")} + phx-connected={hide("#server-error") |> JS.set_attribute({"hidden", ""})} + hidden + > + {gettext("Attempting to reconnect")} + <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" /> + +
    + """ + end + + attr :class, :string, default: "" + + @doc """ + Provides dark vs light theme toggle based on themes defined in app.css. + + See in root.html.heex which applies the theme before page load. + Dark is the canonical default; the moon button is marked with a small + primary dot to signal that. + """ + def theme_toggle(assigns) do + ~H""" +
    +
    + + + + + + +
    + """ + end +end diff --git a/website/lib/website_web/components/layouts/root.html.heex b/website/lib/website_web/components/layouts/root.html.heex new file mode 100644 index 0000000..14239b2 --- /dev/null +++ b/website/lib/website_web/components/layouts/root.html.heex @@ -0,0 +1,90 @@ + + + + + + + <.live_title + default="Lua.ex: Sandboxed Lua 5.3 on the BEAM, built for AI agents" + suffix=" · Lua.ex" + phx-no-format + >{assigns[:page_title]} + + <%!-- Primary SEO --%> + + + + <%!-- OpenGraph --%> + + + + + + + + + + + + <%!-- Twitter --%> + + + + + + + + + + + + + {@inner_content} + + diff --git a/website/lib/website_web/controllers/error_html.ex b/website/lib/website_web/controllers/error_html.ex new file mode 100644 index 0000000..2d3df6a --- /dev/null +++ b/website/lib/website_web/controllers/error_html.ex @@ -0,0 +1,24 @@ +defmodule DemoWeb.ErrorHTML do + @moduledoc """ + This module is invoked by your endpoint in case of errors on HTML requests. + + See config/config.exs. + """ + use DemoWeb, :html + + # If you want to customize your error pages, + # uncomment the embed_templates/1 call below + # and add pages to the error directory: + # + # * lib/website_web/controllers/error_html/404.html.heex + # * lib/website_web/controllers/error_html/500.html.heex + # + # embed_templates "error_html/*" + + # The default is to render a plain text page based on + # the template name. For example, "404.html" becomes + # "Not Found". + def render(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/website/lib/website_web/controllers/error_json.ex b/website/lib/website_web/controllers/error_json.ex new file mode 100644 index 0000000..91b0710 --- /dev/null +++ b/website/lib/website_web/controllers/error_json.ex @@ -0,0 +1,21 @@ +defmodule DemoWeb.ErrorJSON do + @moduledoc """ + This module is invoked by your endpoint in case of errors on JSON requests. + + See config/config.exs. + """ + + # If you want to customize a particular status code, + # you may add your own clauses, such as: + # + # def render("500.json", _assigns) do + # %{errors: %{detail: "Internal Server Error"}} + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.json" becomes + # "Not Found". + def render(template, _assigns) do + %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} + end +end diff --git a/website/lib/website_web/controllers/page_controller.ex b/website/lib/website_web/controllers/page_controller.ex new file mode 100644 index 0000000..aa7b846 --- /dev/null +++ b/website/lib/website_web/controllers/page_controller.ex @@ -0,0 +1,27 @@ +defmodule DemoWeb.PageController do + use DemoWeb, :controller + + def home(conn, _params) do + %{source: fib_source} = hd(Website.LuaSandbox.home_snippets()) + + render(conn, :home, + fib_source: fib_source, + fib_bytecode: compile_bytecode(fib_source) + ) + end + + def about(conn, _params) do + render(conn, :about, page_title: "About") + end + + def health(conn, _params) do + send_resp(conn, 200, "ok") + end + + defp compile_bytecode(source) do + case Website.LuaSandbox.compile(source) do + {:ok, _chunk, blocks} -> blocks + {:error, _} -> [] + end + end +end diff --git a/website/lib/website_web/controllers/page_html.ex b/website/lib/website_web/controllers/page_html.ex new file mode 100644 index 0000000..0d04475 --- /dev/null +++ b/website/lib/website_web/controllers/page_html.ex @@ -0,0 +1,50 @@ +defmodule DemoWeb.PageHTML do + @moduledoc """ + This module contains pages rendered by PageController. + + See the `page_html` directory for all templates available. + """ + use DemoWeb, :html + + embed_templates "page_html/*" + + attr :icon, :string, required: true + attr :title, :string, required: true + attr :accent, :string, default: "primary" + slot :inner_block, required: true + + def feature(assigns) do + ~H""" +
    +
    + <.icon name={@icon} class={["size-5", accent_text(@accent)]} /> +
    +

    {@title}

    +
    + {render_slot(@inner_block)} +
    +
    + """ + end + + defp accent_bg("primary"), do: "bg-primary/15" + defp accent_bg("secondary"), do: "bg-secondary/15" + defp accent_bg("accent"), do: "bg-accent/15" + defp accent_bg("success"), do: "bg-success/15" + defp accent_bg("info"), do: "bg-info/15" + defp accent_bg("warning"), do: "bg-warning/15" + defp accent_bg("error"), do: "bg-error/15" + defp accent_bg(_), do: "bg-primary/15" + + defp accent_text("primary"), do: "text-primary" + defp accent_text("secondary"), do: "text-secondary" + defp accent_text("accent"), do: "text-accent" + defp accent_text("success"), do: "text-success" + defp accent_text("info"), do: "text-info" + defp accent_text("warning"), do: "text-warning" + defp accent_text("error"), do: "text-error" + defp accent_text(_), do: "text-primary" +end diff --git a/website/lib/website_web/controllers/page_html/about.html.heex b/website/lib/website_web/controllers/page_html/about.html.heex new file mode 100644 index 0000000..b872481 --- /dev/null +++ b/website/lib/website_web/controllers/page_html/about.html.heex @@ -0,0 +1,563 @@ + + <%!-- ============== HERO ============== --%> +
    + + + +
    +
    +
    +
    + Our story · since 2012 +
    +

    + A library, three decades
    in the + + making. + +

    +

    + Lua.ex didn't start in 2026. It started in 1986 with Erlang, kept + going in 2012 when Robert Virding gave the BEAM an imperative + language, and landed here after fourteen years of + + Luerl + + quietly powering everything from game servers to spaceships. +

    + +
    +
    + DL +
    +
    +
    + Written by +
    + +
    +
    +
    + +
    + + + Dave Lucia and Robert Virding at Code BEAM Europe 2024 +
    + <.icon + name="hero-play-circle-micro" + class="size-4 text-primary group-hover:text-primary-content" + /> Watch the talk +
    +
    +
    + Dave Lucia & Robert Virding · Code BEAM Europe 2024 +
    +
    +
    +
    +
    + + <%!-- ============== TOUR OF PARADIGMS ============== --%> +
    +
    +
    +
    + 1986 → 2012 +
    +

    + The tour of paradigms. +

    +
    +

    + Erlang itself began as a Prolog experiment in 1986 at Ericsson's + Computer Science Lab. The first Erlang interpreter was literally + a Prolog program. Joe Armstrong, Robert Virding, and Mike + Williams were looking for a better way to program telephone + exchanges, and Prolog was the prototype. It was rewritten as the + JAM bytecode machine around 1989, once the interpreter proved + roughly 40× too slow for production switches. Building one + language inside another was there from day one. +

    +

    + After Erlang, Robert kept building languages on the BEAM as side + projects. He wrote Erlog, a Prolog interpreter in Erlang, and then + + LFE + + (Lisp Flavored Erlang), prototyped in 2007 and first released to + erlang-questions in March 2008. +

    +

    + Lua was a natural target for the next experiment: a small, + tightly-specified language built around embeddability, with a + reference implementation compact enough for one person to port. + Luerl + shipped on February 19, 2012, and Robert has shepherded it ever + since. +

    +
    +
    + +
    + <.icon + name="hero-chat-bubble-left-right-micro" + class="absolute -top-4 -left-4 size-9 p-2 rounded-xl bg-primary/15 text-primary border border-base-300/60 bg-base-100" + /> +
    + I wrote a Prolog in Erlang, that was fun. Then I wrote a functional + Lisp in Erlang, that was fun too. But then I realized it would be + neat to implement an imperative or object-oriented language on the + BEAM, so I picked Lua and had at it. That's how Luerl was born. +
    +
    +
    + RV +
    +
    +
    Robert Virding
    +
    + co-creator of Erlang · author of Luerl +
    +
    + paraphrased from a conversation at Code BEAM EU 2024 +
    +
    +
    + +
    + The name is "Lua" + "Erl". Robert confirmed it himself on Hacker + News. See the original Feb 19, 2012 erlang-questions announcement. +
    +
    +
    +
    + + <%!-- ============== TECHNICAL PUZZLE ============== --%> +
    +
    +
    +
    +
    + The puzzle +
    +

    + Mutable global state, on an immutable runtime. +

    +

    + The core challenge was philosophical as much as engineering: how do + you build a language whose entire semantic model is mutable global + state on top of a language whose entire semantic model is immutable + local state? +

    +

    + The answer turns out to be what you'd do in Erlang anyway: thread + the Lua state through every call as a value. No process + dictionary, no ETS escape hatch. Straight Erlang. +

    +
    + +
    +
    +
    + <.icon name="hero-arrow-path-rounded-square-micro" class="size-5 text-info" /> +
    +
    + Thread the state +
    +
    + A single #luerl{} + record passed through every call, idiomatic Erlang style. +
    +
    +
    +
    + <.icon name="hero-trash-micro" class="size-5 text-accent" /> +
    +
    + Layered GC +
    +
    + Luerl's own collector reclaims dead table indices, sitting on top + of BEAM's GC. +
    +
    +
    +
    + <.icon name="hero-shield-check-micro" class="size-5 text-success" /> +
    +
    + Erlang-shaped API +
    +
    + {ok, _} + / {error, _} + for safe calls, raw exceptions for do/call. +
    +
    +
    +
    +
    +
    + + <%!-- ============== TV LABS PICKS UP LUERL ============== --%> +
    +
    +
    +
    + 2024 · TV Labs +
    +

    + A scripting language for streaming devices. +

    +
    +

    + At TV Labs, we run vision-based integration tests against + real Roku, Fire TV, Apple TV, and game-console hardware. To + describe a test ("tap right, wait for the home screen, take a + screenshot, assert the carousel") I needed a scripting language + I could embed, sandbox, and run thousands of times a day inside + an Elixir release. +

    +

    + Luerl was already the answer. It was the mature, battle-tested + Lua VM on the BEAM, and Robert had been quietly shipping it for + twelve years. The only thing missing was an Elixir-shaped API. +

    +

    + So in 2024 I built one: the + + tv-labs/lua + + package on Hex, wrapping Luerl with the + deflua + macro, the ~LUA + sigil for compile-time syntax checking, and a + Lua.API + behaviour for registering Elixir modules with a Lua VM. +

    +
    +
    + +
    +
    + + + + + tv_test.exs + +
    + <.code_block + id="tvlabs-snippet" + language={:elixir} + class="px-5 py-4 font-mono text-[13px] leading-6 overflow-x-auto" + source={DemoWeb.Snippets.tvlabs_remote_api()} + /> +
    +
    +
    + + <%!-- ============== CODE BEAM TALK ============== --%> +
    +
    +
    + Code BEAM EU · 2024 +
    +

    + Lua on the BEAM, together. +

    +
    + +
    +

    + At Code BEAM Europe 2024 we ended up giving a talk together, Lua on the BEAM, covering Luerl's history, the Elixir + wrapper, and what a next-generation Lua VM on the BEAM could look + like. +

    +

    + Backstage, I asked Robert the obvious question: why + bother writing a Lua interpreter in Erlang in the first place? + That's when he told me the tour-of-paradigms story above. We + spent the rest of the conference talking about what a Luerl 2.0 + could look like: better error messages, real stack traces, + memory sandboxing, all the things that Luerl's age made hard to + retrofit. +

    +
    + + +
    + <.icon name="hero-play-circle-micro" class="size-7 text-primary" /> +
    +
    +
    + Watch the talk · YouTube +
    +
    + Lua on the BEAM · Code BEAM Europe 2024 +
    +
    + Robert Virding & Dave Lucia · 40 min +
    +
    + <.icon + name="hero-arrow-up-right-micro" + class="size-5 text-base-content/40 group-hover:text-primary shrink-0" + /> +
    +
    + + <%!-- ============== THE REWRITE ============== --%> +
    +
    +
    + 2026 · the rewrite +
    +

    + From wrapper to native VM. +

    +
    +

    + The wrapper got us most of the way there. But as the embedded surface + area grew (more tools, more user scripts, more agents) the cracks + showed. Error messages were terse. Stack traces stopped at the Luerl + boundary. There was no way to see the bytecode the VM was actually + running. +

    +

    + What started as a planned Luerl 2.0 became something else: a full + rewrite, in Elixir, top to bottom. A new lexer, a new parser, a new + register-based VM, a new compiler. Lua.ex 1.0 ships as a pure-Elixir + implementation of Lua 5.3. No Luerl in the dependency graph, no + NIFs, no shelling out. Just Elixir. +

    +

    + The goal wasn't to outperform Luerl. It was to give Elixir + developers the same developer experience they get with Phoenix or + Ecto: beautiful errors, compile-time validation, code you can read + when something goes wrong. +

    +
    +
    + +
    + <.feature + icon="hero-document-text-micro" + title="Real stack traces" + accent="warning" + > + Lua frames and + Elixir frames in one trace, blamed by the callee's name. None of that + "attempt to call a nil value" nonsense. + + + <.feature + icon="hero-sparkles-micro" + title="Compile-time validation" + accent="accent" + > + The ~LUA sigil catches syntax errors at compile time. + Add c and ship a pre-compiled chunk in your release. + + + <.feature + icon="hero-magnifying-glass-circle-micro" + title="Bytecode you can read" + accent="info" + > + Every chunk compiles to a register-based opcode stream you can step + through in the playground. + +
    + +

    + Luerl is still the right choice for Erlang projects, and Robert is still + actively shipping it. Lua.ex is the Elixir-native sibling, born from + the same conversations, aimed at the same problem from the other side + of the BEAM. +

    +
    + + <%!-- ============== THANKS ============== --%> +
    +
    +
    + +
    +
    + Standing on the shoulders +
    +

    + Thanks, Robert. +

    +
    +
    +
    +

    + This library exists because Robert Virding wrote Luerl first. + Fourteen years of design decisions, edge cases, and stdlib + implementations live inside Lua.ex's lineage. We learned from + all of them. +

    +

    + Credit also to the long-time Luerl contributors who shaped the API + we ported from: Henning Diedrich, Cees de Groot, Heinz Gies, and + dozens of others on the commit log. And to the Lua team in Rio for designing a + language small enough to fit in your head and powerful enough to + run Roblox. +

    +

    + Lua.ex is built at TV Labs, where we use it every day to script vision-based + integration tests against real streaming devices. +

    +
    + + +
    +
    + + <%!-- ============== CTA ============== --%> +
    +
    +

    + Now go write some Lua. +

    +

    + The story is nice, but the VM is more fun. Open the playground and + watch the opcodes flow. +

    +
    + <.link navigate={~p"/playground"} class="btn btn-primary btn-lg shadow-lg"> + Open the Playground <.icon name="hero-arrow-right-micro" class="size-4" /> + +
    +
    +
    +
    diff --git a/website/lib/website_web/controllers/page_html/home.html.heex b/website/lib/website_web/controllers/page_html/home.html.heex new file mode 100644 index 0000000..f5756d1 --- /dev/null +++ b/website/lib/website_web/controllers/page_html/home.html.heex @@ -0,0 +1,621 @@ + + <%!-- ============== HERO ============== --%> +
    + + + + +
    +
    +
    +
    + + Pure Elixir · Lua 5.3 · agent-ready +
    +

    + Lua, on the + + BEAM. + +
    Scriptable, sandboxed, stupid easy. +

    +

    + An Elixir-native Lua 5.3 VM for embedding untrusted code: AI agent tools, + user-supplied formulas, per-tenant plugins. Zero NIFs, + zero shelling out, every opcode auditable. +

    + +
    + <.link navigate={~p"/playground"} class="btn btn-primary btn-lg shadow-lg"> + <.icon name="hero-play-micro" class="size-5" /> Open the Playground + + <.link navigate={~p"/tour"} class="btn btn-ghost btn-lg border border-base-300"> + Take the Tour <.icon name="hero-arrow-right-micro" class="size-4" /> + +
    + +
    +
    + <.icon name="hero-shield-check-micro" class="size-4 text-success" /> + Sandboxed by default +
    +
    + <.icon name="hero-bolt-micro" class="size-4 text-warning" /> + Register-based VM +
    +
    + <.icon name="hero-cube-micro" class="size-4 text-info" /> + Zero NIFs, zero C +
    +
    + + + Built at + + +
    + +
    + +
    +
    +
    + + + +
    +
    + script.exs +
    +
    ▶ 4 µs
    +
    + <.code_block + id="hero-snippets" + language={:elixir} + rotate + class="px-5 py-4 font-mono text-[13px] leading-6 overflow-x-auto" + snippets={DemoWeb.Snippets.hero()} + /> +
    + + <%!-- floating chip --%> + +
    +
    +
    +
    + + <%!-- ============== WHY LUA ============== --%> +
    +
    +
    +
    +
    + Why Lua? +
    +

    + The scripting language the world already trusts. +

    +

    + Lua is small, fast to learn, and built to be embedded. That's + why Neovim, Roblox, World of Warcraft, Redis, Nginx, and + Adobe Lightroom all chose it. Now you get the same language, + but the host runtime is the BEAM, not a C extension. +

    +
    +
    +
    +
    + Tiny surface +
    +
    + 8 types, ~20 keywords, one core data structure. A weekend to learn. +
    +
    +
    +
    + Built to embed +
    +
    + Designed from day one to live inside a host application, not the other way around. +
    +
    +
    +
    + Battle-tested +
    +
    + Three decades shipping in everything from game engines to load balancers. +
    +
    +
    +
    +
    + Neovim + + Roblox + + WoW + + Redis + + Nginx + + Lightroom + + Wireshark +
    +
    +
    + + <%!-- ============== FEATURE GRID ============== --%> +
    +
    +
    + Why this exists +
    +

    + Everything you'd want in a scripting layer +

    +

    + Built to let your users write code inside your product, without ever + leaving the safety of the BEAM. +

    +
    + +
    + <.feature + icon="hero-cpu-chip-micro" + title="Pure Elixir VM" + accent="primary" + > + Lexer, parser, register-based VM, and stdlib, all written in idiomatic + Elixir. Drops into any Phoenix or OTP release. + + + <.feature + icon="hero-lock-closed-micro" + title="Sandboxed by default" + accent="success" + > + io, os, require, and friends are + disabled. Expose exactly the surface area you want, nothing more. + + + <.feature + icon="hero-puzzle-piece-micro" + title="Elixir ↔ Lua interop" + accent="info" + > + Use deflua to expose any Elixir function. Call Lua back + from Elixir with Lua.call_function!/3. + + + <.feature + icon="hero-sparkles-micro" + title="Compile-time sigil" + accent="accent" + > + ~LUA validates syntax at compile time. + Add the c modifier and ship a pre-compiled chunk in your release. + + + <.feature + icon="hero-document-magnifying-glass-micro" + title="See the bytecode" + accent="warning" + > + Every Lua chunk compiles to a register-based opcode stream. + Inspect every instruction in the playground. + + + <.feature + icon="hero-academic-cap-micro" + title="Beautiful errors" + accent="error" + > + Real stack traces, real source lines, useful messages. Errors blame the + callee by name. None of that "attempt to call a nil value" nonsense. + +
    +
    + + <%!-- ============== AGENT SANDBOXES ============== --%> +
    +
    +
    + For AI agents +
    +

    + Drop a VM in every agent loop. +

    +

    + Each Lua VM is an immutable Elixir value. Spawn one per conversation, + per tool call, per user. Throw it away when you're done. +

    +
    + +
    +
    +

    + The canonical agent-tool pattern +

    +

    + Define the tools the agent can call. Hand it a VM with those tools + loaded. Run whatever Lua the model emits. It can only do what you + exposed, nothing else. No subprocess. No NIF. No surprises. +

    +
      +
    • + <.icon name="hero-check-circle-micro" class="size-5 text-success shrink-0 mt-0.5" /> + One VM per agent conversation, cheap to spawn, garbage collected +
    • +
    • + <.icon name="hero-check-circle-micro" class="size-5 text-success shrink-0 mt-0.5" /> + Tools are plain Elixir functions, arguments and returns marshal automatically +
    • +
    • + <.icon name="hero-check-circle-micro" class="size-5 text-success shrink-0 mt-0.5" /> + No io, os, or + require + by default +
    • +
    • + <.icon name="hero-check-circle-micro" class="size-5 text-success shrink-0 mt-0.5" /> + Replay any opcode the agent ran. Full audit trail in the + playground +
    • +
    +
    + +
    +
    + + + + + agent_tools.exs + +
    + <.code_block + id="agent-tool-code" + language={:elixir} + source={DemoWeb.Snippets.agent_tool()} + /> +
    +
    +
    + + <%!-- ============== COMPILER EXPLORER PROMO ============== --%> +
    +
    +
    +
    +
    + <.icon name="hero-magnifying-glass-circle-micro" class="size-4" /> Compiler Explorer +
    +

    + See your Lua, as the VM sees it. +

    +

    + Like Godbolt, but for Lua bytecode. Type a snippet on the left, watch + the opcodes appear on the right, instruction by instruction, + register by register. Toggle prototypes for nested closures and follow + every closure, call, + and return. +

    +
    + <.link navigate={~p"/playground/fib"} class="btn btn-primary"> + Explore fib(15) + bytecode <.icon name="hero-arrow-right-micro" class="size-4" /> + +
    +
    + +
    +
    +
    +
    -- main.lua
    + <.code_block + id="fib-source" + language={:lua} + class="text-base-content/90" + source={@fib_source} + /> +
    +
    +
    ; bytecode
    +
    + <%= for block <- @fib_bytecode do %> +
    ; {block.name}
    + <%= for ins <- block.instructions, ins.op != :source_line do %> +
    + + {String.pad_leading(Integer.to_string(ins.pc), 2, "0")} + + + {ins.op} {DemoWeb.Bytecode.format_args( + ins.op, + ins.args + )} + +
    + <% end %> + <% end %> +
    +
    +
    +
    +
    +
    +
    + + <%!-- ============== EMBED EXAMPLE ============== --%> +
    +
    +
    +
    + For your app +
    +

    + Drop in. Wire up. Ship. +

    +

    + Define an API module with use Lua.API. Annotate functions + with deflua. Load it into a Lua VM. That's it. Your + users can now write Lua scripts that call back into your Elixir code, + safely. +

    +
      +
    • + <.icon name="hero-check-circle-micro" class="size-5 text-success shrink-0 mt-0.5" /> + Reactive workflows, rules engines, plug-in systems +
    • +
    • + <.icon name="hero-check-circle-micro" class="size-5 text-success shrink-0 mt-0.5" /> + + AI agent sandboxes: one Lua VM per conversation, + tools exposed via deflua + +
    • +
    • + <.icon name="hero-check-circle-micro" class="size-5 text-success shrink-0 mt-0.5" /> + Game logic, automation, embedded DSLs +
    • +
    +
    + +
    +
    + + + + + queue.exs + +
    + <.code_block + id="embed-snippets" + language={:elixir} + rotate + snippets={DemoWeb.Snippets.embed()} + /> +
    +
    +
    + + <%!-- ============== COMPARISON ============== --%> +
    +
    +
    + How it compares +
    +

    + Why this VM, not the others? +

    +

    + Lua on the BEAM isn't new. The developer experience is. + Here's the honest breakdown. +

    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Lua.ex + + this library + + + Luerl + + Erlang-native + + + C Lua via NIF/port + native +
    RuntimePure Elixir on the BEAMErlang on the BEAMC, called over a port or NIF
    Sandboxed by default<.icon name="hero-check-circle-micro" class="size-5 text-success" /><.icon name="hero-check-circle-micro" class="size-5 text-success" />manual
    Crash isolationSupervised, immutable stateSupervised, immutable stateNIF crash = VM crash
    Elixir interop API + deflua macro, ~LUA sigil, Lua.API + Manual function registrationPort marshalling
    Compile-time validation + <.icon name="hero-check-circle-micro" class="size-5 text-success" /> ~LUAc + <.icon name="hero-x-mark-micro" class="size-5 text-base-content/30" /><.icon name="hero-x-mark-micro" class="size-5 text-base-content/30" />
    Bytecode inspectionFirst-classvia tracingvia luac -l
    Error messagesSource line, blame the callee by nameFunctional but terseStandard Lua
    fib(30) throughput*0.71 ips0.88 ips143 ips
    +
    +

    + *Benchee on Apple M1 Pro, Elixir 1.20.0-rc.4 / Erlang 29. Lua.ex + is in the same ballpark as Luerl on tight CPU loops, both ~200× + slower than raw C Lua. For the embedded scripting use case + (AI tools, formulas, plugins), that gap rarely matters. +

    + +
    +
    +
    Pick Lua.ex when…
    +

    + you want ergonomic Elixir interop, compile-time Lua, and + beautiful errors, and you control the host application. +

    +
    +
    +
    Pick Luerl when…
    +

    + you're on the Erlang side and need a mature, battle-tested + Lua 5.3 VM with the longest track record on the BEAM. +

    +
    +
    +
    Pick C Lua when…
    +

    + raw CPU throughput dominates and you can give up BEAM-level + isolation, GC, and supervision in exchange. +

    +
    +
    +
    + + <%!-- ============== CTA ============== --%> +
    +
    +

    + Go play. Then read the docs. +

    +

    + The fastest way to understand this VM is to write Lua and watch the + opcodes flow. +

    +
    + <.link navigate={~p"/playground"} class="btn btn-primary btn-lg shadow-lg"> + Open the Playground <.icon name="hero-arrow-right-micro" class="size-4" /> + + + <.icon name="hero-book-open-micro" class="size-4" /> Read the docs + +
    +
    + + {"{:lua, \"~> 1.0.0-rc.1\"}"} + + +
    +
    +
    +
    diff --git a/website/lib/website_web/endpoint.ex b/website/lib/website_web/endpoint.ex new file mode 100644 index 0000000..34c96b1 --- /dev/null +++ b/website/lib/website_web/endpoint.ex @@ -0,0 +1,58 @@ +defmodule DemoWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :website + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_website_key", + signing_salt: "GltKFM/w", + same_site: "Lax" + ] + + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [session: @session_options]], + longpoll: [connect_info: [session: @session_options]] + + # Serve at "/" the static files from "priv/static" directory. + # + # When code reloading is disabled (e.g., in production), + # the `gzip` option is enabled to serve compressed + # static files generated by running `phx.digest`. + plug Plug.Static, + at: "/", + from: :website, + gzip: not code_reloading?, + only: DemoWeb.static_paths(), + raise_on_missing_only: code_reloading? + + if Mix.env() == :dev do + plug Tidewave, team: [id: "tv-labs", token: "6jxcajjjd63jqd5yfk7zvvcucgi3lpsf4yoqeuq"] + end + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader + end + + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + + plug Plug.MethodOverride + plug Plug.Head + plug Plug.Session, @session_options + plug DemoWeb.Router +end diff --git a/website/lib/website_web/gettext.ex b/website/lib/website_web/gettext.ex new file mode 100644 index 0000000..fc63960 --- /dev/null +++ b/website/lib/website_web/gettext.ex @@ -0,0 +1,25 @@ +defmodule DemoWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), your module compiles translations + that you can use in your application. To use this Gettext backend module, + call `use Gettext` and pass it as an option: + + use Gettext, backend: DemoWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) + + # Domain-based translation + dgettext("errors", "Here is the error message to translate") + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext.Backend, otp_app: :website +end diff --git a/website/lib/website_web/highlight.ex b/website/lib/website_web/highlight.ex new file mode 100644 index 0000000..30ff5a9 --- /dev/null +++ b/website/lib/website_web/highlight.ex @@ -0,0 +1,104 @@ +defmodule DemoWeb.Highlight do + @moduledoc """ + Server-rendered syntax highlighting. + + Elixir runs through Makeup (`Makeup.Lexers.ElixirLexer`). Lua uses a + small hand-rolled tokenizer in this module — `makeup_lua` doesn't + exist on Hex and the static snippets we render don't need a full + Lua 5.3 lexer. Both paths emit Pygments-style class names so the + same CSS in `app.css` styles them. + """ + + @lua_keywords ~w(and break do else elseif end for function goto + if in local not or repeat return then until while) + + @lua_kc ~w(true false nil) + + # Matches in priority order. Longest/most-specific patterns first. + # Each entry is {regex, css_class}. A `nil` class means "no span". + @lua_patterns [ + {~r/\A--\[\[[\s\S]*?(?:\]\]|\z)/, "cm"}, + {~r/\A--[^\r\n]*/, "c1"}, + {~r/\A"(?:\\.|[^"\\\r\n])*"/, "s2"}, + {~r/\A'(?:\\.|[^'\\\r\n])*'/, "s1"}, + {~r/\A\[\[[\s\S]*?(?:\]\]|\z)/, "s"}, + {~r/\A0[xX][\da-fA-F]+(?:\.[\da-fA-F]*)?(?:[pP][+-]?\d+)?/, "mh"}, + {~r/\A\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/, "mf"}, + {~r/\A(?:\.\.\.|\.\.|::|==|~=|<=|>=|<<|>>|\/\/|[+\-*\/%^#=<>~])/, "o"}, + {~r/\A[(){}\[\],;:.]/, "p"}, + {~r/\A[A-Za-z_][A-Za-z0-9_]*/, :ident}, + {~r/\A[\s]+/, nil} + ] + + @doc """ + Returns inner HTML for `source` highlighted in the given language, + ready to embed inside `
    `. + + Supported: `:elixir`, `:lua`. Anything else falls back to escaped + plain text so an unknown language can never crash a page render. + """ + def to_html(source, language \\ :elixir) + + def to_html(source, :elixir) when is_binary(source) do + source + |> Makeup.highlight_inner_html(lexer: Makeup.Lexers.ElixirLexer) + |> Phoenix.HTML.raw() + end + + def to_html(source, :lua) when is_binary(source) do + source + |> tokenize_lua([]) + |> Phoenix.HTML.raw() + end + + def to_html(source, _other) when is_binary(source) do + Phoenix.HTML.html_escape(source) + end + + defp tokenize_lua("", acc), do: acc |> Enum.reverse() |> IO.iodata_to_binary() + + defp tokenize_lua(source, acc) do + {chunk, class, rest} = match_lua(source) + escaped = chunk |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string() + + fragment = + case class do + nil -> escaped + cls -> [~s(), escaped, ""] + end + + tokenize_lua(rest, [fragment | acc]) + end + + defp match_lua(source) do + Enum.find_value(@lua_patterns, fn {regex, class} -> + case Regex.run(regex, source, return: :index) do + [{0, len}] -> + {chunk, rest} = String.split_at(source, len) + {chunk, resolve_lua_class(class, chunk), rest} + + _ -> + nil + end + end) || fallback_lua(source) + end + + defp resolve_lua_class(:ident, chunk) do + cond do + chunk in @lua_kc -> "kc" + chunk in @lua_keywords -> "k" + true -> "n" + end + end + + defp resolve_lua_class(class, _chunk), do: class + + # No pattern matched (e.g. a stray UTF-8 char). Consume one codepoint + # raw so we make forward progress without blowing up the page. + defp fallback_lua(source) do + case String.next_codepoint(source) do + {cp, rest} -> {cp, nil, rest} + nil -> {"", nil, ""} + end + end +end diff --git a/website/lib/website_web/live/opcodes_live.ex b/website/lib/website_web/live/opcodes_live.ex new file mode 100644 index 0000000..963ad3b --- /dev/null +++ b/website/lib/website_web/live/opcodes_live.ex @@ -0,0 +1,232 @@ +defmodule DemoWeb.OpcodesLive do + use DemoWeb, :live_view + + alias DemoWeb.Bytecode + + @categories [ + %{ + id: "loads", + title: "Loads & moves", + blurb: + "Get values into registers: constants, nil, booleans, and register-to-register copies.", + ops: [:load_constant, :load_nil, :load_boolean, :load_env, :move] + }, + %{ + id: "globals-upvalues", + title: "Globals & upvalues", + blurb: "Read and write the global environment and captured outer-scope bindings.", + ops: [ + :get_global, + :set_global, + :get_upvalue, + :set_upvalue, + :get_open_upvalue, + :set_open_upvalue + ] + }, + %{ + id: "tables", + title: "Tables", + blurb: "Allocate, index, field-access, and bulk-fill the universal Lua data structure.", + ops: [:new_table, :get_table, :set_table, :get_field, :set_field, :set_list, :self] + }, + %{ + id: "arithmetic", + title: "Arithmetic & strings", + blurb: "Numeric and bitwise ops, plus string concatenation and length.", + ops: [ + :add, + :subtract, + :multiply, + :divide, + :floor_divide, + :modulo, + :power, + :negate, + :concatenate, + :length, + :bitwise_and, + :bitwise_or, + :bitwise_xor, + :bitwise_not, + :shift_left, + :shift_right + ] + }, + %{ + id: "comparison", + title: "Comparison & logic", + blurb: "Equality, ordering, and short-circuit logical control.", + ops: [:equal, :less_than, :less_equal, :not, :test, :test_true, :test_and, :test_or] + }, + %{ + id: "control", + title: "Control flow", + blurb: + "Loops and structured exits. Most jumps live on the continuation list, not as PC offsets.", + ops: [:while_loop, :repeat_loop, :numeric_for, :generic_for, :break, :scope] + }, + %{ + id: "calls", + title: "Calls & returns", + blurb: + "Invoke and return, including tail calls, varargs, and the method-shim for `obj:method(...)`.", + ops: [:call, :tail_call, :return, :return_vararg, :vararg] + }, + %{ + id: "closures", + title: "Closures", + blurb: "Build nested functions with captured upvalues.", + ops: [:closure] + }, + %{ + id: "meta", + title: "Metadata", + blurb: "Pseudo-instructions for source tracking and debugging.", + ops: [:source_line] + } + ] + + @impl true + def mount(_params, _session, socket) do + {:ok, + socket + |> assign(:page_title, "Opcode reference") + |> assign(:categories, @categories) + |> assign(:query, "")} + end + + @impl true + def handle_event("filter", %{"q" => q}, socket) do + {:noreply, assign(socket, :query, q)} + end + + @impl true + def render(assigns) do + ~H""" + +
    +
    + + +
    +
    +

    + Opcode reference +

    +

    + The Lua compiler in this library lowers source to a flat + stream of register-based + opcodes. There + are no labels and no PC-relative jumps; control flow is + threaded through an explicit continuation list + inside the executor. The full set of opcodes the + disassembler emits is documented below. +

    +
    + + <%= for cat <- @categories do %> + <% ops = visible_ops(cat.ops, @query) %> + <%= if ops != [] do %> +
    +
    +

    + {cat.title} +

    +

    {cat.blurb}

    +
    + +
    + <%= for op <- ops do %> +
    +
    + + {op} + + + {Bytecode.op_signature(op)} + +
    +

    + {Bytecode.opcode_doc(op) || + raw("(undocumented; see source)")} +

    + + # permalink + +
    + <% end %> +
    +
    + <% end %> + <% end %> + + <%= if all_visible(@categories, @query) == 0 do %> +
    + <.icon name="hero-magnifying-glass" class="size-8 mx-auto mb-2 opacity-50" /> +
    No opcodes match {@query}.
    +
    + <% end %> +
    +
    +
    +
    + """ + end + + defp visible_ops(ops, ""), do: ops + + defp visible_ops(ops, query) do + q = String.downcase(String.trim(query)) + Enum.filter(ops, &String.contains?(Atom.to_string(&1), q)) + end + + defp all_visible(cats, q), do: Enum.sum(Enum.map(cats, &length(visible_ops(&1.ops, q)))) +end diff --git a/website/lib/website_web/live/playground_live.ex b/website/lib/website_web/live/playground_live.ex new file mode 100644 index 0000000..9d4d28f --- /dev/null +++ b/website/lib/website_web/live/playground_live.ex @@ -0,0 +1,540 @@ +defmodule DemoWeb.PlaygroundLive do + use DemoWeb, :live_view + + import DemoWeb.BytecodeComponents + alias Website.LuaSandbox + + @max_shared_bytes 20_000 + @lua_timeout_ms 1_500 + + @impl true + def mount(_params, _session, socket) do + socket = + socket + |> assign(:page_title, "Playground") + |> assign(:examples, LuaSandbox.examples()) + |> assign(:active_example, "hello") + |> assign(:source, default_source("hello")) + |> assign(:blocks, LuaSandbox.example_blocks("hello")) + |> assign(:result, nil) + |> assign(:running, false) + |> assign(:show_bytecode, true) + |> assign(:selected_block, 0) + |> assign(:shared_source?, false) + |> assign(:lua_timer_ref, nil) + + {:ok, socket} + end + + @impl true + def handle_params(params, _url, socket) do + cond do + Map.has_key?(params, "example") -> + id = params["example"] + + cond do + not Enum.any?(LuaSandbox.examples(), &(&1.id == id)) -> + {:noreply, + socket + |> put_flash(:error, "No example named \"#{id}\". Showing the default.") + |> push_patch(to: ~p"/playground")} + + socket.assigns.active_example == id -> + {:noreply, socket} + + true -> + source = default_source(id) + + {:noreply, + socket + |> assign(:active_example, id) + |> assign(:source, source) + |> assign(:blocks, LuaSandbox.example_blocks(id)) + |> assign(:result, nil) + |> assign(:selected_block, 0) + |> assign(:shared_source?, false) + |> push_event("lua-editor:set-source", %{source: source, example_id: id})} + end + + Map.has_key?(params, "source") -> + case decode_shared_source(params["source"]) do + {:ok, source} -> + {:noreply, + socket + |> assign(:active_example, nil) + |> assign(:source, source) + |> assign(:blocks, compile_blocks(source)) + |> assign(:result, nil) + |> assign(:selected_block, 0) + |> assign(:shared_source?, true) + |> push_event("lua-editor:set-source", %{source: source, example_id: nil})} + + :error -> + {:noreply, + socket + |> put_flash(:error, "That shared link is invalid or expired.") + |> push_patch(to: ~p"/playground")} + end + + true -> + {:noreply, push_patch(socket, to: ~p"/playground/#{default_example_id()}", replace: true)} + end + end + + defp decode_shared_source(encoded) when is_binary(encoded) do + with {:ok, gzipped} <- Base.url_decode64(encoded, padding: false), + {:ok, source} <- safe_gunzip(gzipped), + true <- byte_size(source) <= @max_shared_bytes do + {:ok, source} + else + _ -> :error + end + end + + defp decode_shared_source(_), do: :error + + defp safe_gunzip(bin) do + {:ok, :zlib.gunzip(bin)} + rescue + _ -> :error + catch + _, _ -> :error + end + + @impl true + def handle_event("source-changed", %{"source" => source}, socket) do + {:noreply, assign(socket, :source, source)} + end + + def handle_event("load-example", %{"id" => id}, socket) do + source = default_source(id) + + {:noreply, + socket + |> assign(:active_example, id) + |> assign(:source, source) + |> assign(:blocks, LuaSandbox.example_blocks(id)) + |> assign(:result, nil) + |> assign(:selected_block, 0) + |> push_event("lua-editor:set-source", %{source: source, example_id: id}) + |> push_patch(to: ~p"/playground/#{id}")} + end + + def handle_event("run", %{"source" => source}, socket) do + {:noreply, start_lua_run(socket, source)} + end + + def handle_event("toggle-bytecode", _, socket) do + {:noreply, update(socket, :show_bytecode, &(!&1))} + end + + def handle_event("select-block", %{"index" => idx}, socket) when is_binary(idx) do + {:noreply, assign(socket, :selected_block, String.to_integer(idx))} + end + + def handle_event("reset", _params, %{assigns: %{active_example: nil}} = socket) do + {:noreply, socket} + end + + def handle_event("reset", _params, socket) do + id = socket.assigns.active_example + source = default_source(id) + + {:noreply, + socket + |> assign(:source, source) + |> assign(:blocks, LuaSandbox.example_blocks(id)) + |> assign(:result, nil) + |> assign(:selected_block, 0) + |> push_event("lua-editor:set-source", %{ + source: source, + example_id: id, + clear_storage: true + })} + end + + @impl true + def handle_async(:lua_run, {:ok, result}, socket) do + socket = + socket + |> finish_lua_run() + |> assign(:result, result) + |> assign(:selected_block, 0) + + socket = + case result do + %{bytecode: [_ | _] = bs} -> assign(socket, :blocks, bs) + _ -> socket + end + + {:noreply, socket} + end + + def handle_async(:lua_run, {:exit, _reason}, socket) do + {:noreply, + socket + |> finish_lua_run() + |> assign(:result, timeout_result())} + end + + @impl true + def handle_info(:lua_timeout, %{assigns: %{running: true}} = socket) do + {:noreply, cancel_async(socket, :lua_run)} + end + + def handle_info(:lua_timeout, socket), do: {:noreply, socket} + + defp start_lua_run(socket, source) do + socket + |> cancel_lua_run() + |> assign(:source, source) + |> assign(:running, true) + |> assign(:lua_timer_ref, Process.send_after(self(), :lua_timeout, @lua_timeout_ms)) + |> start_async(:lua_run, fn -> LuaSandbox.run(source) end) + end + + defp cancel_lua_run(socket) do + if ref = socket.assigns[:lua_timer_ref], do: Process.cancel_timer(ref) + + socket + |> cancel_async(:lua_run) + |> assign(:lua_timer_ref, nil) + end + + defp finish_lua_run(socket) do + if ref = socket.assigns[:lua_timer_ref], do: Process.cancel_timer(ref) + + socket + |> assign(:running, false) + |> assign(:lua_timer_ref, nil) + end + + defp timeout_result do + %{ + status: :timeout, + output: "", + returns: [], + error: "Execution timed out after #{@lua_timeout_ms}ms", + duration_us: @lua_timeout_ms * 1_000, + bytecode: [] + } + end + + defp default_source(id) do + case Enum.find(LuaSandbox.examples(), &(&1.id == id)) do + nil -> hd(LuaSandbox.examples()).source + ex -> ex.source + end + end + + defp default_example_id, do: hd(LuaSandbox.examples()).id + + defp active_blurb(examples, active_id) do + case Enum.find(examples, &(&1.id == active_id)) do + %{blurb: blurb} when is_binary(blurb) and blurb != "" -> blurb + _ -> nil + end + end + + defp has_copyable_output?(%{status: :ok, output: out, returns: rets}) + when out != "" or rets != [], + do: true + + defp has_copyable_output?(%{status: :error}), do: true + defp has_copyable_output?(_), do: false + + defp empty_source?(nil), do: true + defp empty_source?(""), do: true + defp empty_source?(source) when is_binary(source), do: String.trim(source) == "" + defp empty_source?(_), do: false + + @impl true + def render(assigns) do + ~H""" + +
    +
    +
    +

    + Lua Playground +

    +

    + Write Lua, run it on the BEAM, and watch the + register-based bytecode + this VM actually executes. No JavaScript Lua. Every byte is Elixir. +

    +
    +
    + + +
    +
    + +
    + <%= for ex <- @examples do %> + + <% end %> +
    + + <%= if blurb = active_blurb(@examples, @active_example) do %> +
    + <.icon name="hero-light-bulb-micro" class="size-4 text-accent shrink-0 mt-0.5" /> + {raw(blurb)} +
    + <% end %> + +
    +
    + <.editor_panel source={@source} running={@running} active_example={@active_example} /> + <.output_panel result={@result} running={@running} /> +
    + + <%= if @show_bytecode do %> + <.playground_bytecode blocks={@blocks} selected={@selected_block} /> + <% end %> +
    + +
    + <.kbd_card> + <:label>Run + + + + <.kbd_card> + <:label>Indent + Tab / Shift + + Tab + + <.kbd_card> + <:label>Heads-up + + Snippets run in a sandboxed VM with a 1.5s timeout. + + +
    +
    +
    + """ + end + + slot :label, required: true + slot :inner_block, required: true + + defp kbd_card(assigns) do + ~H""" +
    + + {render_slot(@label)} + + + {render_slot(@inner_block)} + +
    + """ + end + + attr :source, :string, required: true + attr :running, :boolean, required: true + attr :active_example, :string, default: nil + + defp editor_panel(assigns) do + ~H""" +
    +
    +
    + + + + main.lua +
    +
    + + +
    +
    +
    + +
    +
    + """ + end + + attr :result, :map, default: nil + attr :running, :boolean, required: true + + defp output_panel(assigns) do + ~H""" +
    +
    +
    Output
    +
    + <%= cond do %> + <% @running -> %> + executing… + <% match?(%{status: :ok}, @result) -> %> + + ok · {format_us(@result.duration_us)} + + <% match?(%{status: :timeout}, @result) -> %> + timeout + <% match?(%{status: :error}, @result) -> %> + error + <% true -> %> + idle + <% end %> + <%= if has_copyable_output?(@result) do %> + + <% end %> +
    +
    +
    + <%= cond do %> + <% @running -> %> +
    Running on the BEAM…
    + <% match?(%{status: :error}, @result) -> %> +
    <%= @result.error %>
    + <%= if @result.output != "" do %> +
    +
    + Output before error +
    +
    <%= @result.output %>
    +
    + <% end %> + <% match?(%{status: :timeout}, @result) -> %> +
    {@result.error}
    + <% match?(%{status: :ok}, @result) -> %> + <%= if @result.output != "" do %> +
    <%= @result.output %>
    + <% end %> + <%= if @result.returns != [] do %> +
    +
    + <.icon name="hero-arrow-uturn-left-micro" class="size-3" /> returned +
    +
    <%= Enum.join(@result.returns, ", ") %>
    +
    + <% end %> + <%= if @result.output == "" and @result.returns == [] do %> +
    + (no output, no return values) +
    + <% end %> + <% true -> %> +
    + Hit Run + or press + to execute. +
    + <% end %> +
    +
    + """ + end + + attr :blocks, :list, required: true + attr :selected, :integer, required: true + + defp playground_bytecode(assigns) do + ~H""" +
    + <.bytecode_panel + blocks={@blocks} + selected={@selected} + variant={:full} + max_height="max-h-[520px]" + /> +
    + """ + end + + defp compile_blocks(source) do + case LuaSandbox.compile(source) do + {:ok, _chunk, blocks} -> blocks + {:error, _} -> [] + end + end + + defp format_us(us) when us < 1_000, do: "#{us} µs" + defp format_us(us) when us < 1_000_000, do: "#{Float.round(us / 1_000, 2)} ms" + defp format_us(us), do: "#{Float.round(us / 1_000_000, 3)} s" +end diff --git a/website/lib/website_web/live/tour_live.ex b/website/lib/website_web/live/tour_live.ex new file mode 100644 index 0000000..055b7dd --- /dev/null +++ b/website/lib/website_web/live/tour_live.ex @@ -0,0 +1,577 @@ +defmodule DemoWeb.TourLive do + use DemoWeb, :live_view + + import DemoWeb.BytecodeComponents + alias Website.LuaSandbox + + @lua_timeout_ms 1_500 + + @impl true + def mount(_params, _session, socket) do + lessons = LuaSandbox.tour_lessons() + + socket = + socket + |> assign(:page_title, "Tour of Lua") + |> assign(:lessons, lessons) + |> assign(:lesson, hd(lessons)) + |> assign(:source, Map.get(hd(lessons), :source, "")) + |> assign(:result, nil) + |> assign(:running, false) + |> assign(:show_bytecode, false) + |> assign(:selected_block, 0) + |> assign(:lua_timer_ref, nil) + + {:ok, socket} + end + + @impl true + def handle_params(params, _url, socket) do + case params do + %{"slug" => slug} -> + case Enum.find(socket.assigns.lessons, &(&1.slug == slug)) do + nil -> + {:noreply, push_patch(socket, to: ~p"/tour")} + + lesson -> + {:noreply, + socket + |> cancel_lua_run() + |> assign(:lesson, lesson) + |> assign(:source, Map.get(lesson, :source, "")) + |> assign(:result, nil) + |> assign(:selected_block, 0)} + end + + _ -> + {:noreply, socket} + end + end + + @impl true + def handle_event("source-changed", %{"source" => source}, socket) do + {:noreply, assign(socket, :source, source)} + end + + def handle_event("run", %{"source" => source}, socket) do + {:noreply, start_lua_run(socket, source)} + end + + def handle_event("reset", _params, socket) do + source = Map.get(socket.assigns.lesson, :source, "") + + {:noreply, + socket + |> cancel_lua_run() + |> assign(:source, source) + |> assign(:result, nil) + |> push_event("lua-editor:set-source", %{source: source})} + end + + def handle_event("toggle-bytecode", _, socket) do + {:noreply, update(socket, :show_bytecode, &(!&1))} + end + + def handle_event("select-block", %{"index" => idx}, socket) when is_binary(idx) do + {:noreply, assign(socket, :selected_block, String.to_integer(idx))} + end + + @impl true + def handle_async(:lua_run, {:ok, result}, socket) do + {:noreply, + socket + |> finish_lua_run() + |> assign(:result, result)} + end + + def handle_async(:lua_run, {:exit, _reason}, socket) do + {:noreply, + socket + |> finish_lua_run() + |> assign(:result, timeout_result())} + end + + @impl true + def handle_info(:lua_timeout, %{assigns: %{running: true}} = socket) do + {:noreply, cancel_async(socket, :lua_run)} + end + + def handle_info(:lua_timeout, socket), do: {:noreply, socket} + + defp start_lua_run(socket, source) do + socket + |> cancel_lua_run() + |> assign(:source, source) + |> assign(:running, true) + |> assign(:lua_timer_ref, Process.send_after(self(), :lua_timeout, @lua_timeout_ms)) + |> start_async(:lua_run, fn -> LuaSandbox.run(source) end) + end + + defp cancel_lua_run(socket) do + if ref = socket.assigns[:lua_timer_ref], do: Process.cancel_timer(ref) + + socket + |> cancel_async(:lua_run) + |> assign(:running, false) + |> assign(:lua_timer_ref, nil) + end + + defp finish_lua_run(socket) do + if ref = socket.assigns[:lua_timer_ref], do: Process.cancel_timer(ref) + + socket + |> assign(:running, false) + |> assign(:lua_timer_ref, nil) + end + + defp timeout_result do + %{ + status: :timeout, + output: "", + returns: [], + error: "Execution timed out after #{@lua_timeout_ms}ms", + duration_us: @lua_timeout_ms * 1_000, + bytecode: [] + } + end + + defp lesson_index(lessons, lesson) do + Enum.find_index(lessons, &(&1.slug == lesson.slug)) || 0 + end + + defp prev_lesson(lessons, lesson) do + idx = lesson_index(lessons, lesson) + if idx > 0, do: Enum.at(lessons, idx - 1), else: nil + end + + defp next_lesson(lessons, lesson) do + idx = lesson_index(lessons, lesson) + Enum.at(lessons, idx + 1) + end + + defp lesson_by_slug(lessons, slug), do: Enum.find(lessons, &(&1.slug == slug)) + + defp runnable?(lesson), do: Map.get(lesson, :runnable, true) + + defp has_editor?(lesson), do: is_binary(Map.get(lesson, :source)) + + defp integration?(lesson), do: lesson.chapter == :integration + + attr :lessons, :list, required: true + attr :lesson, :map, required: true + + defp nav_body(assigns) do + ~H""" +
    +
    +

    + A tour of Lua & Lua.ex +

    +

    + {length(@lessons)} lessons across {length(LuaSandbox.chapters())} chapters. + Every snippet runs live on the BEAM. +

    +
    + + <%= for {{chapter_slug, chapter_title}, chapter_idx} <- Enum.with_index(LuaSandbox.chapters()) do %> +
    +
    + {roman(chapter_idx + 1)}.  {chapter_title} +
    +
      + <%= for lesson <- @lessons, lesson.chapter == chapter_slug do %> +
    1. + <.link + patch={~p"/tour/#{lesson.slug}"} + class={[ + "flex items-center gap-3 px-3 py-1.5 rounded-box transition-colors", + @lesson.slug == lesson.slug && "bg-primary/10 text-primary", + @lesson.slug != lesson.slug && "hover:bg-base-200 text-base-content/80" + ]} + > + + {lesson_index(@lessons, lesson) + 1} + + + {raw(render_inline(lesson.title))} + + +
    2. + <% end %> +
    +
    + <% end %> +
    + """ + end + + @impl true + def render(assigns) do + ~H""" + +
    +
    + + +
    +
    + + Chapter {chapter_label(@lesson.chapter)} · + + {LuaSandbox.chapter_title(@lesson.chapter)} + + + / + + Lesson {lesson_index(@lessons, @lesson) + 1} of {length(@lessons)} + + <%= if integration?(@lesson) do %> + + <.icon name="hero-arrows-right-left-micro" class="size-3" /> Host integration + + <% end %> +
    + +

    + {raw(render_inline(@lesson.title))} +

    + + <%= if obj = Map.get(@lesson, :objective) do %> +
    + <.icon name="hero-flag-micro" class="size-4 text-primary shrink-0 mt-0.5" /> + + You'll learn: {raw( + render_inline(obj) + )} + +
    + <% end %> + +
    + <%= for paragraph <- String.split(@lesson.body, "\n\n") do %> +

    {raw(render_inline(paragraph))}

    + <% end %> +
    + + <%= if elixir = Map.get(@lesson, :elixir_source) do %> +
    +
    +
    + <.icon name="hero-cube-micro" class="size-3.5" /> Elixir · your app +
    + + Reference only + +
    +
    {DemoWeb.Highlight.to_html(elixir, :elixir)}
    +
    + <% end %> + + <%= if has_editor?(@lesson) do %> + <%= unless runnable?(@lesson) do %> +
    + <.icon + name="hero-information-circle-micro" + class="size-4 text-warning shrink-0 mt-0.5" + /> + + Read-only: + this snippet uses canonical Lua features that aren't implemented in Lua.ex yet. It's here as a reference. + +
    + <% end %> + +
    +
    +
    +
    + <%= if integration?(@lesson) do %> + <.icon name="hero-bolt-micro" class="size-3.5 text-primary" /> + <% end %> + {@lesson.slug}.lua +
    +
    + <%= if runnable?(@lesson) do %> + + + + <% end %> +
    +
    +
    + +
    +
    +
    + + <%= if runnable?(@lesson) do %> + <.tour_output result={@result} running={@running} /> + <% end %> + <% end %> + + <%= if ex = Map.get(@lesson, :exercise) do %> +
    + <.icon name="hero-beaker-micro" class="size-4 text-accent shrink-0 mt-0.5" /> +
    + Try it: + {raw(render_inline(ex))} +
    +
    + <% end %> + + <%= if @show_bytecode and has_editor?(@lesson) and runnable?(@lesson) do %> + <.tour_bytecode + source={@source} + result={@result} + selected={@selected_block} + /> + <% end %> + + <%= if (refs = Map.get(@lesson, :see_also)) && refs != [] do %> +
    + + See also + + <%= for slug <- refs, target = lesson_by_slug(@lessons, slug), target != nil do %> + <.link + patch={~p"/tour/#{target.slug}"} + class="inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-xs bg-base-200 hover:bg-base-300/70 border border-base-300/60 text-base-content/80" + > + {target.title} + + <% end %> +
    + <% end %> + +
    + <%= if prev = prev_lesson(@lessons, @lesson) do %> + <.link patch={~p"/tour/#{prev.slug}"} class="btn btn-ghost"> + <.icon name="hero-arrow-left-micro" class="size-4" /> {prev.title} + + <% else %> + <.link navigate={~p"/playground"} class="btn btn-ghost"> + <.icon name="hero-arrow-left-micro" class="size-4" /> Playground + + <% end %> + + <%= if nxt = next_lesson(@lessons, @lesson) do %> + <.link patch={~p"/tour/#{nxt.slug}"} class="btn btn-primary"> + {nxt.title} <.icon name="hero-arrow-right-micro" class="size-4" /> + + <% else %> + <.link navigate={~p"/playground"} class="btn btn-primary"> + Open the full playground <.icon name="hero-arrow-right-micro" class="size-4" /> + + <% end %> +
    +
    +
    +
    +
    + """ + end + + attr :result, :map, default: nil + attr :running, :boolean, required: true + + defp tour_output(assigns) do + ~H""" +
    +
    + + Output + + + <%= cond do %> + <% @running -> %> + running… + <% match?(%{status: :ok}, @result) -> %> + + ok · {format_us(@result.duration_us)} + + <% match?(%{status: :error}, @result) -> %> + + error · {format_us(@result.duration_us)} + + <% true -> %> + idle + <% end %> + +
    +
    + <%= cond do %> + <% @running -> %> + + <% match?(%{status: :error}, @result) -> %> +
    <%= @result.error %>
    + <% match?(%{status: :ok}, @result) -> %> + <%= if @result.output != "" do %> +
    <%= @result.output %>
    + <% end %> + <%= if @result.returns != [] do %> +
    + → {Enum.join(@result.returns, ", ")} +
    + <% end %> + <%= if @result.output == "" and @result.returns == [] do %> + (no output) + <% end %> + <% true -> %> + Hit Run to execute. + <% end %> +
    +
    + """ + end + + attr :source, :string, required: true + attr :result, :map, default: nil + attr :selected, :integer, default: 0 + + defp tour_bytecode(assigns) do + blocks = + cond do + match?(%{bytecode: [_ | _]}, assigns.result) -> + assigns.result.bytecode + + true -> + case LuaSandbox.compile(assigns.source) do + {:ok, _, bs} -> bs + _ -> [] + end + end + + assigns = assign(assigns, :blocks, blocks) + + ~H""" +
    + <.bytecode_panel + blocks={@blocks} + selected={@selected} + variant={:full} + max_height="max-h-[400px]" + empty_label="Run the snippet to see bytecode." + /> +
    + """ + end + + defp format_us(us) when is_integer(us) and us < 1_000, do: "#{us} µs" + + defp format_us(us) when is_integer(us) and us < 1_000_000, + do: "#{Float.round(us / 1_000, 2)} ms" + + defp format_us(us) when is_integer(us), do: "#{Float.round(us / 1_000_000, 3)} s" + + defp roman(1), do: "I" + defp roman(2), do: "II" + defp roman(3), do: "III" + defp roman(4), do: "IV" + defp roman(5), do: "V" + defp roman(n), do: Integer.to_string(n) + + defp chapter_label(chapter) do + case Enum.find_index(LuaSandbox.chapters(), fn {slug, _} -> slug == chapter end) do + nil -> "?" + idx -> roman(idx + 1) + end + end + + # Markdown-lite for lesson body / objective / exercise text: + # `code` → styled inline code + # [text](url) → anchor (external links open a new tab) + defp render_inline(text) do + text = + Regex.replace(~r/\[([^\]]+)\]\(([^)]+)\)/, text, fn _full, anchor, url -> + link_html(anchor, url) + end) + + Regex.replace(~r/`([^`]+)`/, text, fn _full, code -> + ~s|#{code}| + end) + end + + defp link_html(text, "http" <> _ = url) do + ~s|#{text}| + end + + defp link_html(text, url) do + ~s|#{text}| + end +end diff --git a/website/lib/website_web/router.ex b/website/lib/website_web/router.ex new file mode 100644 index 0000000..4569c20 --- /dev/null +++ b/website/lib/website_web/router.ex @@ -0,0 +1,54 @@ +defmodule DemoWeb.Router do + use DemoWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, html: {DemoWeb.Layouts, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", DemoWeb do + get "/health", PageController, :health + end + + scope "/", DemoWeb do + pipe_through :browser + + get "/", PageController, :home + get "/about", PageController, :about + live "/playground", PlaygroundLive, :index + live "/playground/:example", PlaygroundLive, :example + live "/tour", TourLive, :index + live "/tour/:slug", TourLive, :lesson + live "/reference/opcodes", OpcodesLive, :index + end + + # Other scopes may use custom stacks. + # scope "/api", DemoWeb do + # pipe_through :api + # end + + # Enable LiveDashboard and Swoosh mailbox preview in development + if Application.compile_env(:website, :dev_routes) do + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + import Phoenix.LiveDashboard.Router + + scope "/dev" do + pipe_through :browser + + live_dashboard "/dashboard", metrics: DemoWeb.Telemetry + forward "/mailbox", Plug.Swoosh.MailboxPreview + end + end +end diff --git a/website/lib/website_web/snippets.ex b/website/lib/website_web/snippets.ex new file mode 100644 index 0000000..2351874 --- /dev/null +++ b/website/lib/website_web/snippets.ex @@ -0,0 +1,212 @@ +defmodule DemoWeb.Snippets do + @moduledoc """ + Source for the rotating code snippets shown on the marketing pages. + + Keeping them in a module (rather than inline in `.heex`) sidesteps + heredoc-inside-heredoc escape headaches and makes the snippets + unit-testable. + """ + + @hero [ + %{ + label: "rules.exs", + source: """ + # Embed Lua in your Elixir app + # with a single function call. + + defmodule MyApp.Rules do + use Lua.API, scope: "rules" + + deflua double(n), do: n * 2 + end + + # Now your Elixir function is + # callable from any Lua script: + + lua = Lua.new() |> Lua.load_api(MyApp.Rules) + + {[10], _lua} = Lua.eval!(lua, "return rules.double(5)") + """ + }, + %{ + label: "sigil.exs", + source: ~S''' + # Compile Lua at compile-time with the + # ~LUA sigil. `c` returns a compiled chunk, + # ready to run on any state. + + import Lua, only: [sigil_LUA: 2] + + chunk = ~LUA""" + local total = 0 + for i = 1, 100 do total = total + i end + return total + """c + + {[5050], _state} = Lua.run(Lua.new(), chunk) + ''' + }, + %{ + label: "sandbox.exs", + source: """ + # Sandboxed by default. No file system, + # no os.execute, no surprise side-effects. + + lua = Lua.new() + + {:error, err} = + Lua.eval(lua, "return os.execute('rm -rf /')") + + # err.message =~ "attempted to call" + """ + }, + %{ + label: "agent.exs", + source: """ + # Give an LLM a Lua VM with your tools + # bound. It can only call what you expose. + + defmodule Agent.Tools do + use Lua.API, scope: "tools" + + deflua search(q), do: MyApp.Search.run(q) + end + + lua = Lua.new() |> Lua.load_api(Agent.Tools) + + # The model emits Lua. You run it. Done. + {:ok, {results, _}} = Lua.eval(lua, llm_script) + """ + } + ] + + @embed [ + %{ + label: "queue.exs", + source: """ + defmodule Queue do + use Lua.API, scope: "q" + + deflua push(v), state do + queue = Lua.get!(state, [:my_queue]) + + {[], state} = + Lua.call_function!( + state, + [:table, :insert], + [queue, v] + ) + + {[], state} + end + end + + lua = + Lua.new() + |> Lua.load_api(Queue) + |> Lua.set!([:my_queue], []) + + Lua.eval!(lua, \"\"\" + q.push("hello") + q.push("world") + \"\"\") + """ + }, + %{ + label: "formulas.exs", + source: """ + # Let your users define formulas in Lua + # that call back into your domain code. + + defmodule Pricing do + use Lua.API, scope: "pricing" + + deflua discount(amount, pct) do + amount * (1 - pct / 100) + end + end + + lua = Lua.new() |> Lua.load_api(Pricing) + + {[result], _} = + Lua.eval!(lua, "return pricing.discount(100, 15)") + """ + }, + %{ + label: "plugins.exs", + source: """ + # Plug-in system: ship a Lua VM per tenant, + # preload their script, and call into it. + + defmodule Tenant do + def run(script, event) do + Lua.new() + |> Lua.set!([:event], event) + |> Lua.eval!(script) + end + end + + script = ~S\"\"\" + if event.amount > 1000 then + return "review" + else + return "auto-approve" + end + \"\"\" + + {["review"], _} = Tenant.run(script, %{amount: 5_000}) + """ + } + ] + + @agent_tool """ + defmodule MyAgent.Tools do + use Lua.API, scope: "tools" + + deflua search(query), state do + results = MyApp.Search.run(query) + {[results], state} + end + + deflua send_email(to, body), state do + MyApp.Mailer.deliver(to, body) + {[:ok], state} + end + end + + # One VM per agent conversation. + lua = Lua.new() |> Lua.load_api(MyAgent.Tools) + + # The agent emits Lua. You run it. It can only + # do what you exposed -- nothing else. + {:ok, {result, _lua}} = Lua.eval(lua, agent_script) + """ + + @tvlabs_remote_api """ + defmodule RemoteAPI do + use Lua.API + + deflua tap(direction), state do + Device.tap(state.device, direction) + {[], state} + end + + deflua screenshot(), state do + {[Vision.capture!(state.device)], state} + end + end + + Lua.new() + |> Lua.load_api(RemoteAPI) + |> Lua.eval!(\"\"\" + tap("right") + local img = screenshot() + assert(vision.contains(img, "Home")) + \"\"\") + """ + + def hero, do: @hero + def embed, do: @embed + def agent_tool, do: @agent_tool + def tvlabs_remote_api, do: @tvlabs_remote_api +end diff --git a/website/lib/website_web/telemetry.ex b/website/lib/website_web/telemetry.ex new file mode 100644 index 0000000..69f85a8 --- /dev/null +++ b/website/lib/website_web/telemetry.ex @@ -0,0 +1,70 @@ +defmodule DemoWeb.Telemetry do + use Supervisor + import Telemetry.Metrics + + def start_link(arg) do + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) + end + + @impl true + def init(_arg) do + children = [ + # Telemetry poller will execute the given period measurements + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + # Add reporters as children of your supervision tree. + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + def metrics do + [ + # Phoenix Metrics + summary("phoenix.endpoint.start.system_time", + unit: {:native, :millisecond} + ), + summary("phoenix.endpoint.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.start.system_time", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.exception.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.socket_connected.duration", + unit: {:native, :millisecond} + ), + sum("phoenix.socket_drain.count"), + summary("phoenix.channel_joined.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.channel_handled_in.duration", + tags: [:event], + unit: {:native, :millisecond} + ), + + # VM Metrics + summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.total_run_queue_lengths.total"), + summary("vm.total_run_queue_lengths.cpu"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp periodic_measurements do + [ + # A module, function and arguments to be invoked periodically. + # This function must call :telemetry.execute/3 and a metric must be added above. + # {DemoWeb, :count_users, []} + ] + end +end diff --git a/website/mix.exs b/website/mix.exs new file mode 100644 index 0000000..c0f0689 --- /dev/null +++ b/website/mix.exs @@ -0,0 +1,92 @@ +defmodule Demo.MixProject do + use Mix.Project + + def project do + [ + app: :website, + version: "0.1.0", + elixir: "~> 1.15", + elixirc_paths: elixirc_paths(Mix.env()), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps(), + compilers: [:phoenix_live_view] ++ Mix.compilers(), + listeners: [Phoenix.CodeReloader] + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {Demo.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + def cli do + [ + preferred_envs: [precommit: :test] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:tidewave, "~> 0.5", only: [:dev]}, + {:lua, path: ".."}, + {:phoenix, "~> 1.8.7"}, + {:phoenix_html, "~> 4.1"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 1.1.0"}, + {:lazy_html, ">= 0.1.0", only: :test}, + {:phoenix_live_dashboard, "~> 0.8.3"}, + {:esbuild, "~> 0.10", runtime: Mix.env() == :dev}, + {:tailwind, "~> 0.3", runtime: Mix.env() == :dev}, + {:heroicons, + github: "tailwindlabs/heroicons", + tag: "v2.2.0", + sparse: "optimized", + app: false, + compile: false, + depth: 1}, + {:swoosh, "~> 1.16"}, + {:req, "~> 0.5"}, + {:telemetry_metrics, "~> 1.0"}, + {:telemetry_poller, "~> 1.0"}, + {:gettext, "~> 1.0"}, + {:jason, "~> 1.2"}, + {:dns_cluster, "~> 0.2.0"}, + {:bandit, "~> 1.5"}, + {:makeup, "~> 1.2"}, + {:makeup_elixir, "~> 1.0"} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to install project dependencies and perform other setup tasks, run: + # + # $ mix setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + setup: ["deps.get", "assets.setup", "assets.build"], + "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"], + "assets.build": ["compile", "tailwind website", "esbuild website"], + "assets.deploy": [ + "tailwind website --minify", + "esbuild website --minify", + "phx.digest" + ], + precommit: ["compile --warnings-as-errors", "deps.unlock --unused", "format", "test"] + ] + end +end diff --git a/website/mix.lock b/website/mix.lock new file mode 100644 index 0000000..fa6950e --- /dev/null +++ b/website/mix.lock @@ -0,0 +1,45 @@ +%{ + "bandit": {:hex, :bandit, "1.11.1", "1eb33123cc3c17ae0c3447874eb83399ee530f960c39711ed240342fbd4865fa", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d4401016df9abbc6dcd325c0b78b2b193e7c7c96bb68f31e576112be025d84a5"}, + "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, + "circular_buffer": {:hex, :circular_buffer, "1.0.1", "01f0e3d5fe945080692cf6521c0988e9dbb5dc312831cefe77e5b63a4e658160", [:mix], [], "hexpm", "7d4ece3137d49c1f8dd0b3e0aa7c484f4d83a0be5d4b516c282085c1d5f2d7b9"}, + "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, + "expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"}, + "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, + "finch": {:hex, :finch, "0.22.0", "5c48fa6f9706a78eb9036cacb67b8b996b4e66d111c543f4c29bb0f879a6806b", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b94e83c47780fc6813f746a1f1a34ee65cda42da4c5ea26a68f0acc4498e23dc"}, + "fine": {:hex, :fine, "0.1.6", "4bf7151493443c454aac9f2fa2f34f5fefd0346a83fb5586a016c4a135c63247", [:mix], [], "hexpm", "5638eb4495488e885ebec167fa57973e5c35e1a50c344eb7666c90ec1c4e3b12"}, + "gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"}, + "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, + "lazy_html": {:hex, :lazy_html, "0.1.11", "136c8e9cd616b4f4e9c1562daa683880891120b759606dc4c3b6b18058ba5d79", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "3b1be592929c31eca1a21673d25696e5c14cddfe922d9d1a3e3b48be4163883b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.8.0", "b964eaf4416f2dee2ba88968d52239fca5621b0402b9c95f55a08eb9d74803e9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "f3c572c11355eccf00f22275e9b42463bc17bd28db13be1e28f8e0bb4adbc849"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "phoenix": {:hex, :phoenix, "1.8.7", "d8d755b4ff4b449f610223dd706b4ae64155cb720d3dc09c706c079ecea189e4", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "47352f72d6ab31009ef77516b1b3a14745be97b54061fd458031b9d8294869d5"}, + "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.2", "b18b0773a1ba77f28c52decbb0f10fd1ac4d3ae5b8632399bbf6986e3b665f62", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "d1f89c18114c50d394721365ffb428cce24f1c13de0467ffa773e2ff4a30d5b9"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.30", "a84af1610755dc208da35d4d45564485edbf18c3f3c77373c4a650dc994cdcdb", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a353c51ac1e3190910f01a6100c7d5cc02c5e22e7374fd817bd3aedd21149039"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "plug": {:hex, :plug, "1.19.2", "e4950525b22c6789dfb38a3f95d47171ba159da3fc5a33be9643b43d5e8adb98", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6fce20a56af5e60fa5dfecf3f907bb98ec981be43c79a3809a499bc3d133de0"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "req": {:hex, :req, "0.5.18", "48e6431cb4135e8a7815e745177485369a9b4a9924d5fe68ca00eb09ceaed1ef", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21.0 or ~> 0.22.0", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "fa03812c440a9754bf34355e0c5d4f3ed316458db62e3284b7a352ef8dc0b996"}, + "swoosh": {:hex, :swoosh, "1.25.2", "cd3e53b0391439395492e5dce8c22288733f22603e21136162d03cd153669be9", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0aecf65b2845f13f4d440e0945715432bbde2d815e2302adf7df549cd9bdafed"}, + "tailwind": {:hex, :tailwind, "0.4.1", "e7bcc222fe96a1e55f948e76d13dd84a1a7653fb051d2a167135db3b4b08d3e9", [:mix], [], "hexpm", "6249d4f9819052911120dbdbe9e532e6bd64ea23476056adb7f730aa25c220d1"}, + "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, + "thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"}, + "tidewave": {:hex, :tidewave, "0.5.6", "91f35540b5599640443f1d3a1c6166bf506e202840261a6344e384e8813c1f64", [:mix], [{:circular_buffer, "~> 0.4 or ~> 1.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_reload, ">= 1.6.1", [hex: :phoenix_live_reload, repo: "hexpm", optional: true]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "dc82d52b8b6ffc04680544b17cd340c7d4166bb0d63999eb960850526866b533"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, +} diff --git a/website/priv/gettext/en/LC_MESSAGES/errors.po b/website/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 0000000..cdec3a1 --- /dev/null +++ b/website/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,11 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" diff --git a/website/priv/gettext/errors.pot b/website/priv/gettext/errors.pot new file mode 100644 index 0000000..d6f47fa --- /dev/null +++ b/website/priv/gettext/errors.pot @@ -0,0 +1,10 @@ +## This is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here has no +## effect: edit them in PO (`.po`) files instead. + diff --git a/website/priv/static/favicon.ico b/website/priv/static/favicon.ico new file mode 100644 index 0000000..7f372bf Binary files /dev/null and b/website/priv/static/favicon.ico differ diff --git a/website/priv/static/images/dave-robert.webp b/website/priv/static/images/dave-robert.webp new file mode 100644 index 0000000..018d974 Binary files /dev/null and b/website/priv/static/images/dave-robert.webp differ diff --git a/website/priv/static/images/favicon.svg b/website/priv/static/images/favicon.svg new file mode 100644 index 0000000..25989e8 --- /dev/null +++ b/website/priv/static/images/favicon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/website/priv/static/images/logo.svg b/website/priv/static/images/logo.svg new file mode 100644 index 0000000..9f26bab --- /dev/null +++ b/website/priv/static/images/logo.svg @@ -0,0 +1,6 @@ + diff --git a/website/priv/static/images/luerl-logo.png b/website/priv/static/images/luerl-logo.png new file mode 100644 index 0000000..f08a6d0 Binary files /dev/null and b/website/priv/static/images/luerl-logo.png differ diff --git a/website/priv/static/images/og.png b/website/priv/static/images/og.png new file mode 100644 index 0000000..c261f2c Binary files /dev/null and b/website/priv/static/images/og.png differ diff --git a/website/priv/static/images/og.svg b/website/priv/static/images/og.svg new file mode 100644 index 0000000..810aa80 --- /dev/null +++ b/website/priv/static/images/og.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Lua.ex + + + + + + Lua, on the + + + BEAM. + + + + + A pure-Elixir Lua 5.3 VM. + + + Sandboxed, scriptable, agent-ready. + + + + + PURE ELIXIR VM·SANDBOXED·DEFLUA MACRO + + + + + github.com/tv-labs/lua + + + + + + + + + + + + + + BUILT AT + + + + + + + + + + + + agent_tools.exs + + + + defmodule MyAgent.Tools do + use Lua.API, scope: "tools" + + deflua search(query) do + MyApp.Search.run(query) + end + + end + + # one sandboxed VM per chat + lua = Lua.new() + |> Lua.load_api(MyAgent.Tools) + + Lua.eval!(lua, llm_script) + + + diff --git a/website/priv/static/robots.txt b/website/priv/static/robots.txt new file mode 100644 index 0000000..26e06b5 --- /dev/null +++ b/website/priv/static/robots.txt @@ -0,0 +1,5 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/website/rel/overlays/bin/server b/website/rel/overlays/bin/server new file mode 100755 index 0000000..7a25163 --- /dev/null +++ b/website/rel/overlays/bin/server @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +PHX_SERVER=true exec ./website start diff --git a/website/rel/overlays/bin/server.bat b/website/rel/overlays/bin/server.bat new file mode 100644 index 0000000..0aedc00 --- /dev/null +++ b/website/rel/overlays/bin/server.bat @@ -0,0 +1,2 @@ +set PHX_SERVER=true +call "%~dp0\website" start diff --git a/website/scripts/regen-og.sh b/website/scripts/regen-og.sh new file mode 100755 index 0000000..37b29cf --- /dev/null +++ b/website/scripts/regen-og.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Rasterize the Open Graph image (priv/static/images/og.svg) to PNG. +# Social platforms inconsistently support SVG OG images, so the served +# meta tags point at the PNG; the SVG remains the editable source. +# +# Requires: rsvg-convert (`brew install librsvg`). + +set -euo pipefail + +cd "$(dirname "$0")/.." + +SRC="priv/static/images/og.svg" +DST="priv/static/images/og.png" + +if ! command -v rsvg-convert >/dev/null 2>&1; then + echo "error: rsvg-convert not found. Install with: brew install librsvg" >&2 + exit 1 +fi + +rsvg-convert "$SRC" -w 1200 -h 630 -o "$DST" +echo "wrote $DST ($(wc -c < "$DST") bytes)" diff --git a/website/test/support/conn_case.ex b/website/test/support/conn_case.ex new file mode 100644 index 0000000..e51004a --- /dev/null +++ b/website/test/support/conn_case.ex @@ -0,0 +1,37 @@ +defmodule DemoWeb.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use DemoWeb.ConnCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # The default endpoint for testing + @endpoint DemoWeb.Endpoint + + use DemoWeb, :verified_routes + + # Import conveniences for testing with connections + import Plug.Conn + import Phoenix.ConnTest + import DemoWeb.ConnCase + end + end + + setup _tags do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/website/test/test_helper.exs b/website/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/website/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/website/test/website/lua_examples_test.exs b/website/test/website/lua_examples_test.exs new file mode 100644 index 0000000..e90373d --- /dev/null +++ b/website/test/website/lua_examples_test.exs @@ -0,0 +1,153 @@ +defmodule Website.LuaExamplesTest do + use ExUnit.Case, async: true + + alias Website.LuaSandbox + + # Lessons marked `runnable: false` (e.g. coroutines) ship a snippet for + # display but can't execute under the current sandbox. They're covered + # by the rubric tests below; we just skip them from the run-it loop. + @runnable_tour Enum.filter(LuaSandbox.tour_lessons(), &Map.get(&1, :runnable, true)) + + @all Enum.map(LuaSandbox.home_snippets(), &{"home", &1}) ++ + Enum.map(LuaSandbox.examples(), &{"playground", &1}) ++ + Enum.map(@runnable_tour, &{"tour", &1}) + + # Lessons without a `:source` (e.g. the closing "where next" page) are + # prose-only and have nothing to compile or run. + for {source, example} <- @all, Map.has_key?(example, :source) do + @example example + @expect Map.get(example, :expect, :ok) + @label "#{source}/#{example[:id] || example[:slug]}" + + test "#{@label} (#{@expect})" do + result = LuaSandbox.run(@example.source) + + case @expect do + :ok -> + assert result.status == :ok, + "expected #{@label} to run cleanly, got: #{inspect(result.error)}" + + assert match?({:ok, _chunk, _blocks}, LuaSandbox.compile(@example.source)), + "expected #{@label} to compile cleanly" + + :compile_error -> + assert result.status == :error, + "expected #{@label} to fail, but it succeeded" + + assert match?({:error, _msgs}, LuaSandbox.compile(@example.source)), + "expected #{@label} to fail compilation" + + :runtime_error -> + assert result.status == :error, + "expected #{@label} to fail at runtime, but it succeeded" + + assert match?({:ok, _chunk, _blocks}, LuaSandbox.compile(@example.source)), + "expected #{@label} to compile cleanly (runtime-only failure)" + end + end + end + + describe "tour rubric" do + @lessons LuaSandbox.tour_lessons() + + test "every lesson belongs to a known chapter" do + known = Map.new(LuaSandbox.chapters()) + + for lesson <- @lessons do + assert is_atom(lesson.chapter) and Map.has_key?(known, lesson.chapter), + "lesson #{lesson.slug} has unknown chapter #{inspect(lesson.chapter)}" + end + end + + test "every lesson has a non-empty slug, title, objective, body" do + for lesson <- @lessons do + for key <- [:slug, :title, :objective, :body] do + val = Map.fetch!(lesson, key) + + assert is_binary(val) and val != "", + "lesson #{lesson.slug} missing #{key} (got #{inspect(val)})" + end + end + end + + test "every lesson title is <= 32 characters" do + for %{slug: slug, title: title} <- @lessons do + assert String.length(title) <= 32, + "lesson #{slug} title too long (#{String.length(title)}): #{inspect(title)}" + end + end + + test "every lesson body is <= 90 words" do + for %{slug: slug, body: body} <- @lessons do + words = body |> String.split(~r/\s+/, trim: true) |> length() + + assert words <= 90, + "lesson #{slug} body has #{words} words (limit 90)" + end + end + + test "lesson source is within the chapter's line budget" do + for lesson <- @lessons, + source = Map.get(lesson, :source), + is_binary(source) do + line_count = source |> String.trim_trailing() |> String.split("\n") |> length() + limit = if lesson.chapter == :integration, do: 12, else: 18 + + assert line_count <= limit, + "lesson #{lesson.slug} (#{lesson.chapter}) has #{line_count} source lines (limit #{limit})" + end + end + + test "every `see_also` slug points at a real lesson" do + slugs = MapSet.new(@lessons, & &1.slug) + + for lesson <- @lessons, + ref <- Map.get(lesson, :see_also, []) do + assert MapSet.member?(slugs, ref), + "lesson #{lesson.slug} references unknown see_also #{inspect(ref)}" + end + end + + test "chapter IV lessons carry an `:elixir_source` companion pane" do + for lesson <- @lessons, lesson.chapter == :integration do + assert is_binary(Map.get(lesson, :elixir_source)) and + Map.get(lesson, :elixir_source) != "", + "integration lesson #{lesson.slug} missing :elixir_source" + end + end + + test "every named Lua.ex API in chapter IV resolves at runtime" do + # Catches API drift: if Lua.ex renames or removes one of these + # symbols, the tour's Chapter IV goes silently stale. + lua_funcs = [ + {Lua, :new, 0}, + {Lua, :eval!, 2}, + {Lua, :set!, 3}, + {Lua, :get!, 2}, + {Lua, :get!, 3}, + {Lua, :call_function!, 3}, + {Lua, :load_api, 2}, + {Lua, :put_private, 3}, + {Lua, :get_private!, 2} + ] + + for {mod, fun, arity} <- lua_funcs do + Code.ensure_loaded(mod) + + assert function_exported?(mod, fun, arity), + "Lua.ex API drift: #{inspect(mod)}.#{fun}/#{arity} no longer exists" + end + + # Macros aren't reported by function_exported?, so check separately. + Code.ensure_loaded(Lua) + + assert macro_exported?(Lua, :sigil_LUA, 2), + "Lua.ex API drift: Lua.sigil_LUA/2 macro no longer exists" + + Code.ensure_loaded(Lua.API) + + assert macro_exported?(Lua.API, :deflua, 2) or macro_exported?(Lua.API, :deflua, 3), + "Lua.ex API drift: Lua.API.deflua macro no longer exists" + end + end +end diff --git a/website/test/website_web/controllers/error_html_test.exs b/website/test/website_web/controllers/error_html_test.exs new file mode 100644 index 0000000..893fcac --- /dev/null +++ b/website/test/website_web/controllers/error_html_test.exs @@ -0,0 +1,14 @@ +defmodule DemoWeb.ErrorHTMLTest do + use DemoWeb.ConnCase, async: true + + # Bring render_to_string/4 for testing custom views + import Phoenix.Template, only: [render_to_string: 4] + + test "renders 404.html" do + assert render_to_string(DemoWeb.ErrorHTML, "404", "html", []) == "Not Found" + end + + test "renders 500.html" do + assert render_to_string(DemoWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" + end +end diff --git a/website/test/website_web/controllers/error_json_test.exs b/website/test/website_web/controllers/error_json_test.exs new file mode 100644 index 0000000..aa12730 --- /dev/null +++ b/website/test/website_web/controllers/error_json_test.exs @@ -0,0 +1,12 @@ +defmodule DemoWeb.ErrorJSONTest do + use DemoWeb.ConnCase, async: true + + test "renders 404" do + assert DemoWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} + end + + test "renders 500" do + assert DemoWeb.ErrorJSON.render("500.json", %{}) == + %{errors: %{detail: "Internal Server Error"}} + end +end diff --git a/website/test/website_web/controllers/page_controller_test.exs b/website/test/website_web/controllers/page_controller_test.exs new file mode 100644 index 0000000..4a52cb4 --- /dev/null +++ b/website/test/website_web/controllers/page_controller_test.exs @@ -0,0 +1,10 @@ +defmodule DemoWeb.PageControllerTest do + use DemoWeb.ConnCase + + test "GET / renders the Lua showcase landing page", %{conn: conn} do + body = conn |> get(~p"/") |> html_response(200) + assert body =~ "Lua, on the" + assert body =~ "Playground" + assert body =~ "Tour" + end +end