Mattias Bodlund 3 weeks ago
parent
commit
cabf7f526c
26 changed files with 160 additions and 672 deletions
  1. +9
    -0
      CLAUDE.md
  2. +43
    -43
      Gemfile.lock
  3. +6
    -0
      README.txt
  4. +19
    -156
      app/assets/stylesheets/application.css
  5. +0
    -26
      app/controllers/players_controller.rb
  6. +0
    -1
      app/controllers/site_controller.rb
  7. +0
    -202
      app/controllers/stages_controller.rb
  8. +12
    -16
      app/helpers/site_helper.rb
  9. +0
    -2
      app/helpers/stages_helper.rb
  10. +1
    -1
      app/javascript/controllers/assets_controller.js
  11. +1
    -1
      app/views/admin/assets/_asset.html.erb
  12. +1
    -1
      app/views/admin/attachments/_asset.html.erb
  13. +1
    -3
      app/views/languages/index.html.erb
  14. +3
    -6
      app/views/layouts/application.html.erb
  15. +0
    -21
      app/views/stages/bonus.html.erb
  16. +0
    -19
      app/views/stages/chance.html.erb
  17. +0
    -20
      app/views/stages/choice.html.erb
  18. +0
    -18
      app/views/stages/game_over.html.erb
  19. +0
    -49
      app/views/stages/result.html.erb
  20. +0
    -17
      app/views/stages/score.html.erb
  21. +0
    -19
      app/views/stages/show.html.erb
  22. +0
    -24
      config/routes.rb
  23. +62
    -0
      country_codes.csv
  24. +0
    -12
      db/migrate/20250527114853_create_players.rb
  25. +1
    -14
      db/schema.rb
  26. +1
    -1
      db/seeds.rb

+ 9
- 0
CLAUDE.md View File

@ -0,0 +1,9 @@
# Gemini CLI Project Configuration
This project contains documentation for AI agents in the [AGENTS.md](./AGENTS.md) file.
## Core Mandates
- Adhere to the project structure and architectural patterns described in [AGENTS.md](./AGENTS.md).
- Ensure all new features or bug fixes are verified through automated tests.
- Maintain multi-lingual support by updating the necessary translation files in `config/locales/`.

+ 43
- 43
Gemfile.lock View File

