| @ -1 +0,0 @@ | |||
| ruby-3.3.0 | |||
| @ -0,0 +1 @@ | |||
| ruby 3.4.1 | |||
| @ -1,230 +0,0 @@ | |||
| import "@hotwired/turbo-rails" | |||
| var input_range | |||
| const max_value = 180 | |||
| const speed = 12 | |||
| var curr_value | |||
| var raf_id | |||
| var random_salt = Math.random() | |||
| function rand_in_range(from, to) { | |||
| const rand = Math.floor(Math.random() * (to - from + 1)) + from | |||
| const is_neg = Math.random() < 0.5 | |||
| return is_neg ? -rand : rand | |||
| } | |||
| function shuffle_cards() { | |||
| const cards = document.querySelectorAll('.card__container') | |||
| const card_ids = Array.from(cards).map(card => card.getAttribute('data-id')) | |||
| let shown_cards = JSON.parse(localStorage.getItem('shown_cards')) || []; | |||
| let cards_not_shown = card_ids.filter(id => !shown_cards.includes(id)); | |||
| if (cards_not_shown.length <= 0) { | |||
| cards_not_shown = card_ids | |||
| shown_cards = [] | |||
| } | |||
| const random_id = cards_not_shown[Math.floor(Math.random() * cards_not_shown.length)]; | |||
| cards.forEach((card) => { | |||
| if (card.getAttribute('data-id') == random_id) { | |||
| card.style.zIndex = '1' | |||
| card.style.transform = '' | |||
| card.classList.add('active') | |||
| } else { | |||
| card.classList.remove('active') | |||
| card.style.zIndex = 'auto' | |||
| card.style.transform = `rotate(${rand_in_range(2,5)}deg) translate(${rand_in_range(2,5)}px, ${rand_in_range(2,5)}px)` | |||
| } | |||
| }) | |||
| shown_cards.push(random_id); | |||
| localStorage.setItem('shown_cards', JSON.stringify(shown_cards)); | |||
| } | |||
| document.addEventListener("turbo:load", ()=> { | |||
| init() | |||
| }) | |||
| document.addEventListener("turbo:visit", (event) => { | |||
| plausible('pageview', { u: event.detail.url }); | |||
| }) | |||
| document.addEventListener("turbo:before-cache", () => { | |||
| if (input_range) { | |||
| input_range.value = 0; | |||
| input_range.closest('.reveal__container').style.setProperty('--opacity', 1); | |||
| updateRotation(input_range.value) | |||
| } | |||
| let images = document.querySelectorAll("img.fade-in.loaded") | |||
| images.forEach(image => { | |||
| image.classList.remove("loaded") | |||
| }) | |||
| }) | |||
| function init() { | |||
| random_salt = Math.random() | |||
| document.querySelectorAll('.button_shuffle_cards').forEach((item, index) => { | |||
| item.addEventListener('click', (e) => { | |||
| shuffle_cards() | |||
| }) | |||
| if (index == 0) | |||
| shuffle_cards() | |||
| }) | |||
| document.querySelectorAll('#language_select').forEach((language_select) => { | |||
| language_select.addEventListener('change', (e) => { | |||
| document.documentElement.lang = language_select.value | |||
| }) | |||
| }) | |||
| // Choose language confirmation button | |||
| document.querySelectorAll('#confirm_btn').forEach((confirm_btn) => { | |||
| confirm_btn.addEventListener('click', (e) => { | |||
| // window.location.href = '/' + document.getElementById('language_select').value | |||
| Turbo.visit('/' + document.getElementById('language_select').value, {advance: 'advance'}) | |||
| }) | |||
| }) | |||
| // Copy link to clipboard | |||
| document.querySelectorAll('[data-share-title]').forEach((share_btn) => { | |||
| share_btn.addEventListener('click', (e) => { | |||
| if (navigator.share) { | |||
| navigator.share({ | |||
| title: e.currentTarget.getAttribute('data-share-title'), | |||
| text: e.currentTarget.getAttribute('data-share-text'), | |||
| url: window.location.href | |||
| }).then(() => { | |||
| console.log('Thanks for sharing!'); | |||
| }) | |||
| .catch(console.error); | |||
| } else { | |||
| navigator.clipboard.writeText(window.location.href) | |||
| alert('URL copied to clipboard!') | |||
| } | |||
| }) | |||
| }) | |||
| document.querySelectorAll('.card__stack a').forEach((card_link) => { | |||
| card_link.addEventListener('click', (e) => { | |||
| e.preventDefault() | |||
| }) | |||
| }) | |||
| // Open all external links in ny tab | |||
| for (const link of document.links) { | |||
| if (link.hostname.replace(/^www\./i, "") != window.location.hostname.replace(/^www\./i, "") && (link.protocol == 'https:' || link.protocol == 'http:')) | |||
| link.target = '_blank' | |||
| } | |||
| input_range = document.getElementsByClassName('reveal')[0] | |||
| if (input_range) { | |||
| input_range.min = 0; | |||
| input_range.max = max_value; | |||
| } | |||
| if (input_range) { | |||
| input_range.addEventListener('mousedown', unlockStartHandler, false) | |||
| input_range.addEventListener('touchstart', unlockStartHandler, false) | |||
| input_range.addEventListener('mouseup', unlockEndHandler, false) | |||
| input_range.addEventListener('touchend', unlockEndHandler, false) | |||
| input_range.addEventListener("input", (e) => { | |||
| e.currentTarget.closest('.reveal__container').style.setProperty('--opacity', 1 - (e.currentTarget.value/e.currentTarget.max)) | |||
| updateRotation(e.currentTarget.value) | |||
| }) | |||
| } | |||
| let images = document.querySelectorAll("img.fade-in") | |||
| images.forEach(image => { | |||
| image.addEventListener("load", () => { | |||
| image.classList.add("loaded") | |||
| }) | |||
| // For cached images that may already be loaded | |||
| if (image.complete) { | |||
| window.requestAnimationFrame(() => { | |||
| image.classList.add("loaded") | |||
| }) | |||
| } | |||
| }) | |||
| } | |||
| function unlockStartHandler() { | |||
| window.cancelAnimationFrame(raf_id); | |||
| curr_value = +this.value; | |||
| } | |||
| function unlockEndHandler() { | |||
| curr_value = +this.value; | |||
| if (curr_value >= this.max) { | |||
| const card = document.querySelector('.card__container.active') | |||
| //window.location.href = card.parentNode.getAttribute('href') | |||
| Turbo.visit(card.parentNode.getAttribute('href'), { action: 'advance'} ) | |||
| } else { | |||
| raf_id = window.requestAnimationFrame(animateHandler); | |||
| } | |||
| } | |||
| // handle range animation | |||
| function animateHandler() { | |||
| input_range.value = curr_value; | |||
| input_range.closest('.reveal__container').style.setProperty('--opacity', 1 - (input_range.value/input_range.max)); | |||
| updateRotation(input_range.value) | |||
| // continue? | |||
| if (curr_value > -1) { | |||
| window.requestAnimationFrame(animateHandler); | |||
| } | |||
| // decrement | |||
| curr_value = curr_value - speed; | |||
| } | |||
| function updateRotation(angle) { | |||
| document.documentElement.style.setProperty('--flip-deg', angle + 'deg') | |||
| document.documentElement.style.setProperty('--flip-scale', calculateScaleFactor(angle)) | |||
| document.documentElement.style.setProperty('--flip-rotate', calculateRotateFactor(angle) + 'deg') | |||
| } | |||
| function calculateScaleFactor(angle) { | |||
| const radians = angle * (Math.PI / 180) | |||
| return 1 + (0.1 * Math.sin(radians)) | |||
| // return 1 + (0.2 * (angle / 180)) | |||
| } | |||
| function calculateRotateFactor(angle) { | |||
| const radians = angle * (Math.PI / 180) | |||
| return (4 * Math.sin(radians)) + (random_salt * 2) | |||
| } | |||
| @ -1,7 +1,3 @@ | |||
| <turbo-stream action="prepend" targets="#assets"> | |||
| <template> | |||
| <%= render partial: 'asset', object: @asset, formats: :html %> | |||
| </template> | |||
| </turbo-stream> | |||
| <%= turbo_stream.replace params[:uid] do %> | |||
| <%= render partial: 'asset', object: @asset, formats: :html %> | |||
| <% end %> | |||
| @ -0,0 +1,7 @@ | |||
| #!/usr/bin/env ruby | |||
| require "rubygems" | |||
| require "bundler/setup" | |||
| ARGV.unshift("--ensure-latest") | |||
| load Gem.bin_path("brakeman", "brakeman") | |||
| @ -0,0 +1,2 @@ | |||
| #!/usr/bin/env ruby | |||
| exec "./bin/rails", "server", *ARGV | |||
| @ -0,0 +1,8 @@ | |||
| #!/usr/bin/env ruby | |||
| require "rubygems" | |||
| require "bundler/setup" | |||
| # explicit rubocop config increases performance slightly while avoiding config confusion. | |||
| ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) | |||
| load Gem.bin_path("rubocop", "rubocop") | |||
| @ -0,0 +1,10 @@ | |||
| development: | |||
| adapter: async | |||
| test: | |||
| adapter: test | |||
| production: | |||
| adapter: redis | |||
| url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> | |||
| channel_prefix: week2025_production | |||
| @ -0,0 +1,70 @@ | |||
| # Be sure to restart your server when you modify this file. | |||
| # | |||
| # This file eases your Rails 7.2 framework defaults upgrade. | |||
| # | |||
| # Uncomment each configuration one by one to switch to the new default. | |||
| # Once your application is ready to run with all new defaults, you can remove | |||
| # this file and set the `config.load_defaults` to `7.2`. | |||
| # | |||
| # Read the Guide for Upgrading Ruby on Rails for more info on each option. | |||
| # https://guides.rubyonrails.org/upgrading_ruby_on_rails.html | |||
| ### | |||
| # Controls whether Active Job's `#perform_later` and similar methods automatically defer | |||
| # the job queuing to after the current Active Record transaction is committed. | |||
| # | |||
| # Example: | |||
| # Topic.transaction do | |||
| # topic = Topic.create(...) | |||
| # NewTopicNotificationJob.perform_later(topic) | |||
| # end | |||
| # | |||
| # In this example, if the configuration is set to `:never`, the job will | |||
| # be enqueued immediately, even though the `Topic` hasn't been committed yet. | |||
| # Because of this, if the job is picked up almost immediately, or if the | |||
| # transaction doesn't succeed for some reason, the job will fail to find this | |||
| # topic in the database. | |||
| # | |||
| # If `enqueue_after_transaction_commit` is set to `:default`, the queue adapter | |||
| # will define the behaviour. | |||
| # | |||
| # Note: Active Job backends can disable this feature. This is generally done by | |||
| # backends that use the same database as Active Record as a queue, hence they | |||
| # don't need this feature. | |||
| #++ | |||
| # Rails.application.config.active_job.enqueue_after_transaction_commit = :default | |||
| ### | |||
| # Adds image/webp to the list of content types Active Storage considers as an image | |||
| # Prevents automatic conversion to a fallback PNG, and assumes clients support WebP, as they support gif, jpeg, and png. | |||
| # This is possible due to broad browser support for WebP, but older browsers and email clients may still not support | |||
| # WebP. Requires imagemagick/libvips built with WebP support. | |||
| #++ | |||
| # Rails.application.config.active_storage.web_image_content_types = %w[image/png image/jpeg image/gif image/webp] | |||
| ### | |||
| # Enable validation of migration timestamps. When set, an ActiveRecord::InvalidMigrationTimestampError | |||
| # will be raised if the timestamp prefix for a migration is more than a day ahead of the timestamp | |||
| # associated with the current time. This is done to prevent forward-dating of migration files, which can | |||
| # impact migration generation and other migration commands. | |||
| # | |||
| # Applications with existing timestamped migrations that do not adhere to the | |||
| # expected format can disable validation by setting this config to `false`. | |||
| #++ | |||
| # Rails.application.config.active_record.validate_migration_timestamps = true | |||
| ### | |||
| # Controls whether the PostgresqlAdapter should decode dates automatically with manual queries. | |||
| # | |||
| # Example: | |||
| # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date") #=> Date | |||
| # | |||
| # This query used to return a `String`. | |||
| #++ | |||
| # Rails.application.config.active_record.postgresql_adapter_decode_dates = true | |||
| ### | |||
| # Enables YJIT as of Ruby 3.3, to bring sizeable performance improvements. If you are | |||
| # deploying to a memory constrained environment you may want to set this to `false`. | |||
| #++ | |||
| # Rails.application.config.yjit = true | |||
| @ -0,0 +1,30 @@ | |||
| # Be sure to restart your server when you modify this file. | |||
| # | |||
| # This file eases your Rails 8.0 framework defaults upgrade. | |||
| # | |||
| # Uncomment each configuration one by one to switch to the new default. | |||
| # Once your application is ready to run with all new defaults, you can remove | |||
| # this file and set the `config.load_defaults` to `8.0`. | |||
| # | |||
| # Read the Guide for Upgrading Ruby on Rails for more info on each option. | |||
| # https://guides.rubyonrails.org/upgrading_ruby_on_rails.html | |||
| ### | |||
| # Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. | |||
| # If set to `:zone`, `to_time` methods will use the timezone of their receivers. | |||
| # If set to `:offset`, `to_time` methods will use the UTC offset. | |||
| # If `false`, `to_time` methods will convert to the local system UTC offset instead. | |||
| #++ | |||
| # Rails.application.config.active_support.to_time_preserves_timezone = :zone | |||
| ### | |||
| # When both `If-Modified-Since` and `If-None-Match` are provided by the client | |||
| # only consider `If-None-Match` as specified by RFC 7232 Section 6. | |||
| # If set to `false` both conditions need to be satisfied. | |||
| #++ | |||
| # Rails.application.config.action_dispatch.strict_freshness = true | |||
| ### | |||
| # Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. | |||
| #++ | |||
| # Regexp.timeout = 1 | |||
| @ -0,0 +1,22 @@ | |||
| # This migration comes from active_storage (originally 20190112182829) | |||
| class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0] | |||
| def up | |||
| return unless table_exists?(:active_storage_blobs) | |||
| unless column_exists?(:active_storage_blobs, :service_name) | |||
| add_column :active_storage_blobs, :service_name, :string | |||
| if configured_service = ActiveStorage::Blob.service.name | |||
| ActiveStorage::Blob.unscoped.update_all(service_name: configured_service) | |||
| end | |||
| change_column :active_storage_blobs, :service_name, :string, null: false | |||
| end | |||
| end | |||
| def down | |||
| return unless table_exists?(:active_storage_blobs) | |||
| remove_column :active_storage_blobs, :service_name | |||
| end | |||
| end | |||
| @ -0,0 +1,27 @@ | |||
| # This migration comes from active_storage (originally 20191206030411) | |||
| class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0] | |||
| def change | |||
| return unless table_exists?(:active_storage_blobs) | |||
| # Use Active Record's configured type for primary key | |||
| create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t| | |||
| t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type | |||
| t.string :variation_digest, null: false | |||
| t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true | |||
| t.foreign_key :active_storage_blobs, column: :blob_id | |||
| end | |||
| end | |||
| private | |||
| def primary_key_type | |||
| config = Rails.configuration.generators | |||
| config.options[config.orm][:primary_key_type] || :primary_key | |||
| end | |||
| def blobs_primary_key_type | |||
| pkey_name = connection.primary_key(:active_storage_blobs) | |||
| pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name } | |||
| pkey_column.bigint? ? :bigint : pkey_column.type | |||
| end | |||
| end | |||
| @ -0,0 +1,8 @@ | |||
| # This migration comes from active_storage (originally 20211119233751) | |||
| class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0] | |||
| def change | |||
| return unless table_exists?(:active_storage_blobs) | |||
| change_column_null(:active_storage_blobs, :checksum, true) | |||
| end | |||
| end | |||
| @ -0,0 +1,58 @@ | |||
| <!doctype html> | |||
| <html lang="en"> | |||
| <head> | |||
| <title>The server cannot process the request due to a client error (400 Bad Request)</title> | |||
| <meta charset="utf-8"> | |||
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |||
| <meta name="robots" content="noindex, nofollow"> | |||
| <style> | |||
| body { | |||
| background-color: #F0B902; | |||
| color: #000; | |||
| text-align: center; | |||
| font-family: system-ui, sans-serif; | |||
| margin: 0; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| } | |||
| body { | |||
| min-height: 100vh; | |||
| min-height: 100svh; | |||
| } | |||
| h2 { | |||
| margin: 0; | |||
| font-size: 400px; | |||
| line-height: 1; | |||
| position: absolute; | |||
| top: -0.4em; | |||
| left: -0.08em; | |||
| opacity: 0.4; | |||
| font-weight: 700; | |||
| } | |||
| h1 { | |||
| margin: 0; | |||
| font-size: 2em; | |||
| font-weight: 300; | |||
| } | |||
| p { | |||
| margin: 1em 0; | |||
| } | |||
| </style> | |||
| </head> | |||
| <body> | |||
| <div> | |||
| <h2>400</h2> | |||
| <h1>The server cannot process the request due to a client error.</h1> | |||
| <p>Please check the request and try again. If you’re the application owner check the logs for more information.</p> | |||
| </div> | |||
| </body> | |||
| </html> | |||
| @ -0,0 +1,54 @@ | |||
| <!doctype html> | |||
| <html lang="en"> | |||
| <head> | |||
| <title>Your browser is not supported (406)</title> | |||
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |||
| <style> | |||
| body { | |||
| background-color: #F0B902; | |||
| color: #000; | |||
| text-align: center; | |||
| font-family: system-ui, sans-serif; | |||
| margin: 0; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| } | |||
| body { | |||
| min-height: 100vh; | |||
| min-height: 100svh; | |||
| } | |||
| h2 { | |||
| margin: 0; | |||
| font-size: 400px; | |||
| line-height: 1; | |||
| position: absolute; | |||
| top: -0.4em; | |||
| left: -0.08em; | |||
| opacity: 0.4; | |||
| font-weight: 700; | |||
| } | |||
| h1 { | |||
| margin: 0; | |||
| font-size: 2em; | |||
| font-weight: 300; | |||
| } | |||
| p { | |||
| margin: 1em 0; | |||
| } | |||
| </style> | |||
| </head> | |||
| <body> | |||
| <div> | |||
| <h2>406</h2> | |||
| <h1>Your browser is not supported.</h1> | |||
| <p>Please upgrade your browser to continue.</p> | |||
| </div> | |||
| </body> | |||
| </html> | |||