From d135e1dfb52e62cc9cf4b3380df07e4861a0dfd9 Mon Sep 17 00:00:00 2001 From: Mattias Bodlund Date: Thu, 4 Jun 2026 16:41:40 +0200 Subject: [PATCH] na --- Gemfile.lock | 8 +- app/assets/images/ico-last-save.svg | 1 + app/assets/stylesheets/application.css | 83 ++++++++++++++++++- app/controllers/game_controller.rb | 51 +++++++++++- .../concerns/ancestry_with_sorted_url.rb | 10 ++- app/models/node.rb | 5 +- app/models/player.rb | 23 ++++- app/views/game/_stage_header.html.erb | 18 ++++ app/views/game/done.html.erb | 24 ++++++ app/views/game/last_save.html.erb | 20 +++++ app/views/game/result.html.erb | 1 + app/views/game/stage.html.erb | 17 +--- app/views/game/stage_result.html.erb | 32 +++---- app/views/layouts/application.html.erb | 2 +- config/locales/en.yml | 6 +- config/routes.rb | 6 ++ 16 files changed, 254 insertions(+), 53 deletions(-) create mode 100644 app/assets/images/ico-last-save.svg create mode 100644 app/views/game/_stage_header.html.erb create mode 100644 app/views/game/done.html.erb create mode 100644 app/views/game/last_save.html.erb create mode 100644 app/views/game/result.html.erb diff --git a/Gemfile.lock b/Gemfile.lock index 519c9e6..3016c7e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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 diff --git a/app/assets/images/ico-last-save.svg b/app/assets/images/ico-last-save.svg new file mode 100644 index 0000000..5b24664 --- /dev/null +++ b/app/assets/images/ico-last-save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 4d327fb..3c82728 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -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 { diff --git a/app/controllers/game_controller.rb b/app/controllers/game_controller.rb index 3082d13..b0c0074 100644 --- a/app/controllers/game_controller.rb +++ b/app/controllers/game_controller.rb @@ -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 diff --git a/app/models/concerns/ancestry_with_sorted_url.rb b/app/models/concerns/ancestry_with_sorted_url.rb index f574b8e..e2dbd76 100644 --- a/app/models/concerns/ancestry_with_sorted_url.rb +++ b/app/models/concerns/ancestry_with_sorted_url.rb @@ -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 diff --git a/app/models/node.rb b/app/models/node.rb index 8e129cb..2f55b83 100644 --- a/app/models/node.rb +++ b/app/models/node.rb @@ -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 diff --git a/app/models/player.rb b/app/models/player.rb index af7eefa..ff05398 100644 --- a/app/models/player.rb +++ b/app/models/player.rb @@ -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 diff --git a/app/views/game/_stage_header.html.erb b/app/views/game/_stage_header.html.erb new file mode 100644 index 0000000..61b6559 --- /dev/null +++ b/app/views/game/_stage_header.html.erb @@ -0,0 +1,18 @@ +<% is_last_save = @node.path_ids.include?(last_save_node&.id) %> + +
+ +
    + <% n_stages.times do |i| %> + <%= tag.li class: ("done" if is_last_save or stage_index >= (i + 1)) %> + <% end %> +
+ +
+
<%= svg (is_last_save ? t("icons.last_save") : t("icons.stages")[stage_index]) %>
+
+ <%= 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 %> +
+
+
\ No newline at end of file diff --git a/app/views/game/done.html.erb b/app/views/game/done.html.erb new file mode 100644 index 0000000..0010d3e --- /dev/null +++ b/app/views/game/done.html.erb @@ -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" %> + +
+ <%# render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %> +
+ <%= @result_node.attachments[0]&.body&.html_safe %> +
+ <%= svg "ico-wave" %> +
+ +
+ <%= @result_node.attachments[1]&.body&.html_safe %> + +
+ <%= 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" %> +
+
+ + diff --git a/app/views/game/last_save.html.erb b/app/views/game/last_save.html.erb new file mode 100644 index 0000000..80939cb --- /dev/null +++ b/app/views/game/last_save.html.erb @@ -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" %> + + +
+ <%= render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %> +
+ <%= image_attachment.body.html_safe %> +
+ <%= svg "ico-wave" %> +
+ +
+ <% @node.children.each do |node| %> + <%= button_to node.title, url_for(action: "answer_last_save", value: node.id) %> + <% end %> + +
diff --git a/app/views/game/result.html.erb b/app/views/game/result.html.erb new file mode 100644 index 0000000..15ba721 --- /dev/null +++ b/app/views/game/result.html.erb @@ -0,0 +1 @@ +<%- content_for :title, node_title(@node) %> diff --git a/app/views/game/stage.html.erb b/app/views/game/stage.html.erb index 18586a5..1c1eb70 100644 --- a/app/views/game/stage.html.erb +++ b/app/views/game/stage.html.erb @@ -1,22 +1,7 @@ <%- content_for :title, node_title(@node) %> <% image_attachment = @node.attachments.select { |a| a.asset&.file&.image? }[0] %> -
- -
    - <% n_stages.times do |i| %> - <%= tag.li class: ("done" if stage_index >= (i + 1)) %> - <% end %> -
- -
-
<%= svg t("icons.stages")[stage_index] %>
-
- <%= tag.div t("game.stage_i_of_n", i: stage_index, n: n_stages), class: "stage-progress" %> - <%= tag.h1 @node.title %> -
-
-
+<%= render partial: "stage_header" %>
<%= render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %> diff --git a/app/views/game/stage_result.html.erb b/app/views/game/stage_result.html.erb index 867948e..5607f2d 100644 --- a/app/views/game/stage_result.html.erb +++ b/app/views/game/stage_result.html.erb @@ -1,32 +1,26 @@ <%- content_for :title, node_title(@node) %> <% image_attachment = @node.attachments.select { |a| a.asset&.file&.image? }[0] %> -
- -
    - <% n_stages.times do |i| %> - <%= tag.li class: ("done" if stage_index >= (i + 1)) %> - <% end %> -
- -
-
<%= svg t("icons.stages")[stage_index] %>
-
- <%= tag.div t("game.stage_i_of_n", i: stage_index, n: n_stages), class: "stage-progress" %> - <%= tag.h1 @node.title %> -
-
-
+<%= render partial: "stage_header" %>
- <%= render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %> + <%# render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %>
- <%= image_attachment.body.html_safe %> + <%= @result_node.attachments[0]&.body&.html_safe %>
<%= svg "ico-wave" %>
- + <%= @result_node.attachments[1]&.body&.html_safe %> +
+ <% 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 %> +
+ + diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 4fb0276..3ba9e0e 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -22,7 +22,7 @@ <%= 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 %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 6586d5c..d30ea9c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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 diff --git a/config/routes.rb b/config/routes.rb index 332f71c..4122440 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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