@ -1,7 +1,7 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
action_text-trix (2.1.18)
action_text-trix (2.1.19)
railties railties
actioncable (8.1.3) actioncable (8.1.3)
actionpack (= 8.1.3) actionpack (= 8.1.3)
@ -88,13 +88,13 @@ GEM
bcrypt (3.1.22) bcrypt (3.1.22)
bigdecimal (4.1.2) bigdecimal (4.1.2)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.24.0)
bootsnap (1.24.5)
msgpack (~> 1.2) msgpack (~> 1.2)
builder (3.3.0) builder (3.3.0)
concurrent-ruby (1.3.6) concurrent-ruby (1.3.6)
connection_pool (3.0.2) connection_pool (3.0.2)
crass (1.0.6) crass (1.0.6)
css_parser (2.1.0)
css_parser (2.2.0)
addressable addressable
date (3.5.1) date (3.5.1)
date_validator (0.12.0) date_validator (0.12.0)
@ -134,10 +134,10 @@ GEM
prism (>= 1.3.0) prism (>= 1.3.0)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
jbuilder (2.14.1)
jbuilder (2.15.1)
actionview (>= 7.0.0) actionview (>= 7.0.0)
activesupport (>= 7.0.0) activesupport (>= 7.0.0)
json (2.19.4)
json (2.19.7)
kaminari (1.2.2) kaminari (1.2.2)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2) kaminari-actionview (= 1.2.2)
@ -151,7 +151,7 @@ GEM
kaminari-core (= 1.2.2) kaminari-core (= 1.2.2)
kaminari-core (1.2.2) kaminari-core (1.2.2)
language_server-protocol (3.17.0.5) language_server-protocol (3.17.0.5)
lexxy (0.9.10.beta)
lexxy (0.9.15.alpha.1)
rails (>= 8.0.2) rails (>= 8.0.2)
lint_roller (1.1.0) lint_roller (1.1.0)
logger (1.7.0) logger (1.7.0)
@ -164,17 +164,17 @@ GEM
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
marcel (1.1.0)
marcel (1.2.1)
mini_magick (5.3.1) mini_magick (5.3.1)
logger logger
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (6.0.5)
minitest (6.0.6)
drb (~> 2.0) drb (~> 2.0)
prism (~> 1.5) prism (~> 1.5)
mobility (1.3.2) mobility (1.3.2)
i18n (>= 0.6.10, < 2) i18n (>= 0.6.10, < 2)
request_store (~> 1.0) request_store (~> 1.0)
msgpack (1.8.0)
msgpack (1.8.1)
net-imap (0.6.4) net-imap (0.6.4)
date date
net-protocol net-protocol
@ -201,7 +201,7 @@ GEM
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.19.3-x86_64-linux-musl) nokogiri (1.19.3-x86_64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
openssl (4.0.1)
openssl (4.0.2)
parallel (2.1.0) parallel (2.1.0)
parser (3.3.11.1) parser (3.3.11.1)
ast (~> 2.4.1) ast (~> 2.4.1)
@ -236,7 +236,7 @@ GEM
date date
stringio stringio
public_suffix (7.0.5) public_suffix (7.0.5)
puma (8.0.1)
puma (8.0.2)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.8.1) racc (1.8.1)
rack (3.2.6) rack (3.2.6)
@ -285,14 +285,14 @@ GEM
tsort tsort
redis (5.4.1) redis (5.4.1)
redis-client (>= 0.22.0) redis-client (>= 0.22.0)
redis-client (0.28.0)
redis-client (0.29.0)
connection_pool connection_pool
regexp_parser (2.12.0) regexp_parser (2.12.0)
reline (0.6.3) reline (0.6.3)
io-console (~> 0.5) io-console (~> 0.5)
request_store (1.7.0) request_store (1.7.0)
rack (>= 1.4) rack (>= 1.4)
rubocop (1.86.1)
rubocop (1.86.2)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0) lint_roller (~> 1.1.0)
@ -310,7 +310,7 @@ GEM
lint_roller (~> 1.1) lint_roller (~> 1.1)
rubocop (>= 1.75.0, < 2.0) rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0) rubocop-ast (>= 1.47.1, < 2.0)
rubocop-rails (2.34.3)
rubocop-rails (2.35.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
lint_roller (~> 1.1) lint_roller (~> 1.1)
rack (>= 1.1) rack (>= 1.1)
@ -325,21 +325,21 @@ GEM
ffi (~> 1.12) ffi (~> 1.12)
logger logger
securerandom (0.4.1) securerandom (0.4.1)
sidekiq (8.1.3)
sidekiq (8.1.6)
connection_pool (>= 3.0.0) connection_pool (>= 3.0.0)
json (>= 2.16.0) json (>= 2.16.0)
logger (>= 1.7.0) logger (>= 1.7.0)
rack (>= 3.2.0) rack (>= 3.2.0)
redis-client (>= 0.26.0)
redis-client (>= 0.29.0)
stimulus-rails (1.3.4) stimulus-rails (1.3.4)
railties (>= 6.0.0) railties (>= 6.0.0)
stringio (3.2.0) stringio (3.2.0)
thor (1.5.0) thor (1.5.0)
thruster (0.1.20)
thruster (0.1.20-aarch64-linux)
thruster (0.1.20-arm64-darwin)
thruster (0.1.20-x86_64-darwin)
thruster (0.1.20-x86_64-linux)
thruster (0.1.21)
thruster (0.1.21-aarch64-linux)
thruster (0.1.21-arm64-darwin)
thruster (0.1.21-x86_64-darwin)
thruster (0.1.21-x86_64-linux)
timeout (0.6.1) timeout (0.6.1)
tsort (0.2.0) tsort (0.2.0)
turbo-rails (2.0.23) turbo-rails (2.0.23)
@ -360,7 +360,7 @@ GEM
base64 base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
zeitwerk (2.7.5)
zeitwerk (2.8.2)
PLATFORMS PLATFORMS
aarch64-linux aarch64-linux
@ -404,7 +404,7 @@ DEPENDENCIES
web-console web-console
CHECKSUMS CHECKSUMS
action_text-trix (2.1.18) sha256=3fdb83f8bff4145d098be283cdd47ac41caf5110bfa6df4695ed7127d7fb3642
action_text-trix (2.1.19) sha256=7012f59421009cf284aa651294896414d653a61a2417c9b8714c8476d2f74009
actioncable (8.1.3) sha256=e5bc7f75e44e6a22de29c4f43176927c3a9ce4824464b74ed18d8226e75a80f0 actioncable (8.1.3) sha256=e5bc7f75e44e6a22de29c4f43176927c3a9ce4824464b74ed18d8226e75a80f0
actionmailbox (8.1.3) sha256=df7da474eaa0e70df4ed5a6fef66eb3b3b0f2dbf7f14518deee8d77f1b4aae59 actionmailbox (8.1.3) sha256=df7da474eaa0e70df4ed5a6fef66eb3b3b0f2dbf7f14518deee8d77f1b4aae59
actionmailer (8.1.3) sha256=831f724891bb70d0aaa4d76581a6321124b6a752cb655c9346aae5479318448d actionmailer (8.1.3) sha256=831f724891bb70d0aaa4d76581a6321124b6a752cb655c9346aae5479318448d
@ -424,12 +424,12 @@ CHECKSUMS
bcrypt (3.1.22) sha256=1f0072e88c2d705d94aff7f2c5cb02eb3f1ec4b8368671e19112527489f29032 bcrypt (3.1.22) sha256=1f0072e88c2d705d94aff7f2c5cb02eb3f1ec4b8368671e19112527489f29032
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e
bootsnap (1.24.0) sha256=34e6dea61ff4895101aa9c10894ce30186bec73fe2279e0eb52040d8d4cec297
bootsnap (1.24.5) sha256=36b677448524d279b470469aabd5dff4a980e3fa4931a0df68da4a500eb1b6c4
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d
css_parser (2.1.0) sha256=bfb7c9cf3896426b53337e34b4ad391c3cfe8c2f2c839e72f2cdccf615fb5247
css_parser (2.2.0) sha256=23d1b247d7bc78cb2f2fe54629fb755e68d3004f1d1bd5c66d5096d42bff6325
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
date_validator (0.12.0) sha256=68c9834da240347b9c17441c553a183572508617ebfbe8c020020f3192ce3058 date_validator (0.12.0) sha256=68c9834da240347b9c17441c553a183572508617ebfbe8c020020f3192ce3058
dkim (1.1.0) sha256=74f2e3075c89cd2967995f3ee293da9dccccffee21ee593715a068ab39a717f8 dkim (1.1.0) sha256=74f2e3075c89cd2967995f3ee293da9dccccffee21ee593715a068ab39a717f8
@ -453,24 +453,24 @@ CHECKSUMS
importmap-rails (2.2.3) sha256=7101be2a4dc97cf1558fb8f573a718404c5f6bcfe94f304bf1f39e444feeb16a importmap-rails (2.2.3) sha256=7101be2a4dc97cf1558fb8f573a718404c5f6bcfe94f304bf1f39e444feeb16a
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
jbuilder (2.14.1) sha256=4eb26376ff60ef100cb4fd6fd7533cd271f9998327e86adf20fd8c0e69fabb42
json (2.19.4) sha256=670a7d333fb3b18ca5b29cb255eb7bef099e40d88c02c80bd42a3f30fe5239ac
jbuilder (2.15.1) sha256=2430bec28fb0cebacb5875b1009cf9d8bc3c303ccb810c4c8b062a4b51457637
json (2.19.7) sha256=fe432c8639f6efff69f9d73b518a3705d9581ab93156f981ea72806e1e5bcc3e
kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e
kaminari-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909 kaminari-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909
kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430 kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430
kaminari-core (1.2.2) sha256=3bd26fec7370645af40ca73b9426a448d09b8a8ba7afa9ba3c3e0d39cdbb83ff kaminari-core (1.2.2) sha256=3bd26fec7370645af40ca73b9426a448d09b8a8ba7afa9ba3c3e0d39cdbb83ff
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
lexxy (0.9.10.beta) sha256=f67df39cf7717b4a49b1694570128e42e6947a0d64bcbba1b53fa716e93b31fe
lexxy (0.9.15.alpha.1) sha256=5906f921e9ec8b0b7c47853e7fb9fb1b3c97921f4edd701d94a76ba25c45fa84
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04 loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04
mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941 mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941
marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee
marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f
mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4 mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
minitest (6.0.5) sha256=f007d7246bf4feea549502842cd7c6aba8851cdc9c90ba06de9c476c0d01155c
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
mobility (1.3.2) sha256=32fbbb0e53118ef42de20daa6ac94dbb758c628874092eba311b968a1e1d757b mobility (1.3.2) sha256=32fbbb0e53118ef42de20daa6ac94dbb758c628874092eba311b968a1e1d757b
msgpack (1.8.0) sha256=e64ce0212000d016809f5048b48eb3a65ffb169db22238fb4b72472fecb2d732
msgpack (1.8.1) sha256=3fef787cd3965fd119c08a22724a56a93ca25008c3421fc15039f603a8b7c86c
net-imap (0.6.4) sha256=9a5598c67a3022c284d98430ef1d4948e7dbdb62596f61081ea8ca933270a02b net-imap (0.6.4) sha256=9a5598c67a3022c284d98430ef1d4948e7dbdb62596f61081ea8ca933270a02b
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
@ -484,7 +484,7 @@ CHECKSUMS
nokogiri (1.19.3-x86_64-darwin) sha256=77f3fba57d46c53ab31e62fc6c28f705109d1bf6264356c76f132b2be5728d4d nokogiri (1.19.3-x86_64-darwin) sha256=77f3fba57d46c53ab31e62fc6c28f705109d1bf6264356c76f132b2be5728d4d
nokogiri (1.19.3-x86_64-linux-gnu) sha256=2f5078620fe12e83669b5b17311b32532a8153d02eee7ad06948b926d6080976 nokogiri (1.19.3-x86_64-linux-gnu) sha256=2f5078620fe12e83669b5b17311b32532a8153d02eee7ad06948b926d6080976
nokogiri (1.19.3-x86_64-linux-musl) sha256=248c906d2166eca5efb56d52fdee5f9a1f51d69a72e2b64fdac647b4ce39ea3f nokogiri (1.19.3-x86_64-linux-musl) sha256=248c906d2166eca5efb56d52fdee5f9a1f51d69a72e2b64fdac647b4ce39ea3f
openssl (4.0.1) sha256=e27974136b7b02894a1bce46c5397ee889afafe704a839446b54dc81cb9c5f7d
openssl (4.0.2) sha256=1037ad2868ae58df9ad917891c0c0f9815a1172f6846d4bcdd508e4c2ee747c2
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356 parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54 parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
pg (1.6.3) sha256=1388d0563e13d2758c1089e35e973a3249e955c659592d10e5b77c468f628a99 pg (1.6.3) sha256=1388d0563e13d2758c1089e35e973a3249e955c659592d10e5b77c468f628a99
@ -503,7 +503,7 @@ CHECKSUMS
propshaft (1.3.2) sha256=1d56a3e56a92c21bfc29caf07406b5386b00d4c47ddf357cf989a5a234b1389e propshaft (1.3.2) sha256=1d56a3e56a92c21bfc29caf07406b5386b00d4c47ddf357cf989a5a234b1389e
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
puma (8.0.1) sha256=7b94e50c07655718c1fb8ae41a11fc06c7d61293208b3aa608ff71a46d3ad37c
puma (8.0.2) sha256=c8ed871dfbbe66448ea9ffd46692342d9804d4071522b52b5331b7b6e7b686fb
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2 rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2
rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8 rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
@ -517,27 +517,27 @@ CHECKSUMS
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192 rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae
redis-client (0.28.0) sha256=888892f9cd8787a41c0ece00bdf5f556dfff7770326ce40bb2bc11f1bfec824b
redis-client (0.29.0) sha256=0c65bf1f8f6dca22063ddb085c0bb2054feef6f03a84869f4161b18a9a15bea3
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb
rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
rubocop (1.86.2) sha256=bb2e97f635eda42c448f2588f4a6ff78f221b8bdfdf65b1e9b07fbd57521b45d
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035 rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834 rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
rubocop-rails (2.34.3) sha256=10d37989024865ecda8199f311f3faca990143fbac967de943f88aca11eb9ad2
rubocop-rails (2.35.3) sha256=6edd45410866912b9b2e90ae3aeafd31d576df2bb2a9c9408f1667a50c32c7de
rubocop-rails-omakase (1.1.0) sha256=2af73ac8ee5852de2919abbd2618af9c15c19b512c4cfc1f9a5d3b6ef009109d rubocop-rails-omakase (1.1.0) sha256=2af73ac8ee5852de2919abbd2618af9c15c19b512c4cfc1f9a5d3b6ef009109d
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374 ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
sidekiq (8.1.3) sha256=a42f51aca3705d21cb50f37f5ec07e69de8708e126be4cf94b45cf15b84b3762
sidekiq (8.1.6) sha256=be20cd051124b1a16cf97ea9157137abbd30a515c16a5ae9312d2eadd045e40f
stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06 stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
thruster (0.1.20) sha256=c05f2fbcae527bbe093a6e6d84fb12d9d680617e7c162325d9b97e8e9d4b5201
thruster (0.1.20-aarch64-linux) sha256=754f1701061235235165dde31e7a3bc87ec88066a02981ff4241fcda0d76d397
thruster (0.1.20-arm64-darwin) sha256=630cf8c273f562063b92ea5ccd7a721d7ba6130cc422c823727f4744f6d0770e
thruster (0.1.20-x86_64-darwin) sha256=4cc245f3ea2ad238b518ae3934048eade6cc2543bdcfef91a7f95f8194306432
thruster (0.1.20-x86_64-linux) sha256=d579f252bf67aee6ba6d957e48f566b72e019d7657ba2f267a5db1e4d91d2479
thruster (0.1.21) sha256=dc67928f36e5894844579a95e45637a5091db7a7ea05468ee8c2c6eb0a3f77cf
thruster (0.1.21-aarch64-linux) sha256=f5aff78fb7a6431ed3d6ab4bde03a89c461e9a73981dbc97d6990d85c3db235c
thruster (0.1.21-arm64-darwin) sha256=bd8db9f57fae2cbb3fe08ebab49cb47fe49608122dac23daf0ce709adfb9bfc8
thruster (0.1.21-x86_64-darwin) sha256=ccd6acd144fad27856800edfa0573944018333fac8e10a2e5d09726b70c8b0db
thruster (0.1.21-x86_64-linux) sha256=6e2fbcf826540a72d3710ae4db072c2333287ac2ee57e7e52f35bc10900d74a7
timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
turbo-rails (2.0.23) sha256=ee0d90733aafff056cf51ff11e803d65e43cae258cc55f6492020ec1f9f9315f turbo-rails (2.0.23) sha256=ee0d90733aafff056cf51ff11e803d65e43cae258cc55f6492020ec1f9f9315f
@ -549,7 +549,7 @@ CHECKSUMS
web-console (4.3.0) sha256=e13b71301cdfc2093f155b5aa3a622db80b4672d1f2f713119cc7ec7ac6a6da4 web-console (4.3.0) sha256=e13b71301cdfc2093f155b5aa3a622db80b4672d1f2f713119cc7ec7ac6a6da4
websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962 websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962
websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
zeitwerk (2.8.2) sha256=7212a61311083c604184b1ea2574b9aa05cd14f855a0841c06985cabe9181d12
RUBY VERSION RUBY VERSION
ruby 3.4.9 ruby 3.4.9


+ 6
- 0
README.txt View File

@ -1,3 +1,9 @@
Søren Smidt <soren@stupid-studio.com>
-------------------
https://ssmidt79.github.io/save-this-tomato/
API API
------------------- -------------------
curl -H "Authorization: Bearer ikea-tomato-2026" http://localhost:3000/api/v1/leaderboard curl -H "Authorization: Bearer ikea-tomato-2026" http://localhost:3000/api/v1/leaderboard


+ 19
- 156
app/assets/stylesheets/application.css View File

@ -73,36 +73,32 @@
:root { :root {
--clr-white: #fff; --clr-white: #fff;
--clr-black: #000; --clr-black: #000;
--clr-dark-yellow: #F0B902;
--clr-medium-yellow: #FFDE52;
--clr-light-yellow: #FFF3AE;
--clr-text-highlight: #CCE8FE;
--clr-error: #0058a3;
--ff-ikea: "Noto IKEA", "Noto Sans", "Roboto", "Open Sans", system-ui, sans-serif;
--shadow: 0px 0px 10px 2px rgba(0,0,0,0.1);
--clr-green: #3FB83E;
--clr-blue: #4297FF;
--clr-sand: #F1EBDD;
--clr-sand-light: #F7F3EB;
--fs-s: 0.875rem;
--ff-ikea: "Noto IKEA", "Noto Sans", system-ui, sans-serif;
--fs-s: 0.75rem;
--fs-base: 1rem; --fs-base: 1rem;
--fs-lg: 1.125rem; --fs-lg: 1.125rem;
--fs-xl: 1.75rem;
--fs-2xl: 2rem;
--fs-3xl: 2.5rem;
--fs-4xl: 5.625rem;
--fs-xl: 2rem;
--fs-2xl: 3rem;
--fs-3xl: 4rem;
font: 16px/1.3 var(--ff-ikea);
}
--td-s: 400 0.75rem/1.3 var(--ff-ikea);
--td-base: 400 1rem/1.4 var(--ff-ikea);
--td-lg: 400 1.125rem/1.4 var(--ff-ikea);
--td-xl: 700 2rem/1.2 var(--ff-ikea);
--td-2xl: 700 3rem/1.2 var(--ff-ikea);
--td-3xl: 700 4rem/1.2 var(--ff-ikea);
#debug {
position: fixed;
bottom: 0;
left: 0;
font: 0.75rem ui-monospace;
padding: 1em;
font: 16px/1.4 var(--ff-ikea);
} }
.turbo-progress-bar { .turbo-progress-bar {
height: 3px; height: 3px;
background-color: #0058a3; background-color: #0058a3;
@ -110,8 +106,6 @@
body { body {
background-color: var(--clr-dark-yellow);
color: var(--clr-black);
margin: 0; margin: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -120,137 +114,6 @@ body {
min-height: 100dvh; min-height: 100dvh;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
touch-action: manipulation; touch-action: manipulation;
background-image: var(--bg-landscape);
background-position: center center;
background-attachment: fixed;
background-repeat: no-repeat;
background-size: cover;
@media (orientation: portrait) {
background-image: var(--bg-portrait);
}
}
ul[class] {
margin: 0 auto;
padding: 0;
list-style: none;
}
header {
padding: 0.75rem;
flex-grow: 0;
z-index: 11;
position: absolute;
& svg {
width: 203px;
height: auto;
max-width: 60vw;
}
}
path.logo-text {
fill: var(--clr-white);
}
:has(main .with-backdrop) {
& header path.logo-text {
fill: var(--clr-black);
}
& footer {
color: var(--clr-black);
}
}
footer {
position: fixed;
font-size: 0.75rem;
line-height: 1.2;
right: 0.75rem;
top: 0.75rem;
font-family: var(--ff-ikea);
writing-mode: vertical-rl;
text-orientation: mixed;
z-index: 10;
color: var(--clr-white);
}
[data-locale] {
display: none;
}
html[lang="en"] [data-locale='en'],
html[lang="zh"] [data-locale='zh'],
html[lang="hr"] [data-locale='hr'],
html[lang="cs"] [data-locale='cs'],
html[lang="da"] [data-locale='da'],
html[lang="nl"] [data-locale='nl'],
html[lang="fi"] [data-locale='fi'],
html[lang="fr"] [data-locale='fr'],
html[lang="fr-CA"] [data-locale='fr-CA'],
html[lang="de"] [data-locale='de'],
html[lang="hu"] [data-locale='hu'],
html[lang="it"] [data-locale='it'],
html[lang="ja"] [data-locale='ja'],
html[lang="ko"] [data-locale='ko'],
html[lang="nb"] [data-locale='nb'],
html[lang="pl"] [data-locale='pl'],
html[lang="pt"] [data-locale='pt'],
html[lang="ro"] [data-locale='ro'],
html[lang="sr"] [data-locale='sr'],
html[lang="sk"] [data-locale='sk'],
html[lang="sl"] [data-locale='sl'],
html[lang="es"] [data-locale='es'],
html[lang="sv"] [data-locale='sv'],
html[lang="uk"] [data-locale='uk'] {
display: revert;
}
main {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 0.75rem;
}
article {
background-color: var(--clr-white);
padding: 2rem 1.5rem;
max-width: 48ch;
& > *:first-child {
margin-block-start: 0;
}
}
h2 {
line-height: 1.2;
margin-block-end: 1em;
}
p {
margin-block-end: 1em;
}
.action-container {
margin-block-start: 2em;
display: flex;
flex-direction: column;
gap: 0.4em;
} }
button {
appearance: none;
border: none;
background-color: var(--clr-black);
color: var(--clr-white);
padding: 0.6em 1em;
cursor: pointer;
border-radius: 0.4em;
}

+ 0
- 26
app/controllers/players_controller.rb View File

@ -1,26 +0,0 @@
class PlayersController < ApplicationController
include QuizHelperMethods
skip_before_action :require_player!, only: [ :create ]
# POST
def create
# reset_session
@player = Player.create(locale: I18n.locale)
session[:player_id] = @player.id
redirect_to stage_path(id: 1)
end
# POST
def go_again
@player = Player.create(
locale: current_player.locale,
bonus_points: current_player.bonus_points
)
session[:player_id] = @player.id
redirect_to stage_path(id: 1)
end
end

+ 0
- 1
app/controllers/site_controller.rb View File

@ -2,7 +2,6 @@ class SiteController < ApplicationController
before_action :set_locale before_action :set_locale
def index def index
@node = Node.roots.viewable.first @node = Node.roots.viewable.first
not_found and return unless @node not_found and return unless @node


+ 0
- 202
app/controllers/stages_controller.rb View File

@ -1,202 +0,0 @@
class StagesController < ApplicationController
include QuizHelperMethods
before_action :set_locale
before_action :require_player!
before_action :check_stage_access, expect: [ :game_over ]
before_action :set_stage, expect: [ :game_over, :score ]
before_action :set_outcome, only: [ :reveal, :pick, :result ]
helper_method :stage_index, :bonus_node
# GET
def show
end
# GET
def game_over
@node = Node.at_depth(1).game_over.viewable&.first
end
# GET
def score
@node = Node.at_depth(1).score.viewable&.first
end
# POST
def flip
outcome = rand < 0.5 ? "chance" : "choice"
current_player.record_flip(stage_index, outcome)
redirect_to url_for(action: "reveal")
end
# GET Chance or Choice
def reveal
@node = @stage.children.viewable.find_by(template: @outcome)
# redirect_to root_path and return if @outcome.blank? or @node.blank?
render action: @outcome
end
# POST
def pick
# redirect_to root_path and return if @outcome.blank?
@node = @stage.children.viewable.find_by(template: @outcome)
possible_answers = @node.children.viewable
case @outcome
when "chance"
result = possible_answers.pluck(:template).sample
# result = "game_over" if stage_index == 2 # DEV
when "choice"
result = possible_answers.find_by(id: params[:option])&.template
end
current_player.record_pick_result(stage_index, result) unless result.blank?
redirect_to action: "result"
end
# GET
def result
# redirect_to root_path and return if @outcome.blank?
if current_player.progress[stage_index.to_s].keys.include?("bonus_result")
@result = current_player.progress[stage_index.to_s]&.dig("bonus_result")
@node = @stage.children.viewable.find_by(template: "bonus")&.children&.viewable&.find_by(template: @result)
else
@result = current_player.progress[stage_index.to_s]&.dig("result")
@node = @stage.children.viewable.find_by(template: @outcome)&.children&.viewable&.find_by(template: @result)
current_player.update(is_done: true) if @node&.game_over?
end
# redirect_to root_path if @node.blank?
end
# POST
def next
if player_can_proceed?
current_player.update(current_stage: stage_index + 1)
# Game done
if Node.at_depth(1).stage.count < current_player.current_stage
redirect_to action: "score" and return
# Next stage
else
redirect_to stage_path(id: current_player.current_stage) and return
end
else
# redirect_to root_path
not_found
end
end
# POST
def try_bonus
@result = current_player.progress[stage_index.to_s]["result"]
# redirect_to root_path and return unless @result == "bonus"
current_player.record_bonus_pick_result(stage_index, nil)
redirect_to action: "bonus"
end
# GET
def bonus
@result = current_player.progress[stage_index.to_s]["result"]
# redirect_to root_path and return unless @result == "bonus"
@node = bonus_node
end
# POST
def pick_bonus
@result = current_player.progress[stage_index.to_s]["result"]
# redirect_to root_path and return unless @result == "bonus"
@node = @stage.children.viewable.find_by(template: "bonus")
possible_answers = @node.children.viewable
result = possible_answers.find_by(id: params[:option])&.template
current_player.record_bonus_pick_result(stage_index, result) unless result.blank?
redirect_to action: "result"
end
# POST
def get_bonus_point
if current_player.progress[stage_index.to_s]&.dig("bonus_result") == "good"
bonus_points_value = stage_index
else
bonus_points_value = [ stage_index - 1, 0 ].max
end
current_player.update(
bonus_points: bonus_points_value,
is_done: true
)
redirect_to action: "game_over"
end
private
def check_stage_access
if stage_index > current_player.current_stage
redirect_to root_path and return
end
end
def player_can_proceed?
result = current_player.progress[stage_index.to_s]&.dig("result")
return false if stage_index != current_player.current_stage
return true if %w[good best].include?(result)
return true if result == "bad" and current_player.bonus_points >= stage_index
false
end
def set_stage
@stage = Node.at_depth(1).stage.viewable.ordered[stage_index - 1]
# redirect_to root_path and return if @stage.nil?
end
def bonus_node
@bonus_node ||= @stage.children&.viewable&.bonus&.first
end
def set_outcome
@outcome ||= current_player.progress[stage_index.to_s]&.dig("flip")
end
def stage_index
@stage_index ||= params[:id].to_i
end
end

+ 12
- 16
app/helpers/site_helper.rb View File

@ -1,22 +1,19 @@
module SiteHelper module SiteHelper
def frontend_javascript_importmap_tags(only_use=%w"application")
def frontend_javascript_importmap_tags(only_use = %w[application])
only_use = Array(only_use) only_use = Array(only_use)
importmap_json = JSON.parse(Rails.application.importmap.to_json(resolver: self))['imports'].select{ |k,v| only_use.include?(k)}
importmap_json = JSON.parse(Rails.application.importmap.to_json(resolver: self))["imports"].select { |k, v| only_use.include?(k) }
safe_join [ safe_join [
javascript_inline_importmap_tag(JSON.pretty_generate({ "imports" => importmap_json})),
javascript_module_preload_tag(*importmap_json.map{|v| v[1]}),
javascript_inline_importmap_tag(JSON.pretty_generate({ "imports" => importmap_json })),
javascript_module_preload_tag(*importmap_json.map { |v| v[1] }),
javascript_import_module_tag(only_use[0]) javascript_import_module_tag(only_use[0])
], "\n" ], "\n"
end end
def node_title(node) def node_title(node)
parts = [node.page_title.blank? ? node.title : node.page_title ]
parts = [ node.page_title.blank? ? node.title : node.page_title ]
parts << t(:client_name) parts << t(:client_name)
parts.uniq.join(' - ')
parts.uniq.join(" - ")
rescue rescue
t(:client_name) t(:client_name)
end end
@ -32,17 +29,17 @@ module SiteHelper
result = {} result = {}
return result if html.blank? return result if html.blank?
doc = Nokogiri::HTML.fragment(html, 'utf-8')
doc = Nokogiri::HTML.fragment(html, "utf-8")
first_link = doc.at_css('a')
first_link = doc.at_css("a")
first_link.remove if first_link first_link.remove if first_link
result[:title] = doc.at_css('h1')&.text
result[:description] = doc.at_css('div')&.text
result[:title] = doc.at_css("h1")&.text
result[:description] = doc.at_css("div")&.text
result[:link] = first_link&.to_html result[:link] = first_link&.to_html
doc.search('*').each do |node|
while node.children.last and node.children.last.name == 'br'
doc.search("*").each do |node|
while node.children.last and node.children.last.name == "br"
node.children.last.remove node.children.last.remove
end end
end end
@ -51,5 +48,4 @@ module SiteHelper
result result
end end
end end

+ 0
- 2
app/helpers/stages_helper.rb View File

@ -1,2 +0,0 @@
module StagesHelper
end

+ 1
- 1
app/javascript/controllers/assets_controller.js View File

@ -12,7 +12,7 @@ export default class extends Controller {
} }
appendAttachments(event) { appendAttachments(event) {
if (this.fetchingData) return if (this.fetchingData) return
const checkedCheckboxes = this.element.querySelectorAll('input[name="asset_ids[]"]:checked') const checkedCheckboxes = this.element.querySelectorAll('input[name="asset_ids[]"]:checked')


+ 1
- 1
app/views/admin/assets/_asset.html.erb View File

@ -1,7 +1,7 @@
<%= tag.div class: 'asset', id: dom_id(asset) do %> <%= tag.div class: 'asset', id: dom_id(asset) do %>
<div class="asset__thumbnail"> <div class="asset__thumbnail">
<div><%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :jpg)) if asset.file.representable? %></div>
<div><%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :webp)) if asset.file.representable? %></div>
</div> </div>
<%= tag.div asset.title, class: 'asset__title' %> <%= tag.div asset.title, class: 'asset__title' %>


