Mattias Bodlund 2 weeks ago
parent
commit
d135e1dfb5
16 changed files with 254 additions and 53 deletions
  1. +4
    -4
      Gemfile.lock
  2. +1
    -0
      app/assets/images/ico-last-save.svg
  3. +79
    -4
      app/assets/stylesheets/application.css
  4. +49
    -2
      app/controllers/game_controller.rb
  5. +8
    -2
      app/models/concerns/ancestry_with_sorted_url.rb
  6. +2
    -3
      app/models/node.rb
  7. +22
    -1
      app/models/player.rb
  8. +18
    -0
      app/views/game/_stage_header.html.erb
  9. +24
    -0
      app/views/game/done.html.erb
  10. +20
    -0
      app/views/game/last_save.html.erb
  11. +1
    -0
      app/views/game/result.html.erb
  12. +1
    -16
      app/views/game/stage.html.erb
  13. +13
    -19
      app/views/game/stage_result.html.erb
  14. +1
    -1
      app/views/layouts/application.html.erb
  15. +5
    -1
      config/locales/en.yml
  16. +6
    -0
      config/routes.rb

+ 4
- 4
Gemfile.lock View File

@ -88,7 +88,7 @@ GEM
bcrypt (3.1.22)
bigdecimal (4.1.2)
bindex (0.8.1)
bootsnap (1.24.5)
bootsnap (1.24.6)
msgpack (~> 1.2)
builder (3.3.0)
concurrent-ruby (1.3.6)
@ -152,7 +152,7 @@ GEM
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
language_server-protocol (3.17.0.5)
lexxy (0.9.15.alpha.3)
lexxy (0.9.15.alpha.4)
rails (>= 8.0.2)
lint_roller (1.1.0)
logger (1.7.0)
@ -426,7 +426,7 @@ CHECKSUMS
bcrypt (3.1.22) sha256=1f0072e88c2d705d94aff7f2c5cb02eb3f1ec4b8368671e19112527489f29032
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e
bootsnap (1.24.5) sha256=36b677448524d279b470469aabd5dff4a980e3fa4931a0df68da4a500eb1b6c4
bootsnap (1.24.6) sha256=c60bab88c70332290f0a2636a288f675299eb4f804a02a3c085b42eca9da164a
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
@ -462,7 +462,7 @@ CHECKSUMS
kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430
kaminari-core (1.2.2) sha256=3bd26fec7370645af40ca73b9426a448d09b8a8ba7afa9ba3c3e0d39cdbb83ff
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
lexxy (0.9.15.alpha.3) sha256=a82ff518c34b4ee7e59a155eabc2c236369dd0444cd672026ad0596f3fbfcbef
lexxy (0.9.15.alpha.4) sha256=7a0ee226537eca2e17a5466073dd4f6215a00339fcde78e54425e8150b55c125
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04


+ 1
- 0
app/assets/images/ico-last-save.svg View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="22" viewBox="0 0 20 22"><path d="M18.04,3.25l-.83-2.49c-.1-.31-.39-.51-.71-.51h-7.35l-.22.27-2.28,2.73H0v1.5h1.76l.99,16.3c.02.4.35.7.75.7h4.5v-1.5h-3.79l-.94-15.5h13.47l-.35,5.7.75.05.75.05.35-5.8h1.76v-1.5h-1.96ZM9.85,1.75h6.11l.5,1.5h-7.86l1.25-1.5ZM9.64,16.72h-.76l-.21-.4-1.32-2.47.66-.35.66-.35.77,1.44c1.06-1.78,3.19-2.74,5.3-2.17,1.3.35,2.32,1.2,2.93,2.29l-.66.36-.66.36c-.41-.75-1.12-1.33-2-1.57-1.73-.46-3.52.56-3.98,2.3l-.15.56h-.58ZM18.46,17.7l1.22,2.46-.67.33-.67.33-.73-1.47c-1.05,1.82-3.21,2.8-5.33,2.23-1.27-.34-2.28-1.16-2.88-2.21l.65-.38.65-.38c.42.72,1.11,1.28,1.97,1.51,1.73.46,3.52-.56,3.98-2.3l.15-.56h1.46l.21.42Z"/></svg>

