148 lines
5.4 KiB
Elixir
148 lines
5.4 KiB
Elixir
defmodule MtgWheelWeb.WheelLive do
|
|
use MtgWheelWeb, :live_view
|
|
alias MtgWheel.DeckGenerator
|
|
|
|
@spin_duration_ms 3700
|
|
@item_height_px 112
|
|
|
|
@impl true
|
|
def mount(_params, _session, socket) do
|
|
{:ok,
|
|
assign(socket,
|
|
page_title: "MTG Deck Wheel",
|
|
spinning: false,
|
|
result: nil,
|
|
budgets: DeckGenerator.budgets(),
|
|
archetypes: DeckGenerator.archetypes(),
|
|
subthemes: DeckGenerator.subthemes(),
|
|
color_combos: DeckGenerator.color_combos()
|
|
)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("spin", _params, socket) do
|
|
result = DeckGenerator.generate()
|
|
Process.send_after(self(), :stop_spin, @spin_duration_ms)
|
|
{:noreply, assign(socket, spinning: true, result: result)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_info(:stop_spin, socket) do
|
|
{:noreply, assign(socket, spinning: false)}
|
|
end
|
|
|
|
@impl true
|
|
def render(assigns) do
|
|
~H"""
|
|
<div class="min-h-screen bg-gray-950 text-white flex flex-col items-center justify-center p-8 gap-10">
|
|
<h1 class="text-4xl font-bold tracking-tight text-purple-400">MTG Deck Wheel</h1>
|
|
|
|
<div class="grid grid-cols-2 gap-8 md:grid-cols-4">
|
|
<.wheel label="Budget" items={Enum.map(@budgets, &"$#{&1}")} result={@result && @result.budget} spinning={@spinning} duration={2500} />
|
|
<.wheel label="Archetype" items={@archetypes} result={@result && @result.archetype} spinning={@spinning} duration={2900} />
|
|
<.wheel label="Subtheme" items={@subthemes} result={@result && @result.subtheme} spinning={@spinning} duration={3300} />
|
|
<.wheel label="Colors" items={@color_combos} result={@result && @result.colors} spinning={@spinning} color?={true} duration={3600} />
|
|
</div>
|
|
|
|
<button
|
|
phx-click="spin"
|
|
disabled={@spinning}
|
|
class="mt-4 px-10 py-4 rounded-full text-xl font-semibold bg-purple-600 hover:bg-purple-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all shadow-lg shadow-purple-900"
|
|
>
|
|
<%= if @spinning, do: "Spinning...", else: "Spin!" %>
|
|
</button>
|
|
|
|
<div class={["mt-2 rounded-2xl bg-gray-800 border border-purple-700 p-6 w-full max-w-lg text-center shadow-xl", (@result == nil || @spinning) && "invisible"]}>
|
|
<h2 class="text-lg font-semibold text-purple-300 mb-3">Your Deck</h2>
|
|
<div class="grid grid-cols-2 gap-3 text-sm">
|
|
<.stat label="Budget" value={(@result && @result.budget) || ""} />
|
|
<.stat label="Archetype" value={(@result && @result.archetype) || ""} />
|
|
<.stat label="Subtheme" value={(@result && @result.subtheme) || ""} />
|
|
<.stat label="Colors">
|
|
<.color_icons colors={(@result && @result.colors) || ""} size="lg" />
|
|
</.stat>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
end
|
|
|
|
defp wheel(assigns) do
|
|
assigns =
|
|
assigns
|
|
|> assign_new(:color?, fn -> false end)
|
|
|> assign_new(:duration, fn -> 3000 end)
|
|
|
|
~H"""
|
|
<div class="flex flex-col items-center gap-2">
|
|
<span class="text-xs font-semibold uppercase tracking-widest text-gray-400"><%= @label %></span>
|
|
<div class="relative w-28 h-28 rounded-full border-4 border-purple-600 overflow-hidden bg-gray-900 shadow-lg shadow-purple-950">
|
|
<div
|
|
class={["wheel-inner absolute top-0 left-0 right-0 flex flex-col items-center", @spinning && "spinning"]}
|
|
style={wheel_style(@items, @result, @duration, @spinning)}
|
|
>
|
|
<%= for _ <- 1..5 do %>
|
|
<%= for item <- @items do %>
|
|
<div class="wheel-item flex-shrink-0 w-full h-28 flex items-center justify-center text-xs font-bold text-center px-1">
|
|
<%= if @color? do %>
|
|
<.color_icons colors={item} />
|
|
<% else %>
|
|
<%= item %>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
<% end %>
|
|
</div>
|
|
<div class="absolute inset-0 pointer-events-none rounded-full ring-4 ring-purple-500/30"></div>
|
|
</div>
|
|
<span class={["h-6 flex items-center justify-center gap-1", (@result == nil || @spinning) && "invisible"]}>
|
|
<%= if @color? do %>
|
|
<.color_icons colors={@result || ""} size="lg" />
|
|
<% else %>
|
|
<span class="text-sm font-bold text-purple-300"><%= @result %></span>
|
|
<% end %>
|
|
</span>
|
|
</div>
|
|
"""
|
|
end
|
|
|
|
defp wheel_style(items, result, duration_ms, spinning) do
|
|
n = length(items)
|
|
idx = if result, do: Enum.find_index(items, &(&1 == result)) || 0, else: 0
|
|
end_px = -(3 * n + idx) * @item_height_px
|
|
|
|
if spinning do
|
|
"--wheel-end: #{end_px}px; --wheel-duration: #{duration_ms}ms"
|
|
else
|
|
if result, do: "transform: translateY(#{end_px}px)", else: ""
|
|
end
|
|
end
|
|
|
|
defp color_icons(assigns) do
|
|
assigns = assign_new(assigns, :size, fn -> "md" end)
|
|
|
|
~H"""
|
|
<%= for letter <- String.graphemes(@colors) do %>
|
|
<i class={["ms ms-cost", "ms-#{String.downcase(letter)}", @size == "lg" && "ms-2x"]}></i>
|
|
<% end %>
|
|
"""
|
|
end
|
|
|
|
defp stat(assigns) do
|
|
assigns = assign_new(assigns, :value, fn -> nil end)
|
|
|
|
~H"""
|
|
<div class="bg-gray-900 rounded-lg p-3">
|
|
<div class="text-gray-400 text-xs mb-1"><%= @label %></div>
|
|
<div class="text-white font-semibold min-h-[1.5rem] flex items-center justify-center">
|
|
<%= if Map.has_key?(assigns, :inner_block) do %>
|
|
<%= render_slot(@inner_block) %>
|
|
<% else %>
|
|
<%= @value %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
"""
|
|
end
|
|
end
|