+ 1
- 1
app/views/admin/attachments/_asset.html.erb View File

@ -1,6 +1,6 @@
<div class="attachment__asset"> <div class="attachment__asset">
<div class="asset__thumbnail"> <div class="asset__thumbnail">
<div><%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :jpg)) if asset.file.representable? %></div>
<div><%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :webp)) if asset.file.representable? %></div>
</div> </div>
<%= tag.div asset.title, class: 'asset__title' %> <%= tag.div asset.title, class: 'asset__title' %>


+ 1
- 3
app/views/languages/index.html.erb View File

@ -7,8 +7,6 @@
<% end %> <% end %>
<div class="action-container"> <div class="action-container">
<%= button_to t("let_me_try"),
start_path(locale: I18n.locale),
method: :post %>
</div> </div>
</article> </article>

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

@ -6,7 +6,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="theme-color" content="#F0B902"> <meta name="theme-color" content="#F0B902">
<%= tag.meta name: 'description', content: content_for?(:meta_description) ? yield(:meta_description) : (@node.present?? @node.page_description : '') %>
<%= tag.meta name: "description", content: content_for?(:meta_description) ? yield(:meta_description) : (@node.present?? @node.page_description : "") %>
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
<%= csp_meta_tag %> <%= csp_meta_tag %>
@ -20,15 +20,12 @@
<body> <body>
<header> <header>
<%= link_to svg('ikea-foundation-203x22'), url_for(controller: 'languages', action: (I18n.default_locale == I18n.locale ? 'index' : 'show')) %>
<%= link_to svg("ikea-foundation-203x22"), url_for(controller: "languages", action: (I18n.default_locale == I18n.locale ? "index" : "show")) %>
</header> </header>
<main> <main>
<%= yield %> <%= yield %>
</main> </main>
<footer><span>© Inter IKEA Systems B.V. 2026</span></footer>
<%= tag.div content_for(:debug), id: "debug" if Rails.env.development? %>
</body> </body>
</html> </html>

