From e751260d27fdbdb41861de0069dad9afbb0d62b1 Mon Sep 17 00:00:00 2001 From: Mattias Bodlund Date: Wed, 29 Jul 2026 16:41:01 +0200 Subject: [PATCH] na --- app/assets/images/ico-wave.svg | 2 +- app/assets/stylesheets/application.css | 245 +++++++++++++++++++++++-- app/controllers/game_controller.rb | 7 +- app/javascript/application.js | 2 + app/javascript/modal_controller.js | 23 +++ app/views/game/_facts_content.html.erb | 33 ++++ app/views/game/facts.html.erb | 33 +--- app/views/game/stage.html.erb | 6 +- app/views/game/stage_result.html.erb | 13 +- app/views/game/start.html.erb | 4 +- app/views/game/start.turbo_stream.erb | 8 + app/views/layouts/application.html.erb | 4 +- config/importmap.rb | 1 + 13 files changed, 322 insertions(+), 59 deletions(-) create mode 100644 app/javascript/modal_controller.js create mode 100644 app/views/game/_facts_content.html.erb create mode 100644 app/views/game/start.turbo_stream.erb diff --git a/app/assets/images/ico-wave.svg b/app/assets/images/ico-wave.svg index 2f6ef9c..e341f95 100644 --- a/app/assets/images/ico-wave.svg +++ b/app/assets/images/ico-wave.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 29b27c4..db79e25 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,4 +1,4 @@ -@property --glow-deg { +h2@property --glow-deg { syntax: ""; inherits: true; initial-value: 0deg; @@ -247,7 +247,7 @@ header { } } -main { +main, .modal-panel { display: flex; flex-direction: column; min-height: 100vh; @@ -274,7 +274,7 @@ main { & h1, h2 { font: var(--td-xl); font-weight: 400; - margin: 0 auto 0.5rem auto; + margin: 0 auto 0.5rem auto; } } @@ -289,6 +289,26 @@ main { --clr-action: var(--clr-white); } +/* Facts modal — native . Fullscreen sheet on mobile, card on desktop. */ +.facts-modal { + padding: 0; + border: none; + width: 100vw; + max-width: 100vw; + height: 100dvh; + max-height: 100dvh; + overflow-y: auto; + background-color: var(--clr-green); +} + +.facts-modal::backdrop { + background-color: rgba(0, 0, 0, 0.5); +} + +.modal-panel { + background-color: var(--clr-bg); +} + .stage, .last_save { --clr-bg: var(--clr-sand-light); --clr-action: var(--clr-white); @@ -299,6 +319,10 @@ main { } } +.question-container { + display: none; +} + .results { --clr-action: var(--clr-green); margin-block-end: 1rem; @@ -307,7 +331,7 @@ main { .good_answer { & .hero-container { - background-color: var(--clr-green); + background-color: var(--clr-green); } } @@ -323,13 +347,18 @@ main { --clr-bg: var(--clr-sand-light); --clr-action: var(--clr-sand-light); - & .hero-container { - & svg { + .hero-container { + + svg, svg.ico-wave { fill: var(--clr-white); } + + img { + display: none; + } } - & .hero-container article { + .hero-container article { font-size: var(--fs-xl); line-height: 1.2; text-align: left; @@ -420,6 +449,9 @@ main { grid-area: 1 / 1; place-self: end end; fill: var(--clr-bg, var(--clr-green)); + display: block; + width: 190px; + height: 96px; } #inter-ikea-systems { @@ -527,6 +559,7 @@ form:has(.cta) { display: inline-flex; gap: 0.4em; align-items: center; + margin-block-start: 0.75rem; } @@ -554,7 +587,7 @@ form:has(.cta) { gap: 0.5rem; justify-content: center; background-color: var(--clr-bg); - padding: 0 0 1rem 0; + margin: 0.4rem 0 1rem 0; & button { appearance: none; @@ -599,8 +632,6 @@ form:has(.cta) { } .stage-header { - - display: flex; gap: 0.5rem; align-items: center; @@ -666,7 +697,7 @@ form:has(.cta) { } } -dialog { +dialog:not(.facts-modal) { border: none; border-radius: 0.5rem; width: 280px; @@ -910,13 +941,65 @@ dialog::backdrop { -@media (min-width: 800px) { +@media (min-width: 1024px) { header { inset: 1.5625rem 1.75rem auto 1.75rem; box-shadow: 0px 4px 8px 0px #0000000F, 0px 0px 4px 0px #0000000A; + } + + .facts-modal { + width: min(90vw, 42rem); + max-width: min(90vw, 42rem); + height: max-content; + max-height: 90vh; + } + + .modal-panel { + min-height: 0; + --clr-bg: var(--clr-white); + --clr-action: var(--clr-green); + /* Content-sized (not flex-grow) so the card can be max-content tall. */ + .hero-container, + .carousel { + flex: 0 0 auto; + } + + /* Definite height so the picture/img fill it and the card sizes to content. */ + .hero-container { + height: min(30vh, 320px); + } + + /* Overlap the bottom edge so no subpixel sliver of the image shows + beneath the wave on fractional (retina) device-pixel offsets. */ + .hero-container svg.ico-wave { + transform: translateY(1px); + } + + .carousel > li { + --clr-bg: var(--clr-white); + + article p:last-of-type { + margin-block-end: 0; + } + } + + .scroll-marker-group button { + background-color: var(--clr-sand); + &.current { + background-color: var(--clr-black); + } + } + h2 { + max-width: 22ch; + } + + & > article { + padding-block-start: 0.5rem; + margin-block-end: 1rem; + } } #language-menu { @@ -969,7 +1052,16 @@ dialog::backdrop { main { display: grid; - grid-template-columns: 4fr 6fr; + grid-template-columns: 4fr 6fr; + + &.intro { + .hero-container { + grid-column: 1 / span 2; + article { + max-width: 50vw; + } + } + } &.start { article { @@ -996,13 +1088,13 @@ dialog::backdrop { flex: 1; display: flex; flex-direction: column; - justify-content: center; - + justify-content: center; } } .play-time { flex: 0 0 auto; + margin-block-start: 0; } .hero-container { @@ -1013,14 +1105,133 @@ dialog::backdrop { display: block; width: 345px; height: 174px; - - height: auto; transform-origin: 0 0; transform: rotate(90deg) translate(345px, -174px) scaleX(-1); } } } + + &.stage, &.good_answer, &.bad_answer { + grid-template-columns: 6fr 4fr; + grid-template-rows: auto 1fr auto; + height: 100dvh; + overflow: hidden; + + svg.ico-wave { + place-self: start end; + display: block; + width: 345px; + height: 174px; + transform-origin: 0 0; + transform: rotate(-90deg) translate(-345px, 174px); + } + + .hero-container { + grid-area: 1 / 1 / span 3 / span 1; + min-height: 0; + overflow: hidden; + + img { + display: block; + min-height: 0; + } + + article { + display: none; + } + + + + #inter-ikea-systems { + bottom: 1rem; + top: inherit; + } + } + + .stage-header-container { + grid-area: 1 / 2; + padding: 2.5rem; + } + + .question-container { + display: block; + grid-area: 2 / 2; + text-align: left; + padding: 0 2.5rem; + + h2 { + font-size: var(--fs-2xl); + } + } + + .answers-container { + grid-area: 3 / 2; + justify-content: end; + padding: 2.5rem; + gap: 1rem; + } + } + + &.good_answer, &.bad_answer { + grid-template-rows: auto auto 1fr; + --clr-action: var(--clr-sand-light); + + .stage-progress li.done { + background-color: var(--clr-black); + } + + .stage-icon { + background-color: var(--clr-white); + } + + .question-container { + font-size: var(--fs-2xl); + line-height: 1.2; + text-align: left; + font-weight: 400; + padding-bottom: 0.2rem; + + h2 { + margin: 0; + font-weight: 700; + } + + p { + margin: 0; + } + } + + .answers-container { + justify-content: space-between; + font-size: var(--fs-lg); + padding-top: 1.8rem; + + h2 { + font-size: var(--fs-lg); + } + } + } + + &.good_answer { + .stage-header-container, .question-container, .answers-container { + background-color: var(--clr-green); + } + + .hero-container svg.ico-wave { + fill: var(--clr-green); + } + + } + &.bad_answer { + .stage-header-container, .question-container, .answers-container { + background-color: var(--clr-blue); + } + .hero-container svg.ico-wave { + fill: var(--clr-blue); + } + } + } } diff --git a/app/controllers/game_controller.rb b/app/controllers/game_controller.rb index 090c422..c491dec 100644 --- a/app/controllers/game_controller.rb +++ b/app/controllers/game_controller.rb @@ -21,7 +21,12 @@ class GameController < ApplicationController player = Player.create(locale: I18n.locale.to_s, country: country_from_param) ResolvePlayerCountryJob.perform_later(player.id, request.remote_ip) if player.country.blank? session[:player_id] = player.id - redirect_to action: "facts" + + @node = root_node&.children&.facts&.first + respond_to do |format| + format.turbo_stream + format.html { redirect_to action: "facts" } + end end diff --git a/app/javascript/application.js b/app/javascript/application.js index e2db9be..e9674f0 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -6,6 +6,7 @@ import CarouselController from "carousel_controller" import IntroController from "intro_controller" import ChanceController from "chance_controller" import ShareController from "share_controller" +import ModalController from "modal_controller" const application = Application.start() @@ -14,6 +15,7 @@ application.register("carousel", CarouselController) application.register("intro", IntroController) application.register("chance", ChanceController) application.register("share", ShareController) +application.register("modal", ModalController) // Configure Stimulus development experience application.debug = false diff --git a/app/javascript/modal_controller.js b/app/javascript/modal_controller.js new file mode 100644 index 0000000..f1d5aef --- /dev/null +++ b/app/javascript/modal_controller.js @@ -0,0 +1,23 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["dialog"] + + connect() { + this.dialogTarget.showModal() + } + + close() { + this.dialogTarget.close() + } + + // A click whose target is the itself landed on the backdrop + // (the panel fills the content box), so dismiss. + backdropClose(event) { + if (event.target === this.dialogTarget) this.close() + } + + remove() { + this.element.remove() + } +} diff --git a/app/views/game/_facts_content.html.erb b/app/views/game/_facts_content.html.erb new file mode 100644 index 0000000..252849e --- /dev/null +++ b/app/views/game/_facts_content.html.erb @@ -0,0 +1,33 @@ + diff --git a/app/views/game/facts.html.erb b/app/views/game/facts.html.erb index 3c12b61..a873715 100644 --- a/app/views/game/facts.html.erb +++ b/app/views/game/facts.html.erb @@ -1,34 +1,3 @@ <%- content_for :title, node_title(@node) %> -<%- content_for :main_controller, "carousel" %> - - -
- <% @node.attachments.each_with_index do |_attachment, index| %> - - <% end %> -
- -
- <%= link_to tag.span(t("game.got_it_lets_get_started")), {action: "intro"}, class: "cta" %> -
- +<%= render "facts_content" %> diff --git a/app/views/game/stage.html.erb b/app/views/game/stage.html.erb index a80a1b8..8c637b8 100644 --- a/app/views/game/stage.html.erb +++ b/app/views/game/stage.html.erb @@ -4,7 +4,7 @@ <%= render partial: "stage_header" %>
- <%= render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") %> + <%= render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") if image_attachment %>
<%= image_attachment.body.html_safe %>
@@ -12,6 +12,10 @@ <%= svg "ico-wave" %>
+
+ <%= image_attachment.body.html_safe %> +
+
<% @node.children.each do |node| %> <%= button_to node.title, url_for(action: "answer", value: node.id), diff --git a/app/views/game/stage_result.html.erb b/app/views/game/stage_result.html.erb index d972244..b5b96b7 100644 --- a/app/views/game/stage_result.html.erb +++ b/app/views/game/stage_result.html.erb @@ -1,9 +1,10 @@ <%- content_for :title, node_title(@node) %> -<% image_attachment = @node.attachments.select { |a| a.asset&.file&.image? }[0] %> +<% 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") if image_attachment %>
<%= @result_node.attachments[0]&.body&.html_safe %>
@@ -11,9 +12,15 @@ <%= svg "ico-wave" %>
-
- <%= @result_node.attachments[1]&.body&.html_safe %> +
+ <%= @result_node.attachments[0]&.body&.html_safe %> +
+
+
+ <%= @result_node.attachments[1]&.body&.html_safe %> +
+
<% if stage_index == n_stages %> <%= link_to tag.span(t("game.drumroll_see_the_result")), diff --git a/app/views/game/start.html.erb b/app/views/game/start.html.erb index 60d57f0..5bfdc1b 100644 --- a/app/views/game/start.html.erb +++ b/app/views/game/start.html.erb @@ -17,9 +17,9 @@ <%= button_to tag.span(t("game.let_me_try")), start_path, class: "cta" %>
- + <%= tag.div class: "play-time" do %> <%= svg "ico-clock" %> <%= tag.span t("game.play_time") %> <% end %> -
+
\ No newline at end of file diff --git a/app/views/game/start.turbo_stream.erb b/app/views/game/start.turbo_stream.erb new file mode 100644 index 0000000..0dec844 --- /dev/null +++ b/app/views/game/start.turbo_stream.erb @@ -0,0 +1,8 @@ +<%= turbo_stream.append "page" do %> + + <%= render "facts_content" %> + +<% end %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 2dde245..14fe1dc 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -17,9 +17,9 @@ <%= stylesheet_link_tag "reset", "application" %> - <%= frontend_javascript_importmap_tags %w[application @hotwired/turbo-rails @hotwired/stimulus language_menu_controller carousel_controller intro_controller chance_controller share_controller] %> + <%= frontend_javascript_importmap_tags %w[application @hotwired/turbo-rails @hotwired/stimulus language_menu_controller carousel_controller intro_controller chance_controller share_controller modal_controller] %> - + <%= content_for :header %> <% if respond_to?(:current_player) && current_player %> diff --git a/config/importmap.rb b/config/importmap.rb index 5a4a3fa..ee6ae3a 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -19,3 +19,4 @@ pin "carousel_controller", preload: false pin "intro_controller", preload: false pin "chance_controller", preload: false pin "share_controller", preload: false +pin "modal_controller", preload: false