+ 79
- 4
app/assets/stylesheets/application.css View File

@ -277,17 +277,90 @@ main {
--clr-action: var(--clr-white);
}
.stage {
.stage, .last_save {
--clr-bg: var(--clr-sand-light);
--clr-action: var(--clr-white);
& .hero-container article {
color: var(--clr-white);
text-align: left;
}
}
.good_answer {
& .hero-container {
background-color: var(--clr-green);
}
}
.bad_answer {
& .hero-container {
background-color: var(--clr-blue);
}
}
.good_answer,
.bad_answer {
--clr-bg: var(--clr-sand-light);
--clr-action: var(--clr-sand-light);
& .hero-container {
& svg {
fill: var(--clr-white);
}
}
& .hero-container article {
font-size: var(--fs-xl);
line-height: 1.2;
text-align: left;
font-weight: 400;
& h2 {
margin: 0;
font-weight: 700;
}
}
& .answers-container {
background-color: var(--clr-white);
font-size: var(--fs-s);
line-height: 1.5;
text-align: left;
font-weight: 400;
gap: 0;
& div:has(.cta) {
display: flex;
margin-block-start: 3rem;
}
& .cta {
max-width: inherit;
background-color: var(--clr-sand-light);
}
&:has(.cta.last_save) {
font-size: var(--fs-xl);
line-height: 1.2;
}
& h2 {
font-size: var(--fs-s);
line-height: 1.5;
text-align: left;
font-weight: 700;
margin: 0;
}
& p {
margin: 0;
max-width: inherit;
}
}
}
.hero-container, .carousel {
flex: 1 1 0;
@ -338,6 +411,7 @@ main {
form:has(.cta) {
display: flex;
}
.cta {
@ -350,13 +424,14 @@ form:has(.cta) {
background-repeat: no-repeat;
background-position: 1rem 50%;
border: none;
max-width: 17.5rem;
font-size: var(--fs-base);
max-width: 20rem;
border-radius: 100vw;
height: 3rem;
margin: 0 auto;
font-weight: 700;
color: var(--clr-black);
text-decoration: none;
text-decoration: none;
}
.play-time {


+ 49
- 2
app/controllers/game_controller.rb View File

@ -6,7 +6,8 @@ class GameController < ApplicationController
helper_method :root_node,
:stage_index,
:n_stages
:n_stages,
:last_save_node
def index
@node = root_node
@ -40,14 +41,55 @@ class GameController < ApplicationController
# POST
def answer
@answer = @node.children.find_by(id: params[:value])
@answer = @answer.children.sample if @answer&.chance?
current_player.record_answer(stage_index, @answer.id)
redirect_to action: :stage_result, id: params[:id]
if stage_index >= n_stages
redirect_to action: :done
else
redirect_to action: :stage_result, id: params[:id]
end
end
def stage_result
@result_node = @node.descendants.find_by(id: current_player.answer_id_for(stage_index))
not_found and return unless @result_node
end
def last_save
@node = last_save_node
end
# POST
def answer_last_save
@node = last_save_node
not_found and return unless @node
@answer = @node.children.find_by(id: params[:value])
current_player.record_last_save_answer(@answer.id)
redirect_to action: :done
end
def done
if current_player.last_save_answer_id
@node = last_save_node
@result_node = @node.descendants.find_by(id: current_player.last_save_answer_id)
else
@node = stages.last
@result_node = @node.descendants.find_by(id: current_player.answer_id_for(stages.size))
end
end
def result
end
@ -58,6 +100,11 @@ private
end
def last_save_node
@last_save_node ||= root_node.children.last_save.first
end
def stages
@stages ||= root_node.children.stage
end


+ 8
- 2
app/models/concerns/ancestry_with_sorted_url.rb View File

@ -23,8 +23,14 @@ private
def format_slug
I18n.available_locales.each do |l|
l_key = l.to_s.downcase.sub("-", "_")
v = self.root? ? l.to_s :
self.send("slug_#{l_key}").blank? ? (self.title(locale: l) || "").parameterize : self.send("slug_#{l_key}").parameterize
current_slug = self.send("slug_#{l_key}")
v = if self.root?
l.to_s
elsif current_slug.blank? || current_slug == "untitled"
(self.title(locale: l) || "").parameterize
else
current_slug.parameterize
end
self.send(:slug=, v, locale: l)
end


+ 2
- 3
app/models/node.rb View File

@ -7,8 +7,6 @@ class Node < ApplicationRecord
include HasAttachments
include HasTags
has_many :answers, dependent: :destroy
extend Mobility
translates :slug,
:tags,
@ -33,6 +31,7 @@ class Node < ApplicationRecord
good_answer: 4,
bad_answer: 5,
chance: 6,
last_save: 7
}
def available_templates
@ -40,7 +39,7 @@ class Node < ApplicationRecord
case depth
when 1
[ :facts, :intro, :stage ]
[ :facts, :intro, :stage, :last_save ]
when 2
[ :good_answer, :bad_answer, :chance ]
when 3


+ 22
- 1
app/models/player.rb View File

@ -1,29 +1,50 @@
class Player < ApplicationRecord
# progress shape:
# { "1" => { "answer_id" => 42, "result_id" => 99 }, "2" => { ... } }
# { "1" => { "answer_id" => 42, "result_id" => 99 }, "2" => { ... },
# "last_save" => { "answer_id" => 7 } }
LAST_SAVE_KEY = "last_save".freeze
def record_answer(stage, answer_id)
stage_data(stage)["answer_id"] = answer_id
save
end
def record_result(stage, result_id)
stage_data(stage)["result_id"] = result_id
save
end
def answer_id_for(stage)
progress[stage.to_s]&.dig("answer_id")
end
def result_id_for(stage)
progress[stage.to_s]&.dig("result_id")
end
def record_last_save_answer(answer_id)
stage_data(LAST_SAVE_KEY)["answer_id"] = answer_id
save
end
def last_save_answer_id
progress[LAST_SAVE_KEY]&.dig("answer_id")
end
private
def stage_data(stage)
progress[stage.to_s] ||= {}
end
end

+ 18
- 0
app/views/game/_stage_header.html.erb View File

@ -0,0 +1,18 @@
<% is_last_save = @node.path_ids.include?(last_save_node&.id) %>
<div class="stage-header-container">
<ol class="stage-progress" role="progressbar">
<% n_stages.times do |i| %>
<%= tag.li class: ("done" if is_last_save or stage_index >= (i + 1)) %>
<% end %>
</ol>
<div class="stage-header">
<div class="stage-icon"><%= svg (is_last_save ? t("icons.last_save") : t("icons.stages")[stage_index]) %></div>
<div>
<%= tag.div t("game.stage_i_of_n", i: (is_last_save ? n_stages : stage_index), n: n_stages), class: "stage-progress" %>
<%= tag.h1 @node.title %>
</div>
</div>
</div>

+ 24
- 0
app/views/game/done.html.erb View File

@ -0,0 +1,24 @@
<%- content_for :title, node_title(@node) %>
<% image_attachment = @node.attachments.select { |a| a.asset&.file&.image? }[0] %>
<%= render partial: "stage_header" %>
<div class="hero-container">
<%# render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %>
<article>
<%= @result_node.attachments[0]&.body&.html_safe %>
</article>
<%= svg "ico-wave" %>
</div>
<article class="answers-container">
<%= @result_node.attachments[1]&.body&.html_safe %>
<div>
<%= link_to tag.span(t("game.drumroll_see_the_result")),
{ action: "result" },
class: (@result_node.parent.chance? and @result_node.bad_answer?) ? "cta last_save" : "cta" %>
</div>
</article>

+ 20
- 0
app/views/game/last_save.html.erb View File

@ -0,0 +1,20 @@
<%- content_for :title, node_title(@node) %>
<% image_attachment = @node.attachments.select { |a| a.asset&.file&.image? }[0] %>
<%= render partial: "stage_header" %>
<div class="hero-container">
<%= render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %>
<article>
<%= image_attachment.body.html_safe %>
</article>
<%= svg "ico-wave" %>
</div>
<article class="answers-container" data-controller="chance">
<% @node.children.each do |node| %>
<%= button_to node.title, url_for(action: "answer_last_save", value: node.id) %>
<% end %>
</article>

+ 1
- 0
app/views/game/result.html.erb View File

@ -0,0 +1 @@
<%- content_for :title, node_title(@node) %>

+ 1
- 16
app/views/game/stage.html.erb View File

@ -1,22 +1,7 @@
<%- content_for :title, node_title(@node) %>
<% image_attachment = @node.attachments.select { |a| a.asset&.file&.image? }[0] %>
<div class="stage-header-container">
<ol class="stage-progress" role="progressbar">
<% n_stages.times do |i| %>
<%= tag.li class: ("done" if stage_index >= (i + 1)) %>
<% end %>
</ol>
<div class="stage-header">
<div class="stage-icon"><%= svg t("icons.stages")[stage_index] %></div>
<div>
<%= tag.div t("game.stage_i_of_n", i: stage_index, n: n_stages), class: "stage-progress" %>
<%= tag.h1 @node.title %>
</div>
</div>
</div>
<%= render partial: "stage_header" %>
<div class="hero-container">
<%= render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %>


+ 13
- 19
app/views/game/stage_result.html.erb View File

@ -1,32 +1,26 @@
<%- content_for :title, node_title(@node) %>
<% image_attachment = @node.attachments.select { |a| a.asset&.file&.image? }[0] %>
<div class="stage-header-container">
<ol class="stage-progress" role="progressbar">
<% n_stages.times do |i| %>
<%= tag.li class: ("done" if stage_index >= (i + 1)) %>
<% end %>
</ol>
<div class="stage-header">
<div class="stage-icon"><%= svg t("icons.stages")[stage_index] %></div>
<div>
<%= tag.div t("game.stage_i_of_n", i: stage_index, n: n_stages), class: "stage-progress" %>
<%= tag.h1 @node.title %>
</div>
</div>
</div>
<%= render partial: "stage_header" %>
<div class="hero-container">
<%= render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %>
<%# render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %>
<article>
<%= image_attachment.body.html_safe %>
<%= @result_node.attachments[0]&.body&.html_safe %>
</article>
<%= svg "ico-wave" %>
</div>
<article class="answers-container">
<%= @result_node.attachments[1]&.body&.html_safe %>
<div>
<% if @result_node.parent.chance? and @result_node.bad_answer? %>
<%= link_to tag.span(t("game.let_me_try")), {action: "last_save" }, class: "cta last_save" %>
<% else %>
<%= link_to tag.span(t("game.next_stage")), {action: "stage", id: stage_index + 1 }, class: "cta" %>
<% end %>
</div>
</article>

+ 1
- 1
app/views/layouts/application.html.erb View File

@ -22,7 +22,7 @@
<body>
<%= content_for :header %>
<%= tag.main class: @node.template,
<%= tag.main class: [@result_node, @node].compact.map { |n| n.template }.first,
data: {
controller: content_for(:main_controller)
} do %>


+ 5
- 1
config/locales/en.yml View File

@ -24,6 +24,9 @@ en:
but it can boost your score.
ok: Ok
cancel: Cancel
next_stage: Next stage
let_me_try: Let me try
drumroll_see_the_result: Drumroll… see your result
countries:
au: Australia
@ -286,6 +289,7 @@ en:
good_answer: Good answer
bad_answer: Bad answer
chance: Chance
last_save: Last save
categories:
box: Box
@ -334,7 +338,7 @@ en:
date_formats: schedule
subscribers: group
newsletters: mail
last_save: ico-last-save
stages:
-
- ico-soil


+ 6
- 0
config/routes.rb View File

@ -70,6 +70,12 @@ Rails.application.routes.draw do
get "stage/:id/result", to: "game#stage_result"
get "last_save", to: "game#last_save"
post "last_save/answer", to: "game#answer_last_save"
get "done", to: "game#done"
get "result", to: "game#result"
get "", to: "game#index", as: :locale_root
end


Loading…
Cancel
Save