+ 0
- 21
app/views/stages/bonus.html.erb View File

@ -1,21 +0,0 @@
<%-
content_for :title, @node.page_title.blank? ? @node.title : @node.page_title
content_for :meta_description, @node.page_description
content_for :debug, current_player.inspect
%>
<article>
<% @node.attachments.each do |attachment| %>
<%= attachment.body.html_safe %>
<% end %>
<div class="action-container">
<% @node.children.viewable.ordered.each do |answer_option| %>
<%= button_to answer_option.title, url_for(action: 'pick_bonus', option: answer_option.id), method: :post %>
<% end %>
</div>
</article>

+ 0
- 19
app/views/stages/chance.html.erb View File

@ -1,19 +0,0 @@
<%-
content_for :title, @node.page_title.blank? ? @node.title : @node.page_title
content_for :meta_description, @node.page_description
content_for :debug, current_player.inspect
%>
<article>
<% @node.attachments.each do |attachment| %>
<%= attachment.body.html_safe %>
<% end %>
<div class="action-container">
<%= button_to t("flip_the_card"), url_for(action: 'pick'), method: :post %>
</div>
</article>

+ 0
- 20
app/views/stages/choice.html.erb View File

@ -1,20 +0,0 @@
<%-
content_for :title, @node.page_title.blank? ? @node.title : @node.page_title
content_for :meta_description, @node.page_description
content_for :debug, current_player.inspect
%>
<article>
<% @node.attachments.each do |attachment| %>
<%= attachment.body.html_safe %>
<% end %>
<div class="action-container">
<% @node.children.viewable.ordered.each do |answer_option| %>
<%= button_to answer_option.title, url_for(action: 'pick', option: answer_option.id), method: :post %>
<% end %>
</div>
</article>

