diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d028c9b --- /dev/null +++ b/CLAUDE.md @@ -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/`. diff --git a/Gemfile.lock b/Gemfile.lock index db36b70..a132333 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - action_text-trix (2.1.18) + action_text-trix (2.1.19) railties actioncable (8.1.3) actionpack (= 8.1.3) @@ -88,13 +88,13 @@ GEM bcrypt (3.1.22) bigdecimal (4.1.2) bindex (0.8.1) - bootsnap (1.24.0) + bootsnap (1.24.5) msgpack (~> 1.2) builder (3.3.0) concurrent-ruby (1.3.6) connection_pool (3.0.2) crass (1.0.6) - css_parser (2.1.0) + css_parser (2.2.0) addressable date (3.5.1) date_validator (0.12.0) @@ -134,10 +134,10 @@ GEM prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - jbuilder (2.14.1) + jbuilder (2.15.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) - json (2.19.4) + json (2.19.7) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -151,7 +151,7 @@ GEM kaminari-core (= 1.2.2) kaminari-core (1.2.2) language_server-protocol (3.17.0.5) - lexxy (0.9.10.beta) + lexxy (0.9.15.alpha.1) rails (>= 8.0.2) lint_roller (1.1.0) logger (1.7.0) @@ -164,17 +164,17 @@ GEM net-imap net-pop net-smtp - marcel (1.1.0) + marcel (1.2.1) mini_magick (5.3.1) logger mini_mime (1.1.5) - minitest (6.0.5) + minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) mobility (1.3.2) i18n (>= 0.6.10, < 2) request_store (~> 1.0) - msgpack (1.8.0) + msgpack (1.8.1) net-imap (0.6.4) date net-protocol @@ -201,7 +201,7 @@ GEM racc (~> 1.4) nokogiri (1.19.3-x86_64-linux-musl) racc (~> 1.4) - openssl (4.0.1) + openssl (4.0.2) parallel (2.1.0) parser (3.3.11.1) ast (~> 2.4.1) @@ -236,7 +236,7 @@ GEM date stringio public_suffix (7.0.5) - puma (8.0.1) + puma (8.0.2) nio4r (~> 2.0) racc (1.8.1) rack (3.2.6) @@ -285,14 +285,14 @@ GEM tsort redis (5.4.1) redis-client (>= 0.22.0) - redis-client (0.28.0) + redis-client (0.29.0) connection_pool regexp_parser (2.12.0) reline (0.6.3) io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) - rubocop (1.86.1) + rubocop (1.86.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -310,7 +310,7 @@ GEM lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.34.3) + rubocop-rails (2.35.3) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) @@ -325,21 +325,21 @@ GEM ffi (~> 1.12) logger securerandom (0.4.1) - sidekiq (8.1.3) + sidekiq (8.1.6) connection_pool (>= 3.0.0) json (>= 2.16.0) logger (>= 1.7.0) rack (>= 3.2.0) - redis-client (>= 0.26.0) + redis-client (>= 0.29.0) stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.2.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) tsort (0.2.0) turbo-rails (2.0.23) @@ -360,7 +360,7 @@ GEM base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.7.5) + zeitwerk (2.8.2) PLATFORMS aarch64-linux @@ -404,7 +404,7 @@ DEPENDENCIES web-console CHECKSUMS - action_text-trix (2.1.18) sha256=3fdb83f8bff4145d098be283cdd47ac41caf5110bfa6df4695ed7127d7fb3642 + action_text-trix (2.1.19) sha256=7012f59421009cf284aa651294896414d653a61a2417c9b8714c8476d2f74009 actioncable (8.1.3) sha256=e5bc7f75e44e6a22de29c4f43176927c3a9ce4824464b74ed18d8226e75a80f0 actionmailbox (8.1.3) sha256=df7da474eaa0e70df4ed5a6fef66eb3b3b0f2dbf7f14518deee8d77f1b4aae59 actionmailer (8.1.3) sha256=831f724891bb70d0aaa4d76581a6321124b6a752cb655c9346aae5479318448d @@ -424,12 +424,12 @@ CHECKSUMS bcrypt (3.1.22) sha256=1f0072e88c2d705d94aff7f2c5cb02eb3f1ec4b8368671e19112527489f29032 bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e - bootsnap (1.24.0) sha256=34e6dea61ff4895101aa9c10894ce30186bec73fe2279e0eb52040d8d4cec297 + bootsnap (1.24.5) sha256=36b677448524d279b470469aabd5dff4a980e3fa4931a0df68da4a500eb1b6c4 builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a 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_validator (0.12.0) sha256=68c9834da240347b9c17441c553a183572508617ebfbe8c020020f3192ce3058 dkim (1.1.0) sha256=74f2e3075c89cd2967995f3ee293da9dccccffee21ee593715a068ab39a717f8 @@ -453,24 +453,24 @@ CHECKSUMS importmap-rails (2.2.3) sha256=7101be2a4dc97cf1558fb8f573a718404c5f6bcfe94f304bf1f39e444feeb16a io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc 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-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909 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.10.beta) sha256=f67df39cf7717b4a49b1694570128e42e6947a0d64bcbba1b53fa716e93b31fe + lexxy (0.9.15.alpha.1) sha256=5906f921e9ec8b0b7c47853e7fb9fb1b3c97921f4edd701d94a76ba25c45fa84 lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04 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_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef - minitest (6.0.5) sha256=f007d7246bf4feea549502842cd7c6aba8851cdc9c90ba06de9c476c0d01155c + minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1 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-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 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-linux-gnu) sha256=2f5078620fe12e83669b5b17311b32532a8153d02eee7ad06948b926d6080976 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 parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54 pg (1.6.3) sha256=1388d0563e13d2758c1089e35e973a3249e955c659592d10e5b77c468f628a99 @@ -503,7 +503,7 @@ CHECKSUMS propshaft (1.3.2) sha256=1d56a3e56a92c21bfc29caf07406b5386b00d4c47ddf357cf989a5a234b1389e psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 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 rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2 rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8 @@ -517,27 +517,27 @@ CHECKSUMS rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192 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 reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 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-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 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374 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 stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 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 tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f turbo-rails (2.0.23) sha256=ee0d90733aafff056cf51ff11e803d65e43cae258cc55f6492020ec1f9f9315f @@ -549,7 +549,7 @@ CHECKSUMS web-console (4.3.0) sha256=e13b71301cdfc2093f155b5aa3a622db80b4672d1f2f713119cc7ec7ac6a6da4 websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 - zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd + zeitwerk (2.8.2) sha256=7212a61311083c604184b1ea2574b9aa05cd14f855a0841c06985cabe9181d12 RUBY VERSION ruby 3.4.9 diff --git a/README.txt b/README.txt index 064c129..c97d78d 100644 --- a/README.txt +++ b/README.txt @@ -1,3 +1,9 @@ +Søren Smidt +------------------- +https://ssmidt79.github.io/save-this-tomato/ + + + API ------------------- curl -H "Authorization: Bearer ikea-tomato-2026" http://localhost:3000/api/v1/leaderboard diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 452c93c..aa24e01 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -73,36 +73,32 @@ :root { --clr-white: #fff; --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-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 { height: 3px; background-color: #0058a3; @@ -110,8 +106,6 @@ body { - background-color: var(--clr-dark-yellow); - color: var(--clr-black); margin: 0; display: flex; flex-direction: column; @@ -120,137 +114,6 @@ body { min-height: 100dvh; -webkit-font-smoothing: antialiased; 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; -} \ No newline at end of file diff --git a/app/controllers/players_controller.rb b/app/controllers/players_controller.rb deleted file mode 100644 index cd76ca7..0000000 --- a/app/controllers/players_controller.rb +++ /dev/null @@ -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 diff --git a/app/controllers/site_controller.rb b/app/controllers/site_controller.rb index 9f9ddd0..238dd55 100644 --- a/app/controllers/site_controller.rb +++ b/app/controllers/site_controller.rb @@ -2,7 +2,6 @@ class SiteController < ApplicationController before_action :set_locale - def index @node = Node.roots.viewable.first not_found and return unless @node diff --git a/app/controllers/stages_controller.rb b/app/controllers/stages_controller.rb deleted file mode 100644 index 7753610..0000000 --- a/app/controllers/stages_controller.rb +++ /dev/null @@ -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 diff --git a/app/helpers/site_helper.rb b/app/helpers/site_helper.rb index 9c347dd..00609b5 100644 --- a/app/helpers/site_helper.rb +++ b/app/helpers/site_helper.rb @@ -1,22 +1,19 @@ 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) - 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 [ - 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]) ], "\n" end - 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.uniq.join(' - ') + parts.uniq.join(" - ") rescue t(:client_name) end @@ -32,17 +29,17 @@ module SiteHelper result = {} 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 - 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 - 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 end end @@ -51,5 +48,4 @@ module SiteHelper result end - end diff --git a/app/helpers/stages_helper.rb b/app/helpers/stages_helper.rb deleted file mode 100644 index 93ca31e..0000000 --- a/app/helpers/stages_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module StagesHelper -end diff --git a/app/javascript/controllers/assets_controller.js b/app/javascript/controllers/assets_controller.js index 1390627..9c3843b 100644 --- a/app/javascript/controllers/assets_controller.js +++ b/app/javascript/controllers/assets_controller.js @@ -12,7 +12,7 @@ export default class extends Controller { } appendAttachments(event) { - + if (this.fetchingData) return const checkedCheckboxes = this.element.querySelectorAll('input[name="asset_ids[]"]:checked') diff --git a/app/views/admin/assets/_asset.html.erb b/app/views/admin/assets/_asset.html.erb index 0b0700b..f046cfc 100644 --- a/app/views/admin/assets/_asset.html.erb +++ b/app/views/admin/assets/_asset.html.erb @@ -1,7 +1,7 @@ <%= tag.div class: 'asset', id: dom_id(asset) do %>
-
<%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :jpg)) if asset.file.representable? %>
+
<%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :webp)) if asset.file.representable? %>
<%= tag.div asset.title, class: 'asset__title' %> diff --git a/app/views/admin/attachments/_asset.html.erb b/app/views/admin/attachments/_asset.html.erb index fca5b47..f3b23e5 100644 --- a/app/views/admin/attachments/_asset.html.erb +++ b/app/views/admin/attachments/_asset.html.erb @@ -1,6 +1,6 @@
-
<%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :jpg)) if asset.file.representable? %>
+
<%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :webp)) if asset.file.representable? %>
<%= tag.div asset.title, class: 'asset__title' %> diff --git a/app/views/languages/index.html.erb b/app/views/languages/index.html.erb index b4f513c..7d241ff 100644 --- a/app/views/languages/index.html.erb +++ b/app/views/languages/index.html.erb @@ -7,8 +7,6 @@ <% end %>
- <%= button_to t("let_me_try"), - start_path(locale: I18n.locale), - method: :post %> +
\ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index fe911e6..949fdee 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -6,7 +6,7 @@ - <%= 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 %> <%= csp_meta_tag %> @@ -20,15 +20,12 @@
- <%= 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")) %>
<%= yield %>
- -
© Inter IKEA Systems B.V. 2026
- - <%= tag.div content_for(:debug), id: "debug" if Rails.env.development? %> + diff --git a/app/views/stages/bonus.html.erb b/app/views/stages/bonus.html.erb deleted file mode 100644 index 610d0f3..0000000 --- a/app/views/stages/bonus.html.erb +++ /dev/null @@ -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 -%> - - -
- - <% @node.attachments.each do |attachment| %> - <%= attachment.body.html_safe %> - <% end %> - -
- <% @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 %> -
- -
\ No newline at end of file diff --git a/app/views/stages/chance.html.erb b/app/views/stages/chance.html.erb deleted file mode 100644 index 03e6fb0..0000000 --- a/app/views/stages/chance.html.erb +++ /dev/null @@ -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 -%> - - -
- - <% @node.attachments.each do |attachment| %> - <%= attachment.body.html_safe %> - <% end %> - -
- <%= button_to t("flip_the_card"), url_for(action: 'pick'), method: :post %> -
- -
\ No newline at end of file diff --git a/app/views/stages/choice.html.erb b/app/views/stages/choice.html.erb deleted file mode 100644 index bad492c..0000000 --- a/app/views/stages/choice.html.erb +++ /dev/null @@ -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 -%> - - -
- - <% @node.attachments.each do |attachment| %> - <%= attachment.body.html_safe %> - <% end %> - -
- <% @node.children.viewable.ordered.each do |answer_option| %> - <%= button_to answer_option.title, url_for(action: 'pick', option: answer_option.id), method: :post %> - <% end %> -
-
\ No newline at end of file diff --git a/app/views/stages/game_over.html.erb b/app/views/stages/game_over.html.erb deleted file mode 100644 index 27e5bae..0000000 --- a/app/views/stages/game_over.html.erb +++ /dev/null @@ -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 -%> - - -
- - <% @node.attachments.each do |attachment| %> - <%= attachment.body.html_safe %> - <% end %> - -
- <%= button_to t("go_again"), go_again_path(), method: :post %> -
-
\ No newline at end of file diff --git a/app/views/stages/result.html.erb b/app/views/stages/result.html.erb deleted file mode 100644 index 4c78f5f..0000000 --- a/app/views/stages/result.html.erb +++ /dev/null @@ -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 - - -%> - - -
- - <%= @node.attachments[(@result == "bad" and current_player.bonus_points >= stage_index) ? 1 : 0]&.body&.html_safe %> - -
- - <% 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 %> -
- -
\ No newline at end of file diff --git a/app/views/stages/score.html.erb b/app/views/stages/score.html.erb deleted file mode 100644 index 2accfaa..0000000 --- a/app/views/stages/score.html.erb +++ /dev/null @@ -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 -%> - -
- - <% @node.attachments.each do |attachment| %> - <%= attachment.body.html_safe %> - <% end %> - -
- <%= button_to t("go_again"), go_again_path(), method: :post %> -
-
\ No newline at end of file diff --git a/app/views/stages/show.html.erb b/app/views/stages/show.html.erb deleted file mode 100644 index 818e537..0000000 --- a/app/views/stages/show.html.erb +++ /dev/null @@ -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 -%> - - -
- - <% @stage.attachments.each do |attachment| %> - <%= attachment.body.html_safe %> - <% end %> - -
- <%= button_to t("flip"), url_for(action: 'flip'), method: :post %> -
- -
\ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 87e7f2b..9649977 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -50,30 +50,6 @@ Rails.application.routes.draw do 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 - 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 "*url", to: "site#page", constraints: lambda { |req| req.path.exclude?("storage") } end diff --git a/country_codes.csv b/country_codes.csv new file mode 100644 index 0000000..25434a3 --- /dev/null +++ b/country_codes.csv @@ -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 \ No newline at end of file diff --git a/db/migrate/20250527114853_create_players.rb b/db/migrate/20250527114853_create_players.rb deleted file mode 100644 index cb3f8e4..0000000 --- a/db/migrate/20250527114853_create_players.rb +++ /dev/null @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 3a39b5a..34cdb3b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # 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 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 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| t.datetime "created_at", null: false t.string "locale", null: false diff --git a/db/seeds.rb b/db/seeds.rb index d5b7374..516d39c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -8,7 +8,7 @@ User.create( password_confirmation: 'admin', enabled_at: Time.now, role: 'admin' -) +) unless User.find_by(email: "mattias@oncotype.dk") Node.create title: 'Week 2026', slug: '', published_at: Time.now, ancestry: '/'