Mattias Bodlund 1 month ago
parent
commit
e751260d27
13 changed files with 322 additions and 59 deletions
  1. +1
    -1
      app/assets/images/ico-wave.svg
  2. +228
    -17
      app/assets/stylesheets/application.css
  3. +6
    -1
      app/controllers/game_controller.rb
  4. +2
    -0
      app/javascript/application.js
  5. +23
    -0
      app/javascript/modal_controller.js
  6. +33
    -0
      app/views/game/_facts_content.html.erb
  7. +1
    -32
      app/views/game/facts.html.erb
  8. +5
    -1
      app/views/game/stage.html.erb
  9. +10
    -3
      app/views/game/stage_result.html.erb
  10. +2
    -2
      app/views/game/start.html.erb
  11. +8
    -0
      app/views/game/start.turbo_stream.erb
  12. +2
    -2
      app/views/layouts/application.html.erb
  13. +1
    -0
      config/importmap.rb

+ 1
- 1
app/assets/images/ico-wave.svg View File

@ -1 +1 @@
<svg class="ico-wave" xmlns="http://www.w3.org/2000/svg" width="190" height="96" viewBox="0 0 190 96"><path d="M.01,96h189.99c-8.68,0-18.02-1.28-27.76-3.75-27.52-7.02-57.98-23.68-84.83-48.34C62.23,29.96,49.84,14.97,40.54,0c3.7,7.29,6.63,14.51,8.75,21.52,3.83,12.63,5.01,24.56,3.37,35.06-1.64,10.49-6.11,19.49-13.55,26.32-7.44,6.83-17.24,10.93-28.67,12.44-3.35.44-6.83.66-10.44.66h.01Z"/></svg>
<svg class="ico-wave" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 190 96"><path d="M.01,96h189.99c-8.68,0-18.02-1.28-27.76-3.75-27.52-7.02-57.98-23.68-84.83-48.34C62.23,29.96,49.84,14.97,40.54,0c3.7,7.29,6.63,14.51,8.75,21.52,3.83,12.63,5.01,24.56,3.37,35.06-1.64,10.49-6.11,19.49-13.55,26.32-7.44,6.83-17.24,10.93-28.67,12.44-3.35.44-6.83.66-10.44.66h.01Z"/></svg>

+ 228
- 17
app/assets/stylesheets/application.css View File

@ -1,4 +1,4 @@
@property --glow-deg {
h2@property --glow-deg {
syntax: "<angle>";
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 <dialog>. 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);
}
}
}
}


+ 6
- 1
app/controllers/game_controller.rb View File

@ -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


+ 2
- 0
app/javascript/application.js View File

@ -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


+ 23
- 0
app/javascript/modal_controller.js View File

@ -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 <dialog> 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()
}
}

+ 33
- 0
app/views/game/_facts_content.html.erb View File

@ -0,0 +1,33 @@
<div class="modal-panel facts" data-controller="carousel">
<ul class="carousel" data-carousel-target="carousel">
<% @node.attachments.each do |attachment| %>
<li>
<div class="hero-container">
<%= render_responsive_picture(attachment.asset, alt: "Hero", fetchpriority: "high") %>
<%= svg "inter-ikea-systems" %>
<%= svg "ico-wave" %>
</div>
<article>
<%= attachment.body.html_safe %>
</article>
</li>
<% end %>
</ul>
<div class="scroll-marker-group" role="tablist">
<% @node.attachments.each_with_index do |_attachment, index| %>
<button type="button"
class="scroll-marker"
role="tab"
data-carousel-target="marker"
data-index="<%= index %>"
data-action="click->carousel#goto"
aria-label="<%= t("game.go_to_slide", index: index + 1, default: "Go to slide %{index}") %>"></button>
<% end %>
</div>
<article>
<%= link_to tag.span(t("game.got_it_lets_get_started")), {action: "intro"}, class: "cta",
data: { turbo_frame: "_top" } %>
</article>
</div>

+ 1
- 32
app/views/game/facts.html.erb View File

@ -1,34 +1,3 @@
<%- content_for :title, node_title(@node) %>
<%- content_for :main_controller, "carousel" %>
<ul class="carousel" data-carousel-target="carousel">
<% @node.attachments.each do |attachment| %>
<li>
<div class="hero-container">
<%= render_responsive_picture(attachment.asset, alt: "Hero", fetchpriority: "high") %>
<%= svg "inter-ikea-systems" %>
<%= svg "ico-wave" %>
</div>
<article>
<%= attachment.body.html_safe %>
</article>
</li>
<% end %>
</ul>
<div class="scroll-marker-group" role="tablist">
<% @node.attachments.each_with_index do |_attachment, index| %>
<button type="button"
class="scroll-marker"
role="tab"
data-carousel-target="marker"
data-index="<%= index %>"
data-action="click->carousel#goto"
aria-label="<%= t("game.go_to_slide", index: index + 1, default: "Go to slide %{index}") %>"></button>
<% end %>
</div>
<article>
<%= link_to tag.span(t("game.got_it_lets_get_started")), {action: "intro"}, class: "cta" %>
</article>
<%= render "facts_content" %>

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

@ -4,7 +4,7 @@
<%= 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") if image_attachment %>
<article>
<%= image_attachment.body.html_safe %>
</article>
@ -12,6 +12,10 @@
<%= svg "ico-wave" %>
</div>
<article class="question-container">
<%= image_attachment.body.html_safe %>
</article>
<article class="answers-container" data-controller="chance">
<% @node.children.each do |node| %>
<%= button_to node.title, url_for(action: "answer", value: node.id),


+ 10
- 3
app/views/game/stage_result.html.erb View File

@ -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" %>
<div class="hero-container">
<%= render_responsive_picture(image_attachment.asset, alt: "Hero", fetchpriority: "high") if image_attachment %>
<article>
<%= @result_node.attachments[0]&.body&.html_safe %>
</article>
@ -11,9 +12,15 @@
<%= svg "ico-wave" %>
</div>
<article class="answers-container">
<%= @result_node.attachments[1]&.body&.html_safe %>
<article class="question-container">
<%= @result_node.attachments[0]&.body&.html_safe %>
</article>
<article class="answers-container">
<div>
<%= @result_node.attachments[1]&.body&.html_safe %>
</div>
<div>
<% if stage_index == n_stages %>
<%= link_to tag.span(t("game.drumroll_see_the_result")),


+ 2
- 2
app/views/game/start.html.erb View File

@ -17,9 +17,9 @@
<%= button_to tag.span(t("game.let_me_try")), start_path, class: "cta" %>
</div>
<%= tag.div class: "play-time" do %>
<%= svg "ico-clock" %>
<%= tag.span t("game.play_time") %>
<% end %>
</article>
</article>

+ 8
- 0
app/views/game/start.turbo_stream.erb View File

@ -0,0 +1,8 @@
<%= turbo_stream.append "page" do %>
<dialog class="facts-modal"
data-controller="modal"
data-modal-target="dialog"
data-action="close->modal#remove click->modal#backdropClose">
<%= render "facts_content" %>
</dialog>
<% end %>

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

@ -17,9 +17,9 @@
<link rel="icon" sizes="192x192" href="/ikea-favicon-300x300.png">
<%= 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] %>
</head>
<body>
<body id="page">
<%= content_for :header %>
<% if respond_to?(:current_player) && current_player %>


+ 1
- 0
config/importmap.rb View File

@ -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

Loading…
Cancel
Save