+ 0
- 18
app/views/stages/game_over.html.erb View File

@ -1,18 +0,0 @@
<%-
content_for :title, @node.page_title.blank? ? @node.title : @node.page_title
content_for :meta_description, @node.page_description
content_for :debug, current_player.inspect
%>
<article>
<% @node.attachments.each do |attachment| %>
<%= attachment.body.html_safe %>
<% end %>
<div class="action-container">
<%= button_to t("go_again"), go_again_path(), method: :post %>
</div>
</article>

+ 0
- 49
app/views/stages/result.html.erb View File

@ -1,49 +0,0 @@
<%-
content_for :title, @node.page_title.blank? ? @node.title : @node.page_title
content_for :meta_description, @node.page_description
content_for :debug, current_player.inspect
%>
<article>
<%= @node.attachments[(@result == "bad" and current_player.bonus_points >= stage_index) ? 1 : 0]&.body&.html_safe %>
<div class="action-container">
<% if @node.parent&.bonus? %>
<%= button_to t("get_bonus_point"), url_for(action: 'get_bonus_point'), method: :post %>
<% else %>
<% case @result %>
<% when "best", "good" %>
<%= button_to t("next_stage"), url_for(action: 'next'), method: :post %>
<% when "bad" %>
<% if current_player.bonus_points >= stage_index %>
<%= button_to t("use_bonus_point"), url_for(action: 'next'), method: :post %>
<% else %>
<% if bonus_node %>
<%= button_to t("bonus"), url_for(action: 'try_bonus'), method: :post %>
<% else %>
<%= button_to t("go_again"), go_again_path(), method: :post %>
<% end %>
<% end %>
<% when "bonus" %>
<%= button_to t("let_my_try"), url_for(action: 'try_bonus'), method: :post %>
<% when "game_over" %>
<% if bonus_node %>
<%= button_to t("bonus"), url_for(action: 'try_bonus'), method: :post %>
<% else %>
<%= button_to t("go_again"), go_again_path(), method: :post %>
<% end %>
<% end %>
<% end %>
</div>
</article>

+ 0
- 17
app/views/stages/score.html.erb View File

@ -1,17 +0,0 @@
<%-
content_for :title, @node.page_title.blank? ? @node.title : @node.page_title
content_for :meta_description, @node.page_description
content_for :debug, current_player.inspect
%>
<article>
<% @node.attachments.each do |attachment| %>
<%= attachment.body.html_safe %>
<% end %>
<div class="action-container">
<%= button_to t("go_again"), go_again_path(), method: :post %>
</div>
</article>

+ 0
- 19
app/views/stages/show.html.erb View File

@ -1,19 +0,0 @@
<%-
content_for :title, @stage.page_title.blank? ? @stage.title : @stage.page_title
content_for :meta_description, @stage.page_description
content_for :debug, current_player.inspect
%>
<article>
<% @stage.attachments.each do |attachment| %>
<%= attachment.body.html_safe %>
<% end %>
<div class="action-container">
<%= button_to t("flip"), url_for(action: 'flip'), method: :post %>
</div>
</article>

+ 0
- 24
config/routes.rb View File

@ -50,30 +50,6 @@ Rails.application.routes.draw do
end end
scope ":locale", constraints: { locale: /en|zh|hr|cs|da|nl|fi|fr|fr-CA|de|hu|it|ja|ko|nb|pl|pt|ro|sr|sk|sl|es|sv|uk/ } do scope ":locale", constraints: { locale: /en|zh|hr|cs|da|nl|fi|fr|fr-CA|de|hu|it|ja|ko|nb|pl|pt|ro|sr|sk|sl|es|sv|uk/ } do
post "start", to: "players#create", as: :start
post "go_again", to: "players#go_again", as: :go_again
scope "stages/:id", constraints: { id: /\d+/ } do
get "", to: "stages#show", as: :stage
post "flip", to: "stages#flip"
get "reveal", to: "stages#reveal"
post "pick(/:option)", to: "stages#pick"
get "result", to: "stages#result"
post "next", to: "stages#next"
post "bonus", to: "stages#try_bonus"
get "bonus", to: "stages#bonus"
post "pick_bonus(/:option)", to: "stages#pick_bonus"
post "get_bonus_point", to: "stages#get_bonus_point"
end
get "game_over", to: "stages#game_over"
get "score", to: "stages#score"
get "", to: "languages#show" get "", to: "languages#show"
# get "*url", to: "site#page", constraints: lambda { |req| req.path.exclude?("storage") } # get "*url", to: "site#page", constraints: lambda { |req| req.path.exclude?("storage") }
end end


+ 62
- 0
country_codes.csv View File

@ -0,0 +1,62 @@
Code,Country
AU,Australia
AT,Austria
BH,Bahrain
ES,Spain
BE,Belgium
BG,Bulgaria
CA,Canada
CL,Chile
CN,China
CO,Colombia
HR,Croatia
CY,Cyprus
CZ,Czech Republic
DK,Denmark
DO,Dominican Republic
EG,Egypt
EE,Estonia
FI,Finland
FR,France
DE,Germany
GR,Greece
HK,Hong Kong
HU,Hungary
IS,Iceland
IN,India
ID,Indonesia
IE,Ireland
IL,Israel
IT,Italy
JP,Japan
JO,Jordan
KW,Kuwait
LV,Latvia
LT,Lithuania
MY,Malaysia
MX,Mexico
MA,Morocco
NL,Netherlands
NZ,New Zealand
NO,Norway
OM,Oman
PH,Philippines
PL,Poland
PT,Portugal
PR,Puerto Rico
QA,Qatar
RO,Romania
SA,Saudi Arabia
RS,Serbia
SG,Singapore
SK,Slovakia
SI,Slovenia
KR,South Korea
SE,Sweden
CH,Switzerland
TW,Taiwan
TH,Thailand
TR,Turkey
AE,United Arab Emirates
GB,United Kingdom
US,United States of America

+ 0
- 12
db/migrate/20250527114853_create_players.rb View File

@ -1,12 +0,0 @@
class CreatePlayers < ActiveRecord::Migration[8.1]
def change
create_table :players do |t|
t.text :locale
t.integer :current_stage, index: true
t.integer :bonus_points, index: true
t.jsonb :progress, default: {}
t.boolean :is_done, default: false, index: true
t.timestamps
end
end
end

+ 1
- 14
db/schema.rb View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2025_05_27_114853) do
ActiveRecord::Schema[8.1].define(version: 2025_04_27_131737) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql" enable_extension "pg_catalog.plpgsql"
@ -114,19 +114,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_05_27_114853) do
t.index ["url"], name: "index_nodes_on_url", using: :gin t.index ["url"], name: "index_nodes_on_url", using: :gin
end end
create_table "players", force: :cascade do |t|
t.integer "bonus_points"
t.datetime "created_at", null: false
t.integer "current_stage"
t.boolean "is_done", default: false
t.text "locale"
t.jsonb "progress", default: {}
t.datetime "updated_at", null: false
t.index ["bonus_points"], name: "index_players_on_bonus_points"
t.index ["current_stage"], name: "index_players_on_current_stage"
t.index ["is_done"], name: "index_players_on_is_done"
end
create_table "quiz_results", force: :cascade do |t| create_table "quiz_results", force: :cascade do |t|
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.string "locale", null: false t.string "locale", null: false


+ 1
- 1
db/seeds.rb View File

@ -8,7 +8,7 @@ User.create(
password_confirmation: 'admin', password_confirmation: 'admin',
enabled_at: Time.now, enabled_at: Time.now,
role: 'admin' role: 'admin'
)
) unless User.find_by(email: "mattias@oncotype.dk")
Node.create title: 'Week 2026', slug: '', published_at: Time.now, ancestry: '/' Node.create title: 'Week 2026', slug: '', published_at: Time.now, ancestry: '/'

Loading…
Cancel
Save